easy_escape

分析

首先看一下启动程序run.sh

#!/bin/sh
#Using to build docker environment
#export LD_LIBRARY_PATH=/lib/x86_64-linux-gnu/pulseaudio
./qemu-system-x86_64 -L ./dependency -kernel ./vmlinuz-5.4.0-58-generic -initrd ./rootfs.cpio -cpu kvm64,+smep \\\\
	-m 64M \\\\
	-monitor none \\\\
	-device fun \\\\
	-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \\\\
	-nographic

漏洞肯定位于fun设备中,用ida分析一下qemu-system-x86_64,从fun_class_init函数中我们可以得到vendor_id,class_id。在qemu中查看一下resource,看到只有一个内存空间

/ # lspci
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: cafe:babe
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
/ # cat /sys/devices/pci0000\\\\:00/0000\\\\:00\\\\:04.0/resource
0x00000000febf1000 0x00000000febf1fff 0x0000000000040200

这里我们直接进入mmio_read/write函数进行分析,先来看一下mmio_write函数。其中FunState的结构如下

00000000 FunState        struc ; (sizeof=0xA00, align=0x10, copyof_4860)
00000000 pdev            PCIDevice_0 ?
000008F0 mmio            MemoryRegion_0 ?
000009E0 addr            dd ?
000009E4 size            dd ?
000009E8 idx             dd ?
000009EC result_addr     dd ?
000009F0 req             dq ?                    ; offset
000009F8 as              dq ?                    ; offset
00000A00 FunState        ends

void __cdecl fun_mmio_write(FunState *opaque, hwaddr cmd, uint32_t_0 val)
{
  switch ( cmd )
  {
    case 0uLL:
      opaque->size = val;
      break;
    case 4uLL:
      opaque->addr = val;
      break;
    case 8uLL:
      opaque->result_addr = val;
      break;
    case 0xCuLL:
      opaque->idx = val;
      break;
    case 0x10uLL:
      if ( opaque->req )
        handle_data_read(opaque, opaque->req, opaque->idx);
      break;
    case 0x14uLL:
      if ( !opaque->req )
        opaque->req = create_req(opaque->size);
      break;
    case 0x18uLL:
      if ( opaque->req )
        delete_req(opaque->req);
      opaque->req = 0LL;
      opaque->size = 0;
      break;
    default:
      return;
  }
}

函数根据cmdfun结构体中的不同的成员变量赋值,其中当cmd=0x14的时候会为req创建相应的结构体,我们看一下这个函数

FunReq *__cdecl create_req(uint32_t_0 size)
{
  uint32_t_0 i; // [rsp+10h] [rbp-10h]
  uint32_t_0 t; // [rsp+14h] [rbp-Ch]
  FunReq *req; // [rsp+18h] [rbp-8h]

  if ( size > 0x1FBFF )
    return 0LL;
  req = (FunReq *)malloc(0x400uLL);
  memset(req, 0, sizeof(FunReq));
  req->total_size = size;
  t = (req->total_size >> 10) + 1;
  for ( i = 0; i < t; ++i )
    req->list[i] = (char *)malloc(0x400uLL);
  return req;
}

即按照opaque->size的值为req的成员变量total_size赋值,req->list中堆块指针数量由(size>>10) + 1决定,其中最多为127个。当cmd0x18的时候会调用delete_req函数,

void __cdecl delete_req(FunReq *req)
{
  uint32_t_0 i; // [rsp+18h] [rbp-8h]
  uint32_t_0 t; // [rsp+1Ch] [rbp-4h]

  t = (req->total_size >> 10) + 1;
  for ( i = 0; i < t; ++i )
    free(req->list[i]);
  free(req);
}

根据req->total_size的值,依次释放req->list[i]指向的内存空间。当cmd=0x10req!=NULL的时候会调用handle_data_read函数

void __cdecl handle_data_read(FunState *fun, FunReq *req, uint32_t_0 val)
{
  if ( req->total_size && val <= 0x7E && val < (req->total_size >> 10) + 1 )
  {
    put_result(fun, 1u);
    dma_memory_read_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
    put_result(fun, 2u);
  }
}
void __cdecl put_result(FunState *fun, uint32_t_0 val)
{
  uint32_t_0 result; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  result = val;
  dma_memory_write_9(fun->as, fun->result_addr, &result, 4uLL);
}
int __cdecl dma_memory_write_9(AddressSpace_0 *as, dma_addr_t addr, const void *buf, dma_addr_t len)
{
  return dma_memory_rw_24(as, addr, (void *)buf, len, DMA_DIRECTION_FROM_DEVICE);
}
int __cdecl dma_memory_read_9(AddressSpace_0 *as, dma_addr_t addr, void *buf, dma_addr_t len)
{
  return dma_memory_rw_24(as, addr, buf, len, DMA_DIRECTION_TO_DEVICE);
}

这里首先调用put_result将固定的值写入到fun->result_addr指向的物理内存中,然后调用dma_memory_read_9函数将fun->addr+(val << 10)指向的物理内存中的内容写入到req->list[index]指向的内存空间中。即写入数据。

再来看一下mmio_read函数

uint32_t_0 __cdecl fun_mmio_read(FunState *opaque, hwaddr cmd)
{
  uint32_t_0 val; // [rsp+20h] [rbp-10h]

  val = -1;
  switch ( cmd )
  {
    case 0uLL:
      val = opaque->size;
      break;
    case 4uLL:
      val = opaque->addr;
      break;
    case 8uLL:
      val = opaque->result_addr;
      break;
    case 0xCuLL:
      val = opaque->idx;
      break;
    case 0x10uLL:
      if ( opaque->req )
        handle_data_write(opaque, opaque->req, opaque->idx);
      break;
    default:
      return val;
  }
  return val;
}

可以看到是根据cmd的值来决定返回的FunState中的成员变量的值,当cmd=0x10的时候如果req!=NUll那么就会调用handle_data_write函数看一下该函数