intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离

摘要:
每个操作(进程切换、读取/写入msr、执行cpuid等),Windbg也可以正常看到函数的输入代码:

     前面费老大劲学习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能查到函数的入口地址:

      intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离第1张

  windbg也能正常看到函数的入口代码:

     intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离第2张

  这个虚拟地址对应的物理地址:0x220bb40;考虑到页对齐,物理页首地址应该是0x220b000;

     intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离第3张

   正常情况下,页面可执行必然是可读的,但是现在把这里设置一下,可以让页面可执行,但是不可读;

    intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离第4张

  我这里0x48c寄存器最后1位是1,说明支持这种方式:可执行但不可读

      intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离第5张

  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个页;

     intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离第6张

   代码如下,注意核心都在注释了:

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!

     intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离第7张

参考:

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

免责声明:文章转载自《intel:x86架构VT虚拟化(四):x64 无痕hook/shadow walker/页面读写分离》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇WPF TextBox自动滚动到最户一行抖音主页如何添加官网链接下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【前端】本地调试H5页面方案总结

背景 大学毕业快要一年了,用leader的话说我也是有一年开发经验的前端开发工程师了,输出是检验输入的最好方法,那我就慢慢来总结这一年来在工作中总结的经验教训,分享给大家,有不专业和不完善的地方,请大家多多指点反馈,比心~ 做过一段时间的运营需求,一些主要在手机上浏览的页面,遇到的最大问题是调试的问题: 对于简单的网络、APP环境的调试,我们可以通过Chr...

【随笔】手机QQ v4.0

  最近几天广大iOS和Android用户先后更新了手机QQ v4.0,反馈意见褒贬不一,有句话是“好事不出门,坏事传千里”,给用户一个好的更新版本可能1000个用户里面会有1个用户给出五星好评,但是一旦做出了一个有争议的更新版本,那么10个用户里面可能就有1个给出一星评价,这次企鹅就遭殃了,据说从4.0版本推出到现在几天的时间内已经收到3W+个一星评级,...

第18章 启动

IdentityServer是中间件和服务的组合。所有配置都在您的启动类中完成。 18.1 配置服务 您可以通过调用以下方法将IdentityServer服务添加到DI系统: public void ConfigureServices(IServiceCollection services) { var builder = services.Add...

【Vue入门】利用VueCli搭建基本框架--在Home页实现上左右基本布局(五)

上一节讲了简单封装Http请求并调用登陆的Api接口 这节主要说Home的布局展示 一、设置布局代码   Home页布局参考Elementui中的布局代码(采用上,左右结构)  当然也可以用其他的布局 <el-container> <el-header>Header</el-header> <el-cont...

YUV和RGB格式单像素所占内存大小分析

图片的大小定 义为:w * h,宽高分别为w和h 一、YUV格式 1.1.YUV420格式存储方式:先Y,后V,中间是U。其中的Y是w * h,U和V是w/2 * (h/2)举例:如果w = 4,h = 2,则:yyyyyyyyuuvv即 yyyyyyyyuuvv采样规律是:每个像素点都采样Y,奇数行采样1/2个U,不采样V,偶数行采样1/2个V,不采样...

JS-获取URL请求参数

前言:原来做过一个项目,需要实现一个页面打印的功能,由于项目中使用了AngularJS+Bootstrap等前端框架,需要打印的页面又在弹出框中,使用了Bootstrap的模态框后发现打印的效果不太好,后来就使用原生的方式弹出一个新的窗口,不过新的窗口中的某些数据又需要从前一个页面中获取,使用AngularJS框架后发现从后台返回的页面总是被封装成一个对象...