1. 程式人生 > 實用技巧 >intel:x86架構VT虛擬化(四):x64 無痕hook/shadow walker/頁面讀寫分離

intel:x86架構VT虛擬化(四):x64 無痕hook/shadow walker/頁面讀寫分離

前面費老大勁學習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!

參考:

1、https://www.bilibili.com/video/BV1Hb411n7Mw VT應用