QEMU漏洞挖掘


轉載:https://www.tuicool.com/articles/MzqYbia

 

qemu是一個開源的模擬處理器硬件設備的全虛擬化仿真器和虛擬器.

KVM(kernel virtual machine)是一個Linux內核模塊,為用戶層提供硬件虛擬化的特性,QEMU通過kvm模擬一個目標架構的時候,可以實現與主機相同的架構,從而極大提高模擬效率.

安裝配置qemu參見 官方文檔

漏洞挖掘

要想實現從虛擬機里(以后統稱guest主機)影響qemu,繼而影響用戶的主機(以后統稱Host主機),就需要找到Guest主機與qemu通信的方法,再通過分析qemu對虛擬機傳遞數據的處理流程來發掘可利用點,比如虛擬機內傳遞一個異常值,導致qemu堆溢出,然后利用虛擬機傳遞布置的內容構造exploit,就可以實現控制qemu完成任意代碼執行了.

這里,我介紹的是IO通信的挖掘與利用.

IO通信

硬件接入系統的時候,系統會為硬件的寄存器分配連續的IO端口或者IO內存.通過寫入IO端口或內存,就能將數據傳遞給硬件.而qemu虛擬了很多硬件設備,當我們操作這些IO端口的時候,就相當於傳遞了數據進入qemu進程內.

對於 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內存操作需要編寫內核驅動.

挖掘第一步:確定目標設備

開始挖掘前,我們需要先定一個目標設備,而設備列表我們可以通過下列命令獲得:

$ 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代碼.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM