符號執行之angr學習-初識angr

一 參考

安裝好了angr後,如何利用angr來完成自己的工作,實現程序分析是個大問題。我參考了一些技術人員的博客,能穩定介紹這個工具的人不是很多,不過還是讓我找到了幾個:

https://blog.csdn.net/zhuzhuzhu22/article/details/80350441

https://blog.csdn.net/doudoudouzoule/article/category/7195752

https://xz.aliyun.com/t/4039

以及官方倉庫:https://github.com/angr

還有angr docs:https://docs.angr.io/

從這裏面我看到很多,但是對一些基本命令還是不瞭解,因此我把angr docs的一些基本的命令翻譯了下

二 Angr Documentation

第一個操作,導入angr模塊以及加載二進制程序

>>> import angr

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

第二個操作,瞭解導入的二進制程序的基本信息

>>> proj.arch #架構

>>> proj.entry #二進制程序入口點

>>> proj.filename #程序名稱以及位置

3 第三個操作,二進制程序在虛擬地址空間的表示

>>> proj.loader#是通過CLE模塊將二進制對象加載並映射帶單個內存空間

>>> proj.loader.min_addr#proj.loader 的低位地址

>>> proj.loader.max_addr#proj.loader 的高位地址

>>> proj.loader.all_objects #CLE加載的對象的完整列表

>>> proj.loader.shared_objects#這是一個從共享對象名稱到對象的字典映射

>>> proj.loader.all_elf_objects#這是從ELF文件中加載的所有對象

>>> proj.loader.all_pe_objects#加載一個windows程序

>>> proj.loader.main_object#加載main對象

>>> proj.loader.main_object.execstack#這個二進制文件是否有可執行堆棧

>>> proj.loader.main_object.pic#這個二進制位置是否獨立

>>> proj.loader.extern_object#這是“externs對象”,我們使用它來爲未解析的導入和angr內部提供地址

>>> proj.loader.kernel_object#此對象用於爲模擬的系統調用提供地址

>>> proj.loader.find_object_containing(0x400000)#獲得對給定地址的對象的引用

 

直接與這些對象交互以從中提取元數據

>>> obj = proj.loader.main_object#指向一個對象

>>> obj.entry#獲取地址

>>> obj.min_addr, obj.max_addr#地址的地位和高位

>>> obj.segments#檢索該對象的段

>>> obj.sections#檢索該對象的節

>>> obj.find_segment_containing(obj.entry)#通過地址獲得單獨的段

>>> obj.find_section_containing(obj.entry)#通過地址獲得單獨的節

>>> addr = obj.plt['abort']#通過符號獲取地址

>>> obj.reverse_plt[addr]#通過地址獲取符號

>>> obj.linked_base

>>> obj.mapped_base#顯示對象的預鏈接基以及CLE實際映射到內存中的位置

 

>>> malloc = proj.loader.find_symbol('malloc')#接受一個名稱或地址並返回一個符號對象

>>> malloc.name#獲取符號名稱

>>> malloc.owner_obj

>>> malloc.rebased_addr#全局地址空間中的地址

>>> malloc.linked_addr#相對於二進制的預鏈接基的地址

>>> malloc.relative_addr#相對於對象庫的地址

 

>>> malloc.is_export

>>> malloc.is_import#這兩個判斷符號是import還是export

 

>>> main_malloc = proj.loader.main_object.get_symbol("malloc")#不同於loader上的符號查找,在單個對象上,通過get_symbol獲取給定名稱的符號。

>>> main_malloc.resolvedby#提供了對用於解析符號的符號的引用

 

注意:proj = angr.Project('example',load_options={"auto_load_libs":False})

auto_load_libs,支持或禁用CLE自動解析共享庫依賴關係的嘗試,並且默認爲on。

except_missing_libs爲true,當二進制文件有無法解析的共享庫依賴項時,將引發異常。

將字符串列表傳遞給force_load_libs,所列出的內容將作爲未解析的共享庫依賴項處理,

將字符串列表傳遞給skip_libs,以防止將該名稱的任何庫解析爲依賴項。

main_opts是一個從選項名到選項值的映射,

lib_opts是一個從庫名到字典的映射,將選項名映射到選項值。

例:angr.Project(main_opts={'backend': 'ida', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})

 

angr.sim_procedure#替換對庫函數的外部調用,就是模仿庫函數對狀態的影響的python函數

>>> stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'] #這是一個類

>>> proj.hook(0x10000, stub_func())#與類的實例掛鉤

>>> proj.is_hooked(0x10000)#是否掛鉤

>>> proj.unhook(0x10000)

>>> proj.hooked_by(0x10000)#這三條均是一些驗證性的指令

 

>>> @proj.hook(0x20000, length=5)

... def my_hook(state):

... state.regs.rax = 1

#通過使用project .hook(addr)作爲函數裝飾器,可以指定現成函數作爲鉤子使用。還可以選擇指定length關鍵字參數,使執行在鉤子完成後向前跳轉一些字節。

>>> proj.is_hooked(0x20000)

True

使用proj.hook_symbol(name, hook),它提供了一個符號的名稱作爲第一個參數,用於鉤住該符號所在的地址

4 第四個操作:factory,爲了方便分析,foctory提供了幾個常用的函數

4.1 Block:用於從給定地址提取基本代碼塊,angr以基本塊爲單位分析代碼。。

>>> block = proj.factory.block(proj.entry)#從程序的入口點提取一段代碼

>>> block.pp()  #打印代碼塊

>>> block.instructions#該代碼塊共有多少條指令

>>> block.instruction_addrs#每條指令的地址

>>> block.capstone#capstone disassembly

>>> block.vex#VEX IRSB(這是python的內部地址,不是程序地址)

4.2 States:proj = angr.Project('/bin/true')僅僅是一個初始化映像,但是要執行符號執行,還需要simstate,表示模擬程序狀態的特定對象

>>> state = proj.factory.entry_state()#即就是simstate,SimState包含程序的內存、寄存器、文件系統數據……任何可以通過執行更改的“實時數據”均在SimState。

.entry_state()的替換:

.blank_state()#構造了一個“空白石板”空白狀態,其大部分數據未初始化。

full_init_state()構造一個狀態,該狀態可以通過需要在主二進制的入口點之前運行的任何初始化程序執行,例如共享庫構造函數或預初始化程序。

.call_state()構造準備執行給定函數的狀態。

 

>>> s = proj.factory.blank_state()

>>> s.memory.store(0x4000, s.solver.BVV(0x0123456789abcdef0123456789abcdef, 128))#使用.memory.store(addr,val)方法將數據保存在內存中

>>> s.memory.load(0x4004, 6) #

 

>>> state.regs.rip#獲取當前指令指針

>>> state.regs.rax

>>> state.regs.rbp = state.regs.rsp#將寄存器rsp的值給rbp

>>> state.mem[proj.entry].int.resolved#將入口點的內存解釋爲C int

注意:那些不是python int 型!那些是bitvectors式,因此需要對其進行置換。

>>> state.mem[0x1000].uint64_t = state.regs.rdx#將rdx的值存儲在內存的0x1000位置

>>> state.regs.rbp = state.mem[state.regs.rbp].uint64_t.resolved#放棄rbp

>>> state.regs.rax += state.mem[state.regs.rsp + 8].uint64_t.resolved#加rax

 

>>> bv = state.solver.BVV(0x1234, 32)#創建一個32位寬,值爲0x1234的bitvectors,

>>> y = state.solver.BVS("y", 64)#創建一個名爲“y”的位向量符號,長度爲64位

在位數相同的情況下,可以實現對符號的計算

>>> state.solver.eval(bv)  #轉換成python int

>>> state.regs.rsi = state.solver.BVV(3, 64)#將64位寬,值爲3的bitvectors存入寄存器和內存

>>> state.mem[0x1000].long = 4#您可以直接存儲python int,它將被轉換成適當大小的位向量

>>> state.mem[0x1000].long.resolved

注意,mem可以通過.resolved,來以bitvectors獲取該值;也可以通過.concrete,來以python int獲取該值

 

一個通過符號值來求解的過程,注意僅適用於bit向量

>>> state = proj.factory.entry_state()

>>> input = state.solver.BVS('input', 64)

>>> operation = (((input + 4) * 3) >> 1) + input

>>> output = 200

>>> state.solver.add(operation == output)

>>> state.solver.eval(input)

0x3333333333333381

注意:eval是一種通用方法,它可以將任何位向量轉換成python原語

上面的BVS 是bit向量,下面的

>>> a = state.solver.FPV(3.2, state.solver.fp.FSORT_DOUBLE)

通過FPV來創建浮點型向量

raw_to_bv和raw_to_fp方法將位向量解釋爲浮點數,反之亦然:

 

solver.eval(expression)#將爲給定的表達式提供一種可能的解決方案。

solver.eval_one(expression)#將給出給定表達式的解決方案,如果可能有多個解決方案,則拋出一個錯誤。

solver.eval_upto(expression, n)#將給出給定表達式的n個解,如果可能返回的結果小於n,則返回的結果小於n。

solver.eval_atleast(expression, n)#將爲給定的表達式提供n個解決方案,如果可能小於n,則拋出一個錯誤。

solver.eval_exact(expression, n)#將爲給定的表達式提供n個解決方案,如果少於或大於可能,則拋出錯誤。

solver.min(expression) #將給出給定表達式的最小可能解。

solver.max(expression) #將給出給定表達式的最大可能解。

其他關鍵詞:

extra_constraints:可以作爲約束的元組傳遞。

cast_to可以傳遞一個數據類型來轉換結果。

 

4.3  Simulation Managers 模擬管理器

>>> simgr = proj.factory.simulation_manager(state)#首先,我們創建將要使用的模擬管理器。構造函數可以接受狀態或狀態列表。

state被組織成stash,可以forward, filter, merge, and move 。

>>> simgr.active#用於存儲操作

>>> simgr.step()#執行一個基本塊的符號執行,即就是所有狀態向前推進一個基本塊

>>> simgr.active##更新存儲

 

>>> while len(simgr.active) == 1:#直到第一個符號分支,並查看兩個存儲

... simgr.step()

>>> simgr

<SimulationManager with 2 active>

>>> simgr.active

[<SimState @ 0x400692>, <SimState @ 0x400699>]

 

>>> simgr.run()#直接執行程序,直到一切結束,查看會返回死循環數目

>>> simgr

<SimulationManager with 3 deadended>

 

>>> simgr.move(from_stash='deadended', to_stash='authenticated', filter_func=lambda s: b'Welcome' in s.posix.dumps(1))#,move(start,end,optional),可以將start狀態的optional信息移動到end狀態

>>> simgr

<SimulationManager with 2 authenticated, 1 deadended>

 

一些存儲的類型

active#此存儲區包含默認情況下將逐步執行的狀態,除非指定了備用存儲區。

deadended#當一個狀態由於某種原因不能繼續執行時,包括沒有任何有效指令、所有後續的未sat狀態或無效的指令指針,它就會進入死區隱藏。

pruned#當在lazy_resolve存在的情況下發現一個狀態未sat時,將遍歷該狀態層次結構,以確定在其歷史上,它最初何時成爲unsat。所有這一觀點的後裔州(也將被取消sat,因爲一個州不能成爲取消sat)都將被修剪並放入這一儲備中。

unconstrained#無約束的狀態(即,由用戶數據或其他符號數據來源控制的指令指針)放在這裏。

unsat#不可滿足的狀態(即,它們有相互矛盾的約束,比如輸入必須同時爲“AAAA”和“BBBB”)。

 

.explore()#方法:查找到達某個地址的狀態,同時丟棄經過另一個地址的所有狀態

>>> proj = angr.Project('examples/CSCI-4968MBE/challenges/crackme0x00a/crackme0x00a')

>>> simgr = proj.factory.simgr()

>>> simgr.explore(find=lambda s: b"Congrats" in s.posix.dumps(1))#匹配到贏得方法

 

4.4 Analyses 用於程序分析過程

>>> p.analyses.

p.analyses.BackwardSlice        

p.analyses.Reassembler

p.analyses.BinDiff               

p.analyses.StaticHooker

p.analyses.BinaryOptimizer      

p.analyses.VFG

p.analyses.BoyScout              

p.analyses.VSA_DDG

p.analyses.CDG                   

p.analyses.VariableRecovery

p.analyses.CFB                  

p.analyses.VariableRecoveryFast

p.analyses.CFBlanket            

p.analyses.Veritesting

p.analyses.CFG                   

p.analyses.discard_plugin_preset

p.analyses.CFGAccurate           

p.analyses.get_plugin

p.analyses.CFGFast               

p.analyses.has_plugin

p.analyses.CalleeCleanupFinder   

p.analyses.has_plugin_preset

p.analyses.CallingConvention     

p.analyses.plugin_preset

p.analyses.CongruencyCheck       

p.analyses.project

p.analyses.DDG                   

p.analyses.register_default

p.analyses.Disassembly           

p.analyses.register_plugin

p.analyses.GirlScout             

p.analyses.register_preset

p.analyses.Identifier           

p.analyses.release_plugin

p.analyses.LoopFinder            

p.analyses.reload_analyses

p.analyses.ReachingDefinitions   

p.analyses.use_plugin_preset

5 插件

state.globals實現了標準python dict的接口,允許您在狀態上存儲任意數據。

state.history存儲關於狀態在執行過程中所採取的路徑的歷史數據,實際上是由幾個歷史節點組成的鏈表,每個節點代表一輪執行——使用state.history.parent.parent遍歷這個列表

history.description#是在狀態上執行的每一輪執行的字符串描述的列表。

history.bbl_addrs#是由狀態執行的基本塊地址的列表

history.jumpkinds#是狀態歷史中每個控制流轉換的處理的列表

history.guards#是保護狀態遇到的每個分支的條件的列表。

history.events#是執行過程中發生的“有趣事件”的語義列表,如出現符號跳轉條件、程序彈出消息框或執行以退出代碼結束。

history.actions#通常爲空,但如果添加了angr.options.refs選項,它將彈出一個日誌,記錄程序執行的所有內存、寄存器和臨時值訪問。

state.callstack跟蹤模擬程序的調用堆棧

callstack.func_addr#當前正在執行的函數的地址

callstack.call_site_addr#調用當前函數的基本塊的地址

callstack.call_site_addr#從當前函數開始的堆棧指針的值

callstack.call_site_addr#如果當前函數返回,返回的位置

此外,還有其他的插件

6 其他

1 符號引擎

failure engine#故障引擎

syscall engine#系統調用引擎

hook engine#鉤子引擎

unicorn engine#project.factory.successors(state, **kwargs)#依次嘗試所有引擎的代碼

2 斷點

>>> import angr

>>> b = angr.Project('examples/fauxware/fauxware')

>>> s = b.factory.entry_state()#獲取狀態

>>> s.inspect.b('mem_write')#以下3條都是插入斷點

>>> s.inspect.b('mem_write', when=angr.BP_AFTER, action=debug_func)

>>> s.inspect.b('mem_write', when=angr.BP_AFTER, action=angr.BP_IPYTHON)

斷點總結:

mem_read#正在讀取內存。

mem_write#正在寫內存。

reg_read#正在讀取寄存器。

reg_write#正在寫取寄存器。

tmp_read#正在讀取臨時變量。

tmp_write#正在寫取臨時變量。

expr#正在創建一個表達式

statement#正在翻譯IR語句。

instruction#正在翻譯一條新的指令

irsb#一個新的基本塊正在被翻譯。

constraints#新的約束被添加到狀態中。

exit#從執行中生成一個後繼。

symbolic_variable#正在創建一個新的符號變量。

call#調用指令被命中。

address_concretization#正在解析符號內存訪問

同時,這些類型還有不同的屬性,見 https://docs.angr.io/core-concepts/simulation

三 總結

本次博客主要是瞭解了下關於angr的一些基本指令,這篇文章可能感覺有點亂,我並不是按照angr documentation第二篇的順序來翻譯的,而且翻譯效果也比較差。大概就是爲了方便給自己以後查閱提供一個參考。說說翻後的感覺,angr的功能很多,也很強大,應該說能包含的都包含了,但是具體的效果這麼樣,我還不知道。下一篇開始進行簡單的嘗試。

另外,時間倉促,所寫內容難免有疏漏之處,各位大佬可以隨時批評!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章