清单 1. 可能存在溢出漏洞的代码
int vulFunc() { char name[10]; //… return 0; } |
图 1. 溢出前的函数栈
![溢出前的函数栈](https://i-blog.csdnimg.cn/blog_migrate/1783ddbe0935988e8a64cc2a642bf566.png)
图 2. 溢出后的函数栈
![溢出后的函数栈](https://i-blog.csdnimg.cn/blog_migrate/939581bd83ddfab2ce5e49a9bacd74bf.png)
如果能在运行时检测出这种破坏,就有可能对函数栈进行保护。目前的堆栈保护实现大多使用基于 “Canaries” 的探测技术来完成对这种破坏的检测。
启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。
清单 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 的汇编代码
清单 4. demo_sp 的汇编代码
将用 -fstack-protector 选项编译的 demo_sp 与没有堆栈保护的 demo_nosp 的汇编代码相比较,两者最显著的区别就是在函数真正执行前多了 3 条语句:
0x080483a5 <main+17>: mov %gs:0x14,%eax 0x080483ab <main+23>: mov %eax,0xfffffff8(%ebp) 0x080483ae <main+26>: xor %eax,%eax |
0x080483c0 <main+44>: mov 0xfffffff8(%ebp),%edx 0x080483c3 <main+47>: xor %gs:0x14,%edx 0x080483ca <main+54>: je 0x80483d1 <main+61> 0x080483cc <main+56>: call 0x80482fc <__stack_chk_fail@plt> |
这多出来的语句便是 SSP 堆栈保护的关键所在,通过这几句代码就在函数栈框中插入了一个 Canary,并实现了通过这个 canary 来检测函数栈是否被破坏。
程序中, movl $0x1,0xffffff**(%ebp) 对应于 i = 1;
movb $0x61,0xffffff**(%ebp) 对应于 buffer[0] = ‘a’;
图 3. 调整变量顺序前后的函数栈
![调整变量顺序前后的函数栈](https://i-blog.csdnimg.cn/blog_migrate/5ba32448c09c198b4b1bbbb28ba6f79e.png)
以上我们从实现的角度分析了 GCC 中的堆栈保护。下面将用一个小程序 overflow_test.c 来验证 GCC 堆栈保护的实际效果。
清单 5. 溢出攻击模拟程序 overflow_test.c
aktoon@aktoon-thinkpad:~/SCAD/overflow_test$ gcc –fno-stack-protector -o overflow_test ./overflow_test.c |
程序被成功溢出转而执行 shellcode 获得了一个 shell。由于没有开启堆栈保护,溢出得以成功。
aktoon@aktoon-thinkpad:~/SCAD/overflow_test$ gcc –fno-stack-protector -o overflow_test ./overflow_test.c |
在上面的测试中,我们看到编译器的插入的保护代码阻止了通常的溢出攻击。实际上,现在编译器堆栈保护技术确实使堆栈溢出攻击变得困难了。
在某些情况下,攻击者还可以利用函数参数来实现溢出攻击。我们用下面的例子来说明这种攻击的原理。
清单 6. 漏洞代码 vul.c
int func(char *msg) { char buf[80]; strcpy(buf,msg); strcpy(msg,buf); } int main(int argv, char** argc) { func(argc[1]); } |
图 4. func 的函数栈
![func 的函数栈](https://i-blog.csdnimg.cn/blog_migrate/34e7863bdc4c93b6a0fbe575374a6d6d.png)