Angr AEG:堆溢位之Exploit自動生成
*本文原創作者:xiaohan0x00,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載
本文主要介紹如何基於 Angr 進行漏洞挖掘,並自動生成 Exploit。對於 Exploit 的自動生成問題,也被稱為 AEG(Automatic Exploit Generation),其中包含漏洞挖掘、Crash分析、約束條件構造、約束求解、Exploit 生成等環節,下文通過符號執行實現對二進位制程式的自動化漏洞挖掘及利用,並展示完整的 AEG 過程。文中所分析的漏洞程式為 Insomni`Hack 2016 題目之一。 下載地址 ,其中包含漏洞程式原始碼 demo_bin.c、編譯後的可執行程式 demo_bin 以及 Angr 指令碼 solver.py。
0×00 漏洞原理
1、原始碼分析
首先檢視原始碼 demo_bin.c,存在一處堆溢位漏洞,關鍵點如下:
1) component_name[128] 大於 component->name[32];
2) initializa_component( char *cmp_naem) 函式中,在賦值時未檢查緩衝區大小;
3) 呼叫 do_something() 時,產生 Crash。
2、GDB 除錯
分析過程式原始碼後,利用 GDB 動態除錯 demo_bin,以觸發 Crash。首先通過 r2 檢視 initializa_component( char *cmp_naem) 所對應的彙編程式碼。
分析後可知,結構體 component 大小為 36 位元組,其中 component->name[32] 佔用 32 位元組,隨後 4 位元組為函式指標,因此構造 PoC 為 “AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB”。
利用 GDB 載入 demo_bin 並輸入 PoC,在 initializa_component( char *cmp_naem) 函式中,呼叫 malloc() 之後設定斷點。檢視此時 malloc() 所分配的記憶體情況。
繼續除錯,單步至 initializa_component( char *cmp_naem) 函式返回,檢視此時記憶體情況。可見 cmp 在堆上的地址為 “0x804b410”,而 cmp->do_something(1) 所對應的地址 “0x804b430” 此刻已被 “\x42\x42\x42\x42” 所覆蓋,如下圖所示。
當程式執行至 cmp->do_something(1) 時觸發 Segmentation fault,此時 EIP 為 “\x42\x42\x42\x42”,表明程式的控制流已被劫持。
通過以上簡要分析可知,demo_bin 中存在堆溢位漏洞,可導致控制流劫持。在此基礎上,下文主要介紹如何通過 Angr 實現對該漏洞的自動化挖掘以及利用。
0×01 Angr AEG
完整的 AEG 過程,在邏輯上大致可分為以下幾個環節:
1) 漏洞挖掘,Angr 主要採用帶有前置約束及路徑選擇策略的符號執行; 2) 崩潰現場分析:EIP 狀態、記憶體佈局; 3) 約束條件構造; 4) 約束求解,Exploit 生成;
1、漏洞挖掘
在本例中,主要是針對控制流劫持漏洞的挖掘。利用符號執行檢測控制流劫持,關鍵在於 EIP,若 EIP 完全被符號變數所覆蓋,則代表著控制流可以被劫持,此時 Angr 會丟擲 unconstrained 狀態。
solve.py 中 65 ~ 84 行,通過搜尋二進位制程式的狀態空間以實現漏洞挖掘。由於 demo_bin.c 中的漏洞邏輯較為簡單,因此在挖掘過程中並未加入複雜的前置約束以緩解路徑爆炸,也未採用額外的路徑搜尋策略,僅使用 SimulationManager 的 step() 方法,迴圈執行,直到出現 unconstrained 狀態。
注意,在設定 SimulationManager() 時,save_unconstrained 必須設定為 True。
2、崩潰現場分析
指令碼執行不久後,便會觸發 unconstrained 狀態,此時需要對崩潰現場進行分析,以判定 unconstrained 狀態的可利用性。
1) EIP 可控性分析
solve.py 中使用 fully_symbolic() 方法檢查 EIP 中符號變數的數量。其中 state.arch.bits 代表系統字長(The number of bits in a word),state.solver.symbolic() 用以判斷輸入資料是否為符號變數,該方法在 ./angr/state_plugins/solver.py 中實現:
當 EIP 完全被符號變數覆蓋時,代表控制流已被劫持,此時堆及 EIP 狀態如下:
在實際除錯過程中,會出現多次 unconstrained 狀態,但前若干次符號變數均未能完全覆蓋 EIP,因此並不能利用。最終 EIP 被符號變數完全覆蓋時,其內容如下:
2)記憶體佈局分析
在觸發控制流劫持後,需要分析當前狀態下記憶體中符號變數的分佈情況。solve.py 中的 find_symbolic_buffer() 實現相關功能,主要包括查詢符號輸入、追蹤符號變數兩個部分。
(1) 查詢符號輸入
Angr 在處理 scanf 的輸入資料時,採用 streams 模式。預設情況下,stdin、stdout、stderr 均採用該模式。state.posix.stdin 為輸入程式的全部符號變數,除錯結果如下:
(2) 追蹤符號變數
通過 state.solver.get_variables() 追蹤記憶體中的符號變數。該函式位於 ./angr/state_plugins/solver.py 中,返回結果如下:
state.memory.addrs_for_name() 返回符號變數對應的地址,結果如下:
至此,已完成漏洞挖掘與崩潰現場的分析,後續需要結合 Exploit 的方式(ret2text、ret2syscall、ROP 等),構造完整的約束條件,並求解。
3、約束條件構造
對於 Exploit 自動生成問題來說,其關鍵是構造合適的約束條件,並利用 SMT(Satisfiability Modulo Theories) 約束求解器求解,若約束可解,則生成成功,否則生成失敗。其中包括路徑約束、EIP 約束、shellcode 約束等。
本例中,Exploit 方式採用 ret2text 且不考慮安全機制,因此僅需找到一片足以放置 shellcode 的連續空間即可。檢查空間連續性的函式如下:
solve.py 中的 shellcode 大小為 22 位元組,最終構造約束條件如下:
4、約束求解
通過 ep.satisfiable() 對約束條件 extra_constraints=(memory == sc_bvv,ep.regs.pc == buf_addr) 的可解性進行判斷。最終判定為約束可解,並生成 Exploit:
0×02 小結
本文以簡單的堆溢位為例,展示瞭如何利用 Angr 自動生成 Exploit。與此同時,也展示了 AEG 的完整過程。文中並未涉及複雜漏洞的自動利用及符號執行所面臨的路徑爆炸、路徑選擇、約束求解等問題。由於筆者接觸 Angr 時間不久,文中難免存在理解不當之處,望各位大佬批評指正(Orz…)。
*本文原創作者:xiaohan0x00,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載