Ring3下绕过Windows写时复制机制实现全局EAT钩子
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ring3下绕过Windows写时复制机制实现全局EAT钩子相关的知识,希望对你有一定的参考价值。
在注入到某进程中对Ntdll下EAT钩子的时候作用域仅仅只是当前进程,可是明明所有进程的Ntdll模块全是映射的同一个啊。原来Windows支持一种机制,允许两个或两个以上的进程共享同一块存储器。不过操作系统会给共享的存储页指定写时复制属性,当有个进程想修改一个共享页面时,操作系统会从内存中找到一个闲置页面并将修改的页面内容复制到这个闲置页面上,再将虚拟地址空间和这个新的页面映射上。那么只需在下钩前先想办法消掉页面的写时复制属性,再hook ntdll的时候就能在Ring3下实现类似Ring0钩子的效果。
本文先来分析下X86下的实现。
首先我们了解下X86下虚拟地址到物理地址的转换,关于这个网上有很多分析资料以及代码实现。
祭出一张经典老图。
如图所示,X86下内存管理采用的是二级页表结构,页表是连续的,可以看成一维数组,数组每一项的大小是几字节了?回答是不一定,在32位是4字节,64位是8字节,32位中还有一种也是8字节,就是所谓的PAE(物理地址扩展)。对于开启PAE其实都是大同小异。我们先讨论先讨论普通4字节。对于一个32位的虚拟地址,其中高20位对应一个物理页地址,低12位是页内偏移,那么意味着页大小是12位,那么一页就是2^12=4096=1000h(页大小也可能变化,万变不离其中),而20位物理页地址(简称PFN,Page frame number),加上12位页大小,刚好是32位,那么总共能访问的内存就是2^32=4G,注意,这就是32位的极限了,而页表将完整映射对应的4G空间,显示需要4M的页表(2^20 *4)。可是4M的连续储存空间太过于奢侈,所以采取了二级页表的方式,最高10位表示页目录索引,接下来10位表示页表索引,低12位则用作页内偏移。页目录共有2^10=1024项,每个页目录下对应的页表也有2^10=1024项。一项4个字节,那么页表页目录总共占有的空间:1024*4+1024*1024*4=4KB+4MB。
地址解析三部走:
0X01:PDE = PDBR[Directory];
0x02 : PTE = PDE + Table * 4;
0X03 : PhysicalAddress = PTE + Offset;
在页目录和页表中, 只保存了页表或者页物理地址的高20位. 原因很简单, 页表或者页的物理地址, 都要求必须是4KB对齐的, 以便于放在一个页内, 故其低12位全是0. 在这种情况下, 可以只关心其高20位, 低12位安排其他用途。
如图所示的页表地址结构低12位是一些标志信息,那么这些标志位都代表什么呢?
P--位0是存在(Present)标志,用于指明表项对地址转换是否有效。P=1表示有效;P=0表示无效。在页转换过程中,如果说涉及的页目录或页表的表项无效,则会导致一个异常。如果P=0,那么除表示表项无效外,其余位可供程序自由使用,如图4-18b所示。例如,操作系统可以使用这些位来保存已存储在磁盘上的页面的序号。
R/W--位1是读/写(Read/Write)标志。如果等于1,表示页面可以被读、写或执行。如果为0,表示页面只读或可执行。当处理器运行在超级用户特权级(级别0、1或2)时,则R/W位不起作用。页目录项中的R/W位对其所映射的所有页面起作用。
U/S--位2是用户/超级用户(User/Supervisor)标志。如果为1,那么运行在任何特权级上的程序都可以访问该页面。如果为0,那么页面只能被运行在超级用户特权级(0、1或2)上的程序访问。页目录项中的U/S位对其所映射的所有页面起作用。
A--位5是已访问(Accessed)标志。当处理器访问页表项映射的页面时,页表表项的这个标志就会被置为1。当处理器访问页目录表项映射的任何页面时,页目录表项的这个标志就会被置为1。处理器只负责设置该标志,操作系统可通过定期地复位该标志来统计页面的使用情况。
D--位6是页面已被修改(Dirty)标志。当处理器对一个页面执行写操作时,就会设置对应页表表项的D标志。处理器并不会修改页目录项中的D标志。
AVL--该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不会。
可以看到页表的第2位标识着当前物理页面可读可写,所以只需要再钩之前把函数地址传到驱动解析地址,函数所在的页表第二位的标志位改成1就行了。
PAE=ExIsProcessorFeaturePresent(PF_PAE_ENABLED); if(PAE==TRUE) { DbgPrint("PAE page mode.\n"); // 按照PAE page mode尝试计算PDE和PTE,并查看虚拟地址是否在同一页面 //要修改的地址起始处 ulPDEB = ( (((ULONG)pMem)>>18) & 0x3FF8 ) + 0xC0600000; ulPTEB = ( (((ULONG)pMem)>>9) & 0x7FFFF8 ) + 0xC0000000; bPDEB = MmIsAddressValid((PVOID)ulPDEB); bPTEB = MmIsAddressValid((PVOID)ulPTEB); //要修改的地址后边界 ulPDE = ( ((((ULONG)pMem+5))>>18) & 0x3FF8 ) + 0xC0600000; ulPTE = ( ((((ULONG)pMem+5))>>9) & 0x7FFFF8 ) + 0xC0000000; bPDE = MmIsAddressValid((PVOID)ulPDE); bPTE = MmIsAddressValid((PVOID)ulPTE); if ((bPDEB && bPTEB && bPTE)) { DbgPrint("PDE(%d) : 0X%08X -> 0X%08X\n", bPDEB, ulPDEB, *(PULONG)ulPDEB); DbgPrint("PTE(%d) : 0X%08X -> 0X%08X\n", bPTEB, ulPTEB,ulPTE ); } else return STATUS_UNSUCCESSFUL; } else { DbgPrint("Non PAE page mode.\n"); // 按照Non PAE page mode计算PDE和PTE ulPDEB = ( (((ULONG)pMem)>>20) & 0xFFC ) + 0xC0300000; //cr3寄存器起始地址 ulPTEB = ( (((ULONG)pMem)>>10) & 0x3FFFFC ) + 0xC0000000; bPDEB = MmIsAddressValid((PVOID)ulPDEB); bPTEB = MmIsAddressValid((PVOID)ulPTEB); ulPDE = ( (((ULONG)pMem+5)>>20) & 0xFFC ) + 0xC0300000; ulPTE = ( (((ULONG)pMem+5)>>10) & 0x3FFFFC ) + 0xC0000000; bPDE = MmIsAddressValid((PVOID)ulPDE); bPTE = MmIsAddressValid((PVOID)ulPTE); if ((bPDEB && bPTEB && bPTE)) { DbgPrint("PDE(%d) : 0X%08X -> 0X%08X\n", bPDEB, ulPDEB, *(PULONG)ulPDEB); DbgPrint("PTE(%d) : 0X%08X -> 0X%08X\n", bPTEB, ulPTEB, *(PULONG)ulPTEB); } else return STATUS_UNSUCCESSFUL; }
判断是否开启PAE然后翻译地址
if (bPTE==bPTEB)//物理页面是否存在有效 { *(PULONG)ulPTEB |=0x00000002; //修改PTE使指定页Copy on write机制失效 DbgPrint("The copy-on-write attrib in address 0X%08X has been forbidden!\n", pMem); status = STATUS_SUCCESS; } else { *(PULONG)ulPTEB |=0x00000002; *(PULONG)ulPTE |=0x00000002; DbgPrint("The copy-on-write attrib has been forbidden!\n"); status = STATUS_SUCCESS; } break;
然后把PTE标志位置1就行了。修改完后在Ring3下Ntdll钩子就能作用全局了,不要忘记钩完后记得恢复页面属性。
以上是关于Ring3下绕过Windows写时复制机制实现全局EAT钩子的主要内容,如果未能解决你的问题,请参考以下文章