1. 安装 pwntools
首先,需要确保已安装 pwntools
。可以通过 pip 安装:
pip install pwntools
2. 导入和基本使用
from pwn import *
3. ROP 对象创建
要使用 ROP 工具,你首先需要创建一个 ROP 对象:
context.clear(arch='i386')
context.clear(arch='amd64')
elf = ELF('binary') # 加载 ELF 文件
rop = ROP(elf) # 创建 ROP 对象
4. 查找 ROP Gadget
ROP gadget 是一些小段代码,通常以 ret
指令结尾,pwntools
提供了 find_gadget
方法来查找这些 gadget:
>>> rop.eax
Gadget(0x10000004, ['pop eax', 'ret'], ['eax'], 0x8)
>>> hex(rop.eax.address)
'0x10000004'
>>> rop.ecx
Gadget(0x10000006, ['pop ecx', 'pop ebx', 'ret'], ['ecx', 'ebx'], 0xc)
5.修改寄存器的值的poc
调用rop函数并将寄存器值以键值
rop(eax=0x11111111, ecx=0x22222222)
6.创建函数调用栈帧
rop.call('read', [4,5,6])
print(rop.dump())
"""
0x0000: 0x0
0x0004: 0x64636261
0x0008: 0x2
0x000c: 0xdeadbeef read(4, 5, 6)
0x0010: b'eaaa' <return address>
0x0014: 0x4 arg0
0x0018: 0x5 arg1
0x001c: 0x6 arg2
"""
rop.write(7,8,9)
rop.exit()
print(rop.dump())
"""
0x0000: 0x0
0x0004: 0x64636261
0x0008: 0x2
0x000c: 0xdeadbeef read(4, 5, 6)
0x0010: 0x10000000 <adjust @0x24> add esp, 0x10; ret
0x0014: 0x4 arg0
0x0018: 0x5 arg1
0x001c: 0x6 arg2
0x0020: b'iaaa' <pad>
0x0024: 0xdecafbad write(7, 8, 9)
0x0028: 0xfeedface exit()
0x002c: 0x7 arg0
0x0030: 0x8 arg1
0x0034: 0x9 arg2
"""
7.手动向栈写入内容
rop.raw(0)
rop.raw(unpack(b'abcd'))
rop.raw(2)
print(rop.dump())
"""
0x0000: 0x0
0x0004: 0x64636261
0x0008: 0x2
"""
8.附加已知基地址
rop = ROP(binary, base=0x7fffe000)
rop.call('execve', [b'/bin/sh', [[b'/bin/sh'], [b'-p'], [b'-c'], [b'ls']], 0])
print(rop.dump())
"""
0x7fffe000: 0xcafebabe execve([b'/bin/sh'], [[b'/bin/sh'], [b'-p'], [b'-c'], [b'ls']], 0)
0x7fffe004: b'baaa' <return address>
0x7fffe008: 0x7fffe014 arg0 (+0xc)
0x7fffe00c: 0x7fffe01c arg1 (+0x10)
0x7fffe010: 0x0 arg2
0x7fffe014: b'/bin/sh\x00'
0x7fffe01c: 0x7fffe02c (+0x10)
0x7fffe020: 0x7fffe034 (+0x14)
0x7fffe024: 0x7fffe038 (+0x14)
0x7fffe028: 0x7fffe03c (+0x14)
0x7fffe02c: b'/bin/sh\x00'
0x7fffe034: b'-p\x00$'
0x7fffe038: b'-c\x00$'
0x7fffe03c: b'ls\x00$'
"""
9.寻找jmp $sp
小工具
- i386
context.clear(arch='i386')
>>> elf = ELF.from_assembly('nop; jmp esp; ret')
>>> rop = ROP(elf)
>>> jmp_gadget = rop.jmp_esp
>>> elf.read(jmp_gadget.address, 2) == asm('jmp esp')
True
- amd64
context.clear(arch='amd64')
>>> elf = ELF.from_assembly('nop; jmp rsp; ret')
>>> rop = ROP(elf)
>>> jmp_gadget = rop.jmp_rsp
>>> elf.read(jmp_gadget.address, 2) == asm('jmp rsp')
True
- 过滤badchar,这些字符会在发送时截断数据
context.clear(arch='i386')
>>> elf = ELF.from_assembly('nop; pop eax; jmp esp; int 0x80; jmp esp; ret')
>>> rop = ROP(elf, badchars=b'\x02')
>>> jmp_gadget = rop.jmp_esp # It returns the second gadget
>>> elf.read(jmp_gadget.address, 2) == asm('jmp esp')
True
>>> rop = ROP(elf, badchars=b'\x02\x06')
>>> rop.jmp_esp == None # The address of both gadgets has badchar
True
举例说明常见的badchars:
-
\x00
(NULL字节): 最常见的badchar,许多字符串处理函数会在遇到\x00
时停止读取。 -
\x0a
(换行符\n
): 在一些协议中,表示命令或数据的结束。 -
\x0d
(回车符\r
): 类似\x0a
,在网络通信中常与\x0a
一起使用。
10.创建ret2csu poc
-
参数
-
edi – 要填充的三个主寄存器
-
rsi – 要填充的三个主寄存器
-
rdx – 要填充的三个主寄存器
-
rbx – 要填充的可选寄存器
-
rbp – 要填充的可选寄存器
-
r12 – 要填充的可选寄存器
-
r13 – 要填充的可选寄存器
-
r14 – 要填充的可选寄存器
-
r15 – 要填充的可选寄存器
-
call – 对于 PIE 二进制文件必填
-
>>> context.clear(binary=pwnlib.data.elf.ret2dlresolve.get("amd64"))
>>> r = ROP(context.binary)
>>> r.ret2csu(1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> r.call(0xdeadbeef)
>>> print(r.dump())
0x0000: 0x40058a
0x0008: 0x0
0x0010: 0x1
0x0018: 0x600e48
0x0020: 0x1
0x0028: 0x2
0x0030: 0x3
0x0038: 0x400570
0x0040: b'qaaaraaa' <add rsp, 8>
0x0048: 0x4
0x0050: 0x5
0x0058: 0x6
0x0060: 0x7
0x0068: 0x8
0x0070: 0x9
0x0078: 0xdeadbeef 0xdeadbeef()
>>> open('core','w').close(); os.unlink('core') # remove any old core file for the tests
>>> p = process()
>>> p.send(fit({64+context.bytes: r}))
>>> p.wait(0.5)
>>> core = p.corefile
>>> hex(core.pc)
'0xdeadbeef'
>>> core.rdi, core.rsi, core.rdx, core.rbx, core.rbp, core.r12, core.r13, core.r14, core.r15
(1, 2, 3, 4, 5, 6, 7, 8, 9)
11.获取poc并清除缓存
raw_rop = rop.chain()
>>> print(enhex(raw_rop))
120000102f000010010000002600001008000000
>>> rop.clear_cache()