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钩子就能作用全局了,不要忘记钩完后记得恢复页面属性。

时间: 2024-07-31 14:22:31

Ring3下绕过Windows写时复制机制实现全局EAT钩子的相关文章

php变量之写时复制机制(copy on write)

编程思想虽然可以共用,不过语言间的差异还是比较明显的,只是使用者之间没有意识到而己,而了解其中的差异对于编写程序以及把握性能还是有好处的.下面我们来介绍下PHP的一个很重要的机制copy on write,我们先以最简单的变量来介绍这个机制,在说这个之前,笔者先来介绍下弱类型是怎么实现的. 大家都知道,PHP是由C实现的,可是C是强类型语言,PHP怎么做到弱类型语言.一起来看下,PHP变量在C语言低层中的代码, typedef struct _zval_struct zval; typedef

再谈QVector与QByteArray——Qt的写时复制(copy on write)技术

Qt作为一个优秀的跨平台开源C++框架,如果我们只停留在使用它的基础上而不深挖其实现手法,实在是浪费这个知识宝库了~我们在之前的博文QVector的内存分配策略与再谈QVector与std::vector--使用装饰者让std::vector支持连续赋值中简单聊了聊QVector内存分配和赋值方面的一点东西,今天接着从QVector展开谈谈Qt的写时复制技术.老实说,"隐式共享,引用计数,写时复制"也是老调重弹的话题了,不过也是QTL与STL最大的区别之一,这篇博文不详谈"写

php引用和写时复制

在php变量中已经发现 zval结构体中有refcount__gc(引用个数) 和 is_ref__gc(是否被引用) 例如: <?php $a="hello world"; ?> 此时PHP会创建一个zval容器 因为这个变量不是一个引用 所以这个容器的is_ref__gc为false 并且refcount__gc为1 再看下面的代码 <?php $a="hello world"; $b=$a; ?> 这里由于$b并不是引用$a 所以这里的

php变量的引用计数器和写时复制

众所周知,PHP是不支持指针的,但是如果希望两个变量同时指向同一内存块怎么办呢?为了解决这个问题,PHP内核里使用了引用计数器. 上篇博文介绍了PHP变量在内核中的存储方式了,zval结构中下面两个成员变量用于引用计数器: is_ref BOOL值,标识变量是否是引用集合. refcount 计算指向引用集合的变量个数. 看下面的php代码 <?php $a = "this is a"; ?> 一个zval结构的实体称为zval容器.在php语言层创建一个变量就会相应地在p

47-引用计数与写时复制

47-引用计数与写时复制 对于PHP这种需要同时处理多个请求的程序来说,申请和释放内存的时候应该慎之又慎,一不小心便会酿成大错.另一方面,除了要安全申请和释放内存外,还应该做到内存的最小化使用,因为它可能要处理每秒钟数以千计的请求,为了提高系统整体的性能,每一次操作都应该只使用最少的内存,对于不必要的相同数据的复制则应该能免则免.我们来看下面这段PHP代码: <?php $a = 'Hello NowaMagic!'; $b = $a; unset($a); ?> 第一条语句执行后,PHP创建

用户空间缺页异常pte_handle_fault()分析--(下)--写时复制【转】

转自:http://blog.csdn.net/vanbreaker/article/details/7955713 版权声明:本文为博主原创文章,未经博主允许不得转载. 在pte_handle_fault()中,如果触发异常的页存在于主存中,那么该异常往往是由写了一个只读页触发的,此时需要进行COW(写时复制操作).如当一个父进程通过fork()创建了一个子进程时,子进程将会共享父进程的页框.之后,无论是父进程还是子进程要对相应的内存进行写操作,都要进行COW,也就是为自己重新分配一个页框,并

php 垃圾回收机制----写时复制和引用计数

PHP使用引用计数和写时复制来管理内存.写时复制保证了变量间复制值不浪费内存,引用计数保证了当变量不再需要时,将内存释放给操作系统. 要理解PHP内存管理,首先要理解一个概念----符号表. 符号表的概念: 一个变量有两部分组成:变量名和变量值.而符号表就是将变量名映射到内存中变量值所在地址的数组. 写时复制: 当一个变量的值复制到另一个变量时,PHP没有为复制值使用更多的内存.相反,他会跟新符号表来说明这两个变量拥有相同的内存块.所以下面的代码实际上并没有创建新数组: <?php $peopl

PHP写时复制

原文:http://www.huamanshu.com/blog/2014-05-18.html 起源 写时复制英文名字叫“copy-on-write”,简称“cow”,又名“靠” 今天查了下这个"cow",原来起源于*nix内存管理机制,后来广泛应用在其它开发语言上,C++的STL,还有PHP,相信很多人都了解这个写时复制,经常跟别人侃得甚欢. $foo = 'foo'; $bar = $foo; 不会 初写这样的PHP代码时,常会问,这样的话会不会因为复制而占用更多内存,旁边会有人

JAVA中写时复制(Copy-On-Write)Map实现

1,什么是写时复制(Copy-On-Write)容器? 写时复制是指:在并发访问的情景下,当需要修改JAVA中Containers的元素时,不直接修改该容器,而是先复制一份副本,在副本上进行修改.修改完成之后,将指向原来容器的引用指向新的容器(副本容器). 2,写时复制带来的影响 ①由于不会修改原始容器,只修改副本容器.因此,可以对原始容器进行并发地读.其次,实现了读操作与写操作的分离,读操作发生在原始容器上,写操作发生在副本容器上. ②数据一致性问题:读操作的线程可能不会立即读取到新修改的数据