0x00 序言
首先祝各位师傅中秋节快乐!乌托邦送不起月饼,就给各位师傅推送一篇文章吧!
在2004年的Black Hat会议上,NGS(Next Generation Security)安全咨询公司的创始人David Litchfield发表了名为”Windows Heap Overflows“的演讲。我们找到了当时的PPT原文,该演讲对Windows平台的堆内存管理机制做了比较详尽的解读,也是Windows堆学习的经典文献资料,所以我们对原文进行了翻译,邀诸位师傅一同学习。
0x01 介绍
本演示将探讨如何在Windows平台下利用堆缓冲区溢出。堆溢出已经在*nix平台上得到了很好的验证,例如Matt Connover的论文–w00w00关于堆溢出,但它们在Windows上没有得到很好的记载,尽管Halvar Flake的第三代漏洞利用论文涵盖了关键概念。
0x02 基于堆的缓冲区是安全的?????
大多数开发人员都意识到基于堆栈的缓冲区溢出的危险,但仍有太多人认为,如果基于堆的缓冲区发生溢出,这不是太大的问题。
一篇关于安全编码的论文建议,解决基于堆栈的溢出问题的方法是将缓冲区移到堆中!
0x03 什么是堆?
堆是用于存储动态数据的内存区域。每个进程都有一个默认的进程堆,但开发人员可以创建自己的私有堆。从堆中分配空间,并在完成时释放。
0x04 堆函数
堆的函数有:GlobalAlloc、LocalAlloc和HeapAlloc。
但是三个函数的底层都是基于RtlAllocateHeap函数。
0x05 堆设计
每个堆从一个结构开始。除其他数据外,该结构包含128个LIST_ENTRY结构的数组。每个LIST_ENTRY结构包含两个指针——参见winnt.h。这个数组可以在堆结构的0x178字节处找到——称之为FreeList数组。
PS:在看雪《0day安全:软件漏洞分析技术》中,FreeList也称为空表,是空闲双向链表。
第一次创建堆时,有两个指针指向FreeList[0]中设置的第一个空闲块。假设堆基址为0x00350000,则可以在0x00350688处找到第一个可用块。
0x06 那么问题在哪里呢?
当基于堆的缓冲区溢出时,控制信息会被覆盖,因此当缓冲区(分配的块)被释放时,在更新自由列表数组中的指针时,将发生访问冲突。
示例:参见代码A-heap.c中的代码
破坏访问
77F6256F mov dword ptr [ecx],eax
77F62571 mov dword ptr [eax+4],ecx
EAX = 0x42424242
ECX = 0x42424242
如果我们同时拥有EAX和ECX,则会有任意的DWORD覆盖。我们可以用我们选择的32位值覆盖任何32位地址的数据。
0x07 利用堆溢出
-
修复堆
-
未处理的异常筛选器
-
PEB函数指针
-
向量异常处理
-
线程环境块
0x08 修复堆
溢出后,堆已损坏,因此需要修复堆。
许多Windows API调用使用默认进程堆,如果该堆已损坏,则利用漏洞将访问冲突。
可以根据每个漏洞/利用进行修复。耗时且可能会遇到问题。
需要一种对所有攻击都有效的通用方法来修复堆。写一次并重复使用。
修复堆的最佳方法是重置堆,使其“看起来”像一个新的堆。这将保持其他堆数据完整,但允许新的分配。
我们用heap重置溢出堆控制结构。TotalFreeSize并将标志设置为0x14,然后设置heap.FreeList[0]。Flink和heap.自由列表[0]。闪烁到伪控制结构的开始。
请参阅B中的代码–asm修复堆。
PS:很抱歉,没找到这段代码,找到的师傅可以留言回复一下。
0x09 利用漏洞:使用未处理的异常过滤器
未处理的异常筛选器方法是最常用的方法。UEF是“最后的努力”异常处理程序。
位置因操作系统和SP而异。反汇编SetUnhandledExceptionFilter函数。
77E7E5A1 mov ecx,dword ptr [esp+4]
77E7E5A5 mov eax,[77ED73B4]
77E7E5AA mov dword ptr ds:[77ED73B4h],ecx
77E7E5B0 ret 4
UEF = 0x77ED73B4
发生未处理的异常时,执行以下代码块:
77E93114 mov eax,[77ED73B4]
77E93119 cmp eax,esi
77E9311B je 77E93132
77E9311D push edi ***
77E9311E call eax
该方法的本质是设置我们自己的未处理异常过滤器。
EDI被推到堆栈上。超过EDI的0x78字节是指向缓冲区末尾的指针——就在堆管理控制填充之前。
将UEF设置为指向 CALL DWORD PTR [EDI + 0x78]
例如,可以在netapi32.dll、user32.dll和rpcrt4.dll中找到许多。
注意:其他操作系统可能不使用EDI。例如,Windows 2000的指针位于ESI+0x4C和EBP+0x74。
使用此方法时,您需要了解目标系统,即什么操作系统和什么SP级别。
示例:请参见清单C中的代码——堆uef.c和代码清单D-利用uef.c
PS:依然没找到代码啊!!!!!!!!
0x0A 漏洞利用:使用矢量异常处理
矢量异常处理是Windows XP中的新功能。
与传统的基于帧的异常处理不同,其中exception_REGISTRATION结构存储在堆栈上,关于VEH的信息存储在堆上。
指向第一个向量化异常处理程序的指针存储在0x77FC3210。指向_Vectored_Exception_NODE。
struct _VECTORED_EXCEPTION_NODE
{
DWORD m_pNextNode;
DWORD m_pPreviousNode;
PVOID m_pfnVectoredHandler;
}
向量处理程序在任何基于帧的处理程序之前调用!该技术涉及用指向伪VE节点的指针重写指向0x77FC3210处第一个_VECTORED_EXCEPTION_NODE的指针。
77F7F49E mov esi,dword ptr ds:[77FC3210h]
77F7F4A4 jmp 77F7F4B4
77F7F4A6 lea eax,[ebp-8]
77F7F4A9 push eax
77F7F4AA call dword ptr [esi+8]
77F7F4AD cmp eax,0FFh
77F7F4B0 je 77F7F4CC
77F7F4B2 mov esi,dword ptr [esi]
77F7F4B4 cmp esi,edi
77F7F4B6 jne 77F7F4A6
调用向量化异常处理程序的代码。
需要在堆栈上找到指向缓冲区的指针。假设它可以在0x0012FF50处找到。这将成为我们的m_pfnVectoredHandler,使我们的伪_VECTORED_EXCEPTION_NODE地址为0x0012FF48。
记住,在释放的时候,我们会得到任意的DWORD覆盖:
77F6256F mov dword ptr [ecx],eax
77F62571 mov dword ptr [eax+4],ecx
我们将EAX设置为0x77FC320C,将ECX设置为0x0012FF48。
0x77FC320C移动到0x0012FF48,然后0x0012FF48移动到0x77FC3210–因此设置了指针。当异常发生时,0x0012FF48(我们的伪VEN)被移到ESI中,并调用DWORD PTR[ESI+8]。ESI+8是指向缓冲区的指针。
注意:如果堆栈的位置(因此指向缓冲区的指针)移动,则此方法可能不可靠。
示例:参见清单E中的代码——E – heap-vector.c和F–利用向量c
PS:求代码!!!!
0x0B 漏洞利用:PEB中的RtlEnterCriticalSection指针
每个进程包含一个称为进程环境块或PEB的结构。可以从线程信息/环境块TIB/TEB引用PEB。FS:[0]指向TEB。
mov eax,dword ptr fs:[0x30]
mov eax,dword ptr fs:[eax+0x18]
除了包含其他过程特定数据外,PEB还包含一些指向RTLlenterCriticalSection和RtlLeaveCriticalSection的指针。这些指针引用自RtlAccquirePebLock和RtlReleasePebLock。例如,从ExitProcess调用RTLACquirePeBlock。
在Windows NT 4/2000/XP中,PEB的位置是稳定的,因此可以在0x7FFDF020处找到指向RtlEnterCriticalSection的指针。虽然在Windows 2003中可以在同一地址找到PEB,但函数指针不再存在,因此此方法无法在2003中使用。
该方法简单地涉及用将返回缓冲区的指令地址重写PEB中指向RtlEnterCriticalSection的指针。
示例:请参见清单G中的代码——堆peb.c和H–利用peb.c
PS:我累了,随意吧!
0x0C 漏洞利用:TEB异常处理程序指针
每个线程环境块包含指向第一个基于帧的异常处理程序的指针。第一个线程的TEB有一个基地址0x7FFDE000,每个新线程的TEB被分配一个地址,该地址向0x00000000增长。如果一个线程退出并创建了一个新线程,则它将获得前一个线程的TEB的地址。
这可能导致“混乱”的TEB表,并可能使该方法不确定。
但是,如果易受攻击线程的TEB地址稳定,则可以非常有效地使用该方法。
该方法涉及用指向指令的地址重写指向TEB中第一个异常处理程序的指针,该指令将获得返回缓冲区的执行路径。
0x0D 开发:创造!
还有其他方法可以利用基于堆的缓冲区溢出来执行任意代码,以击败将堆标记为不可执行的机制。
假设我们有一个堆被标记为不可执行的进程。这可以通过指针颠覆来解决。
在UnhandledExceptionFilter()函数的故障报告功能中可以找到一个例子。
故障报告代码调用GetSystemDirectoryW(),其中“faultrep.dll”连接到该代码。加载此库并调用ReportFault()函数。
GetSystemDirectoryW()引用kernel32.dll的.data部分中的指针,该指针指向可以找到Windows系统目录的宽字符串的位置。该指针位于0x77ED73BC。在溢出时,我们可以将该指针设置为我们自己的系统目录。
因此,当调用GetSystemDirectoryW()时,“系统”目录是攻击者拥有的目录,甚至可以是UNC路径。攻击者将创建自己的faultrep。导出ReportFault()函数的dll,因此当调用UnhandledExceptionFilter()函数时,可以执行任意代码。
虽然代码路径是有限的,但我认为可以做什么的可能性更多地受到想象力的限制。
0x0E 结论
希望本演示已经演示了基于堆的缓冲区溢出的危险,开发人员不会将其视为良性的。
0x0F 结语
小师傅是个未过英语4级的选手,该文档的翻译大量借助了百度翻译,如有不妥当的地方,请各位师傅在评论区指正批评。
如果想要原文档的师傅,可以在公众号后台回复关键字“我爱你”,获得文档下载链接。
参考:《Windows Heap Overflows》-----David Litchfield
原文始发于微信公众号(乌托邦安全团队):Windows堆溢出探索
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论