Windows内核实验:指令TLB(ITLB)和流水线&&Meltdown CPU漏洞

Windows内核实验:指令TLB(ITLB)和流水线&&Meltdown CPU漏洞

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define PDE(x) (DWORD64 *) (((x >> 21) << 3) + 0xc0600000)
#define PTE(x) (DWORD64 *) (((x >> 12) << 3) + 0xc0000000)
DWORD g_var;//0041A1D0
DWORD64 g_pte;//0x0041A1C8
#pragma section("new_page",read,write)
__declspec(allocate("new_page")) DWORD page1[1024];//0x0041b000
__declspec(allocate("new_page")) DWORD page2[1024];//0x0041c000
void __declspec(naked) IdtEntry() {//裸函数不会帮我们生成栈帧,单纯一个call
//__asm int 3
g_pte = *PTE(0x0041c000);
__asm mov eax, ds: [0x0041c000]//再次确保page2的页面在tlb里,没有被换出
__asm {
mov eax,cr3
mov cr3,eax
}//清除tlb
*PTE(0x0041c000) = *PTE(0x0041b000);//tlb misss
//g_var = page2[0];
_asm {
mov eax,ds:[0x0041c000]
mov g_var,eax
}
*PTE(0x0041c000) = g_pte;
__asm {
mov eax, cr3
mov cr3, eax
}//清除tlb
__asm {
iretd
}
}
void interrupt() {
page1[0] = 0xc3;//ret
page2[0] = 0xc390;//nop ret 载物理内存的同时把虚拟内存放到tlb里
((void(*)(void))page1)();
((void(*)(void))page2)();
__asm {
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("Idtentry address wrong");
system("pause");
exit(-1);
}
//eq 8003f500 0040ee00`00081040
for (size_t i = 0; i < 10;i++) {
interrupt();
if (0xc3 != g_var)
printf("g_var:%x\n", g_var);
}
system("pause");
}

这个程序在我机器上我感觉是由于new_page和IdtEntry之间的距离太大了,导致没有复现成功

简单说一下主要思路把

为了使获得的section page有可执行权限,把page1和page2先当作函数执行了一下,就可以获得可执行权限

kd> !pte 0x0041b000
VA 0041b000
PDE at C0600010 PTE at C00020D8
contains 000000000295C067 contains 0000000002903067
pfn 295c ---DA--UWEV pfn 2903 ---DA--UWEV

kd> !pte 0x0041c000
VA 0041c000
PDE at C0600010 PTE at C00020E0
contains 000000000295C067 contains 000000000A024067
pfn 295c ---DA--UWEV pfn a024 ---DA--UWEV

为什么没有用#pragma section(“new_page”,read,write,execute)获取可执行权限

这个是可以编译通过,但似乎xp系统不允许一个页可读可写可执行,崩溃了

page1 2有了可执行权限,由于流水线技术,指令可能会并行,IdtEntry执行时,如果取指令可以一次到达page1 2,会对该指令先执行,就会导致page1 2被提前访问,放入tlb

Meltdown CPU漏洞

突然想起之前在jyy老师那里听到的cpu熔断漏洞,可以在用户态往内核或其他进程里偷出数据

漏洞的原因和乱序执行有关,属于cpu架构的漏洞

// %rcx: 无权限访问的地址;%rbx: 未载入缓存的数组
xorq %rax, %rax
retry: movzbq (%rcx), %rax // 非法访问;Page Fault
shlq $0xc, %rax
jz retry
movq (%rbx, %rax), %rbx // (%rbx + (%rax) * 4096)
  1. cpu在执行movzbq (%rcx), %rax指令的时候,会造成页错误,但是是先把数据取出来了,然后做数据合法性检查,不合法会处理错误,
  2. 由于这段代码是符合乱序并行的,下面的指令会在rax有数据的一瞬间和1.步骤里检查合法性一起执行
  3. 下面的指令大概率是比检查合法性的代码要快的
  4. movq (%rbx, %rax), %rbx 就会使在未载入缓存的数组中位置是rcx地址处的1byte数据的页进入cache,
  5. 后续访问rbx各页面,比较访问时间就可以获取这个字节的数据 (cache hit和cache miss访问时间差距可见)

漏洞的原因就是访问不合法后,没有将污染的cache清除