张春 发表于 2024-5-14 07:45:28

从 VNCTF2024 的一道题学习QEMU Escape

说在前面

本文的草稿是边打边学边写出来的,文章思绪会与一个“刚打完用户态 pwn 题就去打 QEMU Escape ”的人的思绪相似,在分析结束以后我又在部门比力模糊的地方参加了一些补充,因此阅读起来可能会相对轻松。(当然也不排除这是我自以为是)
题目 github 仓库
题目分析流程

启动文件分析

读 Dockerfile,了解到它在搭起环境以后启动了start.sh,
再读 start.sh,了解到它启动了 xinetd 程序
再读 xinetd,这个程序的重要作用是监听指定 port,并根据预先界说好的配置来启动相应服务。可以看到 server_args 处启动了 run.sh
再读 run.sh,发现它用 QEMU 起了一个程序,通过 -device vn 我们可以知道 vn 是作为 QEMU 中的一个 pci装备 存在的。
通过 IDA 查找字符串 vn_ 可以找到 vn_instance_init,跟进调用 字符串vn_instance_init 的 函数vn_instance_init,再按 x 检察 函数vn_instance_init 的引用,可以看到下面还有一个 vn_class_init ,反汇编后看到
__int64 __fastcall vn_class_init(__int64 a1)
{
 __int64 result; // rax

 result = PCI_DEVICE_CLASS_23(a1);
 *(_QWORD *)(result + 176) = pci_vn_realize;
 *(_QWORD *)(result + 184) = 0LL;
 *(_WORD *)(result + 208) = 0x1234; // 厂商ID (Vendor ID)
 *(_WORD *)(result + 210) = 0x2024; // 设备ID (Device ID)
 *(_BYTE *)(result + 212) = 0x10;
 *(_WORD *)(result + 214) = 0xFF;
 return result;
}通过厂商ID和装备ID,我们可以判定下列 pci 装备中 00:04.0 Class 00ff: 1234:2024 就是我们要找的 vn
/sys/devices/pci0000:00/0000:00:04.0 # lspci
lspci
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:2024
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111进而去/sys/devices/pci0000:00/0000:00:04.0 目录检察该装备 mmio 与 pmio 的注册环境
/sys/devices/pci0000:00/0000:00:04.0 # ls -al
...
...
-r--r--r--  1 0      0             4096 Feb 18 12:18 resource
-rw-------  1 0      0             4096 Feb 18 12:18 resource0
...
...有了 resource0 这个文件,我们就可以在exp里 mmap 做假造地址映射。
并且我们可以看到 vn 这个装备只注册了 mmio,那就考虑用 mmio攻击(点击这里了解 mmio 运行原理)
静态分析

如果我写的不够清楚,读者可以参考 blizzardCTF 里的 strng这一实现,读完这段代码会对 pci 装备的了解提升一个台阶。
我们先补充一些概念:
QEMU 提供了一套完备的模拟硬件给 QEMU 上的 kernel 来利用,而 -device 参数为 kernel 提供了模拟的 pci 装备。
如果 kernel 实现了雷同 linux 的 rootfs,我们就可以通过 lspci 来检察相干 pci,并在/sys/devices/...找到 pci 装备启动时 kernel 分配给 pci 的资源,也就是 resource0 等,这也是前文提到过的。
resource0 可以看作是一大片开关,当我们修改 resource0 中的内容时,可以看做对应开关被启动,pci装备也随着开关的启动而变化,具体表现为“控制寄存器、状态寄存器以及装备内部的内存区域 随着 resource0 的变化而变化”
以是我们可以 open resource0 这个文件,用 mmap 映射它,从而使我们能够在C代码中对 resource0 这片内存进行修改
可是由于 QEMU 也只不过是一个程序,假造的 pci 装备意味着,一定有一片内存存储着 pci 相干的数据
关于 pci 存储数据的这一部门好像就涉及 QOM 了,还没太搞懂,总之跟pci_xx_realize, xx_class_init, xx_instance_init 等函数有关
假设我们的调用链是这样的:
docker -> QEMU -> exp

则 docker 会让 QEMU 误以为自己占据全部内存空间,QEMU 会让 exp 认为自己占据全部内存空间

而 QEMU 的 pci 设备的 MemoryRegion 就存储在 QEMU 的堆区上,我们在程序 exp 中读写 resource0,就相当于操控 vn_mmio_read 和 vn_mmio_write 去读写 QEMU 的堆区,如果我们正好修改到 MemoryRegion 的 xx_mmio_ops 指针,就可以劫持控制流。那么,接下来我们要做的事变就是去读一下 vn_mmio_read 和 vn_mmio_write 的反汇编,了解怎样读写堆区内容。
https://i0.imgs.ovh/2024/02/20/oNxdA.md.png
由于对 QEMU 不是很认识,我只能瞎定名,vn_mmio_write 的大体逻辑是
<ul>
object_dynamic_cast_assert是动态范例转换,我OOP学的很烂以是不清楚这是什么
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 从 VNCTF2024 的一道题学习QEMU Escape