开始使用

为了能够让你亲身实践一下pwntools,首先,我们来看几个简单的例子。

当在编写自己的exp时,我们一般会采用下面的方法,这样的话,pwntools就会将会它的所有功能都导入进来。

>>> from pwn import *

显然,这样的话,你将在全局空间里引用pwntools的所有函数。你现在可以用一些简单函数进行汇编,反汇编,pack,unpack等等其他操作。

整个pwntools的使用文档在这里查看: from pwn import *.

连接

如果你想要pwn一个程序的话,你肯定需要和它进行交互,对吧?pwntools使用它的模块 pwnlib.tubes 使这个变得相当简单。

这个模块会建立一个与进程、socket、端口和其他相关的连接。例如,远程操作连接可以通过 pwnlib.tubes.remote 来实现。

>>> conn = remote('ftp.debian.org',21)
>>> conn.recvline() 
'220 ...'
>>> conn.send('USER anonymous\r\n')
>>> conn.recvuntil(' ', drop=True)
'331'
>>> conn.recvline()
'Please specify the password.\r\n'
>>> conn.close()

它也可以很容易地使得一个监听者处于等待状态:

>>> l = listen()
>>> r = remote('localhost', l.lport)
>>> c = l.wait_for_connection()
>>> r.send('hello')
>>> c.recv()
'hello'

此外,我们也可以利用 pwnlib.tubes.process 来简单地和进程进程交互。

>>> sh = process('/bin/sh')
>>> sh.sendline('sleep 3; echo hello world;')
>>> sh.recvline(timeout=1)
''
>>> sh.recvline(timeout=5)
'hello world\n'
>>> sh.close()

当然,你不仅可以利用程序来和进程进行通信,也可以直接与之 交互

>>> sh.interactive() 
$ whoami
user

当你通过ssh方式进行漏洞利用的时候,可以使用 pwnlib.tubes.ssh.

>>> shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0')
>>> shell['whoami']
'bandit0'
>>> shell.download_file('/etc/motd')
>>> sh = shell.run('sh')
>>> sh.sendline('sleep 3; echo hello world;') 
>>> sh.recvline(timeout=1)
''
>>> sh.recvline(timeout=5)
'hello world\n'
>>> shell.close()

包装整数

在编写exp时,最常见的工作就是在整数之间转换,而且转换后,它们的表现形式就是一个字节序列。通常情况下,我们使用 struct 这个模块。

pwntools通过 pwnlib.util.packing 使之十分简单。这样我们就不需要再记住解包装的代码, 只需要看着说明文档编写代码就行.

>>> import struct
>>> p32(0xdeadbeef) == struct.pack('I', 0xdeadbeef)
True
>>> leet = '37130000'.decode('hex')
>>> u32('abcd') == struct.unpack('I', 'abcd')[0]
True

此外,pack和unpack的操作也支持其它字长,比如8位字长:

>>> u8('A') == 0x41
True

设置目标系统架构及操作系统

我们在操作中特别指定目标机器的系统架构:

>>> asm('nop')
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'
此外,我们也可以通过一次性地在全局的参数 ``context``中设置,操作系统,字节序,大小端,位宽都可以在那里设定。
>>> context.arch      = 'i386'
>>> context.os        = 'linux'
>>> context.endian    = 'little'
>>> context.word_size = 32

当然,你也可以一次性设置好这些变量:

>>> asm('nop')
'\x90'
>>> context(arch='arm', os='linux', endian='big', word_size=32)
>>> asm('nop')
'\xe3 \xf0\x00'

设置日志记录级别

你可以通过context来控制日志记录的级别:

例如,这样设置:

>>> context.log_level = 'debug'

这样,通过管道发送和接收的数据都会被打印在屏幕上。

汇编和反汇编

有时候,你可能需要从互联网上下载一些shellcode,这时你可以使用 pwnlib.asm 模块。

>>> asm('mov eax, 0').encode('hex')
'b800000000'

如果你按照下面的方式来做,会更加容易:

>>> print disasm('6a0258cd80ebf9'.decode('hex'))
   0:   6a 02                   push   0x2
   2:   58                      pop    eax
   3:   cd 80                   int    0x80
   5:   eb f9                   jmp    0x0

而且,你甚至不需要大部分时间去写shellcode。pwntools提供了 pwnlib.shellcraft ,可以在你编写shellcode的时候提供帮助。

如果说我们想执行 setreuid(getuid(), getuid()),之后复制文件描述符4到 stdin, stdout 以及 stderr, 然后弹出一个shell!,那我们就可以这么做

>>> asm(shellcraft.setreuid() + shellcraft.dupsh(4)).encode('hex') 
'6a3158cd80...'

杂项工具

多亏有了 pwnlib.util.fiddling 这个模块,我们不需要写另外的hexdump。

我们可以通过使用模块 pwnlib.cyclic 在触发的崩溃中寻找偏移量或缓冲区大小。

>>> print cyclic(20)
aaaabaaacaaadaaaeaaa
>>> # Assume EIP = 0x62616166 ('faab' which is pack(0x62616166))  at crash time
>>> print cyclic_find('faab')
120

操纵ELF文件

我们也不需要进行硬编码了,因为我们可以使用 pwnlib.elf 来在运行时查看对应的参数。

>>> e = ELF('/bin/cat')
>>> print hex(e.address) 
0x400000
>>> print hex(e.symbols['write']) 
0x401680
>>> print hex(e.got['write']) 
0x60b070
>>> print hex(e.plt['write']) 
0x401680

你也可以给ELF文件打补丁或是保存。

>>> e = ELF('/bin/cat')
>>> e.read(e.address+1, 3)
'ELF'
>>> e.asm(e.address, 'ret')
>>> e.save('/tmp/quiet-cat')
>>> disasm(file('/tmp/quiet-cat','rb').read(1))
'   0:   c3                      ret'