将 esp 与全局变量进行异或的目的是什么?(堆栈金丝雀保护)

逆向工程 拆卸 视窗 x86 缓冲区溢出
2021-07-06 00:33:22

我目前正在查看 Win32 可执行文件的主模块中的一个函数。在堆栈上分配内存(sub esp)并在堆栈上保存一些寄存器后,esp 的值与全局变量进行异或。

mov eax, esp
xor eax, DWORD PTR ds:[<some address>]
push eax

我想知道这是否是某种堆栈保护技术?

编辑:我凭记忆写了这个问题。实际的指令序列是:

sub esp, 0xf0
mov eax, DWORD PTR ds:[<some address>]
xor eax, esp
mov DWORD PTR ss:[esp+0xec], eax
2个回答

是的,这是通常称为“堆栈金丝雀”的实现,这是一种堆栈缓冲区溢出保护方法你描述的那个例子是 Visual Studio 使用的方法,自 Visual Studio 2005 以来默认启用,自 Visual Studio 2003 以来实现。它也被调用是GS protection因为 Visual Studio 提供了标志,/GS启用和/GS-禁用,即保护并覆盖默认行为。

什么是堆栈缓冲区溢出保护?

有多种由不同编译器和第三方保护工具实现的堆栈缓冲区溢出保护技术,但它们都围绕着相同的基本思想:当堆栈缓冲区溢出被利用时,攻击者通常会覆盖位于堆栈上的返回地址以重定向代码执行到受控地址。堆栈金丝雀的工作原理是在ret指令前加上某种验证,即堆栈,特别是返回地址,在ret指令执行之前没有被攻击者更改(这将导致从堆栈中弹出并将弹出的值放入指令指针)。

提供的示例如何保护堆栈免受此类攻击?

为了回答这个问题,我们需要提供您刚刚提供的上半部分的完整实现细节。大多数堆栈溢出金丝雀保护通常包括插入函数 prologepilog,而您只提供了前者。

这是一个带注释的示例序言:

sub         esp,    8           //  allocate 8 bytes for cookie
mov eax,    DWORD   PTR ___security_cookie
xor eax,    esp                 //  xor cookie with current esp
mov DWORD   PTR [esp+8],    eax //  save in stack

这个序言从分配堆栈变量的空间开始,就像任何“常规”函数一样。然后它继续获取在进程启动时随机生成的值,并将其存储在特定的内存地址中,放入寄存器EAX,然后xor使用当前堆栈指针对其进行匹配。然后将结果值存储在堆栈中。

和一个评论示例结语:

mov ecx,    DWORD   PTR     [esp+8]  // Read saved cookie
xor ecx,    esp                      // Xor saved cookie, should result in the same value
call        @__security_check_cookie@4  // Call a short function to validate resulting value is legit, and terminate safely otherwise
add         esp,    8

然后,就在函数的ret指令执行之前,在正常缓冲区溢出攻击情况下,它会获取被覆盖的返回地址并执行攻击者控制的代码,验证原始堆栈 cookie 值是否保留,从而触发安全故障,以防万一值与预期的堆栈 cookie 不同。

大多数基于 cookie/canary 的保护的逻辑如下:

  1. 在函数入口处存储一个特定的值,从攻击者的角度来看是确定的但不可预测的。最好是依赖于函数的执行条件(例如当前的 ESP)。该值应放置在函数的返回地址和任何缓冲区溢出潜在缓冲区或变量之间。
  2. 在函数退出时,就在使用可能被覆盖的返回地址之前,验证存储的 cookie 是否与预期相同,如果以某种方式更改则失败。
  3. 由于经典的缓冲区溢出攻击是以顺序方式重写整个堆栈(意味着要覆盖来自堆栈变量的返回地址buf,位于返回地址之间的堆栈上的所有数据buf也可能被覆盖),任何返回地址的覆盖都必须还修改堆栈金丝雀。只要攻击者在触发函数调用之前无法预测金丝雀,由于在执行之前ret执行的金丝雀验证,攻击就会失败

那是基于堆栈的缓冲区溢出的结尾吗?

不,出于多种原因,关于堆栈溢出利用和保护的斗争仍在继续(并且在某些情况下仍在进行中):

  1. 在 Visual Studio 中实现第一个金丝雀保护后不久,针对 SEH 异常结构(分配在堆栈上以处理异常)的攻击开始了,并提供了几个防 SEH 缓冲区溢出保护(例如 SafeSEH,它经历了多个版本,直到它被在防止此类攻击(包括后者SEHOP)方面完全可靠
  2. 此外,信息泄漏错误用于预测(并增加预测机会)金丝雀值,这使得绕过金丝雀检查并使基于堆栈的缓冲区溢出成为可能。
  3. 与 #2 略有相似,但特定于金丝雀保护,在某些情况下,攻击者可以利用进程的执行流程逐字节缓慢地提取金丝雀值,从而将 2**32(4294967296 种可能性)蛮力减少到仅 256 *4(1024 种可能性)蛮力,使许多攻击更加合理。
  4. 还使用了允许非线性覆盖的缓冲区溢出错误,以“跳过”覆盖堆栈金丝雀(或大部分)以完全避免预测金丝雀值的需要,或将修改范围减少到更低的 1 个字节范围。此类常见示例是仅在特定条件下覆盖的外观或仅覆盖双字的 1 个字节的外观。这些也会使返回地址修改受到限制,但仍然有用(有时甚至更多,在某些情况下也使用了 ASLR)。

没错,这就是微软的堆栈溢出保护,俗称“GS cookie”。编译器安全检查深入

当使用 /GS 编译该函数时,函数 prolog 将额外留出四个字节并添加另外三个指令,如下所示:

sub   esp,24h
mov   eax,dword ptr [___security_cookie (408040h)]
xor   eax,dword ptr [esp+24h]
mov   dword ptr [esp+20h],eax

prolog 包含一条获取 cookie 副本的指令,后跟一个对 cookie 和返回地址进行逻辑异或的指令,最后是将 cookie 存储在堆栈中返回地址正下方的指令。从现在开始,该函数将照常执行。当函数返回时,最后要执行的是函数的结语,这与序言相反。

使用 /GS 编译时,安全检查也放置在尾声中:

mov   ecx,dword ptr [esp+20h]
xor   ecx,dword ptr [esp+24h]
add   esp,24h
jmp   __security_check_cookie (4010B2h)

cookie 的堆栈副本被检索,然后跟随着带有返回地址的 XOR 指令。ECX 寄存器应包含与存储在 __security_cookie 变量中的原始 cookie 匹配的值。然后堆栈空间被回收,然后执行 __security_check_cookie 例程的 JMP 指令而不是执行 RET 指令。