qemu pwn-基础知识
字数 1231 2025-08-24 23:51:15
QEMU PWN 基础知识详解
QEMU概述
QEMU是一个纯软件实现的虚拟化模拟器,几乎可以模拟任何硬件设备。每个QEMU虚拟机对应host上的一个QEMU进程,虚拟机的执行线程(如CPU线程、I/O线程等)对应QEMU进程的一个线程。
虚拟机内存结构
虚拟机内存对应的真实内存结构如下:
Guest' processes Virtual addr space Page Table
Guest kernel
Guest's phy. memory
QEMU process Virtual addr space Page Table
Physical memory
QEMU进程会为虚拟机mmap分配出相应虚拟机申请大小的内存,用于给该虚拟机当作物理内存。例如,使用1G内存启动的虚拟机:
0x7fe37fe00000 0x7fe3bfe00000 rw-p 40000000 0 // 虚拟机对应的内存
虚拟地址到物理地址转换
在QEMU虚拟机中申请内存后,可以通过以下步骤在宿主机中找到对应内存:
- 将虚拟机中的虚拟地址转化为物理地址
- 该物理地址即为QEMU进程为其分配出来的相应偏移
- 使用该地址加上QEMU进程中的内存基址
示例转换代码:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)
int fd;
uint32_t page_offset(uint32_t addr) {
return addr & ((1 << PAGE_SHIFT) - 1);
}
uint64_t gva_to_gfn(void *addr) {
uint64_t pme, gfn;
size_t offset;
offset = ((uintptr_t)addr >> 9) & ~7;
lseek(fd, offset, SEEK_SET);
read(fd, &pme, 8);
if (!(pme & PFN_PRESENT))
return -1;
gfn = pme & PFN_PFN;
return gfn;
}
uint64_t gva_to_gpa(void *addr) {
uint64_t gfn = gva_to_gfn(addr);
assert(gfn != -1);
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}
int main() {
uint8_t *ptr;
uint64_t ptr_mem;
fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
ptr = malloc(256);
strcpy(ptr, "Where am I?");
printf("%s\n", ptr);
ptr_mem = gva_to_gpa(ptr);
printf("Your physical address is at 0x%" PRIx64 "\n", ptr_mem);
getchar();
return 0;
}
PCI设备基础
PCI配置空间
PCI设备都有一个256字节的配置空间(PCI Configuration Space),其中头部64字节是PCI标准规定的。关键部分包括:
- 前16字节:包含头部的类型、设备的总类、设备的性质以及制造商等信息
- 6个BAR(Base Address Registers):记录设备所需要的地址空间的类型、基址及其他属性
BAR格式
BAR的格式如下:
bit 63-4: 地址
bit 3: 预取标志(仅MMIO)
bit 2: 地址大小(1=64位,0=32位)
bit 1: 区间大小(1=>1M,0<=1M)
bit 0: 空间类型(0=MMIO,1=PMIO)
MMIO与PMIO
-
MMIO (Memory-mapped I/O):
- 内存和I/O设备共享同一个地址空间
- CPU使用普通访存指令访问设备I/O
- 需要预留地址区域给I/O设备
-
PMIO (Port-mapped I/O):
- 内存和I/O设备有各自的地址空间
- CPU使用专门的I/O指令(IN/OUT)访问I/O端口
- 也称为Isolated I/O
查看PCI设备
使用lspci命令查看PCI设备:
lspci # 显示所有PCI设备
lspci -t -v # 树状形式输出PCI结构
lspci -v -m -n -s 00:03.0 # 查看特定设备详细信息
设备寻址格式为域:总线:设备.功能,例如0000:00:03.0。
查看设备配置空间
hexdump /sys/devices/pci0000:00/0000:00:03.0/config
查看设备内存空间
lspci -v -s 00:03.0 -x # 显示设备内存和I/O空间信息
cat /sys/devices/pci0000:00/0000:00:03.0/resource # 查看资源分配
QOM编程模型
QEMU使用QOM(QEMU Object Module)面向对象编程模型来实现设备模拟。关键结构体:
TypeInfo
用户定义Type的数据结构:
struct TypeInfo {
const char *name;
const char *parent;
size_t instance_size;
void (*instance_init)(Object *obj);
void (*instance_post_init)(Object *obj);
void (*instance_finalize)(Object *obj);
bool abstract;
size_t class_size;
void (*class_init)(ObjectClass *klass, void *data);
void (*class_base_init)(ObjectClass *klass, void *data);
void (*class_finalize)(ObjectClass *klass, void *data);
void *class_data;
InterfaceInfo *interfaces;
};
TypeImpl
由TypeInfo注册后生成的类型实现。
ObjectClass
所有类的基类:
struct ObjectClass {
Type type;
GSList *interfaces;
const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE];
const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE];
ObjectUnparent *unparent;
GHashTable *properties;
};
Object
具体设备实例:
struct Object {
ObjectClass *class;
ObjectFree *free;
GHashTable *properties;
uint32_t ref;
Object *parent;
};
设备实现示例
static const TypeInfo pci_testdev_info = {
.name = TYPE_PCI_TEST_DEV,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(PCITestDevState),
.class_init = pci_testdev_class_init,
};
static void pci_testdev_class_init(ObjectClass *klass, void *data) {
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
k->init = pci_testdev_init;
k->exit = pci_testdev_uninit;
dc->desc = "PCI Test Device";
}
内存区域操作
QEMU使用MemoryRegion表示内存空间,使用MemoryRegionOps定义操作:
MMIO操作示例
static const MemoryRegionOps pci_testdev_mmio_ops = {
.read = pci_testdev_read,
.write = pci_testdev_mmio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
};
PMIO操作示例
static const MemoryRegionOps pci_testdev_pio_ops = {
.read = pci_testdev_read,
.write = pci_testdev_pio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
};
初始化内存区域
memory_region_init_io(&d->mmio, OBJECT(d), &pci_testdev_mmio_ops, d,
"pci-testdev-mmio", IOTEST_MEMSIZE * 2);
memory_region_init_io(&d->portio, OBJECT(d), &pci_testdev_pio_ops, d,
"pci-testdev-portio", IOTEST_IOSIZE * 2);
pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->portio);
访问I/O空间
内核态访问MMIO
#include <asm/io.h>
#include <linux/ioport.h>
long addr = ioremap(ioaddr, iomemsize);
readb(addr); readw(addr); readl(addr); readq(addr);
writeb(val, addr); writew(val, addr); writel(val, addr); writeq(val, addr);
iounmap(addr);
用户态访问MMIO
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
unsigned char *mmio_mem;
void mmio_write(uint32_t addr, uint32_t value) {
*((uint32_t *)(mmio_mem + addr)) = value;
}
uint32_t mmio_read(uint32_t addr) {
return *((uint32_t *)(mmio_mem + addr));
}
int main(int argc, char *argv[]) {
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
mmio_read(0x128);
mmio_write(0x128, 1337);
}
内核态访问PMIO
#include <asm/io.h>
#include <linux/ioport.h>
inb(port); inw(port); inl(port);
outb(val, port); outw(val, port); outl(val, port);
用户态访问PMIO
#include <sys/io.h>
iopl(3);
inb(port); inw(port); inl(port);
outb(val, port); outw(val, port); outl(val, port);
总结
- QEMU虚拟机内存通过mmap分配,虚拟地址可通过页表转换为宿主机中的地址
- PCI设备通过配置空间和BAR寄存器定义其I/O空间
- QOM模型是QEMU设备模拟的核心,采用面向对象方式实现
- MemoryRegion和MemoryRegionOps用于定义设备的I/O操作
- MMIO和PMIO可以通过内核模块或用户态程序访问