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),从而劫持控制流。
攻击方法:
任意地址写main_loop_tlg ,在堆上伪造QEMUTimerList与QEMUTimer结构体,从而实现控制流劫持;
任意地址写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; QEMUTimer *active_timers; QLIST_ENTRY(QEMUTimerList) list ; QEMUTimerListNotifyCB *notify_cb; void *notify_opaque; 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; QEMUTimerCB *cb; void *opaque; 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)) { ... 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
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标准空间:
获取设备地址:qwb设备的Bus number为0,Device number为2,Function number为0,得出qwb的地址为0x80001000。
COMMAND :用于控制设备的行为,例如启用或禁用I/O空间、内存空间、总线主控等。
总结一下初始化需要操作的步骤:
将MMIO地址写入qwb设备的BAR0地址
通过0xcf8端口设置目标地址
通过0xcfc端口写值
将命令写入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.ops 为system,然后通过执行writeq addr val触发获取flag。
参考文章: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控制流劫持总结:
劫持main_loop_tlg,伪造QEMUTimer执行命令。
QEMU对设备的读写注册了MMIO或者PMIO,劫持相应的读写指针。
每个设备都存在中断,劫持XXXState中中断相关的指针执行命令。
至于地址泄露,QEMU中存在很多能够泄露地址的地方,比如XXXState结构体就存在很多指向QEMU代码段的指针。