上一篇blog中我简要介绍了一下pwntools的各个模块基本的使用方法,这里给出一点其他方面的补充。

GDB调试

对于elf文件来说,可能有时需要我们进行一些动态调试工作这个时候就需要用到gdb,pwntools的gdb模块也提供了这方面的支持。

其中最常用的还是attach函数,在指定process之后可以attach上去调试,配合proc模块就可以得到对应进程的pid非常方便。

但是比较麻烦的是在实现上,attach函数需要开启一个新的terminal,这个terminal的类型必须使用环境变量或者context对象来指定。研究了一番源码之后,找到了解决方案。

1
2
3
s = process('./pwnme')
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
gdb.attach(proc.pidof(s)[0])

proc.pidof(s)[0]能够取出process的id,然后attach上去。context.terminal制定的是终端类型和参数,我用的是gnome-terminal可以这样写,这样运行后会自动打开一个新的gnome-terminal并在里面启动gdb并自动断下来,这样就可以调试了。

还可以使用其他xterm,tmux等其他终端,如果脚本运行在tmux中,可以这样指定

1
2
context.terminal = ['tmux', 'splitw', '-h']
context.terminal = ['tmux', 'splitw', '-v']

这两种可以让gdb运行在横向或者纵向分割出来的tmux窗口中。

另外,也可以在attach的时候指定gdb脚本,这样可以断在自己想的地方。

1
gdb.attach(proc.pidof(s)[0], execute='b *0x400620\nc\n')

DynELF 符号leak

相当好用的一个工具,给出一个函数句柄,可以解析任意符号的位置。这个函数的功能是:输入任意一个address,输出这个address中的data(至少1byte)。

文档中给出了一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
p = process('./pwnme')

def leak(address):
data = p.read(address, 4)
log.debug("%#x => %s" % (address, (data or '').encode('hex')))
return data

d = DynELF(leak, main)
d.lookup(None, 'libc') # libc基址
d.lookup('system', 'libc')

# 指定一份elf的副本可以加速查找过程
d = DynELF(leak, main, elf=ELF('./pwnme'))
d.lookup(None, 'libc')
d.lookup('system', 'libc')

这个例子当然没有实际意义,在应用中我们可以在leak函数中布置rop链,使用write函数leak出一个address的地址,然后返回。接着就可以使用d.lookup函数查找符号了,通常我们都是需要找system的符号。

关于ROP模块

目前ROP模块还不支持build一个x64的rop链(my issue),但是还是可以寻找一些gadget来帮助我们手动布置rop的。

1
2
3
4
5
6
7
8
9
>>> elf = ELF('./pwnme')
>>> rop = ROP(elf)
>>> rop.rdi
(4196515L, {'insns': [u'pop rdi', u'ret'], 'move': 16, 'regs': [u'rdi']})
>>> hex(rop.rdi[0])
0x4008a3
>>> print elf.disasm(0x4008a3, 2)
4008a3: 5f pop edi
4008a4: c3 ret

以这些寄存器结尾的属性保存着能够控制它们的gadget,当然如果没有build功能的话同样 可以使用其它的ropgadget工具也是很方便的。