漏洞分析中常用的堆调试支持

  我们在分析堆漏洞,如堆溢出、UAF等时常常会启用一些堆的调试支持。可能很多人都用过这些功能,比如gflags.exe,比如在分析UAF时使用的+UST +DPH等等。但是却很少有人了解这些东西到底是什么、是怎样发挥的作用。这里我就来讲解一下这几个调试机制。内容参考自《软件调试》,顺带说一句,个人感觉软件调试是一本不可多得的好书,作者不仅精通内核而且对用户层也有深入的了解,个人感觉可以和Windows Internal一起列为必看的书目了。

  首先讲怎么开启堆调试机制,我们最熟悉的方法是使用gflags.exe /i 程序名.exe +具体的标志类型。但是事实上是从注册表中读取的相应进程名的键值。也就是说gflags无非就是在注册表中新建了键值。在Windbg挂载状态下也可以使用!gflag +标识 来启用。

  在讲解具体的调试支持之前要明确一点,堆调试支持是由堆分配机制提供的。可能有些读者不太清楚堆分配与内存分配有什么区别。这里简单讲解一下,事实上,一个进程的虚拟地址空间是由一个“树”的数据结构进行描述的,分配一块虚拟内存就是在树上增加节点。而堆分配机制是先申请一块虚拟内存再利用自己的机制去分发这些内存。所以说堆是完全的应用层的行为,在内核来看,堆是不可见的,是透明的,内核只管进程虚拟地址的数据结构是怎么样的。

  之前在读一些书的时候听到一个名词叫做调试堆,意思是调试下的堆与非调试下的堆是不一样的,哪里不一样也没能搞清楚。这次明白了,原来在调试状态下会自动启用htc hfc hpc。挂载进程后,在Windbg中用!gflag命令即可看到有哪些标志位。此外,对于一个进程来说,堆信息是储存在PEB中的,在进程的PEB中有堆表和堆计数,堆计数统计了堆的数量,而堆表中储存有每个堆的地址。

      • hfc:是用来防止二次释放的,如果发生了二次释放的话,二次释放会被阻止。如果此时有调试器挂载,那么就会抛出一个断点。
      • ust:用来追踪堆的分配过程。很多时候我们找到了一块堆内存,却不知道这个堆内存是谁分配的。ust就是用来解决这个问题的,ust有自己的一块内存区作为数据库。每次堆分配函数被调用时,分配函数的栈回溯都会被保存进数据库,这样,想知道堆是谁分配的就可以看栈回溯了。
      • hvc:用来对于堆块的结构进行全面验证,可以检测出堆溢出。如果一个堆块的头被破坏,再次分配这个堆块时,hvc就会对这个分配操作抛出断点(仅在有调试器的情况下)
      • htc:在堆尾增加8个字节的验证字节。用于防止堆溢出。当这个堆块被释放(Free)时,会验证堆尾的8个字节,如果发现被篡改,就会抛出异常

  hpa页堆结构也是一种可以用来检测堆溢出的机制,而且有以上几个所不具有的优点。上述的几个检测堆溢出的机制都是当发生了涉及溢出堆的堆操作之后才会进行检查产生断点。这样的话,得到一个异常的时候早就不在溢出的第一现场了,这样就很难得到到底是在哪一条指令造成的溢出。hpa机制则是提供了完全不一样的堆管理机制,优点是一但发生溢出马上就会抛出异常,可以直接发现是哪一条指令引发的溢出。页堆的具体实现原理是在分配的堆后面紧挨着分配一个不可访问属性的内存页,这样的话,如果越界马上就异常掉了。这么做的基础是因为Windows系统的内存管理的基本单位就是内存页,一般的内存页是4KB。对于我们来说的字节、DWORD等对于内存管理来说是不存在的,页是操作的最小单位,这一点可以追溯至内存的分页机制。总之,想要把一段内存设为不可执行,那么最少也要是一个页的内存,这一点与硬盘的扇区是一样的,硬盘一次读是不能读一个字节的,至少也要读一个扇区才行。

  如果用户的堆不满一个内存页,就把它分配在第一个页的末尾。这样的话就实现了一但溢出就异常的功能。同时在堆的整体头部也实现了一些数据结构来维护信息。对于每个堆块来说,有一个DPH_HEAP_BLOCK结构

posted @ 2016-06-21 11:59  Ox9A82  阅读(1577)  评论(0编辑  收藏  举报