【问题标题】:How does Visual Studio 2013 detect buffer overrunVisual Studio 2013 如何检测缓冲区溢出
【发布时间】:2015-12-07 07:55:04
【问题描述】:

Visual Studio 2013 C++ 项目有一个/GS 开关,用于在运行时启用缓冲区安全检查验证。自升级到 VS 2013 以来,我们遇到了更多的 STATUS_STACK_BUFFER_OVERRUN 错误,并且怀疑这与改进了新编译器中的缓冲区溢出检查有关。我一直在尝试验证这一点并更好地了解如何检测缓冲区溢出。即使由语句更新的内存仅更改同一范围内堆栈上另一个局部变量的内容,也会报告缓冲区溢出,这让我感到困惑!因此,它不仅必须检查更改不会破坏不属于局部变量“拥有”的内存,而且该更改不会影响除分配给单个更新语句引用的局部变量之外的任何局部变量。这是如何运作的?自 VS 2010 以来它是否发生了变化?

编辑: 这是一个示例,说明了 Mysticial 的解释未涵盖的情况:

void TestFunc1();

int _tmain(int argc, _TCHAR* argv[])
{
   TestFunc1();
   return 0;
}

void TestFunc1()
{
   char buffer1[4] = ("123");
   char buffer2[4] = ("456");
   int diff = buffer1 - buffer2;
   printf("%d\n", diff);
   getchar();
   buffer2[4] = '\0';
}

输出为4,表示即将被覆盖的内存在buffer1 的范围内(紧跟在buffer2 之后),但随后程序因缓冲区溢出而终止。从技术上讲,它应该被视为缓冲区溢出,但我不知道它是如何被检测到的,因为它仍在局部变量的存储中,并没有真正破坏局部变量之外的任何东西。

这张带有内存布局的截图证明了这一点。单步执行后,程序因缓冲区溢出错误而中止。

我刚刚在 VS 2010 中尝试了相同的代码,虽然调试模式捕获了缓冲区溢出(缓冲区偏移量为 12),但在释放模式下它确实没有捕获它(缓冲区偏移量8).所以我认为 VS 2013 收紧了/GS 开关的行为。

编辑 2: 我设法用这段代码偷偷通过了 VS 2013 范围检查。它仍然没有检测到更新一个局部变量的尝试实际上更新了另一个:

void TestFunc()
{
   char buffer1[4] = "123";
   char buffer2[4] = "456";
   int diff;
   if (buffer1 < buffer2)
   {
      puts("Sequence 1,2");
      diff = buffer2 - buffer1;
   }
   else
   {
      puts("Sequence 2,1");
      diff = buffer1 - buffer2;
   }

   printf("Offset: %d\n", diff);
   switch (getchar())
   {
   case '1':
      puts("Updating buffer 1");
      buffer1[diff] = '!';
      break;
   case '2':
      puts("Updating buffer 2");
      buffer2[diff] = '!';
      break;
   }
   getchar(); // Eat enter keypress
   printf("%s,%s\n", buffer1, buffer2);
}

【问题讨论】:

  • 相信它所做的一件事是在堆栈对象附近插入虚拟数据,并在方便时检查它们(例如退出函数)。如果数据发生了变化,那么它就知道有什么东西损坏了它。不过这只是我的猜测。
  • @Mysticial 这无法解释我的测试用例,其中我计算了两个局部变量相邻的缓冲区之间的偏移量,但仍然检测到更新第一个变量的溢出。
  • 显示一个例子来说明你的意思,包括变量地址的输出。我很确定 Mystical 是对的。
  • @MatsPetersson 我已经添加了证明必须有更多事情发生的证据。
  • 在 VS2013、Win32、Debug 中,我看到偏移量为 12,间隙用 0xcc 填充。

标签: c++ visual-c++ memory buffer-overflow


【解决方案1】:

您看到了对 /GS 机制的改进,首先添加到 VS2012。最初 /GS 可以检测缓冲区溢出,但仍然存在一个漏洞,攻击代码可以踩踏堆栈但绕过 cookie。大致是这样的:

void foo(int index, char value) {
   char buf[256];
   buf[index] = value;
}

如果攻击者可以操纵 index 的值,那么 cookie 就没有帮助。这段代码现在重写为:

void foo(int index, char value) {
   char buf[256];
   buf[index] = value;
   if (index >= 256) __report_rangefailure();
}

只是简单的索引检查。触发时,如果没有附加调试器,则会立即使用 __fastfail() 终止应用程序。后台is here.

【讨论】:

  • 如果您查看我在编辑 2 下的示例代码,有趣的是,这并没有触发缓冲区溢出。仔细阅读您的链接,我意识到这是因为溢出检查是专门针对 NULL ('\0') 字符触发的!很有趣,谢谢!
  • 似乎很多这样的情况可以在编译时报告。有什么方法可以在编译时报告其中一些情况?
【解决方案2】:

来自 Visual Studio 2013 中 /GS 上的 MSDN page

安全检查

对于编译器识别为存在缓冲区溢出问题的函数,编译器会在返回地址之前的堆栈上分配空间。在函数入口时,分配的空间会加载一个安全 cookie,该 cookie 在模块加载时计算一次。在函数退出时,以及在 64 位操作系统上展开帧期间,调用辅助函数以确保 cookie 的值仍然相同。不同的值表示可能已发生堆栈覆盖。如果检测到不同的值,则终止进程。

更多详情,同页参考Compiler Security Checks In Depth

/GS 的作用

/GS 开关在缓冲区和返回地址之间提供“减速带”或 cookie。如果溢出覆盖了返回地址,它将不得不覆盖放在它和缓冲区之间的 cookie,从而产生新的堆栈布局:

  • 函数参数
  • 函数返回地址
  • 帧指针
  • 饼干
  • 异常处理程序框架
  • 本地声明的变量和缓冲区
  • 被调用者保存寄存器

稍后将更详细地检查 cookie。函数的执行确实会随着这些安全检查而改变。首先,当调用函数时,要执行的第一条指令在函数的序言中。至少,prolog 会为堆栈上的局部变量分配空间,例如以下指令:

sub esp, 20h

该指令留出 32 个字节供函数中的局部变量使用。当使用 /GS 编译函数时,函数 prolog 将额外留出 4 个字节并添加三个指令,如下所示:

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 存储在返回地址正下方的堆栈中的指令.从现在开始,该函数将照常执行。当函数返回时,最后执行的是函数的 Epilog,它与 prolog 正好相反。没有安全检查,它会回收堆栈空间并返回,如以下指令:

add   esp,20h
ret

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

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 指令。

__security_check_cookie 例程很简单:如果 cookie 没有改变,它执行 RET 指令并结束函数调用。如果 cookie 匹配失败,则例程调用 report_failure。然后 report_failure 函数调用 __security_error_handler(_SECERR_BUFFER_OVERRUN, NULL)。这两个函数都在 C 运行时 (CRT) 源文件的 seccook.c 文件中定义。

【讨论】:

  • 正如您在我新添加的示例代码和屏幕截图中看到的那样,我认为您在此处描述的任何内容都无法捕捉到我看到的被抓到的情况。
  • @BlueMonkMN 这不是 VS13 的改进,缓冲区溢出也会在 VS2012 中报告。在这两种情况下,VS 都使用范围检查
  • 对,我关心的变化是在 VS 2012 中引入的,正如 Hans Passant 所指出的那样。我们从 VS 2010 升级到 VS 2013,所以我不确定是哪个版本引入了这种行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-02
  • 1970-01-01
  • 2012-05-19
  • 1970-01-01
  • 2017-06-26
  • 1970-01-01
相关资源
最近更新 更多