C/C++对内存的访问非常自由,也会带来一些问题。比如对一个局部数组变量的越界写入就可能导致栈数据被意外修改。
为了防止线程栈被有意无意错误改写,VC编译器提供了/GS开关,指示编译器在栈的某些变量的前后留出空白内存区。编译器在原函数体代码前面插入代码,向空白内存区中写入某个数值(security cookie),并在原函数体代码后面插入代码检查空白内存区中的数值是否保持不变。如果发生了变化,则报异常。这被称为Buffer Security Check。
所有符合下面条件的局部变量会受到这一措施的保护:
- An array that is larger than 4 bytes, has more than two elements,and has an element type that is not a pointer type.
- A data structure whose size is more than 8 bytes and contains nopointers.
- A buffer allocated by using the _allocafunction.
- Any class or structure that contains a GS buffer.
比如,以下变量是符合条件的:
- char buffer[20];
- int buffer[20];
- struct { int a; int b; int c; int d; } myStruct;
- struct { int a; char buf[20]; };
比如,以下是不符合条件的(x86平台下),不会生成security cookies:
- char *pBuf[20];
- void *pv[20];
- char buf[4];
- int buf[2];
- struct { int a; int b; };
打开GS开关后,有较大可能性检查到栈内存的非法写入,显然这也不是百分之百的保证。在VS2012下面,不管是Debug还是Release,GS开关默认都是被打开的。
测试程序
#include <cstring>
#include <stdlib.h>
// Vulnerable function
void vulnerable(const char *str) {
char buffer[10] = "abcdefg";
int i = 0;
char buffer2[20] = "ABCDEFGHIJKLMN";
int k = 30;
int l = 50;
for( i = 0 ; i < 10; i++)
buffer[i] = '0' + i;
for( k = 0 ; k < 20; k++)
buffer2[k] = '0' + i;
}
int main() {
char large_buffer[] = "";
vulnerable(large_buffer);
}
下面利用VC的调试功能,对vulnerable函数进行栈分析。下图是运行到vulnerable函数的时候,VC调试器的截图。其中标注了栈顶指针的值、各个局部变量的位置和值。可以看到每个buffer数组的前后都分配了一组"CC"作为隔离带,而变量k和l之间没有隔离带。