zoukankan      html  css  js  c++  java
  • 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应用

  • 相关阅读:
    CVS简介
    快捷搭建JavaWeb开发环境
    局域网Win7 SVN 服务器搭建以及使用(原创)
    给自己 一个方向,让自己不再迷茫
    SQL Server2005安装配置以及测试
    Oracle中表结构和表内容复制
    Myeclipse8.5 svn插件安装两种方式
    Oracle中错误代码ORA02292 违反了完整性约束条件解决
    JSP中字符编码转换问题
    C# sealed关键词
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14130612.html
Copyright © 2011-2022 走看看