熟悉题目
题目描述:
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 |
这个题目给的qemu-system-x86_64的符号是被stripped掉了,相当于增加了点难度
1 | giantbranch@ubuntu:~/qemu_escape/defcon-2018-ec3$ file ./qemu-system-x86_64 |
先看启动脚本,初步猜测漏洞在设备ooo中,肯定也是故意写的有漏洞的设备
1 | #!/bin/sh |
没有符号,不能直接搜索函数名,那就尝试看看字符串
1 | .rodata:0000000000B633A1 aOooMmio db 'ooo-mmio',0 ; DATA XREF: sub_6E64A5+101↑o |
通过ooo_instance_init
,ooo_class_init
就可以定位到相应的函数了,此外我们看到有cat ./flag
,跟过去发现sub_6E65F9
是个执行system("cat ./flag")
的后门函数
先看ooo_class_init
,根据PCIDeviceClass
的定义,可以确定这里vendor_id是0x420,device_id是0x1337,revision是0x69,class_id是0xff
1 | __int64 __fastcall ooo_class_init_6E67DE(__int64 a1) |
从上面看出,那么sub_6E64A5
就是pci_ooo_realize
,里面设置了mmio(下面函数部分已经手动重命名过了)
1 | unsigned __int64 __fastcall pci_ooo_realize_6E64A5(__int64 a1, __int64 a2) |
而off_B63300
就是设置mmio的操作函数指针,那么下面的sub_6E613C
对应ooo_mmio_read,sub_6E61F4
对应ooo_mmio_write
1 | .rodata:0000000000B63300 mmio_ops_off_B63300 dq offset sub_6E613C |
再看ooo_instance_init
,就一些初始化
1 | _QWORD *__fastcall ooo_instance_init_6E6732(__int64 a1) |
我们看看lspci结果(简版的系统,lspci看不到详细信息),根据上面对对ooo_class_init_6E67DE
的分析,的值ooo设备对应的就是下面的00:04.0 Class 00ff: 0420:1337
,同时我们也可以得出lspci默认的格式序号 Class classid: vendor_id:device_id
1 | / # lspci |
查看mmio的地址(pmio是/proc/ioports
)
1 | / # cat /proc/iomem | grep 00:04.0 |
或者这样也行,同样可以看到是fb000000到fbffffff
1 | / # cat /sys/devices/pci0000\:00/0000\:00\:04.0/resource |
重点代码分析
我们根据以往经验重命名下mmio的read和write的参数
首先是read,只有(addr & 0xF00000u) >> 20 != 15
及gbuf_bss_1317940[v4]
不为0才会执行memcpy
memcpy的源地址是以addr的第5个四字节即v4作为gbuf_bss_1317940的索引取出值,再加一个addr的低16位进行偏移
1 | __int64 __fastcall ooo_mmio_read_6E613C(__int64 opaque, int addr, unsigned int size) |
而接下看看write
1 | void __fastcall ooo_mmio_write_6E61F4(__int64 opaque, __int64 addr, __int64 value, unsigned int size) |
通过choose = ((unsigned int)addr & 0xF00000) >> 20;
来进行选择,我命名为choose
1、choose为0时,v11为15时,循环分配15次到对应的全局变量中,否则就只分配一次到对应的全局变量索引中
2、choose为1时,释放内存
3、choose为2时,将我们的值写到对应的地址
上面释放内存的时候,没有将指针置空,导致了UAF漏洞,我们就可以改写堆内存的fd,使用fastbin attack去做这个题目,变成了经典的堆题目了,只不过是qemu,交互不一样。
漏洞利用
利用思路:利用fastbin attack改写那个全局指针数组上面的指针,之后即可任由读写(可以改写free或者malloc的got表地址),当然这里我们只需要写即可。(当时比赛是ubuntu 16.04,不过我也是(*^__^*)
,所以fastbin attack需要绕过size的检查)
注意:
1、执行mmio_write的时候,写入使用32bit写入,不然会两次调用malloc,导致第一次malloc的返回值被覆盖。还有mmap地址复制了之前的代码,mmap大小还是0x1000,导致写入失败,报错如下:
1 | [ 106.208240] exp[86]: segfault at 7f3dab7c4000 ip 0000000000400a69 sp 00007fffeaa50048 error 6 in exp[400000+ca000] |
2、就算free了后改写了fd,也不一定第二个就能申请到fake fd的地址,这是最坑的,根据uaf.io的作者还有我最终的时间,可能qemu在我们之前就malloc了0x60大小的,或者在我们free后,qemu又free了一些0x60的,导致可能我们不止malloc两次才能得到fake fd的地址,所以我们最好循环申请,之后循环写就行
3、还有就是ctrl + A,之后按X可以退出qemu虚拟机
4、由于题目是部署在远程的,busybox实现了telnet和wget,所以我们可以下载我们部署在vps的exp到qemu执行,但是赛后我们方便的还行直接修改本地的文件系统,先创建一个目录(我是建立了rootfs目录),cpio复制进去,之后解压
1 | gunzip initramfs-busybox-x86_64.cpio.gz |
最后写脚本将exp放入该目录,重新打包即可
1 | giantbranch@ubuntu:~/qemu_escape/defcon-2018-ec3$ cat gcc_cp.sh |
最终利用我构造的内存布局如下:(0x1317980是用来构造fake size,0x1317950是循环申请是申请到的fake fd)
1 | gdb-peda$ x /12gx 0x1317940 |
最终我通过改写free_got为cat flag后门函数,其实我就调用了一次free,可以看到qemu自己又free了两次。
分享一下我调试的源码:
1 | giantbranch@ubuntu:~/qemu_escape/defcon-2018-ec3$ cat comline.txt |
最终exp:
1 | // -*- coding: utf-8 -*- |
总结
这个没符号增加了难度,需要从字符串寻找线索,需要熟悉QOM(Qemu Object Model)的知识或者根据以往题目的对比才能像正常题目一样去做题
实际是还是经典的fastbin attack的堆题目,好久没做ctf堆,有点生疏了,坑点就是qemu自身会malloc和free,导致你申请到的fake fd不固定
还有一位大佬是通过write的时候,可以设置偏移(那个是signed __int16
类型,所以可以写-32768到32768),这就相当于堆溢出了,他通过堆溢出,将cat flag后门函数的地址乱写一通,覆盖了一些函数指针,最后通过调用system("echo mem > /sys/power/state");
,这个是让机器睡眠,调用的一些函数被覆盖成了cat flag,还是挺神奇的。
参考
https://github.com/o-o-overflow/chall-ec-3
https://uaf.io/exploitation/2018/05/13/DefconQuals-2018-EC3.html
https://ray-cp.github.io/archivers/qemu-pwn-DefconQuals-2018-EC3
https://blog.bushwhackers.ru/defconquals2018-ec3/