標籤:

使用LuaQEMU對BCM WiFi框架進行模擬和利用

LuaQEMU介紹

當處理固件中的複雜代碼時,通常需要具有某種動態運行時內省(Introspection)以及即時修改行為的能力。例如,當設計一套逆向工程嵌入式解決方案,如蜂窩基帶或自定義操作系統代碼時,研究人員通常都需要藉助某種二進位分析工具才能查看到運行時的協議棧、操作系統任務和內存的運行能力。同樣,當開發二進位組件時,例如自定義模糊器、調試器,都需要通過處理底層系統來完成。

為什麼選擇LuaQEMU?

通過LuaQEMU自定義的模擬環境能為多個CPU架構上的快速原型設計提供支持,同時為二進位代碼進行靈活的API交互提供支持,支持包括包括完整的系統模擬,特定的片上系統(SoC)解決方案的模擬以及對外設的支持。也就是說LuaQEMU是系統模擬器、調試器和動態二進位儀器框架的一個混合模擬器。由於我們在實踐中一直缺少完全符合我們需求的解決方案,因此,可以試著用LuaQEMU來滿足我們的實驗要求。

由於LuaQEMU是一個基於QEMU的框架,所以可以將QEMU內部API暴露給注入QEMU本身的LuaJIT內核。除此之外,還允許目標系統的快速復原,而不附帶任何Lua的本地代碼。

在初步評估使用LuaQEMU時,我們設定了4個預期的功能:

1. 成熟的多架構支持

2. 全系統模擬支持,包括驅動程序和外設,MMU,中斷和定時器

3. 易於長期維護(即使幾乎沒有QEMU內核修改)

4. 無需本機代碼就能輕鬆實現目標原型(例如特定板的定義)

前兩個功能QEMU開箱即用,我們通過在Lua中完全編寫板定義(board definition)而不使用本機代碼,從而獲得目標原型設計的靈活性。實現這一點,就能使得每個硬體架構都帶有一個新引入的Lua板(Lua-board,),可以用於與其他本地板( native board)定義進行交互,而不需要修改QEMU內核代碼。目前我們發現使用這種方法可以輕鬆地轉移到QEMU支持的其他架構。

我們的API要求很簡單:

1.KISS調試API:

曝光和操縱CPU上下文,如寄存器

控制流程跟蹤(斷點,觀察點)

內存讀寫

2.在內存區域捕獲操作(例如對於驅動程序)

3.存儲器的腳本映射(例如,對於載入器)

為此,我們專門選擇了API,因為它允許使用腳本功能輕鬆構建更強大的功能,其中包括自定義樁或鉤子代碼。

為什麼選擇QEMU?

當涉及模擬功能本身時,基於QEMU的方法會成為我們的第一選擇。首先,它是唯一可用的完整系統模擬和虛擬化的免費或開源軟體,除了x86/x64(ARM / AArch64,PPC,Mips,Tricore,Xtensa等)之外,還提供了廣泛的支持架構列表。

此外,它還支持某些軟體的不同版本或兼容級別,不過,它目前不支持PowerPC VLE。然而,QEMU項目本身非常活躍(光2016年超過了7000個)。更重要的是,通用架構的代碼要在實踐中經過很好的測試。

與其他解決方案相比,它還提供了很好的運行時性能,因為它的二進位翻譯和緩存指令通過其翻譯模塊和微型代碼生成器(TCG)。最後,它與其他在實踐中非常有用的功能相結合,例如快照,監視器和gdb調試存根。

Unicorn引擎是利用此功能的工具之一,並通過提供QEMU功能的API,在過去兩年中大大幫助了逆向工程師的工作。

為什麼選擇LuaJIT?

從技術上來講,由於其內置的gdb存根服務與QEMU的模擬交互需要腳本支持。所以我們在早期進行模擬時,實際上是使用QEMU和gdb腳本來實現某些功能的。然而,目前還沒有形成大規模應用,對於數量較大的腳本來說,不是很好維護,更重要的運行速度也很慢。所以,我們選擇將腳本支持直接添加到QEMU本身。

當向進程中注入腳本功能時,有許多選項,例如由frida.re使用。

Lua C API 的正確用法是將Lua作為一門嵌入式語言,並提供完整的 C API供Lua代碼和宿主程序交互,當然,宿主語言最好是C或C++,Lua是一個小巧的腳本語言,非常文檔化,可以作為一個配置和命令式編程的語言,但更重要的是,該方法允許我們暫時表達我們認為重要的任何東西。隨著LuaJIT的功能不斷強大,它還提供了另一個解決方案,因為它的即時編譯(JIT)和一個非常強大的FFI API會與本地代碼進行交互,具有出色的性能。

Broadcom HNDRTE WiFi堆棧

由於Broadcom WiFi晶元佔了移動設備(包括Android設備,iPhone和iPad)市場的大量份額,所以對它進行安全漏洞的調查時最有代表性的。

背景介紹

今年4月初,Google旗下精英團隊GoogleProject Zero的Gal Beniamini發表了他關於利用Broadcom WiFi疊層架構的研究。在他的研究過程中,他發現了幾個關鍵問題,攻擊者能夠利用Broadcom WiFi遠程破壞WiFi SoC並最終導致應用處理器被攻擊。由於我們一直在關注BCM WiFi堆棧,所以我們會試著使用LuaQEMU來重現實驗室環境。我們還將藉此強調如何使用LuaQEMU測試此漏洞。

通過這個漏洞,可以很好地說明為什麼系統模擬是有用的。具體來說,由於這是一個堆緩衝區溢出,需要一個功能和模擬堆實現(工作中的malloc/free)。當然,也可以通過為某些功能提供封裝來抽象出這個細節,但是為了在非分級環境中重現這個漏洞,我們希望堆在真實的設備上運行,而不必完全理解和重新實現其內部邏輯。在設備啟動常式期間,堆又被初始化,這又要求模擬環境下的引導代碼。

此外,我們希望能為其進行漏洞測試注入其他類型的WiFi幀,即遍歷整個WiFi處理代碼,而不是僅僅鉤住易受攻擊的函數。為此,我們選擇了三星Galaxy S6設備上的BCM4358固件作為測試目標(MMB29K.G920FXXU4DPGU)。

測試目標

我們測試的具體目標是:

1.觸發TDLS設置使用LuaQEMU確認緩衝區溢出

2.模擬功能堆棧或堆的系統啟動

3.模擬WiFi接收路徑以注入任意幀

當然,為了達到這個目標,我們不會再附加手動逆向工程。

系統啟動

為了模擬系統啟動並為我們的目標定義一個QEMU板,我們首先需要確定目標架構。研究並評估二進位模式後,我們發現主要的WiFi基帶代碼在ARM內核上運行。在ARM供電的蜂窩基帶領域,基於Cortex-R *和Cortex-M *內核的設計是相當普遍的。

研究表明,Broadcom正在使用Cortex-R4內核,但由於未知的原因,QEMU不正式支持Cortex-r4,由於存在定義其協處理器寄存器,所以就有一些MRC指令導致了未定義的指令異常(BTCM配置MRC / MCR是特定的)。因此,我們使用兼容且不需要補丁的「cortex-r5」作為目標CPU。

由於Broadcom沒有廣泛使用完整的固件文件,而移動設備通常會在快閃記憶體中包含補丁RAM文件,所以RAM文件將被載入到內存中並在內存中被執行。值得注意的是,由於沒有對這些進行加密檢查,所以這些檢查對於將惡意功能注入到運行時的WiFi SoC固件或轉儲ROM是非常有用的。

然而,正如前文所描述的那樣,Broadcom足以讓dhdutil進行讀寫內存。

由於ROM已知地址為零,固定大小為0x180000,因此我們可以利用此工具將Android代碼轉儲到隨機的模擬和反向工程中(dhdutil -i wlan0 membytes -r 0x0 0x180000)。NexMon的運行表明,在ROM存儲器之後,BCMHD驅動器或patchram會從0x18000開始。

總而言之,我們知道了以下系統參數:

1. ROM起始地址:0x0

2. RAM起始地址:0x18000

3. 從基地開始執行ROM代碼

4. ARM Cortex-R4

可以使用ROM和補丁RAM代碼的靜態逆向工程來驗證這些。

板級初始化(Board Initialization)

我們已經提到,希望使用Lua靈活的板定義,而不需要編寫本地代碼。讓我們看看LuaQEMU的電路板如何定義,以下是我們用來模擬BCM WiFi堆棧的電路板的最小定義:

這是一個非常簡單的例子,可以使用庫存QEMU來實現類似的功能,不過我們可以用它來說明這個概念。

machine_type會將我們的目標配置為一個cortex-r5,如上所述,它們類似於Cortex-R4的兼容性。

memory_region塊註冊在QEMU內部的存儲器區域,我們可以使用memory_region塊來將代碼映射到內存。這是使用file_mappings配置條目發生的情況,該條目定義了一組文件及其在內存中的相應地址。LuaQEMU在一開始就負責載入它們,目前保留名稱為「內核」,以便於直接映射到QEMU的-kernel命令行選項,我們正在使用它來載入補丁RAM代碼。從技術上講,這裡不需要-kernel命令行選項,因為引導代碼所需的所有常見功能都包含在ROM,即堆實現和各種libc函數中。可以在這裡添加任意數量的文件。

cpu塊可用於初始化CPU,其寄存器會決定是否複位以及複位時QEMU將跳轉到什麼地址。

系統初始化

這當然不足以達到WiFi堆棧的功能狀態,甚至不需要初始化堆和其他重要的數據結構。

我們期望BCM WiFi可以設置堆棧,配置中斷和陷阱處理程序,配置緩存,配置內存保護(Cortex-r4上的MPU),配置NVRAM,配置DMA/PCIe,初始化內部WiFi介面等。然而,由於我們的目標是根據需要手動反向工程,以達到主要的WiFi處理代碼。

下圖是賽普拉斯文檔的示意圖,紅色注釋為某些組件的功能:

右下方的WiFi天線用於發送和接收WiFi幀,典型的WiFi適配器只有一個天線,雙工器用於將兩個信號(RX和TX)分組或組合成一個。然後,由形成物理WiFi層的DSP內核處理信號。對於這些運行,實時功能很重要。 DOT11MAC(D11)通過接收實際信號並注意確認幀等來監控這些功能的實現。

D11內核和Cortex-r4內核通過DMA操作進行通信。在處理實際的WiFi Layer2/3數據之前,r4內核連續地將數據包從共享FIFO中取出。在架構層面上,D11內核的幀不是原始的WiFi幀,而是封裝並包含物理幀頭。有關這方面的更多詳細信息,請參考Nexmon的運行,賽普拉斯數據表和SoftMAC內核驅動程序。

在這裡要重點了解的是,在啟動初始化代碼和實際的WiFi幀接收代碼之間,還有一些額外的組件與未知功能有關。所以,為了模擬WiFi幀的接收,我們希望儘可能少逆向工程。

實際上,在通過所有引導代碼之後,代碼只是等待中斷出現,這使得它從D11內核中出現一個幀,檢查物理D11幀頭並通過wlc_dpc函數開始處理數據包,這可以通過查找wlc字元串或跟隨中斷處理程序來識別。

因此,雖然我們不知道什麼功能完全由系統啟動,但手動逆向工程卻給了我們一個粗略的想法,讓我們知道需要打什麼代碼來處理框架。因此,如果我們假設在接收路徑上的絕大多數代碼與實際的幀接收無關,並且我們知道要達到的有效地址,那麼我們可以嘗試禁止代碼直到我們跳過儘可能少的相關功能。

測試中出現的錯誤

為了實現目標,我們需要跳過不相關的代碼並運行重要的代碼。LuaQEMU無法很好地解決這個問題,所以此時絕對需要手動逆向工程來跨越代碼路徑(例如我們的例子中的堆初始化)並了解錯誤路徑的樣子。

在這種特殊情況下,我們可以用panic/trap處理程序指明一個問題,並且由某些系統狀態引起的無限循環來發現該問題。

例如,上述代碼在啟動時會從背板地址(backplane address)讀取並預期具體值,直到找到該值為止。對於某些值,上述代碼會立即跳入無限循環。手動逆向工程這些部件相當費時間,手動執行也是如此。但是,LuaQEMU可以幫助我們了解內部CPU狀態。

為此,我們引入了一個Lua回調,可以在多個指令之後使用,實際上是翻譯模塊,且內部沒有改變CPU狀態。這個啟發式簡單地記錄一個執行窗口,產生一個CPU哈希值,並且每當這個哈希值出現時,都會增加一個計數器直到該閾值被找到。一旦被找到,我們就可以確定我們所處的卡住狀態。通過手動剖析相應的代碼,看看我們是否可以簡單地跳過它。

以下是在實踐中的一個例子。

我們將閾值定義為cpu初始化的一部分:

我們進行Lua回調然後繼續簡單地轉儲CPU寄存器:

這使我們能夠快速地注意到其中的問題,並嘗試解決。跳過幾個這樣的卡住的位置,並在相關的地方對註冊內容和內存進行小的修改就足以達到功能堆狀態,這可以通過掛鉤malloc / free功能輕鬆驗證。

其中一個方面是內存保護的設置,這對於XN等漏洞利用的緩解也是有意義的。由於init代碼跨越保護代碼,我們可以使用它來即時轉儲其配置。

總而言之,這種方法使我們能夠將整個系統API變成功能狀態(functional state)。然而,我們完全模擬PCIe設備模擬(即內部WiFi介面)時卻失敗了。因此,我們決定在啟動時完全跳過此代碼,並專註於在實際的接收路徑上模擬所需的數據。

MPU配置

BCM4358中使用的內存保護單元(MPU)是系統啟動過程中的重要部分,因為它定義了所使用的內存區域和許可權。通過使用CP15和opcode 6搜索MCR指令,可以快速識別相應的代碼。儘管可以在運行時轉儲此配置(如Gal所示),但我們想要跟蹤初始化本身。同時,我們能夠在模擬中快速轉儲配置,從而跟蹤Broadcom引入的潛在緩解變更。如果能做到這一點,就不用逆轉BCM的MPU配置的具體邏輯。

在LuaQEMU中卸載配置是微不足道的,因為所需要的是Lua回調,以便在相應的指令中轉儲值和斷點:

在啟動時會出現以下輸出:

我們可以看到,這是與Project Zero發現的相同的內存配置,即所有區域都是rwx,XN不被使用。 WiFi SoC之間似乎很少有偏差。將這一部分重用於較新的映像版本將是有趣的,以查看Broadcom在此空間中的任何更改。

WiFi接收路徑

回到我們模擬WiFi接收路徑的最初目標,重要的是要更好地了解底層代碼。基於SoftMAC實現和手動逆向工程,我們知道幀接收功能從名為wlc_bmac_recv的函數開始。通過使用dma_rx來接受幀數據,dma_rx從上述FIFO中導出幀,並通過調用wlc_recv來處理每個幀。

wlc_recv是WiFi幀數據在首次處理的位置,位元組被解析。該接收路徑通過服務常式處理程序從中斷上下文觸發。在模擬WiFi幀接收時,我們可以直接模擬中斷,即直接對此接收路徑進行設置,並繞過中斷處理。

與系統啟動期間的早期方法類似,我們定義了我們想要防止的錯誤條件。所以,每當代碼觀察到錯誤時,將調用釋放WiFi數據包(我們稱之為packet_free)的處理程序,並返回到中斷循環。只要我們沒有碰到我們感興趣的接收路徑,即數據和控制幀處理程序,更具體地說是我們感興趣的易受攻擊的wlc_tdls_cal_mic_chk處理程序,我們就會一直循環。

一旦模擬器卡住等待中斷,我們將修改控制流程,直接用我們選擇的數據包調用wlc_recv。

WiFi狀態

在任何幀分析程序執行任務之前,我們的模擬方法總會出現幾個問題。

首先,wlc_recv接收兩個參數,一個是數據包有效載荷,包括一個dot11接收頭,一個是指向wlc_info結構的指針。這種結構在整個接收路徑中被使用,並且還包含其他數據結構的各種指針。

從SoftMAC驅動程序中定義的版本摘要如下:

該結構相當大且複雜,並且在BCM WiFi固件中也可能不一樣。因此,我們不能手工製作副本,其內容也不能被忽視。

例如,osh句柄包含被標識的packet_free函數使用的函數指針。hwrxoff將用於確定原始幀數據的偏移量。該結構還包含有關相關WiFi接入點的信息,另外它還包含其本身的硬體地址(即MAC地址),其在幾個地方使用以便確定分組是否被指向適配器(在監視模式的上下文中也是重要的)。

由於該漏洞的性質,即處理SETUP確認消息,該結構也是重要的。WiFi堆棧利用這種結構來跟蹤在處理確認消息之前是否已經發送SETUP請求,幾乎可以通過這種結構訪問所有狀態和WiFi配置設置。很明顯,如果忽視這個結構的內容,就會使我們陷入困境。因此,我們首先需要一個副本,然後決定代碼的哪些部分必須進行修補,以解決潛在的不必要的值和狀態。

為了重播有效的TDLS幀,我們採用了由wpa_supplicant的開發者提供的一個PCAP示例。

在運行時檢查狀態和數據包數據

由於dhdutil允許我們讀取和寫入WiFi SoC內存,因此我們決定編寫一個短的彙編存根。我們將使用它來將內存後面的參數轉儲到wlc_recv,以便稍後在模擬過程中重用它。我們還使用這種方法來更好地了解原始分組數據的結構,因為它與SoftMAC驅動程序不同。

在初始化之後,系統RAM的一大塊將被堆放。我們會隨意利用這個空間內的一個靜態位置作為一個緩衝區來將內存轉儲給我們感興趣的RAM。

以下是我們用於檢查數據包數據的程序集存根:

這可以使用dhdutil -i wlan0 membytes -h 0x0019B710 ….直接寫入內存,修補wlc_recv的一部分,該部分評估監視器模式設置,即正常操作期間不相關的代碼。正如你所看到的,除非使用監視器模式,否則該代碼已經被刪除了,這樣我們的就可以覆蓋它。

然後可以通過從臨時位置讀取,使用dhdutil讀取複製的內存。這大大有助於加快手動RE工作,讓我們在運行時了解相關結構的內容。

由於wlc_info的大量嵌套結構,我們沒有重新使用wlc_info並手動將其重新拼接。而是選擇了一個ramdump,然後我們在運行時動態地附加到我們的模擬環境中。這可以使用dhdutil coredump和跳過形成某種頭文件的0x146位元組來完成。

這樣,我們可以在不完全理解其內容的情況下獲得完全初始化的wlc_info結構。由於堆存在於該空間中,因此要注意的是,這將使我們的功能堆的內容變得有些不同。

調用wlc_recv

此時,我們已經有足夠的功能來使用原始數據包調用wlc_recv。以下是我們用於的Lua代碼:

可以看出,我們動態地將ramdump載入到內存中,重新使用上述的暫存空間來存儲我們的TDLS數據包,並調整內存中的幾個標題位元組以將預期值與wlc_recv相匹配,最後將控制流重定向到wlc_recv()。

接收路徑問題

接收路徑最終可以釋放我們需要解決的數據包,使用稱為wlc_recvfilter的函數來確定是否根據幀的認證狀態和類來導出數據包,其實,我們完全可以繞過這個功能,另外,我們也將跳過根據bsscfg執行檢查的幾個地方。

wlc_recv_data通過比較MAC地址來檢查數據包是否指向自身,我們也跳過這些檢查,以便原始數據包不必與我們的模擬適配器相匹配。

TDLS執行了Gal在他的博客中描述的檢查,但是從解析或安全形度來看並不相關。也就是說,代碼驗證分組中包含的Link-ID IE,評估BSSID,並通過將其與存儲值進行比較來驗證「Snonce」值。

這個過程類似於跳過卡住的代碼路徑,由於我們知道在packet_free的情況下找不到相關的TDLS處理程序,所以可以選擇性地禁用檢查,同時儘可能的減少額外的手動反向工程。

那如何跳過LuaQEMU代碼呢?我們只需使用斷點並調整CPU寄存器。斷點可以在運行時使用lua_breakpoint_insert初始化:

總的來說,要使此方法發揮作用我們總共要修補了17個位置,以獲得相關的引導代碼,並啟用控制和數據幀的接收。我們另外修補了作為TDLS接收路徑一部分的7個位置,使用更精確的wlc_info內容。

觸發漏洞後,只需要一個代碼補丁來調整內存中原始樣本數據包的一些值:

堆追蹤

接下來,我們想使用LuaQEMU來檢查堆分配狀態,不過要注意到TDLS的堆溢出。由於堆的實現,堆溢出可能不會直接可見或以其他方式導致崩潰。所以我們想使用LuaQEMU來跟蹤邊界(OOB)條件下的線性堆,於是我們做了一個非常簡單的實驗,看看LuaQEMU能否幫助我們。

通過為它們添加斷點存根來跟蹤所有相關的malloc和免費通話。

在每個malloc條目上,我們只需記錄分配的大小,函數退出時會返回一個指向分配的緩衝區的指針。現在一個非常簡單的堆OOB檢測只需要在寫入期間留下分配區域時觸發回調。更完整的實現將跟蹤整個堆區及其分配的塊,並捕獲所分配的塊外的任何訪問。要實現這一點,就需要更多關於堆內部的知識。所以為了達到演示的目的,我們決定嘗試一個更簡單的實現,即只關注檢測線性超出條件。

退出鉤計算與分配的堆塊相鄰的dword的位置,並在該地址上放置一個觀察點。一旦這個觀察點觸發,我們就知道發生了一個堆OOB寫入。

LuaQEMU為我們提供了兩種觸發內存訪問的方式:觀察點和陷阱區域。前者類似於在虛擬地址觸發的調試器中的觀察點,而陷阱區域捕獲對物理內存區域的讀取和寫入訪問。此外,陷阱區域處理程序可以調用其本身的讀寫操作。後者也可用於模擬驅動程序或內存映射IO範圍,但兩者在目的上有些相似。

分配的指針,大小和oob_ptr存儲在Lua表中,用於管理目的。免費的鉤子正在利用它們來刪除插入的觀察點:

現在如果發生OOB訪問,我們的bounds_access回調將被觸發。 將LuaQEMU中的觀察點接收地址,長度和訪問類型作為參數,我們可以用它來進行評估。這是一個重要的細節,因為free和malloc本身可以在堆元數據上工作,從而觸及我們標記為OOB的數據。

因此,我們在指示OOB條件之前會過濾malloc和free的內存範圍。

觸發TDLS設置確認OOB寫入

從系統啟動到觸發堆溢出,這大致是LuaQEMU給我們的全部信息:

我們可以使用相同的實現方式來處理其他IE解析問題,而且模擬也不限於數據幀,在我們的測試中,它也處理了控制幀。

數據包加密

當我們第一次嘗試這種方法時,我們不知道如何處理加密。特別是Cortex-r4內核是否在加密數據包上運行還不清楚。看看wlc_recv路徑上的彙編代碼,我們沒有看到數據包解密。這也是有道理的,因為這可能是硬體加速的。

事實上,這不是作為WiFi幀解析的一部分發生的:

在RX FIFO發生之前和TX FIFO發生之後,引擎密碼是公開的。 wlc_info結構包含指向原始幀被加密的信息的指針和會話密鑰材料,但是幀解析中的實際數據處理完全在純文本框架上操作,這是非常有意義的。因為在使用幀數據時,這意味著我們的方法不能用於評估加密實現本身。

總結

雖然本文所講的方法並不會實現完美的全系統模擬,但只要採用合理的方法總會找到我們適用的安全研究的全系統模擬,這當然不意味著所有的代碼路徑都能正確地運行。實際上,如果你看看上面的日誌消息,你就會注意到一個BCM7332晶元已被初始化,這可能是跳過部分初始化的工具。

作為模擬的一個附帶作用,我們可以在新的或不同的固件版本上用合理的方法執行類似的模擬,例如跟蹤Broadcom可能部署的更改。這也為我們進一步的安全研究提供了良好的環境。模糊WiFi幀,同時也可以進行覆蓋分析,也可以使用patchram工具將調試器直接注入WiFi固件。這樣就能在絕大多數情況下運行堆棧,並同時能夠在Lua中對代碼部分進行編碼,而不會失去太多的性能。

當然LuaQEMU不能替代手動逆向工程的其他工作,畢竟這些模擬的辦法還有待進一步實證和改進。

本文翻譯自Comsecuris Security Research & Consulting Blog,如若轉載,請註明原文地址: 使用LuaQEMU對BCM WiFi框架進行模擬和利用 更多內容請關注「嘶吼專業版」——Pro4hou

推薦閱讀:

CouchDB 的遠程代碼執行漏洞淺析
比「壞兔子」更可怕,數千網站正被惡意利用
Neat tricks to bypass CSRF-protection

TAG:信息安全 |