先来看看基于 Red Hat 与 Fedora 衍生版(例如 CentOS)系统用于阻止栈溢出攻击的内核参数,主要包含两项:linux
可执行栈保护,字面含义比较“绕”,程序员
实际上就是用来控制可否执行存储在栈shell
中的代码,其值为1时表示禁止;为0时表示容许;默认为1,表示禁止执行栈编程
中的代码,如此一来,即使覆盖了函数的返回地址致使栈溢出,也没法执行ubuntu
shellcodecentos
查看与修改系统当前的可执行栈保护参数:数组
[root@localhost 桌面]# sysctl -a | grep -e exec安全
ernel.exec-shield = 1服务器
[root@localhost 桌面]# cat /proc/sys/kernel/exec-shielddom
1
[root@localhost 桌面]# sysctl -w kernel.exec-shield=0
kernel.exec-shield = 0
[root@localhost 桌面]# cat /proc/sys/kernel/exec-shield
0
注意:
一,只有在学习和测试栈溢出攻击的原理时,才建议关闭可执行栈保护机制
二,在基于 Debian 与 Ubuntu 衍生版
(例如 BackTrack 5 Release 3)的系统上,不支持可执行栈保护机制:
root@bt:~# uname -a
Linux bt 3.2.6 #1 SMP Fri Feb 17 10:40:05 EST 2012 i686 GNU/Linux
root@bt:~# sysctl -a | grep -e kernel.exec
error: permission denied on key 'vm.compact_memory'
error: permission denied on key 'dev.parport.parport0.autoprobe'
error: permission denied on key 'dev.parport.parport0.autoprobe0'
error: permission denied on key 'dev.parport.parport0.autoprobe1'
error: permission denied on key 'dev.parport.parport0.autoprobe2'
error: permission denied on key 'dev.parport.parport0.autoprobe3'
error: permission denied on key 'net.ipv4.route.flush'
error: permission denied on key 'net.ipv6.route.flush'
root@bt:~#
或许是社区的开发人员认为,有下面另外一种叫作堆栈地址随机化的机制,就足够应对缓冲区溢出攻击了,也多是因为 Debian / Ubuntu 面向运行桌面应用居多的用户群体,它并不像销售给企业公司用户的 Red Hat 类发行版那样,对安全性的要求更为严格,才能保护用户的服务器,资产安全
堆栈地址随机初始化,很好理解,就是在每次将程序加载到内存时,进程地
址空间的堆栈起始地址都不同,动态变化,致使猜想或找出地址来执行
shellcode 变得很是困难,它和可执行栈保护的区别在于,后者即使是找到了
地址也是没法执行的;全部的 2.6 以上版本的 Linux 内核都支持并启用了
这一项特性;
查看与修改系统当前的堆栈地址随机初始化参数:
[root@localhost 桌面]# sysctl -a | grep randomize
kernel.randomize_va_space = 2
[root@localhost 桌面]# cat /proc/sys/kernel/randomize_va_space
2
[root@localhost 桌面]# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root@localhost 桌面]# cat /proc/sys/kernel/randomize_va_space
0
参数值为2时,表示启用随机地址功能;0表示关闭;
基于 Debian 与 Ubuntu 衍生版默认支持而且启用了随机地址功能:
1
2
root@bt:~# sysctl -a | grep -e kernel.randomize
kernel.randomize_va_space = 2
下面,咱们在一个启用了随机地址功能的机器上,查看执行同一个程序4次加载的动态连接共享库的入口地址(linux-gate.so.1),发现每次的入口地址都不一样,验证了随机地址的效果:
[root@centos6-5 桌面]# cat /proc/sys/kernel/randomize_va_space
2
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00dff000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00503000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00213000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x004b5000)
[root@centos6-5 桌面]#
同理,关闭随机地址功能,连续5次查看执行程序时加载的 linux-gate.so.1 入口地址:
[root@centos6-5 桌面]# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
再来看看 GCC 4.1 版本之后引入的两个编译参数:
(默认状况下,编译时不会指定这两个参数,用于阻止缺少安全编码意识的程序员写出存在缓冲区溢出漏洞的程序,
可是站在逆向工程的角度来看,指定这两个参数,特地构造有漏洞的程序,对于学习和理解栈溢出的原理仍是有帮助的;
再次提醒,这里介绍的两个编译参数必须同时打开,并且还要同时禁用前面讲的可执行栈保护与栈地址随机初始化,知足这4个条件后,通常而言,就能够测试 shellcode 的效果了
)
GCC 编译选项 -fno-stack-protector
禁用栈保护功能,默认是启用的;
gcc 的栈保护机制是指,在栈的缓冲区(大小一般由程序员给定)写入内容前
,在结束地址以后与返回地址以前,放入随机的验证码,因为栈帧是从内存高
址段向内存低址段增加的(回顾第一张图),结束地址在高址段;返回地
址在低址段,栈溢出时,会从高址段向低址段覆盖数据,所以若是想要覆盖返
回地址的内容,一定先覆盖结束地址,验证码,才能覆盖返回地址,
所以能够经过比较写入缓冲区先后的验证码是否发生改变,来检测并阻止溢出
攻击
GCC 编译选项 -z execstack
启用可执行栈,默认是禁用的;
该选项与 ld 连接器有关:ld 连接器在连接程序的时候,若是全部的 .o 文
件的堆栈段都标记为不可执行,那么整个库的堆栈段才会被标记为不可执行;
相反,即便只有一个 .o 文件的堆栈段被标记为可执行,那么整个库的堆栈段
将被标记为可执行,
换言之,默认是全部的 .o 文件的堆栈段都标记为不可执行
检查堆栈段可执行性的方法是:
1
[root@centos6-5vm /]# readelf -lW stack-overflow1-test | grep GNU_STACK
找出在输出中有无 E 标记;有 E 标记就说明堆栈段是可执行的,其中,
stack-overflow1-test 为咱们编写的简单栈溢出测试程序,源码以下:
#include <stdio.h>
greeting(char *temp1, char *temp2){
char name[20];
strcpy(name, temp2);
printf("hello %s %s\n", temp1, name);
}
main(int argc, char *argv[]){
greeting(argv[1], argv[2]);
printf("bye %s %s\n", argv[1], argv[2]);
}
这是一个典型的存在缓冲区溢出漏洞的程序,greeting 函数定义了一个只有20字节大小的字符数组(缓冲区),strcpy 函数不检查用户输入的第二个参数(由 main 函数的第二个参数传递)是否超过20字节,就写入这个缓冲区中
咱们用 gcc 默认参数配置来编译这个源文件,而后检查堆栈段的可执行性:
[root@centos6-5vm /]# gcc -v -Wall -o stack-overflow1-test stack-overflow1-test.c
(***************省略部分输出*************
GNU C (GCC) 版本 4.4.7 20120313 (Red Hat 4.4.7-4) (i686-redhat-linux)
由 GNU C 版本 4.4.7 20120313 (Red Hat 4.4.7-4) 编译,GMP 版本 4.3.1,MPFR 版本 2.4.1。
GGC 准则:--param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 5f02f32570d532de29ae0b402446343a
stack-overflow1-test.c:2: 警告:返回类型默认为‘int’
stack-overflow1-test.c: 在函数‘greeting’中:
stack-overflow1-test.c:4: 警告:隐式声明函数‘strcpy’
stack-overflow1-test.c:4: 警告:隐式声明与内建函数‘strcpy’不兼容
stack-overflow1-test.c: 在文件层:
stack-overflow1-test.c:8: 警告:返回类型默认为‘int’
stack-overflow1-test.c: 在函数‘main’中:
stack-overflow1-test.c:11: 警告:在有返回值的函数中,控制流程到达函数尾
stack-overflow1-test.c: 在函数‘greeting’中:
stack-overflow1-test.c:6: 警告:在有返回值的函数中,控制流程到达函数尾
COLLECT_GCC_OPTIONS='-v' '-Wall' '-o' 'stack-overflow1-test' '-mtune=generic' '-march=i686'
as -V -Qy -o /tmp/ccv9qpvk.o /tmp/ccT7rHyO.s
GNU assembler version 2.20.51.0.2 (i686-redhat-linux) using BFD version version 2.20.51.0.2-5.36.el6 20100205
***************省略部分输出*************
能够看到, -Wall 选项(参考前文解释)显示全部的警告和错误信息,对于增长程序的可移植性很是有帮助,例如它指出在源码的二行,greeting 自定义函数没有定义返回类型,将采用默认返回类型:int
另外,在 greeting 函数中,调用 strcpy 函数前未声明和定义(在程序中调用 strcpy 函数须要包含系统头文件 string.h)
一样,咱们没有为 main 函数指定返回类型
在上面的例子中,虽然每一个警告都非致命的语法或词法错误,可是 -Wall 选项确实能够“强制”培养程序员的良好编程习惯
言归正传,检查生成的二进制可执行文件:
[root@centos6-5vm /]# readelf -lW stack-overflow1-test | grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
其中没有 E 标记,这说明该程序即使存在溢出漏洞,可是因为 GCC 的堆栈段不可执行保护机制,该漏洞也没有太大被利用的可能性
咱们“故意”关掉 GCC 的堆栈段不可执行保护机制:指定 -z execstack 选项,再次编译源文件:
[root@centos6-5vm /]# gcc -v -Wall -z execstack -o stack-overflow1-test stack-overflow1-test.c
[root@centos6-5vm /]# readelf -lW stack-overflow1-test | grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
此次的输出中,带有 E 标记,说明堆栈段是可执行的,
再次强调,在 CentOS6.5 中,要真正能利用这个程序测试你编写的 shellcode ,须要执行下面操做:
[root@localhost 桌面]# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root@localhost 桌面]# sysctl -w kernel.exec-shield=0
kernel.exec-shield = 0
[root@localhost 桌面]# gcc -v -fno-stack-protector -z execstack -g -o stack-overflow1-test stack-overflow1-test.c
Ubuntu下面的GCC默认开启了Stack Smashing Protector,若是想在这个系统中学习缓冲区溢出的原理,在编译时要加上fno-stack-protector选项,不然运行时会出现*** stack smashing detected ***: xxx terminated,而不是指望的Segmentation fault。同时还须要加上容许栈执行的选项。
gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -ggdb -o xxx xxx.c