后面两段主要是逆向和内核两位大手子写的
本题叠buf式的考点让几位师傅隔着各种远程投屏互相帮忙调了几个通宵,无数次都在祈求出题人收了神通,不过最后还是做出来了。没打过内核,当时并不觉得把静态c程序exp改成shellcode可以很有可行性,随口提了一句,结果几位大爹说还真可以,属于歪打正着了,最后和IChild大爷被几个socket调用调戏了一通宵到精神恍惚。不知道是否有能在禁止execve/at的情况下更为精妙的可行思路。
大体流程:
1.利用用户态漏洞进行 rop 和布置 shellcode-seg1
2. rop 利用 mprotect 调用成功运行小段 shellcode-seg1
3.在 shellcode-seg1 中用 socket 操作读入大段 shellcode-seg2 (这段直接来自于打内核的 exp binary),并完成场景还原和跳转
4.跳转运行 shellcode-seg2 完成内核漏洞的利用(精神污染),篡改 busybox 的 poweroff 逻辑
5.杀 vm 进程,在防止内核崩掉(需要一点处理)的情况下让 init 高权限顺序执行 poweroff ,利用 8080 端口反弹信息。
用户态程序 vm 是 go 语言写的,在 的帮助下顺利恢复函数名,再手动恢复下自定义结构体的信息就能得到程序逻辑。
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
|
void
__cdecl main_main()
{
__int64
v0;
// x28
_QWORD *http_server;
// x0
__int64
v2;
// x0
__int64
v3;
// x1
__int64
v4;
// x0
void
*server_handle;
// [xsp+38h] [xbp-40h]
__int64
v6[2];
// [xsp+40h] [xbp-38h] BYREF
_QWORD v7[4];
// [xsp+50h] [xbp-28h] BYREF
__int64
v8;
// [xsp+78h] [xbp+0h] BYREF
if
( (unsigned
__int64
)&v8 <= *(_QWORD *)(v0 + 16) )
runtime_morestack_noctxt();
main_initFs();
v6[0] = (
__int64
)&type_string;
v6[1] = (
__int64
)&off_2D7E60;
fmt_Fprintln(&off_2D8F98, qword_45C3E8, v6, 1LL, 1LL);
// "Server started ..."
server_handle = runtime_newobject(&type_http_ServeMux);
net_http___ServeMux__Handle(server_handle, aApiCreateFile, 16LL, &off_2D9438, off_293378);
// 0x1F9A90, main.createFile
net_http___ServeMux__Handle(server_handle, aApiWriteFile, 15LL, &off_2D9438, &off_293388);
// 0x1F9E70, main.writeFile
net_http___ServeMux__Handle(server_handle, aApiRunFile, 13LL, &off_2D9438, off_293380);
// 0x1FA260, main.runFile
http_server = runtime_newobject(&type_http_Server);
http_server[1] = 5LL;
*http_server = a8080;
http_server[2] = &off_2D8E18;
if
( dword_4927C0 )
runtime_gcWriteBarrier(http_server + 3, server_handle);
else
http_server[3] = server_handle;
v2 = net_http___Server__ListenAndServe(http_server);
if
( v2 )
{
v7[2] = 0LL;
v7[3] = 0LL;
v7[0] = &type_string;
v7[1] = &off_2D7E80;
v4 = *(_QWORD *)(v2 + 8);
v7[2] = v4;
v7[3] = v3;
fmt_Fprintln(&off_2D8F98, qword_45C3E8, v7, 2LL, 2LL);
// "Error starting the server:"
}
}
void
__fastcall main_initFs()
{
__int64
v0;
// x28
__int64
v1[2];
// [xsp+30h] [xbp-58h] BYREF
__int64
v2[9];
// [xsp+40h] [xbp-48h] BYREF
__int64
v3;
// [xsp+88h] [xbp+0h] BYREF
if
( (unsigned
__int64
)&v3 <= *(_QWORD *)(v0 + 16) )
runtime_morestack_noctxt();
v2[0] = 0x400000020LL;
v2[1] = 0xC00000B704000015LL;
v2[2] = 32LL;
v2[3] = 0x11900020015LL;
v2[4] = 0xDD00010015LL;
v2[5] = 0x7FFF000000000006LL;
v2[6] = 6LL;
v1[0] = 7LL;
v1[1] = (
__int64
)v2;
syscall_Syscall(SYS_prctl, PR_SET_NO_NEW_PRIVS, 1LL);
v2[7] = (
__int64
)v1;
syscall_Syscall(SYS_prctl, PR_SET_SECCOMP, 2LL, v1);
}
void
__fastcall main_createFile(ResponseWriter_table *Response_vtable,
void
*response,
void
*request)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
if
( (unsigned
__int64
)&v31 <= *(_QWORD *)(v3 + 16) )
runtime_morestack_noctxt();
v30 = Response_vtable->net_http_(_response)_Header(response);
*(_OWORD *)v4 = net_textproto_CanonicalMIMEHeaderKey(aContentType, 12LL);
v35 = v4[0];
value = runtime_newobject(&type_string_array_1_ptr);
*((_QWORD *)value + 1) = 16LL;
*(_QWORD *)value = aApplicationJso;
v6 = (
void
**)runtime_mapassign_faststr(&type_textproto_MIMEHeader, v30, v35, v4[1]);
v6[1] = (
void
*)1;
v6[2] = (
void
*)1;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v6, value);
else
*v6 = value;
v29 = Response_vtable->net_http_(_response)_Header(response);
*(_OWORD *)v7 = net_textproto_CanonicalMIMEHeaderKey(aAccessControlA_3, 27LL);
v35 = v7[0];
v8 = runtime_newobject(&type_string_array_1_ptr);
v33 = v8;
v8[1] = 1LL;
*v8 =
"*"
;
v9 = (
void
**)runtime_mapassign_faststr(&type_textproto_MIMEHeader, v29, v35, v7[1]);
v9[1] = (
void
*)1;
v9[2] = (
void
*)1;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v9, v33);
else
*v9 = v33;
v37 = (main::Req *)runtime_newobject(&type_main_Req);
v28 = *((_QWORD *)request + 9);
v32 = runtime_convI2I(&type_io_Reader, *((_QWORD *)request + 8));
v10 = runtime_newobject(&type_json_Decoder);
*v10 = v32;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v10 + 1, v28);
else
v10[1] = v28;
v11 = encoding_json___Decoder__Decode(v10, &type_main_Req_ptr, v37);
if
( v11 )
{
v24 = (*(
__int64
(__fastcall **)(
__int64
))(v11 + 24))(v12);
net_http_Error(Response_vtable, response, v24, v25, 400LL);
}
else
if
( v37->Size > 0x8000 || (*(_OWORD *)v14 = os_OpenFile(aDevVmfs, 9LL, 0LL, 0x180LL), v13 = v14[0], v14[1]) )
{
net_http_Error(Response_vtable, response, aInternalServer, 21LL, 500LL);
}
else
{
v31 = (_QWORD *)v14[0];
Id = v37->Id;
Size = v37->Size;
v27[1] = 0LL;
v27[3] = 0LL;
v27[4] = 0LL;
v27[0] = Id;
v27[2] = Size;
v36 = v27;
if
( v14[0] )
{
if
( (*(_BYTE *)(*(_QWORD *)v14[0] + 80LL) & 1) != 0 )
{
internal_poll___FD__SetBlocking(*(_QWORD *)v14[0]);
v13 = (
__int64
)v31;
}
v17 = *(_QWORD *)(*(_QWORD *)v13 + 16LL);
}
else
{
v17 = -1LL;
}
syscall_Syscall(SYS_ioctl, v17, 0xFF00LL, v36);
if
( v31 )
{
v26 = v18;
os___file__close(*v31);
v18 = v26;
}
if
( v18
|| (v38 = aSuccess,
v39 = 7LL,
v19 = runtime_convTstring(aSuccess),
v20 = encoding_json_Marshal(&type_main_Message, v19),
v23) )
{
net_http_Error(Response_vtable, response, aInternalServer, 21LL, 500LL);
}
else
{
Response_vtable->net_http_(_response)_Write(response, v20, v21, v22);
}
}
}
void
__fastcall main_writeFile(ResponseWriter_table *Response_vtable,
void
*response,
void
*request)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
if
( (unsigned
__int64
)&v33 <= *(_QWORD *)(v3 + 16) )
runtime_morestack_noctxt();
v32 = Response_vtable->net_http_(_response)_Header(response);
*(_OWORD *)v4 = net_textproto_CanonicalMIMEHeaderKey(aContentType, 12LL);
v37 = v4[0];
v5 = runtime_newobject(&type_string_array_1_ptr);
value = (
__int64
)v5;
v5[1] = 16LL;
*v5 = aApplicationJso;
v6 = (
__int64
*)runtime_mapassign_faststr(&type_textproto_MIMEHeader, v32, v37, v4[1]);
v6[1] = 1LL;
v6[2] = 1LL;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v6, value);
else
*v6 = value;
v31 = Response_vtable->net_http_(_response)_Header(response);
*(_OWORD *)v7 = net_textproto_CanonicalMIMEHeaderKey(aAccessControlA_3, 27LL);
v37 = v7[0];
v8 = runtime_newobject(&type_string_array_1_ptr);
v35 = (
__int64
)v8;
v8[1] = 1LL;
*v8 =
"*"
;
v9 = (
__int64
*)runtime_mapassign_faststr(&type_textproto_MIMEHeader, v31, v37, v7[1]);
v9[1] = 1LL;
v9[2] = 1LL;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v9, v35);
else
*v9 = v35;
v39 = (main::Req *)runtime_newobject(&type_main_Req);
v30 = *((_QWORD *)request + 9);
v34 = runtime_convI2I(&type_io_Reader, *((_QWORD *)request + 8));
v10 = runtime_newobject(&type_json_Decoder);
*v10 = v34;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v10 + 1, v30);
else
v10[1] = v30;
v11 = encoding_json___Decoder__Decode(v10, &type_main_Req_ptr, v39);
if
( v11 )
{
v26 = (*(
__int64
(__fastcall **)(
__int64
))(v11 + 24))(v12);
net_http_Error(Response_vtable, response, v26, v27, 400LL);
}
else
if
( v39->Size > 8 * v39->Data.len
|| (*(_OWORD *)v14 = os_OpenFile(aDevVmfs, 9LL, 0LL, 0600LL), v13 = v14[0], v14[1]) )
{
net_http_Error(Response_vtable, response, aInternalServer, 21LL, 500LL);
}
else
{
v33 = (_QWORD *)v14[0];
v15 = v39->Id;
v16 = v39->Data.data;
v17 = v39->Size;
v18 = v39->ChooseIndex;
v29[4] = 0LL;
v29[0] = v15;
v29[1] = (
__int64
)v16;
v29[2] = v17;
v29[3] = v18;
v38 = v29;
if
( v14[0] )
{
if
( (*(_BYTE *)(*(_QWORD *)v14[0] + 80LL) & 1) != 0 )
{
internal_poll___FD__SetBlocking(*(_QWORD *)v14[0]);
v13 = (
__int64
)v33;
}
v19 = *(_QWORD *)(*(_QWORD *)v13 + 16LL);
}
else
{
v19 = -1LL;
}
syscall_Syscall(SYS_ioctl, v19, 0xFF03LL, v38);
if
( v33 )
{
v28 = v20;
os___file__close(*v33);
v20 = v28;
}
if
( v20
|| (v40 = aSuccess,
v41 = 7LL,
v21 = runtime_convTstring(aSuccess),
v22 = encoding_json_Marshal(&type_main_Message, v21),
v25) )
{
net_http_Error(Response_vtable, response, aInternalServer, 21LL, 500LL);
}
else
{
Response_vtable->net_http_(_response)_Write(response, v22, v23, v24);
}
}
}
void
__fastcall main_runFile(ResponseWriter_table *Response_vtable,
void
*response,
void
*request)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
if
( (unsigned
__int64
)&v52 < 0x18170 || (unsigned
__int64
)&v34[15] <= *(_QWORD *)(v3 + 16) )
{
v53 = Response_vtable;
v54 = response;
v55 = request;
runtime_morestack_noctxt();
}
v55 = request;
v53 = Response_vtable;
v54 = response;
v41 = Response_vtable->net_http_(_response)_Header(response);
*(_OWORD *)v4 = net_textproto_CanonicalMIMEHeaderKey(aContentType, 12LL);
v36 = v4[1];
v46 = v4[0];
v5 = runtime_newobject(&type_string_array_1_ptr);
v45 = v5;
v5[1] = 16LL;
*v5 = aApplicationJso;
v6 = (_QWORD *)runtime_mapassign_faststr(&type_textproto_MIMEHeader, v41, v46, v36);
v6[1] = 1LL;
v6[2] = 1LL;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v6, v45);
else
*v6 = v45;
v40 = v53->net_http_(_response)_Header(v54);
*(_OWORD *)v7 = net_textproto_CanonicalMIMEHeaderKey(aAccessControlA_3, 27LL);
v36 = v7[1];
v46 = v7[0];
v8 = runtime_newobject(&type_string_array_1_ptr);
v44 = v8;
v8[1] = 1LL;
*v8 =
"*"
;
v9 = (_QWORD *)runtime_mapassign_faststr(&type_textproto_MIMEHeader, v40, v46, v36);
v9[1] = 1LL;
v9[2] = 1LL;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v9, v44);
else
*v9 = v44;
v48 = (main::Req *)runtime_newobject(&type_main_Req);
v39 = v55[9];
v43 = runtime_convI2I(&type_io_Reader, v55[8]);
v10 = runtime_newobject(&type_json_Decoder);
*v10 = v43;
if
( dword_4927C0 )
runtime_gcWriteBarrier(v10 + 1, v39);
else
v10[1] = v39;
v11 = encoding_json___Decoder__Decode(v10, &type_main_Req_ptr, v48);
if
( v11 )
{
v32 = (*(
__int64
(__fastcall **)(
__int64
))(v11 + 24))(v12);
net_http_Error(v53, v54, v32, v33, 400LL);
}
else
{
*(_OWORD *)v14 = os_OpenFile(aDevVmfs, 9LL, 0LL, 0600LL);
v13 = v14[0];
if
( v14[1] )
goto
LABEL_31;
v42 = (_QWORD *)v14[0];
v15 = regs_and_code;
do
{
*v15 = 0LL;
v15[1] = 0LL;
v15 += 2;
}
while
( (
__int64
)v15 <= (
__int64
)&v51.len );
v16 = memory;
do
{
*v16 = 0LL;
v16[1] = 0LL;
v16 += 2;
}
while
( (
__int64
)v16 <= (
__int64
)&memory[4094] );
v51.data = memory;
v51.len = 4096LL;
v51.cap = 4096LL;
v17 = v48->Id;
v18 = v48->Size;
v19 = v48->ChooseIndex;
v37[4] = 0LL;
v37[0] = v17;
v37[1] = ®s_and_code[17];
v37[2] = v18;
v37[3] = v19;
v47 = v37;
if
( v14[0] )
{
if
( (*(_BYTE *)(*(_QWORD *)v14[0] + 80LL) & 1) != 0 )
{
internal_poll___FD__SetBlocking(*(_QWORD *)v14[0]);
v13 = (
__int64
)v42;
}
v20 = *(_QWORD *)(*(_QWORD *)v13 + 16LL);
}
else
{
v20 = -1LL;
}
syscall_Syscall(SYS_ioctl, v20, 0xFF04LL, v47);
if
( v42 )
{
v35 = v21;
os___file__close(*v42);
v21 = v35;
}
if
( v21 )
{
LABEL_31:
net_http_Error(v53, v54, aInternalServer, 21LL, 500LL);
}
else
{
v22 = v34;
v23 = regs_and_code;
do
{
v24 = *v23;
v25 = v23[1];
v23 += 2;
*v22 = v24;
v22[1] = v25;
v22 += 2;
}
while
( (
__int64
)v23 <= (
__int64
)&v51.len );
v26 = main_runVM();
v49[0] = aSuccess;
v49[1] = 7LL;
v49[2] = v26;
v27 = runtime_convT(&type_main_VmMessage, v49);
v28 = encoding_json_Marshal(&type_main_VmMessage, v27);
if
( v31 )
net_http_Error(v53, v54, aInternalServer, 21LL, 500LL);
else
v53->net_http_(_response)_Write(v54, v28, v29, v30);
}
}
}
|
开了 sandbox , dump 出来发现是禁用了 execve 和 execveat 。开了个 http server ,有三个接口,每个接口的输入格式都是 main.Req
的 json 形式。提取出来逻辑如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
api post interface: {
"Id"
: uint64,
"Size"
: uint64,
"ChooseIndex"
: uint64,
"Data"
: uint64[]}
// {"Id": 0, "Size": 1, "ChooseIndex": 0, "Data": [1, 2]}
url: ?:8080/api/create-file
assert
(Size <= 0x8000);
gofile_obj = os.OpenFile(
"/dev/vmfs"
, 0, 0o600);
sys_ioctl(gofile_obj->fd, 0xFF00, &(struct_to_kernel) {Id, 0, Size, 0, 0});
os.(_file).close(*gofile_obj);
url: ?:8080/api/write-file:
assert
(Size <= 8 * Data.length);
gofile_obj = os.OpenFile(
"/dev/vmfs"
, 0, 0o600);
sys_ioctl(gofile_obj->fd, 0xFF03, &(struct_to_kernel) {Id, Data.ptr, Size, ChooseIndex, 0});
os.(_file).close(*gofile_obj);
url: ?:8080/api/run-file:
gofile_obj = os.OpenFile(
"/dev/vmfs"
, 0, 0o600);
sys_ioctl(gofile_obj->fd, 0xFF04, &(struct_to_kernel) {Id, output_data, Size, ChooseIndex, 0});
os.(_file).close(*gofile_obj);
retval = main.runVM(output_data);
// uint64[]
Write({
"Text"
:
"success"
,
"Return"
: retval});
|
ioctl 调用说明这里要跟内核交互了。逆内核模块 vmfs ,得到几项功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct struct_to_kernel {
uint64
Id
;
uint64
*
Data;
uint64 Size;
uint64 ChooseIndex;
uint64 method;
};
ff00: create(
Id
, Size)
ff01: mov_to_trash(
Id
, ChooseIndex)
ff02: delete_from_trash(
Id
, ChooseIndex)
ff03: set_data(
Id
, Size, ChooseIndex, Data)
ff04: get_data(
Id
, Size, ChooseIndex, Data)
ff05: sort(method)
/
/
3
: select sort
|
create 会对 Size 做检测,不能大于 0x10000
mov_to_trash 会将 list 中已有的项添加到 trash 中,并释放分配的数组内存,但是没有置为空
delete_from_trash 将 trash 中的一项彻底删除,并将 list 中对应项释放掉并且置为空
set_data 和 get_data 就是向分配的数组中写入数据和拿出数据
sort 会对 list 中的项目排序,不同 method 值采用不同的排序算法。其中当 method = 3 时采用选择排序,而选择排序不是稳定的排序算法,即不能保证排序之前值相同的项在排序后保持相同的顺序,再结合 mov_to_trash 就可以 double free 。不过这是内核的利用部分了,用户态程序是没有 ff01 的接口的,需要先找用户态程序的漏洞。
在内核模块初始化时会自动添加一条 list 中的记录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
v4 = (object *)kmalloc_trace(MEMORY[0x33E0], 0xCC0LL, 0x18LL);
v5 = v4;
if
( v4 )
{
v4->addr = 0LL;
v4->Id = 0x636DB8C2LL;
v4->Size = 0x10000LL;
v6 = (uint64 *)kmalloc_large(0x10000LL, 0xCC0LL);
v5->addr = v6;
if
( v6 )
{
memset
(v6, 0, 0x10000uLL);
list[0] = v5;
}
}
|
Size 为 0x10000 ,而用户态程序可创建的最大大小为 0x8000 ,如果只是看用户态程序是没问题的,而添加的这一条记录就给用户态程序带来了漏洞,因为 run-file 会将数据从内核复制到用户态程序中,用户态程序并没有检查 Size 的大小,用来保存数据的栈内存只有 0x8000 字节(即 4096 个 uint64 ),如果从内核里复制出来的长度大于 0x8000 字节就可以覆盖栈上数据,先覆盖的是 vm 执行时的 memory 结构体,覆盖指针、长度就可以任意读写;再之后是上层函数的 lr 指针,再后面是当前函数的参数,因为函数返回之前要调用第一个参数的一个虚表函数,所以需要设置为一个程序里的值。再后面就是随便栈溢出构造 rop 了。 arm 的 rop 不太好做,不过幸好能找到这样一条 gadget ,大概是类似于x86的 setcontext 的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
.text:000000000007347C LDP X25, X26, [SP,#0xC8]
.text:0000000000073480 LDP X23, X24, [SP,#0xB8]
.text:0000000000073484 LDP X21, X22, [SP,#0xA8]
.text:0000000000073488 LDP X19, X20, [SP,#0x98]
.text:000000000007348C LDP X16, X17, [SP,#0x88]
.text:0000000000073490 LDP X14, X15, [SP,#0x78]
.text:0000000000073494 LDP X12, X13, [SP,#0x68]
.text:0000000000073498 LDP X10, X11, [SP,#0x58]
.text:000000000007349C LDP X8, X9, [SP,#0x48]
.text:00000000000734A0 LDP X6, X7, [SP,#0x38]
.text:00000000000734A4 LDP X4, X5, [SP,#0x28]
.text:00000000000734A8 LDP X2, X3, [SP,#0x18]
.text:00000000000734AC LDP X0, X1, [SP,#8]
.text:00000000000734B0 LDR X30, [SP,#0x1F0]
.text:00000000000734B4 LDUR X29, [SP,#-8]
.text:00000000000734B8 LDR X27, [SP]
.text:00000000000734BC ADD SP
|
再结合 go 程序里自带的 syscall 函数,加上前面的任意地址读写,加上程序没开 pie ,可以直接在 bss 段写入 shellcode 之后调用 mprotect 改 bss 段权限,再跳转到 shellcode 执行,在 shellcode 中调用 accept 得到与用户交互的 fd ,这样就可以读取更多的 shellcode 用于执行,这样就可以进入到内核利用了。
接下来就是这题比较恶心的点了。调试时首先循环 read 4 号 fd ,发现不阻塞且无法得到大于 0 的结果。最后关掉 4 ,然后用对 socket fd 3 调用 sys_accept ,调了半天还是 accept 不进来,最后发现原来是 accept 不阻塞。最后循环 accept 和 recvfrom ,才开始稳定读取 shellcode 并利用。
由于禁用掉了 execve 和 execveat ,于是只能把用来打内核的 exp 的 binary 给读出来,作为 shellcode 来执行。 mmap 出代码段,即 exp 中的 0x23300000 和 stack 段,全部内存 dump 进去还原现场,设置完 sp 和 pc ,最后从 start 开始执行
周二事情比较多,加上俺也确实不太会逆向,所以专业的事情让专业的逆向爷爷来做。所以当我真正上手这个题的时候逻辑好像跟我无关了。
在我的视角中,只知道有内核菜单堆的几个功能,其中 0xff01 的 free 之后还留着 Dangling Pointer。并且,这个 Dangling Pointer 还会在选择排序之后被换位... 然后就是... Double Free~
补充一点:选择排序是一种不稳定的排序算法。在这个题中,选择排序会导致相同 id 不同 idx 的两个 object 位置发生交换。当其中一个已经是 Dangling Pointer 的时候,进行选择排序,再尝试 free 另一个 object。因为发生了交换,所以第二次 free 的还是原来的那个 object。这就构成了 Double Free。
POC 如下:
1
2
3
4
5
6
7
|
PRINT_AND_EXECUTE(dev_alloc(2, 0xc0););
PRINT_AND_EXECUTE(dev_alloc(2, 0xc0););
PRINT_AND_EXECUTE(dev_alloc(1, 0xc0););
PRINT_AND_EXECUTE(dev_alloc(1, 0xc0););
PRINT_AND_EXECUTE(dev_free(2, 1););
PRINT_AND_EXECUTE(dev_select_sort(););
PRINT_AND_EXECUTE(dev_free(2, 0););
|
于是接下来就进入到常规 Linux kernel exploitation 的环节。Double Free 在内核堆利用中算是比较好利用的一种,而且这个题还可以控制 double free object 的大小,所以思路应该有很多。比如用直接用 tty_struct 泄露+ROP。
不过俺之前被 aarch64 的 ROP 整怕了,而且也不知道新内核里面 gadget 够不够用,思来想去还是别赌了,还是打稳定一点的 cross-cache 吧...
思路呢就和去年 N1CTF 的 Praymoon 差不多。指路牌 ->
这里简单概括一下:
一切都看起来很简单,俺也在 6 个小时的时间内调通了内核 exp。此时正是凌晨 4 点,用户态那边也基本弄完了,一切都在向好的方向发展...
所以,为什么俺们又从早上搞到了晚上 9 点呢?
前情提要,用户态 vm 开了 seccomp,ban 掉了 execve 和 execveat。如果只是一个用户态题目,rop 执行 orw 也就结束了,但是... 我们还有一个内核 exp 的二进制需要上传执行。没有 shell,怎么传?传不了一点。
和用户态爷爷(还是 IChild)商量了一下,可以在用户态执行 shellcode 之后,就可以给我 mmap 一段 rwx 内存,我就可以手动把二进制文件"加载"到内存中,然后执行。(WHO NEEDS EXECVE? UH?)
单步跟了一下,发现问题出现在栈上。 libc_start_main 会从栈上拿 env。所以,我们这次把一个合法的栈也拷贝进内存了。
然后。。。继续 SegmentFault。。。 WTF???
而且这次死的地方十分玄学,死在内核 exp 刚刚进去的 mmap syscall 中,也就是程序进了 mmap syscall 中再也没出来。。。
于是果断地拉来了巨神,在巨神的指导下把 aarch64 的 strace 编译好放到了 rootfs 里面,运行...
“这个 SIGURG 是你预期的吗?”
“【黑人问号】哪来的 SIGURG?”
“但它就是被 SIGURG 杀了...”
于是在 shellcode 里面 handle 了一下 SIGURG(此处省略 4 个小时。。。)
...
终于在马拉松式的调试一天之后,成功改完了 busybox,复用 8080 端口 nc -e 拿到了反弹的 root shell!
更多【kctf-2023 第九题 突破防线 98k战队wp】相关视频教程:www.yxfzedu.com