最近 Forta Foundation 讓我幫他們做一個監控機器人,要求給定一個集合的惡意合約(Malicious Contracts),判斷當前創造的合約是否與惡意合約集合中的任意一個合約相似。這幾天瘋狂看了十幾篇論文,最終終於比較圓滿的完成了這個需求。整個任務感覺非常有意思和挑戰性,最終項目在這裡。
Forta Network 是什麼#
Forta 的官網簡介如下:
The leading decentralized security & operational monitoring network for wallets, developers, and investors
簡單的說,Forta Network 是一個用於安全監控的分佈式計算平台。開發者可以首先按照需求開發 Forta Bot,Forta Bot 裡可以處理區塊鏈上的每一條交易。然後開發者可以把 Forta Bot 上傳到 Forta Network,就可以 24 小時處理區塊鏈信息了。
比如說,開發者可以編寫一個 Forta Bot 來實時監控算法穩定幣是否脫鉤。如果脫鉤,則發出警告。還比如開發者可以編寫一個 Forta Bot 來監控某個項目的合約是否變更了所有者,可以用於提前探測攻擊行為。
核心困難#
由於大部分上傳到區塊鏈的合約都是不開源的,所以我們不能直接對合約的源代碼進行語義分析。相反,我們只能對合約的 Bytecode 進行分析。Bytecode 失去了源代碼中的大量語義信息,所以不管是學術上還是工業上,對合約 Bytecode 的相似度檢測都罕被探索。
解決方案#
我們最終使用函數級別的相似度檢測。總的來說,我們的方法的步驟如下:
- 反編譯合約 Bytecode 到 OPcode 和 CFG。
- 遍歷 CFG,提取合約的每一個函數,並按函數中 Basicblock 的順序提取每一個函數中所有指令。
- 將一個函數的所有指令向量化到一個稠密向量。
- 通過函數間的相似度計算合約間的相似度。
反編譯#
眾所周知,由於在編譯的過程中程式不可避免的損失了大量的信息,完全靠譜的反編譯是不存在的。但是,僅僅將合約從 Bytecode 反編譯到 OPcode 是完全可能且靠譜的。這就像在 x86 上把二進制翻譯成彙編語言一樣,直接照著說明書一句一句翻譯就好了。所以我們反編譯的第一步就是按照 EVM 文檔將合約的 Bytecode 翻譯成 Opcode。
之後,有了 Opcode 後,我們雖然很難直接從 Opcode 知道這個合約要幹什麼,但是我們可以知道合約代碼中的執行順序是怎麼樣的。例如,當遇到 jump
指令的時候我們大概就知道這裡會有一次跳躍。我們可以通過分析代碼的 Opcode 來將合約進一步變成一張 Control Flow Graph (CFG)。
在 CFG 中,每一個塊被稱為 Basicblock,比較官方的定義為:
In compiler construction, a basic block is a straight-line code sequence with no branches in except to the entry and no branches out except at the exit
大概意思就是,一個 Basicblock 表示這一部分代碼只能從頭部指令進入開始運行,然後從尾部指令結束允許。用人話講就是一個 Basicblock 裡的代碼一旦被運行就是一起被運行,不會從中間跳走到另外一個 Basicblock。
有了 CFG 後,我們可以從 dispatcher()
中找到合約包含的每一個函數以及每一個函數的偏移,然後通過靜態分析可以定位每一個函數包含了哪些 Basicblock。到此,大部分反編譯就完成了。
提取指令#
由於我們進行的是函數間的相似度計算,我們的處理自然以函數為單位。有了上一小節編譯得到的 CFG 後,我們可以按照 CFG 的執行順序遍歷每一個函數所涉及的所有指令。這裡,由於我們是按照 CFG 的順序來遍歷的,所以遍歷後的結果天然具有一定的語義。最終,我們把每一個指令轉化成字符串,不同指令以空格隔開,一個合約的指令就被提取出來了。
向量化#
由於一個函數可能包含成千上萬的指令,使用基於深度學習的向量化方法可能會過於複雜且低效。這裡,我們使用的是基於 word2vec
的 doc2vec
來無監督地對提取的指令做向量化。如果有條件的話讀者完全可以試試基於對比學習的向量化方法,例如基於對比學習的 codebert
。
在訓練的時候,我們使用了 slither-audited-smart-contracts
數據集,其包含大約 12 萬條合約的 Bytecode。將數據集處理完後會有大約 200 萬個函數供我們訓練。這麼多數據對於 doc2vec
來說應該是夠用了,但是如果讀者想嘗試基於對比學習的向量化方法的話,可以試試剛開源的 paradigm-data-portal 數據集。
合約相似度計算#
最後,有了函數間的相似度後,我們定義合約 $C_1$ 和 $C_2$ 的相似度為:
其中,$f_i$ 表示 $C_1$ 的第 $i$ 個函數,$f_2^$ 表示 $C_2$ 中與 $f_1$ 最相似的函數,$\bar {f_2}$ 表示 $f_i$ 與 $C_2$ 中任意函數的平均相似度. $P (f_i, f_2^)$ 和 $P (f_i, \bar {f_2})$ 是 $f_i$ 與 $f_2^*$ 和 $\bar {f_2}$ 分別相似的概率。 $P (\cdot)$ 表示為:
其中, $k$ 是一個超參數,$cos (\cdot, \cdot)$ 是兩個向量的餘弦相似度。
最終,有了 $Sim (C_1, C_2)$ 後,我們計算 $C_1$ 和 $C_2$ 的相似度評分為:
這個 $score$ 可以用來區分相似合約和不相似的合約。