简体   繁体   English

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

[英]How does Visual Studio 2013 detect buffer overrun

Visual Studio 2013 C++ projects have a /GS switch to enable buffer security check validation at runtime. Visual Studio 2013 C ++项目具有/GS开关,可在运行时启用缓冲区安全检查验证。 We are encountering many more STATUS_STACK_BUFFER_OVERRUN errors since upgrading to VS 2013, and suspect it has something to do with improved checking of buffer overrun in the new compiler. 自升级到VS 2013以来,我们遇到了更多STATUS_STACK_BUFFER_OVERRUN错误,并怀疑它与改进的新编译器中缓冲区溢出检查有关。 I've been trying to verify this and better understand how buffer overrun is detected. 我一直在尝试验证这一点,并更好地了解如何检测缓冲区溢出。 I'm befuddled by the fact that buffer overrun is reported even when the memory updated by a statement only changes the contents of another local variable on the stack in the same scope! 即使由语句更新的内存仅更改同一范围内堆栈上另一个局部变量的内容,也会报告缓冲区溢出这一事实让我感到困惑! So it must be checking not only that the change doesn't corrupt memory not "owned" by a local variable, but that the change doesn't affect any local variable other than that allocated to the one referenced by the individual update statement. 因此,它必须不仅检查更改不会破坏本地变量“不拥有”的内存,而且还要检查更改不会影响除分配给单个更新语句引用的那个之外的任何本地变量。 How does this work? 这是如何运作的? Has it changed since VS 2010? 自VS 2010以来它有变化吗?

Edit: Here's an example illustrating a case that Mysticial's explanation doesn't cover: 编辑:这是一个例子,说明了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';
}

The output is 4 indicating that the memory about to be overwritten is within the bounds of buffer1 (immediately after buffer2 ), but then the program terminates with a buffer overrun. 输出为4表示即将被覆盖的内存在buffer1的范围内(紧接在buffer2之后),但程序终止时缓冲区溢出。 Technically it should be considered a buffer overrun, but I don't know how it's being detected since it's still within the local variables' storage and not really corrupting anything outside local variables. 从技术上讲,它应该被认为是缓冲区溢出,但我不知道它是如何被检测到的,因为它仍然在局部变量的存储内,并没有真正破坏局部变量之外的任何东西。

This screenshot with memory layout proves it. 带有内存布局的屏幕截图证明了这一点。 After stepping one line the program aborted with the buffer overrun error. 步进一行后,程序因缓冲区溢出错误而中止。 带内存布局的调试器屏幕截图

I just tried the same code in VS 2010, and although debug mode caught the buffer overrun (with a buffer offset of 12), in release mode it did not catch it (with a buffer offset of 8). 我只是想相同的代码在VS 2010中,虽然调试模式陷入缓冲区溢出(带缓冲12的偏移量),在发行模式下,它没有赶上它(带缓冲的8偏移)。 So I think VS 2013 tightened the behavior of the /GS switch. 所以我认为VS 2013收紧了/GS交换机的行为。

Edit 2: I managed to sneak past even VS 2013 range checking with this code. 编辑2:我设法用这段代码偷偷溜过VS 2013范围检查。 It still did not detect that an attempt to update one local variable actually updated another: 它仍然没有检测到更新一个局部变量的尝试实际更新了另一个:

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

You are seeing an improvement to the /GS mechanism, first added to VS2012. 您正在看到/ GS机制的改进,首先添加到VS2012。 Originally /GS could detect buffer overflows but there's still a loop-hole where attacking code can stomp the stack but bypass the cookie. 最初/ GS可以检测到缓冲区溢出但是仍然存在一个循环漏洞,攻击代码可以攻击堆栈但绕过cookie。 Roughly like this: 大概是这样的:

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

If the attacker can manipulate the value of index then the cookie doesn't help. 如果攻击者可以操纵索引的值,则cookie无效。 This code is now rewritten to: 此代码现在重写为:

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

Just plain index checking. 只是简单的索引检查。 Which, when triggered, instantly terminates the app with __fastfail() if no debugger is attached. 当触发时,如果没有附加调试器,则会立即使用__fastfail()终止应用程序。 Backgrounder is here . Backgrounder 就在这里

From the MSDN page on /GS in Visual Studio 2013 : 从Visual Studio 2013中/GS上的MSDN页面

Security Checks 安全检查

On functions that the compiler recognizes as subject to buffer overrun problems, the compiler allocates space on the stack before the return address. 在编译器识别为缓冲区溢出问题的函数上,编译器在返回地址之前在堆栈上分配空间。 On function entry, the allocated space is loaded with a security cookie that is computed once at module load. 在函数入口上,分配的空间加载了一个安全cookie,该cookie在模块加载时计算一次。 On function exit, and during frame unwinding on 64-bit operating systems, a helper function is called to make sure that the value of the cookie is still the same. 在函数退出时,以及在64位操作系统上的帧展开期间,将调用辅助函数以确保cookie的值仍然相同。 A different value indicates that an overwrite of the stack may have occurred. 不同的值表示可能发生了堆栈的覆盖。 If a different value is detected, the process is terminated. 如果检测到不同的值,则终止该过程。

for more details, the same page refers to Compiler Security Checks In Depth : 有关更多详细信息,同一页面指的是编译器安全检查深度

What /GS Does 什么/ GS做

The /GS switch provides a "speed bump," or cookie, between the buffer and the return address. / GS开关在缓冲区和返回地址之间提供“减速带”或cookie。 If an overflow writes over the return address, it will have to overwrite the cookie put in between it and the buffer, resulting in a new stack layout: 如果溢出写入返回地址,则必须覆盖放在它和缓冲区之间的cookie,从而产生新的堆栈布局:

  • Function parameters 功能参数
  • Function return address 功能返回地址
  • Frame pointer 帧指针
  • Cookie 曲奇饼
  • Exception Handler frame 异常处理程序框架
  • Locally declared variables and buffers 本地声明的变量和缓冲区
  • Callee save registers Callee保存寄存器

The cookie will be examined in more detail later. 稍后将更详细地检查cookie。 The function's execution does change with these security checks. 函数的执行确实随着这些安全检查而改变。 First, when a function is called, the first instructions to execute are in the function's prolog. 首先,当调用函数时,要执行的第一个指令位于函数的序言中。 At a minimum, a prolog allocates space for the local variables on the stack, such as the following instruction: prolog至少为堆栈上的局部变量分配空间,例如以下指令:

sub esp, 20h

This instruction sets aside 32 bytes for use by local variables in the function. 该指令留出32个字节供函数中的局部变量使用。 When the function is compiled with /GS, the functions prolog will set aside an additional four bytes and add three more instructions as follows: 当使用/ GS编译函数时,函数prolog将预留另外四个字节并添加另外三个指令,如下所示:

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

The prolog contains an instruction that fetches a copy of the cookie, followed by an instruction that does a logical xor of the cookie and the return address, and then finally an instruction that stores the cookie on the stack directly below the return address. prolog包含一条指令,用于获取cookie的副本,然后是执行cookie的逻辑xor和返回地址的指令,最后是一条指令,用于将cookie存储在返回地址正下方的堆栈中。 From this point forward, the function will execute as it does normally. 从现在开始,该函数将按正常方式执行。 When a function returns, the last thing to execute is the function's epilog, which is the opposite of the prolog. 当一个函数返回时,最后要执行的是函数的epilog,它与prolog相反。 Without security checks, it will reclaim the stack space and return, such as the following instructions: 如果没有安全检查,它将回收堆栈空间并返回,如下面的说明:

add   esp,20h
ret

When compiled with /GS, the security checks are also placed in the epilog: 使用/ GS编译时,安全检查也会放在epilog中:

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

The stack's copy of the cookie is retrieved and then follows with the XOR instruction with the return address. 检索堆栈的cookie副本,然后使用带有返回地址的XOR指令进行检索。 The ECX register should contain a value that matches the original cookie stored in the __security_cookie variable. ECX寄存器应包含与__security_cookie变量中存储的原始cookie匹配的值。 The stack space is then reclaimed, and then, instead of executing the RET instruction, the JMP instruction to the __security_check_cookie routine is executed. 然后回收堆栈空间,然后执行__security_check_cookie例程的JMP指令,而不是执行RET指令。

The __security_check_cookie routine is straightforward: if the cookie was unchanged, it executes the RET instruction and ends the function call. __security_check_cookie例程很简单:如果cookie没有改变,它会执行RET指令并结束函数调用。 If the cookie fails to match, the routine calls report_failure. 如果cookie无法匹配,则例程调用report_failure。 The report_failure function then calls __security_error_handler(_SECERR_BUFFER_OVERRUN, NULL). 然后,report_failure函数调用__security_error_handler(_SECERR_BUFFER_OVERRUN,NULL)。 Both functions are defined in the seccook.c file of the C run-time (CRT) source files. 这两个函数都在C运行时(CRT)源文件的seccook.c文件中定义。

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

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