摘要:
本文主要在之前版本的代码基础上,分析下gcc如何进行栈保护以避免栈溢出攻击的。
测试的平台:
1. ubuntu 9; gcc 4.4.1; Gdb 7.0-ubuntu
2. ubuntu系统安装在virtual box 3.2.8虚拟机上;
正文:
#include<string.h> void overflow(char* arg) { char buf[12]; strcpy(buf, arg); } int main(int argc, char *argv[]) { if(argc > 1) overflow(argv[1]); return 0; }
使用到的源代码如上所示,分别采用开启堆栈保护和不开启堆栈保护的方式编译源代码,默认的方式就是开启堆栈保护的,而要关闭堆栈保护,只要在gcc的编译器命中中加入"-fno-stack-protector"参数即可。我们采用gdb对两个版本的代码进行反汇编(具体命令参照本系列1),通过对比发现两者的main函数一致,没有差别,具体如下:
图1
通过查看overflow版本的反汇编,可以发现两者之间存在差别,具体的代码如下:
图2
图3
图3是开启堆栈保护后的代码,可以发现该版本中增加了堆栈保护的语句,其中主要的思想是在特定地方插入特殊字节,然后在退出函数前检查该位置的字节,如果发生改变,那么就认为发生栈溢出,下面的代码完成特定字节的设定:
Mov eax, gs:0x14 Mov [ebp-0xc], eax
然后在退出函数之前,对特定字符进行检查,具体代码如下:
Mov eax, [ebp-0xc] Xor eax, gs:0x14
从原理上而言,栈保护还是很简单的。上面的代码中,字符的复制从0x18(省略前面部分)往低地址开始,而我们的特殊字符直接是放在0xc这个地方,0x18减去0xc大概有12个字节。这个0xc是否会随着buf的大小而发生改变,下面我们将buf[12]修改为buf[17],看看0xc或者0x18的值是否会发生变化?下面看图:
图4
图4中可以发现,特殊字节放的位置0xc并没有发生变化,而复制字符串的首地址发生改变,从原来的0x18修改为0x1d;0x1d-0xc刚好是17(十进制),也即随着buf的改变是复制地址的变化,并且保证复制起始地址与特殊字节所在位置的距离刚好是字符串的长度。
结束语:
从汇编代码来看,gcc提供的栈保护策略也不复杂。在知道保护原理后,我们如何来实现栈溢出攻击就是另外一个问题了。