文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载angr 是一套以 Python 撰寫的跨平台開源二進位混合執行Concolic即 concrete symbolic引擎本指南基於 CTF-Wiki 倉庫中 angr.md 一文整理而成完整覆蓋其安裝流程、Project/SimState/SimulationManager三大核心物件、Claripy 位向量與約束求解、模擬檔案系統、函數 Hook 與 SimProcedure 等實戰 API。讀完本指南你將能獨立使用 angr 對 CTF 逆向題目進行自動化分析以符號執行取代逐條閱讀組合語言來節省解題時間。什麼是 angrangr 是一個使用 Python 編寫的跨平台開源二進位混合執行引擎——「混合」指的是 Concolicconcrete symbolic即它將具體執行concrete execution與符號執行symbolic execution結合在一起程式既在真實的機器狀態下執行同時又允許輸入以「符號變數」的形式參與運算再由約束求解器反推出滿足特定條件的輸入值。angr 爲我們提供了一系列實用的二進位分析工具在 CTF 逆向題目當中其強大的混合執行引擎可以幫助我們更好地進行自動化分析從而大幅度節省解題時間。在 CTF-Wiki 倉庫中angr 與 Unicorn Engine 一同被歸類於「模擬執行」工具分類下見 docs/zh-tw/mkdocs.yml 中- 模擬執行:一節是逆向工具鏈中「不逐條讀指令、讓機器自己找答案」的代表性方案。安裝angr 本體可以直接透過 pip 進行安裝$ pip3 install angr除本體外angr 開發團隊還維護了兩個常用的周邊工具angr-management圖形化的 angr 介面安裝後直接在終端輸入angr-management即可直接啟動$ pip3 install angr-managementangrop可自動收集 ROP gadget 以及構建 ROP chain 的工具$ pip3 install angrop在 CTF-Wiki 倉庫的 Pwn 環境搭建範例中angr 也被整合進了逆向/Pwn 的標準工具鏈。參見 環境搭建文件 中的 Dockerfile 範例其透過python3 -m pip install一次性安裝了pwntools、z3-solver、unicorn、capstone、angr等一系列工具因此在該 Docker 鏡像建置完成後angr 已可直接import使用。基本用法本節講述 angr 的基本用法以及常用 API。需要注意的是angr 的 API 版本演進較快本文中的輸出以範例執行時2022-2023 年前後的 angr 版本爲準若你安裝的版本更新部分類名或輸出格式可能略有差異但核心用法保持一致。Project一切分析的起點若要使用 angr 分析一個二進位檔案第一步是建立一個angr.Project類——我們一切後續操作都將基於這個類實例展開 import angr bin_path ./test # file to be analyzed proj angr.Project(bin_path) WARNING | 2022-11-23 19:25:30,006 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.上述 WARNING 來自 angr 的加載器元件cle.loader當目標爲 PIEposition-independent executable時angr 會以0x400000爲基礎位址載入。這條警告通常不影響分析只需在設定 find 位址時留意此基礎位址即可。透過一個 project我們可以獲取對應二進位檔案的基本資訊 proj.arch # architecture of the binary file Arch AMD64 (LE) hex(proj.entry) # entry point of the binary file 0x401060 proj.filename # name of the binary file ./test其中arch是一個archiinfo.Arch類實例其包含了執行該檔案的 CPU 資訊等各種數據arch.bits與arch.bytesCPU 的字長單位爲位/字節。arch.name架構名例如X86。arch.memory_endness端序大端爲Endness.BE小端爲Endness.LE。順帶一提原始碼中還有一個「中端序」Endness.ME——在 x86 的 16-bit 字元組定址等特殊場景下會出現這種大小端混合的表示方式。factory實用類工廠project.factory爲我們提供了一些實用類的構造器是日常使用頻率最高的入口之一。block基本塊angr 以基本塊basic block爲單位分析程式碼我們可以透過project.factory.block(address)獲取給定位址所在的基本塊——一個Block類實例 block proj.factory.block(proj.entry) # extract the basic block block.pp() # pretty-print of disassemble code of the block _start: 401060 endbr64 401064 xor ebp, ebp 401066 mov r9, rdx 401069 pop rsi 40106a mov rdx, rsp 40106d and rsp, 0xfffffffffffffff0 401071 push rax 401072 push rsp 401073 lea r8, [__libc_csu_fini] 40107a lea rcx, [__libc_csu_init] 401081 lea rdi, [main] 401088 call qword ptr [0x403fe0] block.instructions # instructions in the block 12 block.instruction_addrs # addr of each instruction (4198496, 4198500, 4198502, 4198505, 4198506, 4198509, 4198513, 4198514, 4198515, 4198522, 4198529, 4198536)block.pp()以 pretty-print 形式輸出該基本塊的反組譯結果便於快速檢視。block.instructions基本塊中包含的指令數。block.instruction_addrs每一條指令的位址元組。state模擬執行狀態angr 使用SimState類表示一個模擬的程式狀態simulated program state。我們對程式的各種操作本質上就是從一個 state 步進到另一個 state 的過程。使用project.factory.entry_state()可以獲取程式入口處的初始執行狀態使用project.factory.blank_state(addr)則可以獲取從指定位址開始執行的「空白狀態」不會載入程式入口的初始化流程適合從中間位址直接開跑 state proj.factory.entry_state() state proj.factory.blank_state(0xdeadbeef)state 物件上掛載了一系列資訊獲取接口state.regs寄存器狀態組其中每個寄存器都是一個位向量BitVector可透過寄存器名稱來訪問對應寄存器例如state.regs.esp - 12。state.mem該狀態的記憶體訪問接口可直接透過state.mem[addr].type完成記憶體訪問。例如寫入state.mem[0x1000].long 4讀取時還需指定.resolved返回位向量或.concrete返回實際 Python 值例如state.mem[0x1000].long.concrete。state.memory另一種形式的記憶體訪問接口state.memory.load(addr, size_in_bytes)獲取該位址上指定大小的位向量。state.memory.store(addr, bitvector)將一個位向量存儲到指定位址。state.posixPOSIX 相關的環境接口例如state.posix.dumps(fileno)可獲取對應檔案描述符fileno上的字元流——1是標準輸出0是標準輸入。除了上述資訊獲取接口外還有求解方法對應的接口state.solver將在後續章節中講解。simulation_manager模擬執行器angr 將「對一組狀態執行」的方法獨立成一個SimulationManager類。以下兩種寫法等效 proj.factory.simgr(state) SimulationManager with 1 active proj.factory.simulation_manager(state) SimulationManager with 1 active比較重要的兩個方法simgr.step()以基本塊爲單位的單步執行。simgr.explore()進行路徑探索找到滿足相應條件的狀態。simgr.explore()的默認參數是find即期望條件。當模擬執行器在路徑探索的過程中發現當前狀態滿足該條件時該狀態會被放到simgr.found列表中若無法找到則該列表爲空。期望條件通常可以是一個目標位址例如希望執行到0x80492F0 simgr.explore(find0x80492F0) # explore to a specific address WARNING | 2023-07-17 04:04:28,825 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior. WARNING | 2023-07-17 04:04:28,825 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by: WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages. WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff60 with 4 unconstrained bytes referenced from 0x819af30 (strcmp0x0 in libc.so.6 (0x9af30)) WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff70 with 12 unconstrained bytes referenced from 0x819af30 (strcmp0x0 in libc.so.6 (0x9af30)) SimulationManager with 1 active, 16 deadended, 1 found上述 WARNING 描述的是「程式訪問了未指定值的記憶體」的常見情況angr 會自動產生一個不受約束的符號變數並繼續執行。若想消除這類警告可按提示三種方式處理① 在初始 state 中設定對應值② 增加 state optionZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}使未知區域置零③ 增加SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}以靜默處理。從執行結果SimulationManager with 1 active, 16 deadended, 1 found可以看到探索結束後滿足條件的 1 個狀態已被放入found。期望條件也可以是自定義的、以狀態爲參數的布爾函數。例如若想尋找一條輸出了指定字串的執行路徑可以判斷該字串是否出現在標準輸出的字元流中——透過state.posix.dumps(檔案描述符)獲取對應描述符上的字元流 def foo(state): ... return bGood in state.posix.dumps(1) ... simgr.explore(findfoo) SimulationManager with 17 deadended, 1 found除了find參數外也可以指定avoid參數——模擬器執行中應當要避開的條件。當一個狀態符合該條件時其會被放在.avoided列表中並不再往後執行。類似地avoid參數可以是某個位址也可以是自定義的布爾函數。此外還可以透過指定num_find參數來指定需要尋找的符合條件的狀態數量若未指定則會在.found列表中儲存所有符合條件的狀態。Claripy位向量與求解引擎Claripy是 angr 的求解引擎solver engine其內部會無縫混合使用幾種後端concrete bitvectors、SAT solvers 等。對我們而言一般不需要直接與之交互但通常會使用它提供的一些接口來構造位向量。bitvector位向量位向量bitvector是 angr 求解引擎中的一個重要部分它表示一組位a sequence of bits。可以透過claripy.BVV(int_value, size_in_bits)或claripy.BVV(string_value)創建帶有具體值concrete value的指定位元長度位向量值bitvector value bvv claripy.BVV(barttnba3) bvv BV64 0x617274746e626133 bvv2 claripy.BVV(0xdeadbeef, 32) bvv2 BV32 0xdeadbeef相同長度的位向量可以直接進行運算對於不同長度的位向量則需要先透過.zero_extend(extended_bits)完成位擴展0 填充後再運算。需要注意的是位向量的值運算同樣存在溢位 bvv2 bvv2.zero_extend(32) bvv bvv2 BV64 0x617274754d102022 bvv * bvv BV64 0x9842ff8e63f3b029位向量除了代表具體值的bitvector value以外還有代表符號變數symbolic variable的bitvector symbol可以透過claripy.BVS(name, size_in_bits)創建帶名字的指定位元長度位向量符號 bvs claripy.BVS(x, 64) bvs BV64 x_0_64 bvs2 claripy.BVS(y, 64) bvs2 BV64 y_1_64位向量符號與位向量值之間同樣可以進行運算組合成更加複雜的運算式 bvs3 (bvs * bvs2 bvv) / bvs bvs3 BV64 (x_0_64 * y_1_64 0x617274746e626133) / x_0_64可以透過.op與.args獲得位向量的運算類型與參數 bvv.op BVV bvs.op BVS bvs3.op __floordiv__ bvs3.args (BV64 x_0_64 * y_1_64 0x617274746e626133, BV64 x_0_64) bvv.args (7021802812440994099, 64)可以看到BVV的.args由數值, 位元數組成而一個複合運算式如__floordiv__的.args則是它的兩個運算元——這正是 Claripy 以抽象語法樹AST形式表示符號運算式的方式。狀態求解前面提到state.solver提供了一些基於狀態的求解接口例如 solver 同樣有創建位向量的.BVV()與.BVS()接口。當需要對位向量符號求解出具體值時可以先將位向量符號存放到狀態的記憶體/寄存器中之後用 simgr 探索到對應的狀態再使用state.solver.eval()成員函數來獲取對應位向量在當前狀態下的值。以下是一個完整例子bvs_to_solve claripy.BVS(bvs_to_solve, 64) init_state proj.factory.entry_state() init_state.memory.store(0xdeadbeef, bvs_to_solve) simgr proj.factory.simgr(init_state) simgr.explore(find 0xbeefdead) solver_state simgr.found[0] print(solver_state.solver.eval(bvs_to_solve))其流程是建立符號變數 → 存入記憶體 → 建立模擬執行器 → 探索到目標位址 → 從found中取出狀態 → 用solver.eval()求出符號變數在該狀態含其約束下的具體值。記憶體操作對於一個狀態的記憶體可使用state.memory的對應接口進行操作state.memory.load(addr, size_in_bytes)獲取該位址上指定大小的位向量。state.memory.store(addr, bitvector)將一個位向量儲存到指定位址。需要注意的是若要儲存具體值則需要透過endness參數指定大小端序。Emulated Filesystem模擬檔案系統在 angr 中與檔案系統間的操作是透過SimFile對象完成的。SimFile是對儲存的抽象模型一個SimFile對象可以表示一系列的位元組、符號等。可以透過angr.SimFile()創建模擬檔案下面示範創建帶有具體值與符號變數的 SimFile import angr, claripy sim_file angr.SimFile(a_file, content flag{F4k3_f19!}\n) bvs claripy.BVS(bvs, 64) sim_file2 angr.SimFile(another_file, bvs, size8) # size in bytes therecontent參數檔案的具體內容字串或位元組串。第二個位置參數若傳入位向量則檔案內容包含符號變數此時需以size指定檔案大小單位爲位元組——這在分析「程序從檔案讀取輸入並進行比對」的題目時非常有用讓檔案內容成爲符號變數angr 即可自動求出能通過比對的內容。模擬檔案需要與特定的狀態進行關聯透過state.fs.insert(sim_file)或sim_file.set_state(state)可將 SimFile 插入到一個狀態的檔案系統中 state.fs.insert(test_file, sim_file)還可以直接從檔案中讀取內容 pos 0 data, actural_read, pos sim_file.read(pos, 0x100)其中read(pos, size)返回(讀到的資料, 實際讀取長度, 新的位置)三元組pos是當前讀取位置。對於流Streams例如標準 IO、TCP 連接等類型的檔案可以用angr.SimPackets()創建 sim_packet angr.SimPackets(my_packet) sim_packet angr.storage.file.SimPackets object at 0x7f75626a2e80SimPackets 以「封包」的形式抽象網路流/互動式輸入輸出適合模擬需要持續收發資料的場景。Constraints約束條件前面講到位向量之間可以進行運算類似地位向量之間也可以進行比較運算其結果爲Bool類型的對象 bvv claripy.BVV(0xdeadbeef, 32) bvv2 claripy.BVV(0xdeadbeef, 32) bvv bvv2 Bool True bvs claripy.BVS(bvs, 32) bvs bvv bvv2 Bool bvs_0_32 0xbd5b7dde bvs2 claripy.BVS(bvs2, 32) bvs2 bvs * bvv bvv2 Bool bvs2_1_32 bvs_0_32 * 0xdeadbeef 0xdeadbeef對於帶有符號值的比較而言Bool類型的對象直接表示了對應的式子因此可以作爲約束條件被添加到一個狀態當中。透過state.solver.add()爲對應狀態添加約束 state.solver.add(bvs bvv bvv2) state.solver.add(bvs2 bvs * bvv bvv2) state.solver.eval(bvs2) # get the concrete value under constraints添加約束後再使用state.solver.eval()即可求出在這些約束共同限制下的具體值——這就是 angr 求解的經典三步造變數 → 加約束 → 求值。除了Bool類以外Claripy 還提供了一些以位向量作爲結果的運算操作例如條件運算式claripy.If() claripy.If(bvs bvs2, bvs, bvs2) BV32 if bvs_0_32 bvs2_1_32 then bvs_0_32 else bvs2_1_32If(condition, true_expr, false_expr)返回一個「條件成立取前者、否則取後者」的符號運算式可用於在符號層面構造分支邏輯。Function hook函數 Hook有的時候我們需要 hook 掉某個函數此時可以使用project.hook(addr call_insn_addr, hook my_function, length n)來 hook 掉對應的 call 指令call_insn_addr被 hook 的 call 指令的位址。my_function我們的自定義 Python 函數。lengthcall 指令的長度hook 完成後需要跳過該指令繼續執行。自定義函數應爲接收state作爲參數的函數。angr 提供了 decorator 語法糖因此以下兩種寫法都可以# method 1 project.hook(0x1234, length5) def my_hook_func(state): # do something, this is an example state.regs.eax 0xdeadbeef # method 2 def my_hook_func2(state): # do something, this is an example state.regs.eax 0xdeadbeef proj.hook(addr 0x5678, hook my_hook_func2, length 5)兩種方式等價method 1 利用 decorator 直接掛在函數定義上method 2 則在定義後手動呼叫project.hook()。hook 的典型用途包括跳過sleep等耗時/無關函數、直接替換某個驗證函數的返回邏輯、或是對指定函數的返回值做符號化處理。Simulated Procedure模擬函數過程在 angr 中angr.SimProcedure類用來表示在一個狀態上的一個執行過程——即函數實際上是「一個 SimProcedure」。我們可以透過創建一個繼承自angr.SimProcedure的類並重寫run()方法的方式來表示一個自定義函數其中run()方法的參數即爲該函數所接收的參數class MyProcedure(angr.SimProcedure): def run(self, arg1, arg2): # do something, thiss an example return self.state.memory.load(arg1, arg2)自定義函數過程主要用於對檔案中的原有函數進行替換例如 angr 缺省會用內置的一些 SimProcedure 來替換掉一些庫函數如strcmp、puts等這樣符號執行就不會「陷進」庫函數的內部實現而能在符號層面直接處理其語義。若我們已經有該二進位檔案的符號表可以直接使用project.hook_symbol(symbol_str, sim_procedure_instance)來自動 hook 掉檔案中所有的對應符號其中run()方法的參數爲被替換函數所接收的參數示例如下import angr import claripy class MyProcedure(angr.SimProcedure): def run(self, arg1, arg2): # do something, thiss an example return self.state.memory.load(arg1, arg2) proj angr.Project(./test) proj.hook_symbol(func_to_hook, MyProcedure())透過hook_symbol只要知道函數名就不必手動查位址angr 會自動在每次呼叫該符號處執行我們的 SimProcedure。在 SimProcedure 的run()過程中還可以使用一些有用的成員函數來控制執行流程ret(expr)函數返回。jump(addr)跳轉到指定地址。exit(code)終止程序。call(addr, args, continue_at)呼叫檔案中的函數。inline_call(procedure, *args)內聯地呼叫另一個 SimProcedure。stash狀態的分組管理在 angr 中不同的狀態被組織到 simulation manager 的不同的stash當中我們可以按照自己的需求對狀態進行步進、過濾、合併、移動等操作。stash 類型angr 一共有以下幾種內建 stashsimgr.active活躍的狀態列表。在未指定替代的情況下會被模擬器默認執行。simgr.deadended死亡的狀態列表。當一個狀態無法再被繼續執行時例如沒有有效指令、無效的指令指針、不滿足其所有的後繼 successors便會被歸入該列表。simgr.pruned被剪枝的狀態列表。在指定了LAZY_SOLVES時狀態僅在必要時檢查可滿足性當一個狀態在指定了LAZY_SOLVES時被發現是不可滿足的unsat狀態層state hierarchy將被遍歷以確認在其歷史中最先變爲不滿足的時刻該點及其所有後代都會被剪枝pruned並放入該列表。simgr.unconstrained不受約束的狀態列表。當創建SimulationManager時指定了save_unconstrainedTrue則被認爲不受約束的unconstrained即指令指針被用戶數據或其他來源的符號化數據控制狀態會被歸入該列表。simgr.unsat不可滿足的狀態列表。當創建SimulationManager時指定了save_unsatTrue則被認爲無法被滿足的unsatisfiable即存在約束衝突的狀態例如在同一時刻要求輸入既是AAAA又是BBBB狀態會被歸入該列表。還有一種不是 stash 的狀態列表——errored若在執行中產生了錯誤則狀態與其產生的錯誤會被包裹在一個ErrorRecord實例中可透過record.state與record.error訪問該 record 會被插入到errored中我們可以透過record.debug()啟動一個調試視窗。stash 操作可以使用stash.move()在 stash 之間轉移放置狀態 simgr.move(from_stash unconstrained, to_stash active)在轉移中還可以透過指定filter_func參數來進行過濾 def filter_func(state): ... return barttnba3 in state.posix.dumps(1) ... simgr.move(from_stash unconstrained, to_stash active, filter_func filter_func)stash 本質上就是一個 list因此在初始化時可以透過字典的方式指定每個 stash 的初始內容 simgr proj.factory.simgr(init_state, ... stashes { ... active:[init_state], ... found:[], ... })stash 機制讓「把不受約束的狀態撈回來繼續探索」「把已找到的狀態隔離保存」等操作成爲標準流程是編寫進階自動化腳本時的重要工具。結語與延伸閱讀angr 將「符號執行 具體執行 約束求解」整合進一套簡潔的 Python API 中以Project載入二進位檔案以SimState描述程式狀態以SimulationManager驅動路徑探索以Claripy完成位向量運算與求解再輔以SimFile、SimProcedure、hook 與 stash 等機制處理檔案輸入、庫函數與狀態管理——掌握了這套核心 API即可對大量 CTF 逆向題目實現「讓程式自己找出正確輸入」的自動化解題。在本倉庫中你可以繼續閱讀以下相關資料深入學習模擬執行工具Unicorn Engine另一個以 CPU 模擬器思路實現的執行工具與 angr 的符號執行思路互補。約束求解工具Z3angr 的 Claripy 底層同樣依賴 SAT/SMT 求解器理解 Z3 有助於理解求解原理。Pwn 環境搭建 Dockerfile其中已整合安裝angr等工具的完整環境範例。逆向工具分類目錄可見 docs/zh-tw/mkdocs.yml 中的「模擬執行」小節了解 angr 在整個 CTF-Wiki 逆向工具鏈中的定位。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐ctf-wiki 哈希攻擊實戰指南暴力破解、長度擴展攻擊與可逆哈希逆向ctf wiki 哈希攻擊實戰指南暴力破解、長度擴展攻擊與可逆哈希逆向 本篇技術指南以 ctf wiki https://link.gitcode.com/i文档网络安全教程CTF-Wiki Android 逆向關鍵代碼定位實戰從靜態分析到動態調試的方法體系CTF Wiki Android 逆向關鍵代碼定位實戰從靜態分析到動態調試的方法體系 本文以 CTF Wiki 倉庫中 Android 逆向基本介紹 http文档网络安全教程ctf-wiki 工控安全ICS CTF系列工控設備發現與掃描實戰指南ctf wiki 工控安全ICS CTF系列工控設備發現與掃描實戰指南 本文為 ctf wiki 開源知識庫中「 工控安全ICS https://li文档网络安全教程上一篇yuzu Switch模拟器不花一分钱30分钟在PC上跑Switch游戏下一篇ESPectre 路线图全解读从 v3.0 共享 Wi-Fi CSI 感知平台到 v5.0 IEEE 802.11bf 的演进规划创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
