Angr-Learn-0x01

7resp4ss發表於2024-03-07

Angr-Learn-0x01

介紹

本文可以理解為官方文件的簡單翻譯+一部分個人理解,並不會在此介紹angr該怎麼使用(如果想快速開始angr,可以嘗試從angr_ctf中學習),而是打算簡單說說它的設計。

以程式設計的理念來分析二進位制檔案必須克服幾個問題,它們大致是:

  • 將二進位制檔案載入到分析程式中
  • 將二進位制檔案轉換為中間表示(IR
  • 進行確切的分析
    • 對程式部分或者全部的靜態分析
    • 對程式狀態空間的符號探索
    • 上面兩種的組合

angr可以應對上訴這些問題

About Angr

Angr框架的總體架構包含如下幾個部分:

  • 載入器CLE:用於解析載入二進位制檔案,識別檔案格式,從ELF/PE頭中提取架構、程式碼段和資料段等程式資訊

  • 架構資料庫Archinfo:根據程式架構資訊,載入對應的CPU架構模型,包括暫存器、位寬、大小端等資料

  • 翻譯器PyVEX:將程式機器碼翻譯成中間語言VEX,VEX是開源二進位制插樁工具Valgrind所使用的中間語言,angr需要處理不同的架構,所以它選擇一種中間語言來進行它的分析

  • 模擬執行引擎SimEngine:對VEX指令進行解釋執行,支援具體值執行和符號值執行,執行時支援自定義函式Hook和斷點,支援自定義路徑探索策略

  • 約束求解器Claripy:將符號執行中生成的路徑約束轉化成SMT公式,使用Z3進行求解

  • OS模擬器SimOS:用於模擬程式與系統環境互動,提供了許多模擬的libc函式和系統呼叫,使用者也可以自行編寫Hook函式進行模擬

    angr-1.drawio

解析二進位制檔案 -> 獲取架構資訊 -> 使用翻譯器翻譯

核心概念

執行以下語句將二進位制檔案載入:

>>import angr
>>proj = angr.Project('/bin/true')

基本屬性

這裡的基本屬性是指二進位制檔案的基本屬性,如下:

  • CPU架構(arch)
  • 檔名(filename)
  • 入口點地址(entry)
>>import monkeyhex # this will format numerical results in hexadecimal
>>proj.arch
<Arch AMD64 (LE)>
>>proj.entry
0x401670
>>proj.filename
'/bin/true'

載入

angr利用CLE模組對二進位制檔案進行載入,CLE的執行結果是載入後的程式,可以透過.loader獲取各種屬性:

>>proj.loader
<Loaded true, maps [0x400000:0x5004000]>

>>proj.loader.shared_objects # may look a little different for you!
{'ld-linux-x86-64.so.2': <ELF Object ld-2.24.so, maps [0x2000000:0x2227167]>,
 'libc.so.6': <ELF Object libc-2.24.so, maps [0x1000000:0x13c699f]>}

>>proj.loader.min_addr
0x400000
>>proj.loader.max_addr
0x5004000

>>proj.loader.main_object  # we've loaded several binaries into this project. Here's the main one!
<ELF Object true, maps [0x400000:0x60721f]>

>>proj.loader.main_object.execstack  # sample query: does this binary have an executable stack?
False
>>proj.loader.main_object.pic  # sample query: is this binary position-independent?
True

factory

factory是angr中比較重要的一個類。

可以用project.factory.block(addr)從給定地址獲取基本塊資訊,它的返回值就是基本塊。

>>block = proj.factory.block(proj.entry) # lift a block of code from the program's entry point
<Block for 0x401670, 42 bytes>

>>block.pp()                          # pretty-print a disassembly to stdout
0x401670:       xor     ebp, ebp
0x401672:       mov     r9, rdx
0x401675:       pop     rsi
0x401676:       mov     rdx, rsp
0x401679:       and     rsp, 0xfffffffffffffff0
0x40167d:       push    rax
0x40167e:       push    rsp
0x40167f:       lea     r8, [rip + 0x2e2a]
0x401686:       lea     rcx, [rip + 0x2db3]
0x40168d:       lea     rdi, [rip - 0xd4]
0x401694:       call    qword ptr [rip + 0x205866]
>>block.instructions                  # how many instructions are there?
0xb
>>block.instruction_addrs             # what are the addresses of the instructions?
[0x401670, 0x401672, 0x401675, 0x401676, 0x401679, 0x40167d, 0x40167e, 0x40167f, 0x401686, 0x40168d, 0x401694]
>>block.capstone                       # capstone disassembly
<CapstoneBlock for 0x401670>
>>block.vex                            # VEX IRSB (that's a Python internal address, not a program address)
<pyvex.block.IRSB at 0x7706330>
狀態

前面的project物件其實只是程式碼了程式“初始化記憶體映像”,而當我們使用angr進行程式執行的時候,代表我們在使用SimState模擬程式狀態,下面這行程式碼就是執行SimState的起點。

>>state = proj.factory.entry_state()
<SimState @ 0x401670>

SimState 的狀態包含程式的記憶體、暫存器、檔案系統資料等任何可以透過執行更改的“實時資料,我們可以使用例如:state.regs和state.mem來訪問該狀態的暫存器和記憶體:

>>state.regs.rip        # get the current instruction pointer
<BV64 0x401670> # or symbolic variable:<BV64 reg_48_11_64{UNINITIALIZED}>
>>state.regs.rax
<BV64 0x1c>
>>state.mem[proj.entry].int.resolved  # interpret the memory at the entry point as a C int
<BV32 0x8949ed31>

但值得注意的是,返回值都是位向量而不是python的整數型別,因此如果我們要進行對暫存器或者記憶體的賦值,我們也要將資料轉換為位向量。

>>state.regs.rsi = state.solver.BVV(3, 64)
模擬管理

模擬管理可以簡單理解為模擬執行管理,它是angr中的主要結構,用於狀態的執行、模擬。我們可以建立我們要使用的模擬管理器,傳入的引數應該是一個狀態列表。

>>simgr = proj.factory.simulation_manager(state)
<SimulationManager with 1 active>
>>simgr.active
[<SimState @ 0x401670>]

模擬管理可以包含多個狀態。上面這段程式碼中active是預設傳入的狀態的狀態(因為一個狀態有不同的狀態)。

然後我們可以透過simgr.step()基本塊的符號執行,執行後可以看到儲存的狀態會發生更新。

分析

我們可以利用angr進行各種分析,從而從程式中提取一些有趣的資訊。

>>proj.analyses.            # Press TAB here in ipython to get an autocomplete-listing of everything:
 proj.analyses.BackwardSlice        proj.analyses.CongruencyCheck      proj.analyses.reload_analyses
 proj.analyses.BinaryOptimizer      proj.analyses.DDG                  proj.analyses.StaticHooker
 proj.analyses.BinDiff              proj.analyses.DFG                  proj.analyses.VariableRecovery
 proj.analyses.BoyScout             proj.analyses.Disassembly          proj.analyses.VariableRecoveryFast
 proj.analyses.CDG                  proj.analyses.GirlScout            proj.analyses.Veritesting
 proj.analyses.CFG                  proj.analyses.Identifier           proj.analyses.VFG
 proj.analyses.CFGEmulated          proj.analyses.LoopFinder           proj.analyses.VSA_DDG
 proj.analyses.CFGFast              proj.analyses.Reassembler

以下是構建和使用快速控制流圖的一個例子

# Originally, when we loaded this binary it also loaded all its dependencies into the same virtual address  space
# This is undesirable for most analysis.
>>> proj = angr.Project('/bin/true', auto_load_libs=False)
>>> cfg = proj.analyses.CFGFast()
<CFGFast Analysis Result at 0x2d85130>

# cfg.graph is a networkx DiGraph full of CFGNode instances
# You should go look up the networkx APIs to learn how to use this!
>>> cfg.graph
<networkx.classes.digraph.DiGraph at 0x2da43a0>
>>> len(cfg.graph.nodes())
951

# To get the CFGNode for a given address, use cfg.get_any_node
>>> entry_node = cfg.get_any_node(proj.entry)
>>> len(list(cfg.graph.successors(entry_node)))
2