EKLAVYA -- 利用神經(jīng)網(wǎng)絡(luò)推斷二進(jìn)制文件中函數(shù)的參數(shù)
這一次介紹一篇文章,名為Neural Nets Can Learn Function Type Signatures From Binaries
來自于新加坡國立大學(xué)Zhenkai Liang團(tuán)隊(duì),發(fā)在了Usenix Security 2017上
問題介紹以及形式化定義
該工作主要關(guān)注的問題是函數(shù)的參數(shù)推斷,包含兩個(gè)部分:
- 參數(shù)的個(gè)數(shù)
- 參數(shù)的類型,比如int, float等
傳統(tǒng)方法通常會使用一些先驗(yàn)知識,將指令的語義,ABI慣例 (Application Binary Interface),編譯器的風(fēng)格等進(jìn)行編碼。
一旦編譯器發(fā)生了改變,指令集合發(fā)生了改變,那么我們就需要重新引入一些先驗(yàn)知識。
如果我們可以擺脫,或者說是減少這些先驗(yàn)知識的利用,那么就不會受限了!
那么,使用神經(jīng)網(wǎng)絡(luò)來進(jìn)行自動(dòng)化的學(xué)習(xí)和推斷,就是一種思路了。
前提假設(shè)
- 我們首先能知道一個(gè)函數(shù)的邊界 (boundary)
- 在一個(gè)函數(shù)內(nèi)部,我們知道它的指令邊界
- 我們知道代表一個(gè)函數(shù)調(diào)用(function dispatch)的指令,比如call
通過反匯編工具,我們可以滿足上述假設(shè)。
值得一提的是,函數(shù)邊界也可以使用神經(jīng)網(wǎng)絡(luò)來做,有興趣的讀者可以參考 Dawn Song 發(fā)在Usenix Security 2015 的 Recognizing functions in binaries with neural networks.
這里,首先給出一些符號的定義:
我們定義我們的模型為
定義函數(shù)
反匯編出來的代碼為
,
代表函數(shù)
的第
個(gè)字節(jié)
-
函數(shù)
的第
條指令可以被寫成
- 其中
是對應(yīng)指令的起始字節(jié)的位置索引
-
是該指令所包含的字節(jié)數(shù)
- 其中
一個(gè)包含
條指令的函數(shù)
可以被表示為
-
如果一個(gè)函數(shù)
有一個(gè)直接調(diào)用 call 對于函數(shù)
, 我們將該條call指令之前的所有指令拿出來,稱為 caller snippet,可譯為調(diào)用者片段。定義為
- 其中
對應(yīng) call 函數(shù)
的那一條指令
- 如果
是一個(gè)間接調(diào)用,我們令
- 其中
-
我們會收集函數(shù)
的所有調(diào)用者的調(diào)用者片段,記為
- 其中
是調(diào)用
的所有函數(shù)的集合
- 其中
由于調(diào)用者片段的長度可能非常長,這里文章設(shè)置為不超過500條指令
我們的函數(shù) 接受輸入
, 輸出兩個(gè)變量:
- 函數(shù)
的參數(shù)個(gè)數(shù)
- 函數(shù)
的每一個(gè)參數(shù)的類型
- C-風(fēng)格的參數(shù)類型可以被定義為 int, char, float, void*, enum, union, struct
方法設(shè)計(jì)
我們這里先直接給出整體的方法流程圖:

簡單來說,可以劃分成兩個(gè)模塊:
- 指令編碼模塊
- 首先,將輸入的二進(jìn)制文件進(jìn)行函數(shù)抽取、指令的分割、調(diào)用點(diǎn)的抽取。
- 然后對指令進(jìn)行Word Embedding編碼,得到對應(yīng)的向量表示,這一部分可以參照 NLP 中的 Word2Vec。
- 參數(shù)還原模塊
- 將這些數(shù)據(jù)切分成訓(xùn)練集和測試集,分別使用4個(gè)遞歸神經(jīng)網(wǎng)絡(luò)(RNN)來從兩個(gè)方面(調(diào)用者和被調(diào)用者)推斷函數(shù)參數(shù)的個(gè)數(shù)以及類型,也就是對應(yīng)上圖中的4個(gè)任務(wù)(Task2,Task4 對應(yīng)被調(diào)用者,Task1,Task3對應(yīng)調(diào)用者)。
究竟是怎么推斷多個(gè)參數(shù)類型的呢?
一個(gè)RNN,輸入一個(gè)序列,只能推斷出來一個(gè)類型。
所以這篇文章的實(shí)現(xiàn)是,訓(xùn)練多個(gè)RNN,每一個(gè)RNN獨(dú)立推斷固定位置的參數(shù)類型。
先用一個(gè)RNN推斷出來參數(shù)個(gè)數(shù),然后分別使用多個(gè)RNN來推斷不同的位置的參數(shù)。
數(shù)據(jù)準(zhǔn)備
該文章使用一些linux的包,然后使用clang和gcc來進(jìn)行編譯,通過設(shè)定debug模式,就可以直接在binary中的DWARF字段找到對應(yīng)函數(shù)邊界、參數(shù)個(gè)數(shù)以及類型,作為ground truth。
構(gòu)建了兩個(gè)數(shù)據(jù)集:
- 數(shù)據(jù)集1: 包含了3個(gè)流行的linux包(binutils,coreutils 以及 findutils), 使用了O0到O3的優(yōu)化等級進(jìn)行編譯
- 數(shù)據(jù)集2: 將數(shù)據(jù)集1包含的linux包進(jìn)行擴(kuò)展,多增加5個(gè) (sg3utils, utillinux, inetutils, diffutils 和 usbutils), 也在4個(gè)優(yōu)化等級上進(jìn)行編譯
訓(xùn)練集和測試集的劃分比例為8:2
不平衡的數(shù)據(jù)
在數(shù)據(jù)集的構(gòu)造中,會出現(xiàn)不同類的數(shù)據(jù)比例相距甚遠(yuǎn)的情況。比如參數(shù)為pointer類型的數(shù)據(jù)就是union類型的數(shù)百倍,大部分函數(shù)都是少于3個(gè)參數(shù)。該文章中并沒有解決這個(gè)問題。
實(shí)驗(yàn)結(jié)果

這里貼出來Task1和Task2,也就是通過調(diào)用者和被調(diào)用者,推斷參數(shù)個(gè)數(shù)的結(jié)果
可以看到:
- 優(yōu)化級別越高,越難推斷,但并沒有嚴(yán)格的遞增關(guān)系
- 參數(shù)個(gè)數(shù)越多,越難推斷,和訓(xùn)練數(shù)據(jù)量也有關(guān)系
- 從調(diào)用者方面,更容易推斷出來參數(shù)個(gè)數(shù)

上面是,關(guān)于參數(shù)類型推斷的結(jié)果
可以看到:
- 優(yōu)化級別似乎干擾性不強(qiáng),甚至優(yōu)化級別越高,推斷類型越精確
- 參數(shù)的位置越靠后,越難推斷出來了類型
- 從調(diào)用者和被調(diào)用者兩方面來推斷,差別不是很大