编译器堆栈保护原理
我们知道攻击者利用堆栈溢出漏洞时,通常会破坏当前的函数栈。例如,攻击者利用清单1 中的函数的堆栈溢出漏洞时,典型的情况是攻击者会试图让程序往 name数组中写超过数组长度的数据,直到函数栈中的返回地址被覆盖,使该函数返回时跳转至攻击者注入的恶意代码或 shellcode 处执行(关于溢出攻击的原理参见《Linux 下缓冲区溢出攻击的原理及对策》)。
溢出攻击后,函数栈变成了图 2所示的情形,与溢出前(图 1)比较可以看出原本堆栈中的 EBP,返回地址已经被溢出字符串覆盖,即函数栈已经被破坏。
清单 1. 可能存在溢出漏洞的代码
int vulFunc() {
char name[10];
//…
return 0;
}


GCC 中的堆栈保护实现
Stack Guard是第一个使用Canaries 探测的堆栈保护实现,它于 1997年作为GCC的一个扩展发布。最初版本的Stack Guard使用 0x00000000作为canary word。尽管很多人建议把 Stack Guard 纳入GCC,作为 GCC的一部分来提供堆栈保护。但实际上,GCC 3.x没有实现任何的堆栈保护。直到GCC 4.1 堆栈保护才被加入,并且 GCC4.1 所采用的堆栈保护实现并非Stack Guard,而是 Stack-smashing Protection(SSP,又称 ProPolice)。
SSP 在 Stack Guard 的基础上进行了改进和提高。它是由IBM的工程师Hiroaki Rtoh开发并维护的。与 Stack Guard相比,SSP 保护函数返回地址的同时还保护了栈中的 EBP 等信息。此外,SSP还有意将局部变量中的数组放在函数栈的高地址,而将其他变量放在低地址。这样就使得通过溢出一个数组来修改其他变量(比如一个函数指针)变得更为困难。
GCC 4.1 中三个与堆栈保护有关的编译选项
-fstack-protector:
启用堆栈保护,不过只为局部变量中含有char数组的函数插入保护代码。
-fstack-protector-all:
启用堆栈保护,为所有函数插入保护代码。
-fno-stack-protector:
禁用堆栈保护。
GCC 中的 Canaries 探测
下面通过一个例子分析 GCC 堆栈保护所生成的代码。分别使用-fstack-protector选项和 -fno-stack-protector 编译清单2中的代码得到可执行文件 demo_sp (-fstack-protector),demo_nosp (-fno-stack-protector)。
清单 2. demo.c
int main() {
int i;
char buffer[64];
i = 1;
buffer[0] = 'a';
return 0;
}
然后用 gdb分别反汇编demo_sp,deno_nosp。
清单 3. demo_nosp 的汇编代码
(gdb) disas main
Dump of assembler code for function main:
0x08048344 <main+0>: lea 0x4(%esp),%ecx
0x08048348 <main+4>: and $0xfffffff0,%esp
0x0804834b <main+7>: pushl 0xfffffffc(%ecx)
0x0804834e <main+10>: push %ebp
0x0804834f <main+11>: mov %esp,%ebp
0x080483

本文深入探讨了GCC中的编译器堆栈保护技术,包括堆栈保护原理、GCC的具体实现,如Canaries探测和局部变量顺序调整。通过示例分析了GCC 4.1中的相关编译选项,并揭示了堆栈保护的局限性,如只保护控制信息而不保护局部变量,以及可利用函数参数进行攻击等。
最低0.47元/天 解锁文章
5193

被折叠的 条评论
为什么被折叠?



