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應用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM