C语言中模拟/GS编译选项实现栈溢出检测

这其实是我在大二的时候给微软俱乐部捉虫大赛出的题目之一。代码基本上是写好了的,故意留出了一些bug,只可惜没人尝试来做。

关于/GS编译选项

VS在某一个版本之后添加了/GS编译选项,增强了对溢出检测的能力。随后对Windows XP进行了全面的重新编译,发布了SP2,安全性大大提高。

/GS的原理其实比较简单:

  1. 程序开始时,在堆的某个位置初始化一个security cookie。cookie的位置是不固定的。
  2. 存放cookie地址的指针大概在PEB之后的某个位置,地址也不固定,但是有一定的范围。对于这一点的记忆有点模糊了,手头也没有当时看的资料,网上也没搜到……确切查到了如果不对再来更新。
  3. 构建栈帧时,在所有的局部变量之前压入一个security cookie的副本。
  4. 函数返回时,检查栈中的security cookie副本与全局的security cookie是否一致,如果不一致则判定为栈溢出,执行相应的动作。

这些步骤之中很多自然需要由编译器来做,不过我们同样可以在源代码的层面模拟此类动作。当然,实际应用的价值也许并不大。

 在C语言中模拟/GS编译选项

下面的模拟与VS的/GS编译选项稍有不同。我们在栈中并不保存security cookie,而是保存cookie与当前栈帧中的ebp(即上一个栈帧的栈底)的异或值。由于当前ebp寄存器中保存的是当前栈帧栈底的地址,因此需要间访一次。在函数退出的时候,检查异或值即可。

采用这种机制的原因是,我们没有办法保证security cookie在所有临时变量之下,因此只好变通一下,跟ebp取个异或。

代码中对return做了define,因此所有函数的返回都必须以return的形式显示返回。没想到C语言里面怎么实现ScopeGuard,只能在return上下手了。

#include <stdio.h> 
#include <stdlib.h> 
#include <time.h> 
#include <string.h> 
/*
* A SIMPLE Security Cookie Example
* To Examine the Stack Overflow
*/
#define DEFALUT_SECURITY_COOKIE 0xE8CFF7E8 
int __security_cookie = DEFALUT_SECURITY_COOKIE;  
int __assistant_flag = 0;  
int cookie = 0, *ebp = 0;  
void __security_init_cookie()  
{
    __security_cookie ^= time(NULL);
}
void __security_check_fail()  
{
    printf("Stack Overflow!!n");
    exit(0);
}
#define __mainStartup (main) 
#define main() _main(int argc, char** argv) 
#define ENABLE_SECURITY_CHECK \
int cookie, *ebp, __assistant_flag;   \
__asm__("movl %%ebp, %0;":"=g"(ebp));   \
cookie = __security_cookie ^ *ebp;   \
__assistant_flag = 1; 

#define return if(__assistant_flag){ \
__asm__(" movl %%ebp, %0;":"=g"(ebp));   \
if((cookie ^ (*ebp)) != __security_cookie)   \
__security_check_fail(); }   \
return 

/**
* =======================================
* DO NOT CHANGE THE CODE FROM HERE
*/
int main();  
int __mainStartup(int argc, char** argv)  
{
    __security_init_cookie();
    return _main(argc, argv);
}
/* End of Security Cookie Example */

/* ---------------------------------------------
* User Code Begin
* Now Let's See How to Use Our Security Check
* Functions with ENABLE_SECURITY_CHECK MUST use return-statement to return
* DO NOT CHANGE THE CODE
*/
int main()  
{
    char s[5];
    ENABLE_SECURITY_CHECK
    strcpy(s, "Welcome to 5th Debug Contest!n");
    /* What a Stupid Overflow Example */
    return 0;
}

`

上面的代码中main和mainStartup的宏定义只是为了替换掉入口函数。

在不希望使用栈溢出检测的函数中,不添加ENABLE_SECURITY_CHECK即可。为了保证能够正常编译运行,定义了三个同样名称的全局变量。

不足之处

上面的代码如果在溢出时恰好将__assistant_flag设置为0了,也可以绕过后面的检测。这是不足之一;

security cookie的地址固定,不足之二;

重新定义了return,因此所有函数返回的地方需要显示return,不足之三。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注