微软在堆中也增加了一些安全校验操作,使得原本是不容易的堆溢出变得困难重重:
* PEB Random:在 Windows XP SP2 之后,微软不再使用固定的 PEB 基址 0x7FFDF000,而是使用具有一定随机性的基址,从而影响了 DWORD SHOOT 对 PEB 中函数的攻击。
* Safe Unlink:微软改写了操作双向链表的代码,在卸载 free list 中的堆块时更加小心。SP2 在进行删除操作时,提前验证堆块的完整性,以防止 DWORD SHOOT:
1 int safe_remove(ListNode * node) 2 { 3 if( (node->blink->flink==node)&&(node->flink->blink==node) ) 4 { 5 node -> blink -> flink = node -> flink; 6 node -> flink -> blink = node -> blink; 7 return 1; 8 } else { 9 // raise exception 10 return 0; 11 } 12 }
* Heap Cookie:与栈中类似,堆中也引入了 cookie,用于检测堆溢出的发生。cookie 布置在堆首中原堆块的 segment table 的位置,占 1 字节:
* 元数据加密:Windows Vista 及后续版本的系统中开始使用这项措施。块首中的一些重要数据在保存时会与一个 4 字节的随机数进行异或加密,使用时再异或解密。这样就不能直接破坏这些数据了。
堆的研究者之一 Matt Conover 在 CanSecWest 04 的演讲议题 Windows Heap Exploitation (Win2K SP0 through WinXP SP2) 中,针对 PEB random 机制,指出变动只是在 0x7FFDF000 ~ 0x7FFD4000 之间,随机区间不大,在多线程状态下容易被预测出来。
而 Heap Cookie 只占 1 字节,在研究其生成随机的算法之后仍存在破解可能。
对于 Safe Unlink 也有人找到了一些破解思路。
但这些突破的思路要在 XP SP2 之后成功实施并利用,需要十分苛刻的条件,堆溢出变得难如登天。
溢出堆中的数据
但堆保护措施是对堆的各个关键数据结构进行保护,对堆中的数据不提供保护,所以攻击的第一个思路,是溢出堆中存放的关键数据结构:重要变量、数据、函数指针…
利用 chunk 重设大小攻击堆
Safe Unlink 是从 FreeList[n] 上拆卸 chunk 时对双向链表进行验证,但是,将一个 chunk 插入到 FreeList[n] 时没有进行校验!如果能伪造一个 chunk 并将其插入到 FreeList[n] 上就可以造成某种攻击。如下两种情况会发生插入操作:
1 内存释放后 chunk 不再被使用时。 2 当 chunk 的内存空间大于申请的大小,剩余的空间会被建成一个新的 chunk 链入链表中。
上述第二种情况提供了可以利用的机会。先考虑申请 chunk 的过程,从 FreeList[] 上申请空间的过程如下:
1 将 FreeList[0] 上最后一个 chunk 与申请的大小进行比较,如果 chunk 的大小 ≥ 申请的大小,则继续分派,否则扩展空间(若超大堆块链表无法满足分配,则扩展堆) 2 从 FreeList[0] 的第一个 chunk 依次检测,直到找到第一个符合要求的 chunk,然后卸载 3 分配好空间后,如果 chunk 有剩余空间,剩余空间会建成新的 chunk 并插入到链表中
这个过程中,第一各情况没有机会,第二种情况有 Safe Unlink 进行保护。但 Safe Unlink 存在一个问题:即使 Safe Unlink 检测到 chunk 结构被破坏,还是会允许后续的一些操作,如重设 chunk 大小。