本文系原創,轉載請說明出處
Please Subscribe Wechat Official Account:信安科研人,獲取更多的原創安全資訊
參考發表在2020年軟工頂會ISSTA的論文《An Empirical Study on ARM Disassembly Tools》
作者公開研究在:https://github.com/valour01/arm_disasssembler_study
找出ARM反組合工具的假設和現實之間的差異,即ARM反組合工具反組合程式碼的效果到底怎麼樣?什麼因素影響著反組合的效果?
1、嵌入式裝置逐漸增多,大部分嵌入式裝置使用的是ARM架構,安全評估的需求逐漸增多
2、安全評估中的動態分析方法需要執行firmware,嵌入式裝置的外設(如I/O)多樣性對執行firmware(基於模擬的測試方法)是個巨大的阻礙——靜態分析仍然有效,即使用靜態分析方法評估firmware的安全性
現有靜態分析方法大多使用現成的工具來反組合stripped的ARM二進位制程式,這些方法都基於一個前提:假定這些現有的工具很可靠,已經解決了反組合技術中存在的問題。
然而,這種假設真的可靠嗎?
"Stripped"的二進位制檔案是指已經剝離了符號表和偵錯資訊的可執行檔案或共用庫。在編譯原始碼並生成可執行檔案或共用庫時,編譯器通常會將一些額外的資訊儲存在二進位制檔案中,包括符號表、偵錯資訊、編譯器指令等。
符號表包含了原始碼中定義的變數名、函數名以及其他符號的資訊,用於在偵錯和符號解析過程中進行原始碼到二進位制檔案之間的對映。偵錯資訊包括原始碼的行號、變數儲存位置以及其他與偵錯相關的資訊,以幫助開發者進行程式偵錯和錯誤排查。
然而,一旦程式開發和偵錯階段已經完成,為了減小二進位制檔案的大小並保護原始碼的隱私,可以使用剝離過程將這些符號表和偵錯資訊等額外資訊從二進位制檔案中移除。剝離之後的檔案被稱為"stripped"的二進位制檔案。
Stripped的二進位制檔案相比非stripped的二進位制檔案具有較小的檔案大小,因為移除了不相關的資訊。但是,它們相對於偵錯和符號解析來說缺乏可讀性和可理解性,因為符號和偵錯資訊已經丟失。
1、內聯資料
在組合語言中,內聯資料指的是將資料直接嵌入到組合指令中,而不是從外部源載入資料。這樣可以方便地在組合程式碼中使用常數、設定值或其他靜態資料。
2、ARM提供兩種指令集
3、沒有明確的函數呼叫指令。
在ARM架構下,函數呼叫是通過一系列指令來實現的。常見的方式是使用跳轉指令(例如BL指令)來跳轉到函數的入口地址,並將返回地址儲存在堆疊中。函數執行完畢後,通過返回指令(例如BX指令)來回到呼叫點,並從堆疊中恢復返回地址。
按照不同的編譯器、編譯器選項、二進位制混淆方法,構造了近2000個ARM二進位制,在8個現有反組合工具上評估,評估這些工具的定位or識別指令邊界和函數邊界的能力,以判斷反組合的效果。
評估標準:
評估這些工具的定位或識別指令邊界和函數邊界的能力
"We find that the existence of both ARM and Thumb instruction sets, and the reuse of the BL instruction for both function calls and branches bring serious challenges to disassembly tools"
也就是:ARM和Thumb指令集的共存以及BL指令在函數呼叫和分支中的重用給反組合工具的反組合準確率帶來了挑戰。
指令集架構,又稱指令集或指令集體系,是電腦架構中與程式設計有關的部分,包含了基本資料型別,指令集,暫存器,定址模式,儲存體系,中斷,例外處理以及外部I/O。指令集架構包含一系列的opcode即操作碼,以及由特定處理器執行的基本命令。
ARM架構,過去稱作高階精簡指令集機器(英語:Advanced RISC Machine,更早稱作艾康精簡指令集機器,Acorn RISC Machine),是一個
ARM架構和處理器關係如下表(來自
ARM指令集
ARM和Thumb是ARM架構下的兩種指令集。
ARM指令集是基於32位元的指令編碼,具有較高的效能和靈活性,適合用於需要高效能和複雜功能的應用。
Thumb指令集是ARM架構下的一種16位元的指令編碼,相比於32位元的ARM指令集,Thumb指令集具有更低的程式碼密度和較低的功耗消耗,適用於資源有限的嵌入式系統和移動裝置。
Thumb指令集包括兩種模式:
1. Thumb-1:最初引入的Thumb指令集,只能執行16位元的Thumb指令。
2. Thumb-2:引入了更多的32位元指令,同時相容32位元的ARM指令集,可以在Thumb和ARM模式之間切換。
在執行時,ARM處理器可以選擇性地在ARM模式和Thumb模式之間切換,根據需要執行不同大小的指令。這樣可以在需要高效能時切換到ARM模式,而在需要低功耗和較小的程式碼大小時切換到Thumb模式。(對應了研究結論中的指令集共存)
Thumb和ARM之間的切換方法:
BLX label指令將ARM切換到Thumb指令集,或者從Thumb切換到ARM指令集。
BX Rm指令通過暫存器Rm的第0位確定目標指令集。如果為0,則目標指令集是ARM;否則是Thumb。
其他分支指令(如POP {PC, Rm ...})的目標指令集也取決於目標地址的最後一位。這給靜態確定目標指令集的反組合工具帶來了嚴重挑戰,特別是對於採用線性掃描策略的反組合工具。
這對反組合工具來說帶來了嚴重的阻礙,尤其是對於那些利用線性掃描策略(第2.2節)的工具來說,靜態確定目標指令集的問題變得更為複雜。
需要注意的是,Thumb指令集的指令寬度較小,可能會導致效能相對較低,一些複雜的操作可能需要多條指令來完成。因此,在選擇使用ARM模式還是Thumb模式時,需要權衡效能和程式碼密度之間的因素,根據具體的應用場景做出選擇。
在這個例子中,首先展示了直接跳轉的情況。使用 jmp
指令直接跳轉到標籤 target_label
處。當執行到該指令時,程式將無條件地跳轉到 target_label
的地址,繼續執行後續的指令。
接下來是間接跳轉的情況。首先,將目標地址儲存在一個記憶體位置 target_address
中,然後通過裝載這個地址到暫存器 eax
中(使用 mov
指令),再通過 jmp eax
指令實現間接跳轉。當執行到這個間接跳轉指令時,程式將根據 eax
暫存器中儲存的地址,動態決定跳轉到哪個標籤處繼續執行。
這個例子展示了直接跳轉和間接跳轉的組合程式碼。直接跳轉使用的是一個靜態確定的目標地址,而間接跳轉使用的是一個動態計算得到的跳轉地址。
請注意,這裡給的是x86的跳轉例子,而對於ARM架構的跳轉:
直接跳轉(Direct Jump):
直接跳轉使用 B
指令,可用於無條件跳轉到一個特定的標籤或地址。
間接跳轉(Indirect Jump):
間接跳轉使用 BX
或 BLX
指令,通過暫存器中的內容來間接確定跳轉的目標地址。
什麼是函數呼叫?函數呼叫允許程式在需要時跳轉到特定的程式碼段(函數),執行函數中定義的一系列操作,並在函數執行完畢後返回到呼叫點,繼續執行後續的指令。
當涉及到x86組合的函數呼叫時,主要使用CALL和RET指令。下面是一個x86組合語言的例子:
在上述範例中,
1 call
指令用於呼叫函數 my_function
,它會將當前指令的下一條指令的地址(即返回地址)壓入棧中,並跳轉到 my_function
的起始地址開始執行。
在函數執行完畢後,通過 ret
指令從棧中取出儲存的返回地址,並將控制權返回到呼叫點繼續執行後續指令。
下面是一個組合程式碼的範例,展示瞭如何進行函數呼叫:
在上述範例中,程式首先宣告了一個名為 my_function
的函數。然後,在主程式的程式碼中,使用 BL
指令呼叫了這個函數。
1 函數呼叫過程中,當前指令的地址被儲存到連結暫存器(LR)中,然後執行相應的函數體程式碼。
2 在函數執行完畢後,通過 BX LR
指令從連結暫存器(LR)中取出儲存的返回地址,返回到呼叫點繼續執行後續指令。
需要注意的是,BL指令在ARM架構中既可以用於函數呼叫(子程式呼叫),也可以用於分支跳轉。
當BL指令用於函數呼叫時,它將當前指令的地址(或加上偏移量)儲存在連結暫存器(LR)中,然後跳轉到目標函數執行。在函數執行完成後,通過從連結暫存器(LR)中獲取儲存的返回地址,實現返回到呼叫點。
當BL指令用於分支跳轉時,它直接跳轉到目標標籤或地址處執行,不涉及返回地址的儲存。這在某些情況下可以用於實現條件分支、迴圈跳轉等功能。
現階段的反組合技術一般分為兩種,一種是線性掃描(linear sweep),另一種是遞迴遍歷。
詳細的技術原理我找到了倆講解詳細明瞭的部落格:
線性掃描:如其名,線性掃描線性的掃描每一段程式碼,因此線性掃描的優點是反組合覆蓋率是100%,缺點是無法識別資料和程式碼之間的邊界,導致將資料識別成程式碼。代表工具objdump。
遞迴遍歷:從一個二進位制檔案的的入口點開始反組合,當遇到一個新分支後,將新分支定為新的入口點,繼續反組合,如此迴圈,直到沒有新的分支出現。優點是能夠識別資料區域,且當程式中存在直接跳轉分支且分支目標可以在編譯或載入時靜態確定時,處理器可以根據這個目標來切換不同的指令集。缺點就是基本無法達到100%覆蓋率。代表工具IDA。
對於這兩種技術的優缺點,作者給出了一個例子:
基本要素:BB代表基本塊,BB2和BB3之間有inline Data,代表內聯資料。BB1到BB2是直接跳轉,BB2到BB3是間接跳轉。
線性掃描持續掃描所有的資料。遞迴掃描面臨一個棘手的問題,上面說到直接跳轉倒是容易識別,但是間接跳轉就不一定了。
儘管已經有一些方法被提出來用於檢測跳轉表(一種間接分支方法)的目標,但如何可靠地檢測其他型別的間接分支(例如函數指標)仍然是一個尚未解決的研究問題。
作者發現成功解析間接跳轉目標可以提高反組合的準確率。
反組合工具如何識別出函數?
通常利用函數的簽名來檢測函數。例如通過掃描二進位制檔案中已知的函數開始和結尾來進行函數識別。
然而,這種方法的侷限性在於函數簽名(如開始和結尾)可能會缺失。此外,維護一個常新的簽名資料庫是一項繁瑣的任務。
有研究者提出一種的新的方法來識別函數邊界,通過識別可區分的函數呼叫指令,如X86的Call指令來複原函數邊界。然而,正如2.2節所述,ARM架構下的Thumb指令集一般使用BL指令跳轉和函數呼叫,這種具備多用途的指令讓這種方法的準確率大大下降。
非商業工具:angr , BAP , Objdump , Ghidra , Radare2
商業工具:Binary Ninja , Hopper, IDA Pro
• RQ1:反組合工具對整個資料集的準確性如何?
• RQ2:影響反組合工具結果的因素是什麼,原因是什麼?
• RQ3:不同型別和選項的工具是否會產生不同的結果?
• RQ4:這些反組合工具的效率如何?
還得是你IDA
影響很大
(1)有些工具無法在ARM和thumb指令集之間轉換;
(2)有些工具對thumb指令不支援更支援ARM指令;
(3)指令邊界和函數邊界的相關性很大,很多工具對指令邊界都識別的不精確,會極大影響函數邊界識別。
核心原因:
這是因為Thumb指令集的二進位制檔案中,BL標籤指令既用作函數呼叫,又用作直接跳轉分支。
具體來說,BL標籤(BLX標籤)指令用於直接呼叫函數。對於ARM指令集,編譯器使用
B tag
等指令來進行直接跳轉分支。
然而,對於Thumb指令集,B標籤的範圍受限(16位元Thumb為±2KB)[4]。編譯器傾向於將BL標籤用作直接跳轉分支(16位元Thumb的範圍為±4MB),這與函數呼叫相同。
這讓反組合工具混淆了,錯誤地將直接跳轉分支解釋為函數呼叫。這會導致在識別函數邊界時產生很高的錯誤正例,從而導致低精度。
有影響,比如在ARMv7 CPU架構中,編譯器在直接分支時使用B標籤指令,而不是重新使用BL標籤指令。這有助於反組合工具區分直接分支指令和函數呼叫指令,從而提高了識別函數邊界的精確度值。
混淆給反組合工具在定位函數邊界方面引入了挑戰,特別是在控制流扁平化方面。根本原因是由於混淆工具插入的直接分支所重用的BL標籤指令。
(1)針對ARM指令集制定特定的反組合策略
如逐漸流行的thumb指令集,目前並沒有很多工具支援識別,這就需要進行完善;
使用混合反組合技術(《Control Flow Integrity for COTS Binaries》. In Proceedings of the 22nd USENIX Security Symposium)定位、識別內聯資料,比如radare2,該工具如果識別出一個無效指令,那麼就會嘗試切換指令模式,或者通過資料參照分析來驗證這個無效code是否是內聯資料。
(2)提高函數邊界的識別技術
現階段的工具常用函數簽名的方式來識別函數邊界,效果比較差。
作者建議:開發人員可以使用基於機器學習的機制首先檢測函數,然後通過考慮不同基本塊之間的內部邏輯進行靜態分析,以減少誤報和漏報。此外,反組合工具還可以進一步分析BL標籤指令,以瞭解它是否是一個函數呼叫。作者認為對BL標籤指令的使用進行進一步分析可以極大地提高函數邊界的結果。