在前一篇文章中,我們介紹了QSEE的漏洞及利用,接下來(lái)讓我們將重點(diǎn)轉(zhuǎn)移到QSEE shellcode。
之前討論過(guò),QSEE可以被提權(quán)——這里的提權(quán)不僅包含直接與TrustZone內(nèi)核交互并訪問(wèn)硬件——安全的TrustZone文件系統(tǒng)(SFS),也包括一些系統(tǒng)內(nèi)存的直接訪問(wèn)形式。
本文我們要討論在不需要內(nèi)核漏洞的情況下,如何利用“安全世界”的內(nèi)存訪問(wèn)權(quán)限劫持“普通世界”中運(yùn)行的Linux內(nèi)核。
與QSEE交互
在上一篇文章中,當(dāng)用戶控件的Android應(yīng)用與QSEE中運(yùn)行的trustlet進(jìn)行交互時(shí),必須通過(guò)一個(gè)特殊的Linux 內(nèi)核設(shè)備“qseecom”,該設(shè)備發(fā)送由QSEOS處理的SMC調(diào)用,并傳遞到請(qǐng)求的trustlet中,以便被處理:
每個(gè)發(fā)送到trustlet的命令都有一對(duì)對(duì)應(yīng)的輸入和輸出緩沖區(qū),用于傳遞“普通世界”和trustlet之間的通信信息。
但是,有一些更快的通信模式所必需的特殊用例——例如,當(dāng)解密較大的DRM保護(hù)的媒體文件時(shí),為了保證順利播放,需要使用盡量少的通信消耗。
另外,有一些設(shè)備中包含trustlet是為了確保設(shè)備的完整性。例如,三星提供了一個(gè)“TrustZone-based Integrity Measurement Architecture (TIMA)”框架來(lái)保證設(shè)備完整性,TIMA會(huì)對(duì)“普通世界”內(nèi)核定期檢查,驗(yàn)證是否與原廠內(nèi)核相匹配。
因此,Trustlet需要與“普通世界”進(jìn)行快速通信,同時(shí)需要具備一定的檢驗(yàn)系統(tǒng)內(nèi)存的能力——聽(tīng)起來(lái)有些危險(xiǎn)!下面讓我們來(lái)深入分析。
繼續(xù)對(duì)“widevine”trustlet的研究,以下代碼為用于DRM加密內(nèi)存塊的命令:
該函數(shù)接收表示輸入和輸出緩沖區(qū)的指針,這兩個(gè)緩沖區(qū)可以是用戶提供的任意緩沖區(qū)。因此,如果想要訪問(wèn)他們需要一些準(zhǔn)備。該函數(shù)通過(guò)調(diào)用cacheflush_register完成準(zhǔn)備,一旦加密進(jìn)程完成,通過(guò)調(diào)用cacheflush_deregister釋放緩沖區(qū)。
分析發(fā)現(xiàn),cacheflush_register和cacheflush_deregister都是圍繞QSEE系統(tǒng)調(diào)用的簡(jiǎn)單的封裝程序:
那么這些系統(tǒng)調(diào)用的作用是什么呢?
查看QSEOS相關(guān)代碼發(fā)現(xiàn)這些調(diào)用的名字是有些誤導(dǎo)性的——實(shí)際上,qsee_prepare_shared_buf_for_secure_read只能使數(shù)據(jù)緩存中的給定范圍無(wú)效(QSEE會(huì)查看更新的數(shù)據(jù)),qsee_prepare_shared_buf_for_nosecure_read可以刪除數(shù)據(jù)緩存中給定的范圍(“普通世界”可以收到QSEE做出的更改)
至于qsee_register_shared_buffer——該系統(tǒng)調(diào)用主要用于將給定范圍實(shí)際映射到QSEE。其工作原理如下:
經(jīng)過(guò)完整性檢測(cè),該函數(shù)會(huì)驗(yàn)證給定的內(nèi)存區(qū)域是否位于“安全世界”。如果這就是問(wèn)題所在,那是因?yàn)閠rustlet正在試圖通過(guò)映射和修改TZBSP或QSEOS使用的內(nèi)存區(qū)域攻擊TrustZone內(nèi)核。由于這一行為十分危險(xiǎn),“安全世界”中只有少數(shù)特定的區(qū)域可以映射到QSEE。如果給定的地址范圍沒(méi)有在特定的區(qū)域中,該操作就會(huì)被拒絕。
然而,對(duì)于“普通世界”中的任意地址,系統(tǒng)不會(huì)做任何額外的檢查。這就意味著QSEOS允許使用qsee_register_shared_buffer將物理地址映射到“普通世界”。劫持Linux內(nèi)核
由于QSEE擁有所有“普通世界”內(nèi)存的讀寫(xiě)權(quán)限,理論上我們可以直接在物理內(nèi)存中定位“普通世界”運(yùn)行的Linux內(nèi)核并注入代碼。
讓我們來(lái)創(chuàng)建一個(gè)不需要內(nèi)核符號(hào)的QSEE shellcode——該方法可以用在所有的QSEE環(huán)境中,定位并劫持運(yùn)行的Linux內(nèi)核。
啟動(dòng)設(shè)備后,引導(dǎo)程序使用Android引導(dǎo)鏡像中指定的數(shù)據(jù),將Linux內(nèi)核提取到給定的物理地址并執(zhí)行:
Linux內(nèi)核的物理加載地址就可以通過(guò)全局可讀的/proc/iomem文件用于任意進(jìn)程:
然而,簡(jiǎn)單地獲取內(nèi)核加載地址并不是全部——系統(tǒng)中存在大量的內(nèi)核鏡像和內(nèi)核符號(hào)。因此,我們需要找到所有動(dòng)態(tài)使用運(yùn)行時(shí)內(nèi)核內(nèi)存的符號(hào)。要知道,Linux內(nèi)核在內(nèi)部維護(hù)著一個(gè)內(nèi)核符號(hào)列表,允許內(nèi)核函數(shù)使用特殊的搜索函數(shù)kallsyms_lookup_name查找這些符號(hào)。
內(nèi)核符號(hào)列表中的名稱(chēng)使用build時(shí)生成的256為霍夫曼編碼進(jìn)行壓縮,霍夫曼表存儲(chǔ)在內(nèi)核鏡像中,在相同的位置還有代表索引的相應(yīng)的描述符,用于解壓名稱(chēng),當(dāng)然還包含符號(hào)的實(shí)際地址。
為了訪問(wèn)符號(hào)表中的所有信息,我們首先需要在內(nèi)核鏡像中找到它。
如果幸運(yùn)的話,符號(hào)表的第一個(gè)區(qū)域——SymbolAddress Table,通常由兩個(gè)指向內(nèi)核虛擬加載地址(由于沒(méi)有內(nèi)核地址空間隨機(jī)分配KASLR機(jī)制,可通過(guò)對(duì)物理加載地址計(jì)算得出)的指針開(kāi)始。另外,該符號(hào)地址為內(nèi)核虛擬地址范圍內(nèi)的單調(diào)非遞減地址——以此來(lái)確定指向內(nèi)核虛擬加載地址的連個(gè)連續(xù)指針。符號(hào)地址表如下:
既然已經(jīng)找到了內(nèi)核鏡像中的符號(hào)表,接下來(lái)需要做的就是解壓該表,來(lái)遍歷并查找任何符號(hào)。
使用上述方法找到內(nèi)核中的符號(hào)表后,我們就可以定位并從QSEE中劫持內(nèi)核函數(shù)。根據(jù)以往的內(nèi)核利用經(jīng)驗(yàn),我們可以從一個(gè)很少用到的網(wǎng)絡(luò)協(xié)議PPPOLAC中劫持一個(gè)函數(shù)指針。
該函數(shù)指針存儲(chǔ)在以下內(nèi)核結(jié)構(gòu)體中:
當(dāng)PPPOLAC套接字關(guān)閉時(shí),覆蓋其中的release指針會(huì)導(dǎo)致內(nèi)核執(zhí)行用戶提供的函數(shù)指針。總結(jié)
綜上所述,獲取Linux內(nèi)核中的代碼執(zhí)行權(quán)限需要執(zhí)行以下步驟:
1、獲取QSEE代碼執(zhí)行權(quán)限
2、使用qsee_register_shared_buffer映射QSEE中的所有內(nèi)核地址
3、找到內(nèi)核符號(hào)表
4、在符號(hào)表中查找“pppolac_proto_ops”符號(hào)
5、覆蓋指向用戶提供的函數(shù)地址的指針
6、使用qsee_prepare_shared_buf_for_nosecure_read清除QSEE中的改變
7、使用PPPOLAC套接字使內(nèi)核調(diào)用用戶提供的函數(shù)
完整利用代碼傳送門(mén)。
注意,該代碼目前只能一次讀取一個(gè)DWORD,所以運(yùn)行緩慢,歡迎提供改善意見(jiàn)(例如,同時(shí)讀取較大的內(nèi)存塊會(huì)提速)。