Slitherを使用してスマートコントラクトのバグを見つける方法
Slitherの使用方法
このチュートリアルでは、Slitherを使って、スマートコントラクトのバグを自動で検出する方法を学びます。
- インストール
- コマンドラインの使用方法
- 静的解析入門: 静的解析の簡単な紹介
- API: Python APIの説明
インストール
SlitherにはPython 3.6以上が必要です。 pipでインストールすることも、Dockerを使用してインストールすることもできます。
pipによるSlitherのインストール:
pip3 install --user slither-analyzerDockerによるSlitherのインストール:
docker pull trailofbits/eth-security-toolboxdocker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox最後のコマンドは、現在のディレクトリにアクセスできるDockerでeth-security-toolboxを実行します。 ホストからファイルを変更し、Dockerからファイル上のツールを実行できます。
Docker内で、以下を実行します。
solc-select 0.5.11cd /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は以下の通りです。
SlitherはsolcによってエクスポートされたASTを使用します。
ASTは簡単に構築できますが、入れ子構造になっています。 そのため、解析が必ずしも簡単ではない場合があります。 例えば、a + b <= aという式で使われる演算を特定するには、まず<=を解析し、次に+を解析しなければなりません。 一般的なアプローチは、木構造を再帰的に走査する、いわゆるビジターパターンを使用することです。 Slitherには、ExpressionVisitor (opens in a new tab)に汎用的なビジターが含まれています。
次のコードはExpressionVisitorを使用して、式に加算が含まれているかどうかを検出します。
1from slither.visitors.expression.expression import ExpressionVisitor2from slither.core.expressions.binary_operation import BinaryOperationType34class HasAddition(ExpressionVisitor):56 def result(self):7 return self._result89 def _post_binary_operation(self, expression):10 if expression.type == BinaryOperationType.ADDITION:11 self._result = True1213visitor = HasAddition(expression) # expression はテスト対象の式です14print(f'The expression {expression} has a addition: {visitor.result()}')すべて表示制御フローグラフ (CFG)
2番目に一般的なコード表現は、制御フローグラフ (CFG) です。 その名の通り、すべての実行パスを公開するグラフベースの表現です。 各ノードには、1つまたは複数の命令が含まれます。 グラフのエッジは、制御フロー操作 (if/then/else、ループなど) を表します。 前の例のCFGは次のようになります。
CFGは、ほとんどの解析がその上に構築される表現です。
他にも多くのコード表現が存在します。 それぞれの表現には、実行したい解析に応じて長所と短所があります。
解析
Slitherで実行できる最も簡単な種類の解析は、構文解析です。
構文解析
Slitherは、パターンマッチングのようなアプローチを用いて、コードのさまざまな構成要素とその表現を走査し、矛盾や欠陥を見つけることができます。
例えば、以下の検出器は構文関連の問題を探します。
-
状態変数のシャドーイング (opens in a new tab): すべての状態変数を反復処理し、継承されたコントラクトの変数をシャドーイングしているものがないかチェックします (state.py#L51-L62 (opens in a new tab))
-
不正確なERC20インターフェース (opens in a new tab): 不正確なERC20関数のシグネチャを探します (incorrect_erc20_interface.py#L34-L55 (opens in a new tab))
意味解析
構文解析とは対照的に、意味解析はより深く掘り下げ、コードの「意味」を解析します。 この系統には、いくつかの広範な種類の解析が含まれます。 それらはより強力で有用な結果につながりますが、記述もより複雑になります。
意味解析は、最も高度な脆弱性検出に使用されます。
データ依存性解析
variable_aの値がvariable_bに影響されるパスが存在する場合、変数variable_aはvariable_bにデータ依存していると言われます。
次のコードでは、variable_aはvariable_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 += 13}解析はいつ停止すべきかを知る必要があります。 ここには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 Slither2slither = 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日

