メインコンテンツへスキップ

Slitherを使用してスマートコントラクトのバグを見つける方法

Solidity
スマートコントラクト
セキュリティ
テスト
上級
Trailofbits
2020年6月9日
13 分の読書

Slitherの使用方法

このチュートリアルでは、Slitherを使って、スマートコントラクトのバグを自動で検出する方法を学びます。

インストール

SlitherにはPython 3.6以上が必要です。 pipでインストールすることも、Dockerを使用してインストールすることもできます。

pipによるSlitherのインストール:

pip3 install --user slither-analyzer

DockerによるSlitherのインストール:

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には、多くの一般的なバグを見つけるための事前定義された検出器のセットが付属しています。 コマンドラインからSlitherを呼び出すとすべての検出器が実行されるため、静的解析に関する詳細な知識は必要ありません。

slither project_paths

検出器に加えて、Slitherにはそのprinters (opens in a new tab)tools (opens in a new tab)を介したコードレビュー機能があります。

crytic.io (opens in a new tab)を使用すると、プライベートな検出器やGitHubとの統合にアクセスできます。

静的解析

Slither静的解析フレームワークの機能と設計については、ブログ投稿 (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)のような形式的手法に基づくツール) の基礎ともなっています。

ここでは、静的解析の技術と研究者を網羅的に検討するわけではありません。 その代わり、皆さんがバグを発見しコードを理解するためにSlitherをより効果的に使えるよう、Slitherがどのように機能するかを理解するために必要なことに焦点を当てます。

コード表現

単一の実行パスについて推論する動的解析とは対照的に、静的解析では一度にすべてのパスを対象として推論します。 そのために、異なるコード表現に依存しています。 最も一般的なものは、抽象構文木 (AST) と制御フローグラフ (CFG) の2つです。

抽象構文木 (AST)

ASTは、コンパイラがコードを解析するたびに使用されます。 これは、おそらく静的解析を実行できる最も基本的な構造です。

要するに、ASTは構造化された木であり、通常、各リーフには変数または定数が含まれ、内部ノードはオペランドまたは制御フロー操作です。 次のコードを考えてみましょう。

1function safeAdd(uint a, uint b) pure internal returns(uint){
2 if(a + b <= a){
3 revert();
4 }
5 return a + b;
6}

対応するASTは以下の通りです。

AST

SlitherはsolcによってエクスポートされたASTを使用します。

ASTは簡単に構築できますが、入れ子構造になっています。 そのため、解析が必ずしも簡単ではない場合があります。 例えば、a + b <= aという式で使われる演算を特定するには、まず<=を解析し、次に+を解析しなければなりません。 一般的なアプローチは、木構造を再帰的に走査する、いわゆるビジターパターンを使用することです。 Slitherには、ExpressionVisitor (opens in a new tab)に汎用的なビジターが含まれています。

次のコードはExpressionVisitorを使用して、式に加算が含まれているかどうかを検出します。

1from slither.visitors.expression.expression import ExpressionVisitor
2from slither.core.expressions.binary_operation import BinaryOperationType
3
4class HasAddition(ExpressionVisitor):
5
6 def result(self):
7 return self._result
8
9 def _post_binary_operation(self, expression):
10 if expression.type == BinaryOperationType.ADDITION:
11 self._result = True
12
13visitor = HasAddition(expression) # expression はテスト対象の式です
14print(f'The expression {expression} has a addition: {visitor.result()}')
すべて表示

制御フローグラフ (CFG)

2番目に一般的なコード表現は、制御フローグラフ (CFG) です。 その名の通り、すべての実行パスを公開するグラフベースの表現です。 各ノードには、1つまたは複数の命令が含まれます。 グラフのエッジは、制御フロー操作 (if/then/else、ループなど) を表します。 前の例のCFGは次のようになります。

CFG

CFGは、ほとんどの解析がその上に構築される表現です。

他にも多くのコード表現が存在します。 それぞれの表現には、実行したい解析に応じて長所と短所があります。

解析

Slitherで実行できる最も簡単な種類の解析は、構文解析です。

構文解析

Slitherは、パターンマッチングのようなアプローチを用いて、コードのさまざまな構成要素とその表現を走査し、矛盾や欠陥を見つけることができます。

例えば、以下の検出器は構文関連の問題を探します。

意味解析

構文解析とは対照的に、意味解析はより深く掘り下げ、コードの「意味」を解析します。 この系統には、いくつかの広範な種類の解析が含まれます。 それらはより強力で有用な結果につながりますが、記述もより複雑になります。

意味解析は、最も高度な脆弱性検出に使用されます。

データ依存性解析

variable_aの値がvariable_bに影響されるパスが存在する場合、変数variable_avariable_bにデータ依存していると言われます。

次のコードでは、variable_avariable_bに依存しています。

1// ...
2variable_a = variable_b + 1;

Slitherには、その中間表現 (後のセクションで説明) のおかげで、組み込みのデータ依存性 (opens in a new tab)機能が備わっています。

データ依存性の使用例は、危険な厳密等価性検出器 (opens in a new tab)にあります。 ここでは、Slitherは危険な値との厳密な等価比較を探し (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を走査してエッジをたどる場合、すでに訪れたノードに遭遇する可能性があります。 例えば、以下のようにループが存在する場合です。

1for(uint i; i < range; ++){
2 variable_a += 1
3}

解析はいつ停止すべきかを知る必要があります。 ここには2つの主要な戦略があります。(1) 各ノードを有限回反復する、(2) いわゆる_不動点_を計算する。 不動点とは、基本的に、そのノードを解析しても、もはや有意義な情報が得られないことを意味します。

不動点の使用例は、リエントランシー検出器に見られます。Slitherはノードを探索し、外部呼び出し、ストレージへの書き込みと読み込みを探します。 不動点に達すると (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) とは、元の言語よりも静的解析に適した言語のことです。 Slitherは、Solidityを独自の中間表現である SlithIR (opens in a new tab) に変換します。

基本的なチェックを書きたいだけなら、SlithIRを理解する必要はありません。 しかし、高度な意味解析を書く予定がある場合は、役立つでしょう。 SlithIR (opens in a new tab) および SSA (opens in a new tab) プリンターは、コードがどのように変換されるかを理解するのに役立ちます。

APIの基本

Slitherには、コントラクトとその関数の基本的な属性を探索できるAPIがあります。

コードベースを読み込むには、次のようにします。

1from slither import Slither
2slither = Slither('/path/to/project')
3

コントラクトと関数の探索

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): シグネチャからFunctionを返します
  • 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のサブセット)

最終更新: 2025年2月3日

このチュートリアルは役に立ちましたか?