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

CTF QEMU 虚拟机逃逸总结

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

不知什么来源的题目

来源:https://kirin-say.top/2019/11/06/QEMU-Escape-in-Cloud-Security-Game/

漏洞:也不能说漏洞,在mmio_read里面直接给了system的后门

代码语言:javascript
代码运行次数:0
运行
复制
signed __int64 __fastcall rfid_read(__int64 a1, unsigned __int64 a2)
{
  size_t v2; // rax

  if ( ((a2 >> 20) & 0xF) != 15 )
  {
    v2 = strlen(magic_code);
    if ( !memcmp(code, magic_code, v2) )
      system(command);
  }
  return 0x42066LL;
}

利用:通过rfid_write将code赋值为跟magic_code相等,再设置一下command,最后调用rfid_read即可完成逃逸

BlizzardCTF 2017 Strng

题目链接:https://github.com/rcvalle/blizzardctf2017

漏洞:在pmio中对于数组的索引没有限制,导致越界读写

代码语言:javascript
代码运行次数:0
运行
复制
uint64_t __fastcall strng_pmio_read(STRNGState *opaque, hwaddr addr, unsigned int size)
{
  uint64_t result; // rax
  uint32_t v4; // edx

  result = -1LL;
  if ( size == 4 )
  {
    if ( addr )
    {
      if ( addr == 4 )
      {
        v4 = opaque->addr;
        if ( !(v4 & 3) )
          result = opaque->regs[v4 >> 2];    <========= 越界读
      }
    }
    else
    {
      result = opaque->addr;
    }
  }
  return result;
}

void __fastcall strng_pmio_write(STRNGState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
  if ( size == 4 )
  {
    if ( addr )
    {
      if ( addr == 4 )
      {
        v4 = opaque->addr;
        if ( !(v4 & 3) )
        {
          v5 = v4 >> 2;
          if ( (_DWORD)v5 == 1 )
          {
......
......
          }
          else
          {
            opaque->regs[v5] = val;    <========越界写
          }
        }
      }
    }
......
......
  }
}

利用: 1、通过数组越界,获得任意读写来泄露函数指针,计算出system函数地址 2、之后改写rand_r函数指针为system,调用rand_r函数即可劫持控制流

HITB-GSEC-2017-babyqemu

题目下载:https://github.com/kitctf/writeups/blob/master/hitb-gsec-2017/babyqemu/babyqemu.tar.gz

漏洞:也是对于dma_buf数组的索引没有限制,导致任意地址读写,只不过这个题目需要我们将虚拟地址转化为物理地址

代码语言:javascript
代码运行次数:0
运行
复制
void __fastcall hitb_dma_timer(HitbState *opaque)
{
  dma_addr_t v1; // rax
  __int64 v2; // rdx
  uint8_t *v3; // rsi
  dma_addr_t v4; // rax
  dma_addr_t v5; // rdx
  uint8_t *v6; // rbp
  uint8_t *v7; // rbp

  v1 = opaque->dma.cmd;
  if ( v1 & 1 )
  {
    if ( v1 & 2 )
    {
      v2 = (unsigned int)(LODWORD(opaque->dma.src) - 0x40000);
      if ( v1 & 4 )
      {
        v7 = (uint8_t *)&opaque->dma_buf[v2];
        ((void (__fastcall *)(uint8_t *, _QWORD))opaque->enc)(v7, LODWORD(opaque->dma.cnt));
        v3 = v7;
      }
      else
      {
        v3 = (uint8_t *)&opaque->dma_buf[v2];   <=======越界读
      }
      cpu_physical_memory_rw(opaque->dma.dst, v3, opaque->dma.cnt, 1); //读出来后传递给dma.dst 
      v4 = opaque->dma.cmd;
      v5 = opaque->dma.cmd & 4;
    }
    else
    {
      v6 = (uint8_t *)&opaque[0xFFFFFFDBLL].dma_buf[(unsigned int)opaque->dma.dst + 0x510];  //这里ida有点问题,其实这里是opaque->dma_buf[opaque->dma.dst-0x40000]
      LODWORD(v3) = (_DWORD)opaque + opaque->dma.dst - 0x40000 + 0xBB8;
      cpu_physical_memory_rw(opaque->dma.src, v6, opaque->dma.cnt, 0);  // 越界写opaque->dma_buf[opaque->dma.dst-0x40000]
      v4 = opaque->dma.cmd;
      v5 = opaque->dma.cmd & 4;
      if ( opaque->dma.cmd & 4 )
      {
        v3 = (uint8_t *)LODWORD(opaque->dma.cnt);
        ((void (__fastcall *)(uint8_t *, uint8_t *, dma_addr_t))opaque->enc)(v6, v3, v5);
        v4 = opaque->dma.cmd;
        v5 = opaque->dma.cmd & 4;
      }
    }
    opaque->dma.cmd = v4 & 0xFFFFFFFFFFFFFFFELL;
    if ( v5 )
    {
      opaque->irq_status |= 0x100u;
      hitb_raise_irq(opaque, (uint32_t)v3);
    }
  }
}

利用: 1、通过数组越界读泄露函数指针enc,由于这个qemu-system-x86_64的导入表有system,所以我们直接可以算出system 2、用system覆盖enc函数 3、写入opaque->dma_buf为要执行的命令,比如cat flag 4、使用cmd=1|2|4时,调用enc函数,劫持控制流

QEMU Escape — vm_escape from 0CTF 2017 Finals

没有公开下载的题目 wp: https://blog.eadom.net/writeups/qemu-escape-vm-escape-from-0ctf-2017-finals-writeup/

漏洞: 1、对于phys_mem_write的len没有限制,导致可以越界读,同样用到了物理地址(需要将虚拟地址转化为物理地址) 2、uaf漏洞,在vdd_mmio_write中可以设置dma_timer的过期时间,而在vdd_dma_timer中调用了opaque->dma_state->phys_mem_read/write,而pci_vdd_uninit函数会将dma_state释放;所以加入pci_vdd_uninit函数,在vdd_dma_timer之前调用,就是uaf了。

利用: 1、通过phys_mem_write的越界读泄露程序地址还有libc地址(其实这里libc地址用不到,因为导入表有system了,只要程序地址加上偏移就行) 2、将我们要执行的命令复制到opaque->dma_buf 3、在vdd_mmio_write中可以设置dma_timer的过期时间 4、往/sys/bus/pci/slots/4/power写入0,从而触发pci_vdd_uninit去free dma_state 5、通过vdd_linear_write申请dma_state同样大小的内存,并用正确的值填充,具体代码如下,主要是将cmd设置为2,而phys_mem_read函数指针,设置为system,最后上面的dma_timer时间到了就会调用opaque->dma_state->phys_mem_read,从而让我们劫持控制流

代码语言:javascript
代码运行次数:0
运行
复制
void put_fake_dma(void)
{
    struct dma fakedma;
    fakedma.cmd = 2;
    fakedma.phys_mem_read = system_addr;
    memcpy(pbuf, (void *)&fakedma, sizeof(fakedma));
    set_dmalen(0x330);
    set_dmastate_src(virt_to_phys(pbuf));
    set_dmastate_dst(virt_to_phys(pbuf));
    outb(0, VDB_PORT + 0);
}

Defcon 2018 - EC3

题目:https://github.com/o-o-overflow/chall-ec-3

漏洞:在mmio_write中,free的时候,没有将指针置空,导致了uaf漏洞,就跟常规的堆题一样,使用fastbin attack

代码语言:javascript
代码运行次数:0
运行
复制
void __fastcall ooo_mmio_write_6E61F4(__int64 opaque, __int64 addr, __int64 value, unsigned int size)
{
 ......
 ......
  choose = ((unsigned int)addr & 0xF00000) >> 20;
  switch ( choose )
  {
    case 1u:
      free(gbuf_bss_1317940[((unsigned int)addr_copy & 0xF0000) >> 16]);
      break;
    case 2u:
      v12 = ((unsigned int)addr_copy & 0xF0000) >> 16;
      v8 = addr_copy;
      memcpy((char *)gbuf_bss_1317940[v12] + (signed __int16)addr_copy, &value_point[4], size);
      break;
    case 0u:
      v11 = ((unsigned int)addr_copy & 0xF0000) >> 16;
      if ( v11 == 15 )
      {
        for ( i = 0; i <= 14; ++i )
          gbuf_bss_1317940[i] = malloc(8LL * *(_QWORD *)&value_point[4]);
      }
      else
      {
        gbuf_bss_1317940[v11] = malloc(8LL * *(_QWORD *)&value_point[4]);
      }
      break;
  }
}

利用: 1、在gbuf_bss_1317940中构0x7f绕过fastbin检查,比如在gbuf_bss_1317940[8]中存放malloc的返回值 2、利用fastbin attack获得指向gbuf_bss_1317940的指针 3、利用上面的指针,将free或者malloc的got地址写入到gbuf_bss_1317940[10]中 4、通过edit gbuf_bss_1317940[10]即可修改got表 5、最后调用free或者malloc即可劫持控制流,这里有个cat flag的后门函数,劫持到那即可

题目难点在于没有符号表,需要逆向,还有就是qemu自身也会申请或者释放0x60大小的堆,所以需要循环申请和循环修改提升成功率

强网杯 qwb 2019 final execchrome

题目:https://github.com/ray-cp/vm-escape/tree/master/qemu-escape/qwb-final-2019-ExecChrome

只有qemu-system-x86_64,可能需要修复需要的lib,需要的库超级多

漏洞: 1、 nvme_mmio_read存在越界读漏洞,也是对索引没有限制

代码语言:javascript
代码运行次数:0
运行
复制
uint64_t __cdecl nvme_mmio_read(NvmeCtrl *opaque, hwaddr addr, unsigned int size)
{
......
......
  memcpy(&val, &ptr[addr], sizea);
  return val;
}

2、 在nvme_mmio_write中的nvme_write_bar函数中故意添加了代码,也是导致NvmeCtrl->bar.cap之后的0-0x1000偏移任意地址写

代码语言:javascript
代码运行次数:0
运行
复制
if ( sizea == 2 )
     {
       *(_WORD *)((char *)&NvmeCtrl->bar.cap + offset) = dataa;
     }
     else if ( sizea > 2 )
     {
       if ( sizea == 4 )
       {
         *(_DWORD *)((char *)&NvmeCtrl->bar.cap + offset) = dataa;
       }
       else if ( sizea == 8 )
       {
         *(uint64_t *)((char *)&NvmeCtrl->bar.cap + offset) = dataa;
       }
     }
     else if ( sizea == 1 )
     {
       *((_BYTE *)&NvmeCtrl->bar.cap + offset) = dataa;
     }

漏洞利用: 1、利用nvme_mmio_read进行程序地址以及堆地址的泄露,got表中存在system,所以程序地址加一个偏移就得到了system got的地址 2、通过NvmeCtrl->bar.cap之后的0-0x1000偏移任意地址写,在bar后面的地址伪造一个admin_sq->timer,timer中的cb设置为system got的地址 3、也是NvmeCtrl->bar.cap之后的0-0x1000偏移任意地址写,修改admin_cq->timer指针,指向上面伪造的timer

最后竟然通过只有通过重启动触发timer的调用…

XNUCA 2018 SSD

题目:https://github.com/w0lfzhang/vmescape/tree/master/xnuca

漏洞:还是常规的fastbin的uaf放到qemu里面,free的时候没将指针置空

代码语言: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->index)) = State->value_9F8;
        break;
      case 3:
        free(*(void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->index));
        break;
      case 1:
        v3 = (void **)(State->heaplist_9E0 + 8LL * (unsigned int)State->index);
        *v3 = malloc((unsigned int)State->mallocSize);
        break;
    }
    result = State;
    State->cmd_9D0 &= 0xFFFFFFFB;
  }
  return result;
}

漏洞利用: 通过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了

强网杯2019线上赛qwct

题目:https://github.com/ray-cp/vm-escape/tree/master/qemu-escape/qwb-preliminary-2019-qwct

漏洞:

通过strlen()到\x00才截止可以越界读

代码语言:javascript
代码运行次数:0
运行
复制
v18 = strlen((const char *)opaque->crypto.output_buf);
      v19 = size_copy == 1;
      v20 = size_copy == 1;
      if ( addr < v18 + 0x3000 && v19 )
      {
        if ( (opaque->crypto.statu - 6) & 0xFFFFFFFFFFFFFFFDLL )
        {
          result = -1LL;
        }
        else
        {
          v22 = *((_BYTE *)opaque + addr - 0x15C0);
          result = v22;
        }
        return result;

aes_encrypt_function和aes_decrypto_function都有8字节溢出

代码语言:javascript
代码运行次数:0
运行
复制
  if ( v7 )
  {
    v9 = 0LL;
    do
    {
      v10 = (__int64)&output_buf[v9];
      v11 = (__int64)&v4[v9];
      v9 += 16LL;
      AES_ecb_encrypt(v11, v10, (__int64)&aes, 0LL);
    }
    while ( v7 > v9 );
    v18 = 0LL;
    v19 = 0;
    v12 = 0;
    for ( i = 0LL; ; v12 = *((_BYTE *)&v18 + (i & 7)) )
    {
      v14 = output_buf[i] ^ v12;
      v15 = i++;
      *((_BYTE *)&v18 + (v15 & 7)) = v14;
      if ( v7 == i )
        break;
    }
    v16 = v18;
  }
  else
  {
    v16 = 0LL;
  }
  *(_QWORD *)&output_buf[v7] = v16;
  return 1;
}

漏洞利用: 1、mmio_write不能直接填充output_buf,所以我们通过调用stream_encrypto_fucntion去填充疑惑后都是非0的,那么strlen计算就会超过0x800,那就可以越界读,读取到函数指针stream_encrypto_fucntion的地址,从而算出程序的基址,及system_plt地址

2、虽然aes_encrypt_function和aes_decrypto_function都有8字节溢出,但是我们需要控制output_buf的值,我们才能最终控制计算出来的值(即循环异或,第一次是异或0,第二次是异或上一次的结果),直接通过aes_encrypt_function利用有点困哪,我们难以控制加密后的值。但是我们先aes加密,再通过aes解密,那么我们就可以精准控制解密结果了。

题外话

关注下有没常见套路整数溢出漏洞什么的

附录

关于exp传到qemu中

远程环境

  • telnet:telnet XXX.XXX.XXX.XXX 6666 > pwn.b64 && base64 -d pwn.b64 > pwn
  • 有无wget
  • 实在不行难道 echo?

本地环境

1、scp 2、sftp 3、文件系统打包

代码语言:javascript
代码运行次数:0
运行
复制
//解压
cpio -idmv < rootfs.img

//编译+打包
gcc -o exp -static exp.c
cp ./exp ./rootfs/root
cd ./rootfs && find . | cpio -o --format=newc > ../XXX

调用计算器,浏览器

代码语言:javascript
代码运行次数:0
运行
复制
// char *para="google-chrome –no-sandbox file:///home/qwb/Desktop/success.mp4";
// >>> from pwn import *
// >>> map(hex, unpack_many("gnome-calculator"))
// ['0x6d6f6e67', '0x61632d65', '0x6c75636c', '0x726f7461']

msfvenom -p linux/x64/exec cmd=”export DISPLAY=:0.0&&xcalc” -f dw

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 不知什么来源的题目
  • BlizzardCTF 2017 Strng
  • HITB-GSEC-2017-babyqemu
  • QEMU Escape — vm_escape from 0CTF 2017 Finals
  • Defcon 2018 - EC3
  • 强网杯 qwb 2019 final execchrome
  • XNUCA 2018 SSD
  • 强网杯2019线上赛qwct
  • 题外话
  • 附录
    • 关于exp传到qemu中
    • 调用计算器,浏览器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档