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