QEMU漏洞挖掘

转载:https://www.tuicool.com/articles/MzqYbialinux

 

qemu是一个开源的模拟处理器硬件设备的全虚拟化仿真器和虚拟器.vim

KVM(kernel virtual machine)是一个Linux内核模块,为用户层提供硬件虚拟化的特性,QEMU经过kvm模拟一个目标架构的时候,能够实现与主机相同的架构,从而极大提升模拟效率.centos

安装配置qemu参见 官方文档ruby

漏洞挖掘

要想实现从虚拟机里(之后统称guest主机)影响qemu,继而影响用户的主机(之后统称Host主机),就须要找到Guest主机与qemu通讯的方法,再经过分析qemu对虚拟机传递数据的处理流程来发掘可利用点,好比虚拟机内传递一个异常值,致使qemu堆溢出,而后利用虚拟机传递布置的内容构造exploit,就能够实现控制qemu完成任意代码执行了.架构

这里,我介绍的是IO通讯的挖掘与利用.dom

IO通讯

硬件接入系统的时候,系统会为硬件的寄存器分配连续的IO端口或者IO内存.经过写入IO端口或内存,就能将数据传递给硬件.而qemu虚拟了不少硬件设备,当咱们操做这些IO端口的时候,就至关于传递了数据进入qemu进程内.ide

对于 IO端口 ,咱们能够经过下列方式传递数据:函数

#include <sys/io.h>
iopl(3)//赋予当前程序读写IO端口的权限,也可使用IOperm()来临时启用
//读取
inb(port);//byte
inw(port);//word=2 bytes
inl(port);//dwords=4 bytes
//写入
outb(val,port);
outw(val,port);
outl(val,port);

对于 IO内存 ,可使用下列方式传递数据:测试

#include <asm/io.h>
#include <linux/ioport.h>

long addr=ioremap(ioaddr,iomemsize);
readb(addr);
readw(addr);
readl(addr);
readq(addr);//qwords=8 btyes

writeb(val,addr);
writew(val,addr);
writel(val,addr);
writeq(val,addr);
iounmap(addr);

须要注意的是,IO端口操做能够直接使用gcc编译,再经过root权限执行就能够,而IO内存操做须要编写内核驱动.gradle

挖掘第一步:肯定目标设备

开始挖掘前,咱们须要先定一个目标设备,而设备列表咱们能够经过下列命令得到:

$ qemu-system-x86_64 -device ?

若想了解设备的使用信息能够这样:

$ qemu-system-x86_64 -device mptsas1068,help

例如:若是咱们想加载mptsas设备,可使用以下命令:

$ qemu-system-x86_64 -m 2048 --enable-kvm -device mptsas1068 -hda /folder/with/img/centos.img

固然可能还须要其它参数设置,这个就看你我的需求了.

另外,每一个设备其实对应的都是一个或多个c文件,咱们也能够在qemu源码的 hw 目录里找,而后根据文件里的关键词,对比前面获取的设备列表,判断是属于哪一个设备.

同时,不少默认设备咱们不须要主动加载.对于默认设备,咱们只须要找到对应的c文件就能够开始测试了.

挖掘第二步:肯定设备对应的IO端口\IO内存

在guest主机里,咱们执行以下命令获取设备信息:

$ lspci

若是认不出来,咱们能够这样:

$ lsmod |grep mptsas(假设驱动就叫mptsas) 
有可能驱动并非叫mptsas,这个时候你就例举全部的驱动,而后对比不加载设备时结果的区别来判断.

$ modinfo mptsas

若是还认不出来,咱们就这样:

$ lspci -nnv

这下你总知道了吧.并且咱们还知道了这个设备对应的IO端口是 0xc000 ,大小为256(意味着最大能够inl(0xc000+255)).IO内存是 0xfebc0000-febc3fff,febb0000-febbffff ,能够看到它有两块IO内存

针对mptsas设备,从 lspci 命令,咱们知道其对应的bus信息是 00:04.0 ,咱们也能够经过下列方式获取对应的IO内存和端口的信息.

$ cat /proc/iomem| grep 00:04.0
$ cat /proc/ioports|grep 00:04.0

除此之外,还有一种方法能够快速获取全部信息:

$ lshw

有的设备是特殊设备(好比usb设备,virtio设备),你不必定找获得对应名字,就须要你结合对应c文件的各类关键词以及加载时列表的关键词来找.

若是加载了一个设备,找不到io信息,你谷歌一下.好比以前测试vmware-vga的时候,我是这样搜索的”How can I find what video driver is in use on my system”

挖掘第三步:找到对应源文件

若是你是直接经过文件肯定的设备能够跳过这一步.

若是你是经过设备列表选定目标,就须要找寻该设备的c源文件了.否则你都不知道谁在处理你传的数据,不是很尴尬.

找文件的技巧也就是先去hw目录,而后找到对应设备的子目录,好比我举例的mptsas,咱们经过lspci命令知道,其属于scsi设备,所以就在scsi目录查找,轻松找到两个相关文件 mptconfig.c 和 mptsas.c ,至于mptendian.c,看一眼就知道不重要啦.固然了,有时候咱们不必定能找到关键词怎么办?? 经过source Insight 的关键词搜索,搜索全部文件来匹配关键词.

挖掘第四步: 编写交互代码

终于要开始随心所欲了,先来看看怎么写测试代码:

#include <sys/io.h>
void main(){
iopl(3);
inb(0xc050);
}

//针对IO内存
#include <asm/io.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/random.h>

long pmem;//注意这里不要使用指针类型,否则后面地址加偏移的时候很容易出错
void m_init(){
printk("m_init\n");
int i,cmd,cmd_size;
int va,offset;
pmem=ioremap(0xfebf0000,0x8000);//映射io内存
offset=0x10;//根据设备状况而定
if (pmem){
writel(value,pmem+offset);//一般状况下都是写4字节,你也能够根据源码的处理方式选择
}else printk("ioremap fail\n");
iounmap(pmem);
return;
}
void m_exit(){
printk("m_exit\n");
return;
}
module_init(m_init);
module_exit(m_exit);

[更多IO操做详细信息] http://www.oschina.net/question/565065_67988 )

挖掘第五步: 审计源码

咱们已经找处处理输入的文件了,怎么找到哪个函数是第一个处理咱们输入的函数呢??

通用的方法:找包含 read,write 关键字的函数名.

针对mptsas,找的结果以下:

经过不断找函数的caller,咱们最终会找到第一个处理的函数,而有的设备有多个映射IO内存和端口,就意味着有多个处理函数分别对应不一样的内存和端口.

自此能说的也很少了,你都已经知道怎么控制数据了,剩下就是去查看qemu怎么处理数据了.

qemu在获取数据的时候也会经过调用Guest系统的内存来读取数据,处理guest地址转换的函数叫 dma_memory_read,dma_memory_write ,因此记得注意地址不要出错.

有的模块有一个内存是拿来放数据的,好比以前测试vmware-vga的时候,一段IO内存就是从结构s-fifo[0x1000] 来读取的.而不是使用函数来处理.

改天再附上测试mptsas的fuzz代码.