Windows内存损坏漏洞的现代探索:Stack Overflow(2)

 2024-01-13 05:00:50  阅读 0

易语言动态链接库教程_易语言api动态调用dll_易语言动态菜单

在上一篇文章中,我们向读者介绍了堆栈溢出漏洞以及当前系统针对此类漏洞提供的缓解措施。 在本文中,我们将继续向读者详细介绍SEH劫持技术。

SEH劫持技术

进程中的每个线程都可以注册一个函数(默认情况下如此),以便在触发异常时调用。 指向这些函数的指针通常存储在堆栈上的结构中。 在任何版本上启动一个32位应用程序时,都会至少注册一个这样的应用程序,并将相关数据存储在堆栈中,如下图所示:

图6 线程初始化过程中,NTDLL默认注册的一个SEH帧

上面突出显示的结构包含一个指向下一个 SEH 记录的指针(也存储在堆栈中),后跟一个指向函数的指针(在本例中是 NTDLL.DLL 库中的函数)。

typedef struct _EXCEPTION_REGISTRATION_RECORD {
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

在内部,指向 SEH 列表的指针存储在每个线程的 TEB 的偏移量 0 处,并且每个列表都链接到下一个。 如果抛出的异常无法正确处理,它将执行下一个异常,依此类推。

易语言动态菜单_易语言api动态调用dll_易语言动态链接库教程

图7 SEH链的堆栈布局

因此,SEH实际上为攻击者提供了一种理想的绕过堆栈的方法。 我们可以利用堆栈溢出,覆盖现有的 SHE(肯定至少有一个),并使应用程序崩溃(考虑到我们有能力破坏堆栈内存,这当然是理所当然的事情)。 这将导致在易受攻击的函数最终调用 KIE 之前,EIP 被重定向到结构中被覆盖的地址。 因此,应用程序在执行之前根本没有机会发现其堆栈已损坏。

#include
#include
#include
void Overflow(uint8_t* pInputBuf, uint32_t dwInputBufSize) {
char Buf[16] = { 0 };
memcpy(Buf, pInputBuf, dwInputBufSize);
}
EXCEPTION_DISPOSITION __cdecl FakeHandler(EXCEPTION_RECORD* pExceptionRecord, void* pEstablisherFrame, CONTEXT* pContextRecord, void* pDispatcherContext) {
printf("... fake exception handler executed at 0x%p\r\n", FakeHandler);
system("pause");
return ExceptionContinueExecution;
}
int32_t wmain(int32_t nArgc, const wchar_t* pArgv[]) {
uint32_t dwOverflowSize = 0x20000;
uint8_t* pOverflowBuf = (uint8_t*)HeapAlloc(GetProcessHeap(), 0, dwOverflowSize);
printf("... spraying %d copies of fake exception handler at 0x%p to the stack...\r\n", dwOverflowSize / 4, FakeHandler);
for (uint32_t dwOffset = 0; dwOffset < dwOverflowSize; dwOffset += 4) {
*(uint32_t*)&pOverflowBuf[dwOffset] = FakeHandler;
}
printf("... passing %d bytes of data to vulnerable function\r\n", dwOverflowSize);
Overflow(pOverflowBuf, dwOverflowSize);
return 0;
}

图 8 具有自定义 SEH 的喷射堆栈,覆盖现有结构

图9 堆栈溢出并覆盖现有默认SEH ON

我们没有在 EXE 中的函数上设置断点,而是得到一个异常(代码)。 这是派生出的安全缓解异常。 是一种仅适用于 32 位 PE 文件的安全缓解措施。 在 64 位 PE 文件中,有一个名为 . GS功能在2003年版本中发布,随后在2005年版本中成为默认设置。

易语言动态菜单_易语言api动态调用dll_易语言动态链接库教程

它是什么以及它是如何工作的?

2019年为默认设置。 它是使用 / 标志进行配置的,我们可以在 -> -> -> -> Image Has Safe 中进行相应设置。 编译后的 PE 文件在名为 的表中包含有效 SEH 地址的列表,我们可以在其数据目录中指定该表。 每当触发异常时,在执行链表中每个链接的地址之前,都会检查是否在图像内存的范围内(表明与加载的模块有关)。 如果是,则根据该地址进行检查。 相关模块是否有效。

在图 8 中,我们通过堆栈溢出来注册它,以这种方式创建的内容不会被编译器识别(因此不会添加到其中)。 通常,编译器会将作为 __try 语句的副作用创建的表添加到该表中。 禁用后,再次运行该代码将导致堆栈溢出并执行注入的代码。

易语言动态菜单_易语言动态链接库教程_易语言api动态调用dll

图10 堆栈溢出,导致执行一个假SEH,该SEH被编译到PE EXE镜像的主镜像中

当然,虽然从2005年开始就默认启用了,但在现代应用程序中是否仍然存在禁用加载的PE代码呢? 在我自己探索这个问题时,我编写了一个 PE 文件扫描工具来检测整个系统范围内的每个文件是否存在(或缺乏)漏洞缓解措施。 当我使用此扫描工具处理 10 个虚拟机上的文件夹(并筛选出非 PE)时,结果令人大跌眼镜。

易语言api动态调用dll_易语言动态链接库教程_易语言动态菜单

图10 虚拟机上文件夹PE缓解措施扫描统计

看来微软本身也有相当多的非PE,尤其是今天仍然附带的DLL。 扫描我的文件文件夹后,结果更有说服力。 大约 7% 的 PE 文件缺乏保护。 事实上,虽然我的虚拟机上安装的第三方应用程序很少,但几乎每个应用程序从 7-zip 到 Text 到 Tools 都至少包含一个非模块。 即使进程的地址空间中只有一个这样的模块也足以绕过其堆栈缓解措施并使用本文中探讨的技术利用堆栈溢出漏洞。

值得注意的是,PE 在两种不同的情况下可以被认为是有效的,如下所示,这是我的工具在扫描时使用的标准:

数据目录中存在上述和大于零的条件。 该标志设置在 R. 字段中。

假设将未受保护的模块加载到易受攻击的应用程序中,对于作者来说仍然存在很大的障碍。 返回到图 10,虽然通过堆栈溢出成功执行了一个伪造的 SEH,但该 SEH 本身被编译到 PE EXE 映像中。 因此,为了实现任意代码执行,我们需要执行一个存储在堆栈上的假SEH(一)。

DEP&ASLR

由于DEP和ASLR防御机制的存在,使用我们的作为堆栈上的伪异常存在多个障碍:

由于ASLR机制,我们不知道堆栈上的地址,因此我们无法将其嵌入到溢出内容中以喷射到堆栈上。 由于DEP机制的存在,栈本身和扩展默认是不可执行的。

随着 2004 年 XP SP2 的推出,DEP 首次在世界范围内得到广泛采用,从那时起,DEP 已成为当今使用的几乎所有现代应用程序和操作系统的共同功能。 它是通过使用硬件层内存页的PTE头中的一个特殊位(NX,也称为不可执行位)来实现的,该位在其中所有新分配的内存上都会被默认设置。 这意味着攻击者必须显式创建可执行内存区域,方法是通过 .DLL! 等 API 分配具有可执行权限的新内存,或者使用 .DLL! 等 API 复制现有的不可执行内存区域。 可执行内存被修改为可执行。 这样做的一个副作用是,由于默认情况下堆栈和堆不可执行,因此我们无法直接从这些位置执行。 也就是说,我们首先要为其开辟一块可执行的内存区域。

从写作的角度来看,理解 DEP 的关键在于 DEP 是一种全有或全无的缓解措施:它要么适用于进程内的所有内存,要么不适用于进程内的所有内存。 如果生成进程的主 EXE 是使用 / 标志编译的,则整个进程将启用 DEP。 与 DEP 或 ASLR 等缓解措施相比,不存在非 DEP DLL 模块之类的东西。

易语言动态链接库教程_易语言api动态调用dll_易语言动态菜单

从写作的角度来看,DEP 的解决方案长期以来被理解为面向返回的编程(ROP)。 原则上,现有的可执行内存将与攻击者提供的堆栈一起以小片段的形式回收,以实现我们划分可执行区域的目标。 在创建自己的 ROP 链时,我选择使用 .DLL! API使存储的堆栈区域可执行。 这个API的原型如下:

BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORDflNewProtect,
PDWORD lpflOldProtect
);

在ASLR出现之前,如果可以通过溢出来控制堆栈,则可以将这五个参数作为常量植入到堆栈中,然后触发EIP重定向以指向.DLL中的函数(其基地址是静态的)。 这里唯一的问题是 - 我们不知道作为第一个参数传递或用作返回地址的确切地址。 后来攻击者通过使用NOP技术(在其前面填充一个大NOP指令,即0x90)解决了这个问题。 然后,编写者可以推断堆栈中的粗略区域,并选择该范围内的地址并将其直接嵌入到溢出内容中,通过 NOP sled 将这种猜测转化为精确的代码执行。

随着 2006 年 Vista 中 ASLR 的出现,ROP 链的创建变得有点棘手,因为现在:

DLL的基址和基地址变得不可预测。 地址很难猜。 包含可执行代码片段的模块的地址变得不可预测。

这不仅对ROP链提出了更多要求,也要求其实现更加精准。 因此,NOP雪橇(1996年左右的经典形式)成为了ASLR时代的牺牲品。 这也导致ASLR旁路技术成为DEP旁路技术的先决条件。 如果不绕过ASLR从而定位到漏洞进程中至少一个模块的基地址,则无法知道ROP的地址,无法执行ROP链,也无法调用函数来绕过DEP。

要创建现代 ROP 链,我们首先需要一个可以在运行时预测其基地址的模块。 在大多数现代利用技术中,这是通过使用内存泄漏漏洞来实现的(该主题将在字符串畸形和堆损坏系列的续集中探讨)。 为了简单起见,我选择在易受攻击的进程的地址空间(来自我的 10 个虚拟机的目录)中引入一个非 ASLR 模块。 在继续之前,了解非 ASLR 模块背后的概念(以及它们在编写过程中的作用)非常重要。

从写作的角度来看,以下是我认为最有价值的 ASLR 概念:

2019 年,ASLR 为默认设置。 它是使用 / 标志进行配置的,我们可以在项目设置的 -> -> -> -> Base 字段中进行配置。 当使用此标志编译 PE 文件时,它(默认情况下)总是会创建一个数据目录(存储在 PE 文件的 .reloc 部分中)。 如果没有此重定位信息,就不可能重建模块的基地址并执行 ASLR。 编译后的 PE 将在其 R. 标头中设置该标志。 当加载 PE 时,会为其选择一个随机基地址,并使用重定位部分重定位其代码/数据中的所有绝对地址。 这个随机地址在每次启动时都是不同的。 如果用于启动进程的主PE(EXE)启用了ASLR,也会导致堆栈和堆被随机化。

您可能会注意到,这实际上会导致两种不同的情况,其中可能会出现非 ASLR 模块。 第一种情况是当模块被显式编译以排除 ASLR 标志时(或在该标志存在之前编译),第二种情况是当设置了 ASLR 标志但由于缺少重定位而无法应用时。

开发人员的一个常见错误是在编译器中将 ASLR 标志与“strip”选项结合使用,认为生成的二进制文件受到 ASLR 保护,而实际上它仍然容易受到攻击。 从历史上看,非 ASLR 模块非常常见,甚至在 Web 浏览器攻击中被滥用,并在商业恶意软件中取得了巨大成功。 如今此类模块变得越来越稀缺,很大程度上是因为 ASLR 已成为 IDE 等 IDE 中默认启用的安全缓解措施。 令人惊讶的是,我的扫描软件在我的虚拟机上发现了大量非 ASLR 模块,其中许多位于 和 目录中。

易语言动态链接库教程_易语言api动态调用dll_易语言动态菜单

图12 在我的10虚拟机的目录中找到的非ASLR模块

值得注意的是,图 12 中所示的所有非 ASLR 模块都具有非常不同(且唯一)的基地址。 这些是已编译的 PE 文件,不打算使用 ASLR,很可能是出于性能或兼容性原因。 它们将始终加载到 r 中指定的图像基地址。 (图 12 中突出显示的值)。 显然,这些唯一的图像基地址是编译器在创建时随机选择的。 通常,PE文件在其PE头中包含默认的图像基地址值,例如(对于EXE)和(对于DLL)。 这种特意创建的非 ASLR 模块与错误创建的非 ASLR 模块形成鲜明对比(如下图 13 所示)。

图 13 在我的 10 个虚拟机的“Files”目录中找到的非 ASLR 模块

这是在最新版本的 HXD Hex 中作为重定位剥离(毫无戒心的开发人员的旧优化习惯)的副作用而创建的非 ASLR 模块的一个主要示例。 值得注意的是,您可以在上面的图 13 中看到,与图 12 中的模块(具有随机基地址)不同,这些模块都具有相同的默认映像基地址(已编译到其 PE 标头中间)。 这与 PE 标头中存在的标志相结合,表明编译它们的开发人员假设它们将使用随机地址而不是 at 加载,并认为它们将受到 ASLR 机制的保护。 然而,在实践中,我们可以确定它们总是加载到该地址,即使启用了 ASLR - 因为操作系统无法在初始化期间重置它们的基地址而不重新定位数据。

通过回收非ASLR模块的可执行部分(通常是.text部分)中的代码,我们能够构造相应的ROP链来调用.DLL! API并禁用堆栈的DEP保护机制。

从图12中可以看出,我选择了ROP链中的非ASLR模块.dll,因为它不仅缺乏ASLR保护,而且还缺乏(考虑到我们必须了解写入堆栈的假SEH)溢出/堆栈枢轴地址,这是一个至关重要的细节)。 另外,.DLL! 也是通过IAT进口的。 这个细节大大简化了ROP链的创建过程。 这将在下一篇文章中深入讨论。

概括

在本文中,我们向读者详细介绍了SEH劫持技术,以及DED和ASLR防御机制。 在接下来的文章中,我们将继续向读者讲解如何创建ROP链。

标签: 堆栈 模块 溢出

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码