跳至主要內容

如何使用斯立瑟尋找智能合約錯誤

Solidity
智能合約
安全性
測試
進階
Trailofbits
2020年6月9日
11 分鐘閱讀

如何使用斯立瑟

本教學旨在展示如何使用斯立瑟自動尋找智能合約中的錯誤。

安裝

斯立瑟需要 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 顯示如下:

AST

斯立瑟使用 solc 匯出的 AST。

雖然易於建構,但 AST 是一種巢狀結構。有時候,這並不是最直接的分析方式。例如,要識別表達式 a + b <= a 使用的操作,您必須先分析 <=,然後再分析 +。常見的方法是使用所謂的存取者模式 (visitor pattern),它會遞迴地瀏覽樹狀結構。斯立瑟在 ExpressionVisitor (opens in a new tab) 中包含了一個通用的存取者。

以下程式碼使用 ExpressionVisitor 來偵測表達式是否包含加法:

控制流程圖 (CFG)

第二種最常見的程式碼表示法是控制流程圖 (CFG)。顧名思義,它是一種基於圖形的表示法,揭露了所有的執行路徑。每個節點包含一個或多個指令。圖中的邊緣代表控制流程操作 (if/then/else、迴圈等)。我們前面範例的 CFG 如下:

CFG

CFG 是大多數分析建立其上的表示法。

還存在許多其他的程式碼表示法。根據您想要執行的分析,每種表示法都有其優缺點。

分析

您可以使用斯立瑟執行的最簡單分析類型是語法分析。

語法分析

斯立瑟可以瀏覽程式碼的不同元件及其表示法,使用類似模式比對的方法來尋找不一致和缺陷。

例如,以下偵測器會尋找與語法相關的問題:

語意分析

與語法分析相反,語意分析會更深入地分析程式碼的「意義」。這個系列包含了一些廣泛的分析類型。它們能產生更強大且有用的結果,但編寫起來也更複雜。

語意分析用於最進階的漏洞偵測。

資料相依性分析

如果存在一條路徑,使得 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)

FunctionModifier 物件具有:

  • 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 的子集)