前面费老大劲学习VT的基本原理和框架代码,到底能用来干啥了?
第三方程序(比如CE、PChunter、某些程序自带的CRC检测功能、windows自带的patch guard等)读物理页的时候返回一个结果,执行的时候又返回一个结果,以此骗过第三方程序对物理页内容的检查,这就是业界俗称的shadow walker。此技术可用于无痕hook!
1、本次拿IDT的0x0E号中断page fault举例:熟悉操作系统的人都不陌生,操作系统的缺页异常无时不在,换句话说这个函数无时无刻都在被调用。现在通过PChunter能查到函数的入口地址:
windbg也能正常看到函数的入口代码:
这个虚拟地址对应的物理地址:0x220bb40;考虑到页对齐,物理页首地址应该是0x220b000;
正常情况下,页面可执行必然是可读的,但是现在把这里设置一下,可以让页面可执行,但是不可读;
我这里0x48c寄存器最后1位是1,说明支持这种方式:可执行但不可读
2、这里回顾一下EPT的原理:虚拟机的GPA要转成HPA,必须经过如下各级页表的转换,下面是各层级的归纳总结:
(一个绿块占用4KB,最大能管理2MB内存)
(一个橙块占用4KB,最大能管理1GB内存)
(一个红块占用4KB,最大能管理512GB内存)
(一个蓝块占用4KB,最大能管理256T内存)
蓝色小块1个页,红色小块1个页,橙色小块2个页,绿色小块1024个页;
代码如下,注意核心都在注释了:
EptPteEntry* g_fake_page;
ULONG64 g_fake_page_pa;
EptPteEntry* fake_PteEntry;
EptPml4Entry* EptInitialization()
{
EptPml4Entry* ept_PML4T;
PHYSICAL_ADDRESS FirstPtePA, FirstPdePA, FirstPdptePA;
ept_PML4T = (EptPml4Entry*)(ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'aa'));//蓝
RtlZeroMemory(ept_PML4T, PAGE_SIZE);
EptPdpteEntry* ept_PDPTE = (EptPdpteEntry*)(ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'aa'));//红
RtlZeroMemory(ept_PDPTE, PAGE_SIZE);
FirstPdptePA = MmGetPhysicalAddress(ept_PDPTE);
ept_PML4T->Read = 1;
ept_PML4T->Write = 1;
ept_PML4T->Execute = 1;
ept_PML4T->PhysAddr = FirstPdptePA.QuadPart >> 12;
g_pPml4T = ept_PML4T;
g_pPdpteTable = ept_PDPTE;
//生成一个假页面
g_fake_page = (EptPteEntry* )ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE,'fake');
RtlZeroMemory(g_fake_page, PAGE_SIZE);
g_fake_page_pa = MmGetPhysicalAddress(g_fake_page).QuadPart;
for (ULONG64 a = 0;a < NUM_PAGES;a++)//红,循环一次管理1GB;本人虚拟机2GB内存,所以循环2次
{
EptPdeEntry* ept_PDE = (EptPdeEntry*)(ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'aa')); // 普通模式
RtlZeroMemory(ept_PDE, PAGE_SIZE);
FirstPdePA = MmGetPhysicalAddress(ept_PDE);
ept_PDPTE->Read = 1;
ept_PDPTE->Write = 1;
ept_PDPTE->Execute = 1;
ept_PDPTE->PhysAddr = FirstPdePA.QuadPart >> 12;
ept_PDPTE++;
g_pPdeTable[a] = ept_PDE;
for (int b = 0;b < 512;b++)//橙,循环一次管理2MB,所有循环完成管理1GB
{
EptPteEntry* ept_PTE = (EptPteEntry*)(ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'aa')); // 普通模式
g_pPteTable[a][b] = ept_PTE;
RtlZeroMemory(ept_PTE, PAGE_SIZE);
FirstPtePA = MmGetPhysicalAddress(ept_PTE);
ept_PDE->PhysAddr = FirstPtePA.QuadPart >> 12;
ept_PDE->Read = 1;
ept_PDE->Write = 1;
ept_PDE->Execute = 1;
ept_PDE++;
for (int c = 0;c < 512;c++)//绿,循环一次管理4KB;所有循环完成管理2MB
{
ept_PTE->PhysAddr = (a * (1 << 30) + b * (1 << 21) + c * (1 << 12)) >> 12;
if(0x220b000 == (((a * (1 << 30) + b * (1 << 21) + c * (1 << 12))) & 0xffffffff)){
//Asm_int3();
//FGP_VT_KDPRINT(("ept_PTE->PhysAddr = 0x%x\n", *((PULONG64)ept_PTE->PhysAddr)));//这里会异常,因为这块内存末尾3byte都是0,读写执行都不允许
ept_PTE->Read = 0; //我们的目标页面,只能执行,不能读写;当CE、pchunter读这个页面时就会产生异常,进入exithandler处理
ept_PTE->Write = 0;
ept_PTE->Execute = 1;
ept_PTE->MemoryType = EPT_MEMORY_TYPE_WB;//write-back
fake_PteEntry = ept_PTE;//导出pte项指针
}
else //其他页面正常可读可写可执行
{
ept_PTE->Read = 1;
ept_PTE->Write = 1;
ept_PTE->Execute = 1;
ept_PTE->MemoryType = EPT_MEMORY_TYPE_WB;
}
ept_PTE++;
}
}
}
return ept_PML4T;
}
对应的异常处理函数HandleEptViolation()中每次遇到读写都挂上假页面:
if (pEpt_Attribute->Read)// Read Access
{
//假页面给挂载上,同时允许可读可写
fake_PteEntry->PhysAddr = g_fake_page_pa>>12;
fake_PteEntry->Read = 1;
fake_PteEntry->Write = 1;
fake_PteEntry->Execute = 0;
}
if (pEpt_Attribute->Write)// Write Access
{
//假页面给挂载上,同时允许可读可写
fake_PteEntry->PhysAddr = g_fake_page_pa >> 12;
fake_PteEntry->Read = 1;
fake_PteEntry->Write = 1;
fake_PteEntry->Execute = 0;
}
效果:连windbg都被骗了,这个页面读出来的全是0!