繁体   English   中英

Visual Studio 2013如何检测缓冲区溢出

[英]How does Visual Studio 2013 detect buffer overrun

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);
}

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

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

如果攻击者可以操纵索引的值,则cookie无效。 此代码现在重写为:

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

只是简单的索引检查。 当触发时,如果没有附加调试器,则会立即使用__fastfail()终止应用程序。 Backgrounder 就在这里

从Visual Studio 2013中/GS上的MSDN页面

安全检查

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

有关更多详细信息,同一页面指的是编译器安全检查深度

什么/ GS做

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

  • 功能参数
  • 功能返回地址
  • 帧指针
  • 曲奇饼
  • 异常处理程序框架
  • 本地声明的变量和缓冲区
  • Callee保存寄存器

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

sub esp, 20h

该指令留出32个字节供函数中的局部变量使用。 当使用/ 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的逻辑xor和返回地址的指令,最后是一条指令,用于将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文件中定义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM