前面費老大勁學習VT的基本原理和框架代碼,到底能用來干啥了?
VT中,host通過exit事件監控guest的一舉一動,稍微“大”一點的動作(進程切換、讀寫msr、執行cpuid等)都會在guest觸發exit,回到host的handle函數處理,在VT框架中,host對guest有絕對的監控和處理的全力,所以業界通常把VT框架下的程序稱為-1環,比操作系統的0環都低,很形象地說明了host的權限范圍;VT中非常重要的一個模塊EPT,gtest中任何讀寫實際物理內存的操作都需要通過EPT轉換一遍,這個轉換環節一旦出任何問題,導致轉換到了錯誤的物理地址,都會導致guest讀寫物理內存失敗;本文利用這個原理,讓guest對host同一塊物理地址的讀和寫分離:第三方程序(比如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,必須經過如下各級頁表的轉換,下面是各層級的歸納總結:
(1)綠色的PTE:一個entry占用8byte,可以映射到一個物理頁;一個PTE有4096byte,能容納512個entry,也就能管理512*4K=2M的內存(一個綠塊占用4KB,最大能管理2MB內存)
(2)橙色的PDE:一個entry占用8byte,可以映射到一個PTE;一個PDE有4096byte,能容納512個entry,也就能管理512個PTE,那么一個PDE能管理512*2M=1GB的內存(一個橙塊占用4KB,最大能管理1GB內存)
(3)紅色PDPTE:一個entry占用8byte,可以映射到一個PDE;一個PDPTE有4096byte,能容納512個entry,也就能管理512個PDE,那么一個PDPTE能管理512*1GB=512GB的內存(一個紅塊占用4KB,最大能管理512GB內存)
(4)藍色PML4E:同上,一個PML4E管理512個PDPTE,512個PML4E一共能管理512*512GB=256T內存(一個藍塊占用4KB,最大能管理256T內存)
本人測試的虛擬機內存2GB為例,按照上面的推算方式,申請內存時需要藍色小塊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!
參考: