Windows堆溢出探索

admin 2023年2月3日11:03:01评论15 views字数 4667阅读15分33秒阅读模式

0x00 序言

首先祝各位师傅中秋节快乐!乌托邦送不起月饼,就给各位师傅推送一篇文章吧!

在2004年的Black Hat会议上,NGS(Next Generation Security)安全咨询公司的创始人David Litchfield发表了名为”Windows Heap Overflows“的演讲。我们找到了当时的PPT原文,该演讲对Windows平台的堆内存管理机制做了比较详尽的解读,也是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 结论

希望本演示已经演示了基于堆的缓冲区溢出的危险,开发人员不会将其视为良性的。

Windows堆溢出探索

0x0F 结语

小师傅是个未过英语4级的选手,该文档的翻译大量借助了百度翻译,如有不妥当的地方,请各位师傅在评论区指正批评。

如果想要原文档的师傅,可以在公众号后台回复关键字“我爱你”,获得文档下载链接。


参考:《Windows Heap Overflows》-----David Litchfield

原文始发于微信公众号(乌托邦安全团队):Windows堆溢出探索

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月3日11:03:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows堆溢出探索https://cn-sec.com/archives/1290734.html

发表评论

匿名网友 填写信息