QEMU-PWN记录

QEMU-PWN记录

关于QEMU基础知识参考raycp大师傅的文章。

笔者这里也推荐两本书入门QEMU或者说虚拟化安全:《QEMU/KVM源码解析与应用》、《深度探索Linux系统虚拟化:原理与实现》

关于QEMU的QOM机制,笔者也从书中做了一点点小的摘抄:

类型的注册 type_init

类型的初始化 class_init

对象的初始化 instnce_init realize (设备的具象化)

类型的注册. TypeInfo->TypeImpl. 哈希表(Name: TypeImpl)

类型的初始化. type_initialize 1.设置TypeImpl的域; 2.调用class_init

对象的初始化. object-new -> object_new_with _type -> object_initialize_with_type -> object_init_whith_type(递归调用父类型的初始化函数和自身的初始函数)每一个对象都会有一个xxxState与之对应。

经过对象的初始化后,仅仅是构造出了对象,但是相应的xxxState并没有初始化完成,还需要设置对象的realized属性对设备具现化。这个时候会调用相应的realize函数,对xxxState

HITB 2017 babyqemu

用户与PCI设备的交互:通过MMIO这一段内存区域(少量数据传送,多用于设置参数、控制命令等);通过DMA进行大量数据交互(直接与PCI设备的物理内存进行交互,需要将用户态虚拟地址转换为物理地址)。

https://xuanxuanblingbling.github.io/ctf/pwn/2022/06/09/qemu/

https://www.giantbranch.cn/2020/01/02/CTF%20QEMU%20%E8%99%9A%E6%8B%9F%E6%9C%BA%E9%80%83%E9%80%B8%E4%B9%8BHITB-GSEC-2017-babyqemu/

HWS 2021 FastCP

关键:分配连续的物理页。

在进行拷贝时,虽然用户可以申请一个大于0x1000在虚拟地址上连续的内存空间,但其未必是物理地址空间上连续的多个页。当使用cpu_physical_memory_rw函数进行较长的内存拷贝时,一定要确保给出的空间在物理地址上连续。

Qemu常用的控制流原语

在bss段,存在一个全局变量main_loop_tlg,其是一个QemuTimerList数组。当QEMUTimer->expire_time超时(设置为-1会直接触发),就会执行QEMUTimer->cb(opaque),从而劫持控制流。

攻击方法:

  1. 任意地址写main_loop_tlg ,在堆上伪造QEMUTimerList与QEMUTimer结构体,从而实现控制流劫持;
  2. 任意地址写main_loop_tlg[0]->active_timers ,在堆上伪造QEMUTimer结构体,从而实现控制流劫持。
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
typedef void QEMUTimerListNotifyCB(void *opaque, QEMUClockType type);
typedef void QEMUTimerCB(void *opaque);

struct QEMUTimerList {
QEMUClock *clock;
QemuMutex active_timers_lock; // size: 0x38
QEMUTimer *active_timers; // 0x40
QLIST_ENTRY(QEMUTimerList) list;
QEMUTimerListNotifyCB *notify_cb; // 0x58
void *notify_opaque; // 0x60

/* lightweight method to mark the end of timerlist's running */
QemuEvent timers_done_ev;
};

typedef enum {
QEMU_CLOCK_REALTIME = 0,
QEMU_CLOCK_VIRTUAL = 1,
QEMU_CLOCK_HOST = 2,
QEMU_CLOCK_VIRTUAL_RT = 3,
QEMU_CLOCK_MAX
} QEMUClockType;

struct QEMUTimerListGroup {
QEMUTimerList *tl[QEMU_CLOCK_MAX];
};

extern QEMUTimerListGroup main_loop_tlg;

struct QEMUTimer
{
int64_t expire_time;
QEMUTimerList *timer_list;

// cb(opaque)
QEMUTimerCB *cb; // func_ptr
void *opaque; // param

QEMUTimer *next;
int attributes;
int scale;
};


void main_loop_wait(int nonblocking)
{
// ...
qemu_clock_run_all_timers();
}
bool qemu_clock_run_all_timers(void)
{
bool progress = false;
QEMUClockType type;

for (type = 0; type < QEMU_CLOCK_MAX; type++) {
if (qemu_clock_use_for_deadline(type)) {
progress |= qemu_clock_run_timers(type);
}
}
return progress;
}

bool qemu_clock_run_timers(QEMUClockType type)
{
return timerlist_run_timers(main_loop_tlg.tl[type]);
}

bool timerlist_run_timers(QEMUTimerList *timer_list)
{
// ...
switch (timer_list->clock->type) {
case QEMU_CLOCK_REALTIME: // ...
case QEMU_CLOCK_VIRTUAL: // ...
case QEMU_CLOCK_HOST: // ...
case QEMU_CLOCK_VIRTUAL_RT:
if (!replay_checkpoint(CHECKPOINT_CLOCK_VIRTUAL_RT)) {
goto out;
}
break;
}
current_time = qemu_clock_get_ns(timer_list->clock->type);
qemu_mutex_lock(&timer_list->active_timers_lock);
while ((ts = timer_list->active_timers)) {
...
/* run the callback (the timer list can be modified) */
qemu_mutex_unlock(&timer_list->active_timers_lock);
cb(opaque);
qemu_mutex_lock(&timer_list->active_timers_lock);

progress = true;
}
// ...
}

参考文章:https://www.anquanke.com/post/id/254906

image-20240708105413016

image-20240708105729666

QWB 2021 EzTest

缺乏对应的链接库,直接google搜索,然后apt安装即可。

使用QTest(https://www.qemu.org/docs/master/devel/qtest.html)协议进行交互

关键点:如何定位MMIO基地址?

之前QEMU逃逸类型的题目都是在Linux系统下进行操作,mmio可以通过类似/sys/devices/pci0000:00/0000:00:04.0/resource1的路径来操作,但是在qtest命令行下要怎么操作呢?

配置PCI标准空间:

wN1TRIKfUQYEoub

获取设备地址:qwb设备的Bus number为0,Device number为2,Function number为0,得出qwb的地址为0x80001000

4HeUZ67czkb9VGl

COMMAND:用于控制设备的行为,例如启用或禁用I/O空间、内存空间、总线主控等。

GcODnj5Ietxbd2m

总结一下初始化需要操作的步骤:

  1. 将MMIO地址写入qwb设备的BAR0地址

    • 通过0xcf8端口设置目标地址

    • 通过0xcfc端口写值

  2. 将命令写入qwb设备的COMMAND地址,触发pci_update_mappings

    • 通过0xcf8端口设置目标地址

    • 通过0xcfc端口写值

1
2
3
4
outl 0xcf8 0x80001010
outl 0xcfc 0xfebc0000
outl 0xcf8 0x80001004
outw 0xcfc 0x107

劫持QWBState->mmio.opssystem,然后通过执行writeq addr val触发获取flag。

image-20240709143419152

参考文章:https://matshao.com/2021/06/15/QWB2021-Quals-EzQtest/

DefconQuals 2018 EC3

题目去掉了符号,搜索字符串以及比对其它有符号的qemu可以确定关键操作函数。

之后就是一个libc堆题,把堆塞到了qemu逃逸中。

参考文章:https://xz.aliyun.com/t/6778?time__1311=n4%2BxnD0DRDyDgDIohDlaoQPRiDBDjrxAKwtYx&alichlgref=https%3A%2F%2Fwww.google.com%2F

总结

既然都写了,就做一个小的总结吧。

QEMU控制流劫持总结:

  1. 劫持main_loop_tlg,伪造QEMUTimer执行命令。
  2. QEMU对设备的读写注册了MMIO或者PMIO,劫持相应的读写指针。
  3. 每个设备都存在中断,劫持XXXState中中断相关的指针执行命令。

至于地址泄露,QEMU中存在很多能够泄露地址的地方,比如XXXState结构体就存在很多指向QEMU代码段的指针。


QEMU-PWN记录
http://example.com/2024/06/20/QEMU-PWN/
作者
l1s00t
发布于
2024年6月20日
许可协议