之前,你只看到了angr最原始的加載方法——你加載了/bin/true,之后又以不加載共享庫文件的方式加載了一遍。你也看到了proj.loader和它能做的一些事情?,F(xiàn)在,我們將會深入這些接口的細(xì)微之處,讓它們告訴你更細(xì)節(jié)的信息。
我們簡要提到了angr的二進(jìn)制加載組件,CLE。CLE代表“CLE Loads Everything”,負(fù)責(zé)加載二進(jìn)制文件(以及任何這個文件以來的庫),并用一種容易操作的方式將它們展示給angr的其余組件。
加載器 (The Loader)
讓我們重新加載/bin/true并且深入學(xué)習(xí)如何和loader交互:

已加載的對象
CLE加載器(cle.Loader)代表一整個被加載的二進(jìn)制對象的集合,它們被加載并且映射到同一個內(nèi)存空間中。每一個二進(jìn)制對象都被一個能夠處理對應(yīng)文件類型(cle.Backend的子類)的加載器后端加載。比如,cle.ELF被用來加載ELF格式的二進(jìn)制文件。
內(nèi)存中也會有一些和任何被加載的二進(jìn)制文件都無關(guān)的對象。比如,被用來提供線程本地存儲(Thread-Local storage)支持的對象和用于提供未解析的符號的外部對象。
譯者注:這里的線程本地存儲應(yīng)該指的是TLS
你可以使用loader.all_objects來獲取CLE已經(jīng)加載的所有對象,也可以指定更加具體的類別來訪問這些對象:

你可以直接和這些對象交互來獲取元數(shù)據(jù):
- 獲取ELF的內(nèi)存分段和文件分段

-
獲取PLT表信息
markdown-img-paste-20180115215255922.png
- 展示預(yù)鏈接 基址和實際裝載到的內(nèi)存基址:

我這里的
obj.linked_base返回0,而文檔中返回的是和裝載地址一樣的值,由于不太了解預(yù)鏈接過程,目前猜測是系統(tǒng)相關(guān)的,這里不深究。
符號和重定位
在使用CLE的同時也可以使用符號。一個“符號”在形式化執(zhí)行的世界中是一個基礎(chǔ)概念,它將一個名字有效地(effectively)映射到一個地址。
從CLE中獲取符號,最簡單的方法是使用loader.find_symbol, 它接收一個名字或者一個地址并返回一個符號對象。

一個符號最有用的屬性是它的名字、父對象(owner)、和它的地址。但是一個符號的“地址”卻是一個含糊的概念。有三種方式來準(zhǔn)確表述一個符號的“地址”:
-
.rebased_addr是一個符號在全局地址空間中的地址。這就是你直接打印symbol.addr會顯示的內(nèi)容。

.linked_addr是符號相對于二進(jìn)制文件預(yù)鏈接基址的地址,這個地址和用諸如readelf(1)顯示的地址是一樣的。.relative_addr是符號相對于對象基址的地址。在一些文獻(xiàn)中(尤其在windows的文獻(xiàn)中),這樣的地址被稱為RVA(相對虛擬地址)

除了提供調(diào)試信息,符號還支持動態(tài)鏈接概念。libc提供了malloc作為導(dǎo)出函數(shù),并且主程序("/bin/true")使用這個函數(shù)。如果我們要求CLE直接從主程序?qū)ο笾薪o出一個malloc的符號,它將會告訴我們這是一個“導(dǎo)入符號”。導(dǎo)入符號沒有與其關(guān)聯(lián)的有意義的地址,但是它提供了能夠用于解析它的對象的引用,可以用.resolvedby來獲得引用。

說明:
在之前的loader中,我們使用
find_symbol方法,因為它執(zhí)行一個搜索操作來找到指定的符號。在一個獨立的對象中,這個方法是
get_symbol,因為一個給定的名字只會對應(yīng)一個符號。
導(dǎo)入符號(import symbol)和導(dǎo)出符號(export symbol)的關(guān)系應(yīng)該在內(nèi)存中以一種特殊的方式被記錄,這種方式引出另一種特殊的概念——“重定位”?!爸囟ㄎ弧闭f的是:當(dāng)你將一個[import]和一個導(dǎo)出符號匹配的時候,請將導(dǎo)出符號的地址按照[format]的形式寫到[location]。我們可以通過obj.relocs(獲取Relocation實例)看到一個對象的完成重定位表,或者通過obj.imports看到符號名和他們的重定位地址的映射關(guān)系。對于導(dǎo)出符號表,angr中沒有相應(yīng)對象與之對應(yīng)。


一個對象中需要重定位的相應(yīng)導(dǎo)入符號可以用.symbol獲取。重定位將會寫入的地址可以通過訪問一個Symbol的地址的任何方式獲取,你還可以通過.owner_obj來獲得一個請求重定位的對象的引用。
譯者注:經(jīng)過測試,
.symbol似乎已經(jīng)被.symbols_by_addr替代,具體情況我詢問作者之后再來確認(rèn)。測試情況如下:

關(guān)于
.owner_obj的測試,之前創(chuàng)建的之前創(chuàng)建的main_malloc返回的是主程序?qū)ο蟮囊茫?/p>

重定位信息不能以比較漂亮的形式展示出來,因為重定位的地址是python內(nèi)置的,是和我們的程序無關(guān)的。
譯者注:原文是“so”,我覺得邏輯不通,故譯為"because"
這里“和我們的程序無關(guān)”的意思應(yīng)該是重定位的地址有python內(nèi)部決定,對于我們的程序而言不需要關(guān)心。
如果一個導(dǎo)入符號不能被解析為任何導(dǎo)出符號,比如找不到對應(yīng)的共享庫文件,CLE將會自動更新loader.extern_obj來表明這個符號由CLE導(dǎo)出。
加載選項
如果你正在使用angr.Project加載一些東西并且你想要給cle.loader實例傳一個選項來創(chuàng)建Project,你可以直接傳入關(guān)鍵字參數(shù)給Project的構(gòu)造函數(shù),你傳入的關(guān)鍵字就會被傳給CLE。如果你想要知道所有能夠被作為選項傳入的參數(shù),你可以查看CLE的API文檔。在本文檔中,我們只看一些重要的并且被頻繁使用的選項。
基本選項
我們已經(jīng)用過了auto_load_libs選項——它控制CLE是否自動加載共享庫文件,默認(rèn)是自動加載的。另外,有一個相反的選項except_missing_libs,這個選項如果被設(shè)置為true,將在二進(jìn)制包含無法解析的共享庫時拋出一個異常。
你可以傳入一個字符串列表給force_load_libs選項,每一個被列出的字符串將會被當(dāng)做一個不可解析共享庫依賴,或者你可以傳入一個字符串列表給skip_libs來防止列表列出的共享庫的被作為依賴添加。另外,你可以傳入一個字符串列表給custom_ld_path選項,這個選項中的字符串會被作為額外的搜索共享庫文件的路徑,這些路徑將會比任何默認(rèn)路徑先被搜索,默認(rèn)路徑包括:被加載文件所在的路徑,當(dāng)前工作路徑,系統(tǒng)庫路徑。
Per-Binary 選項
如果你想要對一個特定的二進(jìn)制對象設(shè)置一些選項,CLE也能滿足你的需求。參數(shù)main_opts和lib_opts接收一個以python字典形式存儲的選項組。main_opts接收一個形如{選項名1:選項值1,選項名2:選項值2……}的字典,而lib_opts接收一個庫名到形如{選項名1:選項值1,選項名2:選項值2……}的字典的映射。
譯者注:lib_opts是二級字典,原因是一個二進(jìn)制文件可能加載多個庫,而main_opts指定的是主程序加載參數(shù),而主程序一般只有一個,因此是一級字典。
這些選項的內(nèi)容因不同的后臺而異,下面是一些通用的選項:
- backend —— 使用哪個后臺,可以是一個對象,也可以是一個名字(字符串)
- custom_base_addr —— 使用的基地址
- custom_entry_point —— 使用的入口點
- custom_arch —— 使用的處理器體系結(jié)構(gòu)的名字
例子:

譯者注:我嘗試修改/bin/true的加載基址,但是似乎沒有效果?
后臺(Backends)
CLE目前有能夠靜態(tài)加載ELF,PE,CGC,Mach-O和ELF核心轉(zhuǎn)儲文件的后臺,并且支持使用IDA加載二進(jìn)制和加載文件到一個平坦地址空間。大部分情況下,CLE將會自動檢測二進(jìn)制文件來決定使用哪個后臺,所以除非你在做一些很奇怪的工作,一般情況下你不需要指定使用哪個后臺。
你可以和上一節(jié)描述的一樣,用傳入一個鍵的方式強制CLE使用指定的后臺。一些后臺不能被自動檢測,因此必須用custom_arch指定。鍵值不需要匹配任何一個架構(gòu):根據(jù)你給出的任意架構(gòu)的任意通用標(biāo)識符,angr都能夠識別出你指的是哪個架構(gòu)。
為了指定使用的后臺,使用下表列出的名字:
| 名字 | 描述 | 是否需要用custom_arch指定? |
|---|---|---|
| elf | 基于PyELFTools的ELF文件靜態(tài)加載器 | 不需要 |
| pe | 基于PEFile的靜態(tài)PE文件加載器 | 不需要 |
| mach-o | Mach-O文件的靜態(tài)加載器,不支持動態(tài)鏈接或者變基 | 不需要 |
| cgc | Cyber Grand Challenge系統(tǒng)中的二進(jìn)制文件的靜態(tài)加載器 | 不需要 |
| backedcgc | 允許指定內(nèi)存和寄存器支持的CGC二進(jìn)制文件靜態(tài)加載器 | 不需要 |
| elfcore | ELF核心轉(zhuǎn)儲文件的靜態(tài)加載器 | 不需要 |
| ida | 啟動ida來分析文件 | 需要 |
| blob | 按照平坦模式加載文件到內(nèi)存中 | 需要 |
關(guān)于表中提到的CGC,這個youtube視頻展示了什么是CGC,簡單來說就是一個簡化的操作系統(tǒng),這個系統(tǒng)構(gòu)建的目的主要是用于CTF比賽中供各隊伍使用自己的AI來自動化分析和尋找這個簡化的系統(tǒng)上運行的程序的漏洞,CGC比賽是CTF的另一種形式。
符號函數(shù)摘要集(Symbolic Function Summaries)
譯者注:這里的“摘要”不是指對符號函數(shù)的一個概括和總結(jié),而是符號執(zhí)行中的一個概念
默認(rèn)情況下,Project都會嘗試用SimProcedures這個符號摘要集(symbolic summaries)替換主程序的外部調(diào)用。SimProcedure使用
pyhton函數(shù)高效地模擬外部庫函數(shù)對state的影響。我們已經(jīng)在SimProcedure中實現(xiàn)了一個完整的函數(shù)集
。這些內(nèi)建過程(procedures)能夠在angr.SIM_PROCEDURES字典中獲得,這個字典是一個兩層結(jié)構(gòu),第一層的鍵是包名(libc,posix,win32,stubs),第二層的鍵是庫函數(shù)的名字。用SimProcedure替代實際庫函數(shù)的執(zhí)行雖然存在一些潛在的不準(zhǔn)確性,但是可以讓你的分析更加可控,
當(dāng)找不到某個函數(shù)的摘要時:
如果
auto_load_libs的值為True(默認(rèn)值),那么真正的庫函數(shù)就會被執(zhí)行。這可能是你想要的,也可能不是,取決于實際的函數(shù)是什么。比如說,一些libc函數(shù)十分復(fù)雜,難以分析,并且極有可能導(dǎo)致用于確定執(zhí)行的路徑的狀態(tài)數(shù)爆炸。如果
auto_load_libs是False,那么外部函數(shù)就是“未解析”的狀態(tài),并且Project對象將會將它們解析為叫做ReturnUnconstrained的通用“stub” SimProdurce。就如它的名字所描述的:每次這個符號被調(diào)用,它都會將一個唯一的無約束符號作為返回值。如果
use_sim_procedures(這是angr.Project的參數(shù),不是cle.loader的參數(shù))是False(默認(rèn)是True),那么只有外部對象提供的符號才會被SimProcedures替代,并且他們將會被一個ReturnUnconstrainedstub替代。你可以給
angr.Project傳以下參數(shù)來指定一個不想被SimProcedure替代的符號:
exclude_sim_procedures_list和exclude_sim_procedures_func。查看
angr.Project._register_object的源碼來獲取精確的算法。
鉤子(Hooking)
angr使用python函數(shù)摘要替換庫函數(shù)代碼的機制叫做Hooking,而且你也可以這么做!在執(zhí)行一次模擬(simulation)時,每一步執(zhí)行angr都會檢查當(dāng)前地址是否被下了鉤子(hooked),并且如果檢查到鉤子,就會執(zhí)行鉤子函數(shù),而不是那個地址里的二進(jìn)制碼。使用APIproj.hook(addr,hook)可以完成hook,這里參數(shù)中的hook是一個SimProcedure實例。你可以用.is_hooked,.unhook和.hook_by屬性來管理你project中的鉤子,這些屬性的含義就如字面的意思,這里就不解釋了。
通過把proj.hook(addr)作為一個函數(shù)裝飾器(function decorator)。,你可以指定你自己的函數(shù)作為hook函數(shù)。如果你這么做了,你還可以指定一個可選的關(guān)鍵詞參數(shù)length來決定在你的hook函數(shù)執(zhí)行結(jié)束之后,程序跳過多少字節(jié)的機器碼再繼續(xù)執(zhí)行。

此外,你可以使用proj.hook_symbol(name,hook),提供一個符號名稱作為第一個參數(shù),這樣第二個參數(shù)指定的鉤子函數(shù)會被下到這個符號被調(diào)用的每個地址中。它的一個很重要的用途是用來擴展angr的內(nèi)建庫SimProcedure的行為。因為這些庫函數(shù)僅僅是一些類,你可以寫它們的子類,重寫它們的方法,并且把這些子類使用在hook中。
譯者注:hook_symbol的使用方法如下

So far so good!!
到目前為止,你應(yīng)該在CLE加載器和angr Project的級別上對如何控制你分析時的環(huán)境有了一個理性的認(rèn)識。你應(yīng)該還理解了angr采用了一個合理的方式來簡化它的分析:通過hook復(fù)雜的庫函數(shù),用SimProcedure這樣的只在總體上表現(xiàn)庫函數(shù)產(chǎn)生的影響的對象替代它們。
為了弄清使用CLE加載器和它的后臺能做的所有事情,你可以查閱CLE的API文檔。
