如何使用斯立瑟尋找智能合約錯誤
如何使用斯立瑟
本教學旨在展示如何使用斯立瑟自動尋找智能合約中的錯誤。
安裝
斯立瑟需要 Python >= 3.6。可以透過 pip 或使用 Docker 安裝。
透過 pip 安裝斯立瑟:
pip3 install --user slither-analyzer
透過 Docker 安裝斯立瑟:
docker pull trailofbits/eth-security-toolbox
docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox
最後一個命令會在可存取您目前目錄的 Docker 中執行 eth-security-toolbox。您可以從主機變更檔案,並從 Docker 對檔案執行工具
在 Docker 內部執行:
solc-select 0.5.11
cd /home/trufflecon/
執行腳本
若要使用 Python 3 執行 Python 腳本:
python3 script.py
命令列
命令列與使用者定義腳本的比較。 斯立瑟內建了一組預先定義的偵測器,可尋找許多常見的錯誤。從命令列呼叫斯立瑟將執行所有偵測器,不需要具備靜態分析的詳細知識:
slither project_paths
除了偵測器之外,斯立瑟還透過其列印程式 (printers) (opens in a new tab)和工具 (opens in a new tab)提供程式碼審查功能。
使用 crytic.io (opens in a new tab) 來存取私人偵測器和 GitHub 整合。
靜態分析
斯立瑟靜態分析框架的功能與設計已在部落格文章 (1 (opens in a new tab)、2 (opens in a new tab)) 和一篇學術論文 (opens in a new tab)中進行了說明。
靜態分析有不同的類型。您可能已經了解到,像 clang (opens in a new tab) 和 gcc (opens in a new tab) 這樣的編譯器依賴於這些研究技術,但它也是 (Infer (opens in a new tab)、CodeClimate (opens in a new tab)、FindBugs (opens in a new tab) 以及基於形式化方法的工具,如 Frama-C (opens in a new tab) 和 Polyspace (opens in a new tab)) 的基礎。
我們不會在此詳盡地回顧靜態分析技術和研究。相反地,我們將專注於了解斯立瑟運作方式所需的知識,以便您能更有效地使用它來尋找錯誤並理解程式碼。
程式碼表示法
與推論單一執行路徑的動態分析相反,靜態分析會同時推論所有路徑。為此,它依賴於不同的程式碼表示法。最常見的兩種是抽象語法樹 (AST) 和控制流程圖 (CFG)。
抽象語法樹 (AST)
每次編譯器解析程式碼時都會使用 AST。它可能是執行靜態分析最基本的結構。
簡而言之,AST 是一棵結構化樹,通常每個葉節點包含一個變數或常數,而內部節點則是運算元或控制流程操作。考慮以下程式碼:
function safeAdd(uint a, uint b) pure internal returns(uint){
if(a + b <= a){
revert();
}
return a + b;
}
對應的 AST 顯示如下:
斯立瑟使用 solc 匯出的 AST。
雖然易於建構,但 AST 是一種巢狀結構。有時候,這並不是最直接的分析方式。例如,要識別表達式 a + b <= a 使用的操作,您必須先分析 <=,然後再分析 +。常見的方法是使用所謂的存取者模式 (visitor pattern),它會遞迴地瀏覽樹狀結構。斯立瑟在 ExpressionVisitor (opens in a new tab) 中包含了一個通用的存取者。
以下程式碼使用 ExpressionVisitor 來偵測表達式是否包含加法:
from slither.visitors.expression.expression import ExpressionVisitor
from slither.core.expressions.binary_operation import BinaryOperationType
class HasAddition(ExpressionVisitor):
def result(self):
return self._result
def _post_binary_operation(self, expression):
if expression.type == BinaryOperationType.ADDITION:
self._result = True
visitor = HasAddition(expression) # expression 是要測試的表達式
print(f'The expression {expression} has a addition: {visitor.result()}')
控制流程圖 (CFG)
第二種最常見的程式碼表示法是控制流程圖 (CFG)。顧名思義,它是一種基於圖形的表示法,揭露了所有的執行路徑。每個節點包含一個或多個指令。圖中的邊緣代表控制流程操作 (if/then/else、迴圈等)。我們前面範例的 CFG 如下:
CFG 是大多數分析建立其上的表示法。
還存在許多其他的程式碼表示法。根據您想要執行的分析,每種表示法都有其優缺點。
分析
您可以使用斯立瑟執行的最簡單分析類型是語法分析。
語法分析
斯立瑟可以瀏覽程式碼的不同元件及其表示法,使用類似模式比對的方法來尋找不一致和缺陷。
例如,以下偵測器會尋找與語法相關的問題:
-
狀態變數遮蔽 (opens in a new tab):迭代所有狀態變數,並檢查是否有任何變數遮蔽了繼承合約中的變數 (state.py#L51-L62 (opens in a new tab))
-
不正確的 ERC-20 介面 (opens in a new tab):尋找不正確的 ERC-20 函式簽章 (incorrect_erc20_interface.py#L34-L55 (opens in a new tab))
語意分析
與語法分析相反,語意分析會更深入地分析程式碼的「意義」。這個系列包含了一些廣泛的分析類型。它們能產生更強大且有用的結果,但編寫起來也更複雜。
語意分析用於最進階的漏洞偵測。
資料相依性分析
如果存在一條路徑,使得 variable_a 的值受到 variable_b 的影響,則稱變數 variable_a 資料相依於 variable_b。
在以下程式碼中,variable_a 相依於 variable_b:
// ...
variable_a = variable_b + 1;
由於其中間表示法 (將在後面的章節中討論),斯立瑟內建了資料相依性 (opens in a new tab)功能。
資料相依性用法的範例可以在危險的嚴格相等偵測器 (opens in a new tab)中找到。在這裡,斯立瑟將尋找與危險值的嚴格相等比較 (incorrect_strict_equality.py#L86-L87 (opens in a new tab)),並會通知使用者應該使用 >= 或 <= 而不是 ==,以防止攻擊者困住合約。除此之外,偵測器會將呼叫 balanceOf(address) 的回傳值視為危險 (incorrect_strict_equality.py#L63-L64 (opens in a new tab)),並將使用資料相依性引擎來追蹤其使用情況。
不動點計算
如果您的分析瀏覽 CFG 並沿著邊緣前進,您很可能會看到已經造訪過的節點。例如,如果出現如下所示的迴圈:
for(uint i; i < range; ++){
variable_a += 1
}
您的分析將需要知道何時停止。這裡有兩個主要策略:(1) 對每個節點迭代有限次數,(2) 計算所謂的_不動點 (fixpoint)_。不動點基本上意味著分析此節點不會提供任何有意義的資訊。
使用不動點的範例可以在重入偵測器中找到:斯立瑟探索節點,並尋找外部呼叫、對儲存的寫入和讀取。一旦達到不動點 (reentrancy.py#L125-L131 (opens in a new tab)),它就會停止探索,並透過不同的重入模式 (reentrancy_benign.py (opens in a new tab)、reentrancy_read_before_write.py (opens in a new tab)、reentrancy_eth.py (opens in a new tab)) 分析結果以查看是否存在重入。
使用有效率的不動點計算來編寫分析,需要充分了解分析如何傳播其資訊。
中間表示法
中間表示法 (IR) 是一種旨在比原始語言更適合靜態分析的語言。斯立瑟將 Solidity 轉換為其專屬的 IR:SlithIR (opens in a new tab)。
如果您只想編寫基本檢查,則不需要了解 SlithIR。然而,如果您計畫編寫進階的語意分析,它將會派上用場。SlithIR (opens in a new tab) 和 SSA (opens in a new tab) 列印程式將幫助您了解程式碼是如何轉換的。
API 基礎知識
斯立瑟有一個 API,可讓您探索合約及其函式的基本屬性。
若要載入程式碼庫:
from slither import Slither
slither = Slither('/path/to/project')
探索合約與函式
Slither 物件具有:
contracts (list(Contract):合約清單contracts_derived (list(Contract):未被其他合約繼承的合約清單 (合約的子集)get_contract_from_name (str):從名稱回傳合約
Contract 物件具有:
name (str):合約名稱functions (list(Function)):函式清單modifiers (list(Modifier)):函式清單all_functions_called (list(Function/Modifier)):合約可觸及的所有內部函式清單inheritance (list(Contract)):繼承的合約清單get_function_from_signature (str):從簽章回傳函式get_modifier_from_signature (str):從簽章回傳修飾符 (Modifier)get_state_variable_from_name (str):從名稱回傳狀態變數 (StateVariable)
Function 或 Modifier 物件具有:
name (str):函式名稱contract (contract):宣告該函式的合約nodes (list(Node)):組成函式/修飾符 CFG 的節點清單entry_point (Node):CFG 的進入點variables_read (list(Variable)):讀取的變數清單variables_written (list(Variable)):寫入的變數清單state_variables_read (list(StateVariable)):讀取的狀態變數清單 (variables`read 的子集)state_variables_written (list(StateVariable)):寫入的狀態變數清單 (variables`written 的子集)

