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
", *((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应用

原文地址:https://www.cnblogs.com/theseventhson/p/14130612.html