最近、Forta Foundation は、悪意のある契約(Malicious Contracts)のセットが与えられた場合、作成された契約が悪意のある契約のセット内のいずれかの契約と類似しているかどうかを判断する監視ロボットを作成するように私に依頼しました。数日間、数十の論文を熟読し、最終的にこの要件を満たすために頑張って作業を完了しました。このプロジェクトの最終成果物はこちらです。
Forta Network とは#
Fortaの公式ウェブサイトによると:
ウォレット、開発者、投資家向けの主要な分散型セキュリティおよび運用モニタリングネットワーク
簡単に言えば、Forta Network はセキュリティ監視のための分散型計算プラットフォームです。開発者はまず、Forta Bot を要件に応じて開発し、Forta Bot 内でブロックチェーン上のすべてのトランザクションを処理できます。その後、開発者は Forta Bot を Forta Network にアップロードすることで、24 時間ブロックチェーン情報を処理できるようになります。
たとえば、開発者はステーブルコインのアンカリングをリアルタイムで監視するための Forta Bot を作成することができます。もしアンカリングが解除された場合、警告が発せられます。また、開発者は特定のプロジェクトの契約が所有者に変更されたかどうかを監視する Forta Bot を作成することもでき、攻撃行為を事前に検知するために使用できます。
主な課題#
ブロックチェーンにアップロードされる契約の大部分はオープンソースではないため、契約のソースコードを直接的に意味解析することはできません。その代わり、契約のバイトコードを分析する必要があります。バイトコードにはソースコードの多くの意味情報が失われているため、学術的にも産業的にも、契約のバイトコードの類似度検出はほとんど研究されていません。
解決策#
最終的に、私たちは関数レベルの類似度検出を使用しました。私たちの手法のステップは次のとおりです:
- 契約のバイトコードを OP コードと CFG に逆コンパイルする。
- CFG をトラバースし、契約の各関数を抽出し、関数内の基本ブロックの順序で各関数のすべての命令を抽出する。
- 1 つの関数のすべての命令を密なベクトルに変換する。
- 関数間の類似度を計算して契約間の類似度を計算する。
逆コンパイル#
広く知られているように、コンパイルの過程でプログラムは不可避的に多くの情報を失ってしまいますので、完全に信頼できる逆コンパイルは存在しません。ただし、契約をバイトコードから OP コードに逆コンパイルすることは完全に可能で信頼性があります。これは x86 上でバイナリをアセンブリ言語に変換するのと同じです。マニュアルに従って一文ずつ翻訳するだけです。したがって、逆コンパイルの最初のステップは、EVM ドキュメントに従って契約のバイトコードを OP コードに変換することです。
その後、OP コードが得られると、OP コードから契約がどのように実行されるかを知ることができます。たとえば、jump
命令に遭遇した場合、ここでジャンプが行われることがわかります。コードの OP コードを分析することで、契約を制御フローグラフ(CFG)に変換することができます。
CFG では、各ブロックは基本ブロックと呼ばれます。公式の定義によれば:
コンパイラ構築において、基本ブロックとは、エントリーポイント以外に分岐がなく、出口ポイント以外に分岐がない直線のコードシーケンスです。
つまり、基本ブロックは、実行が開始される最初の命令から実行が終了する最後の命令までのコードの一部を表します。人間の言葉で言えば、基本ブロック内のコードは一度実行されると、途中で別の基本ブロックにジャンプすることはなく、最後の命令まで実行されるということです。
CFG が得られると、dispatcher()
から契約に含まれる各関数と各関数のオフセットを見つけることができます。その後、静的解析を使用して、各関数がどの基本ブロックを含んでいるかを特定することができます。ここまでで、ほとんどの逆コンパイルが完了しました。
命令の抽出#
関数間の類似度計算を行うため、私たちの処理は自然に関数単位で行われます。前のセクションでコンパイルされた CFG が得られたので、CFG の実行順序に従って関数が関与するすべての命令をトラバースすることができます。ここで、CFG の順序でトラバースするため、トラバース後の結果はある程度意味を持っています。最終的に、各命令を文字列に変換し、異なる命令をスペースで区切って、契約の命令を抽出します。
ベクトル化#
1 つの関数には数千もの命令が含まれる可能性があるため、深層学習ベースのベクトル化手法を使用すると複雑すぎて効率が悪い場合があります。ここでは、word2vec
ベースのdoc2vec
を使用して、抽出した命令を教師なしでベクトル化しました。もし可能なら、読者は比較学習ベースのベクトル化手法(たとえば、比較学習ベースのcodebert
)を試してみることもできます。
トレーニング時には、slither-audited-smart-contracts
データセットを使用しました。このデータセットには約 12 万の契約のバイトコードが含まれています。データセットを処理すると、約 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)$ は 2 つのベクトルのコサイン類似度です。
最終的に、$Sim (C_1, C_2)$ が得られると、$C_1$ と $C_2$ の類似度スコアは次のように計算されます:
この $score$ は、類似した契約と非類似な契約を区別するために使用できます。