スマートコントラクトの分解 (3): 機能セレクター
猎豹区块链安全
2018-12-17 06:15
本文约4218字,阅读全文需要约17分钟
スマートコントラクトの「関数セレクター」セクションの基礎となるコードを一緒に開きます


こんにちは、私と一緒にスマートコントラクトの分解を続けていきたいと思います。この記事はシリーズの第 3 部です。前回の記事をまだ読んでいない場合は、ぜひご覧ください。

(1)はじめに: 基本的なコードと操作方法。

(2)作成および実行時のコード解析。

単純な Solidity スマート コントラクトの EVM バイトコードを分解しています。

前回の記事では、スマート コントラクトのバイトコードが作成と運用の 2 つの部分に分かれていることを確認し、その理由を理解しました。作成部分を深く理解した後、次は、スマート コントラクトのバイトコードの探索を開始します。ランタイム部分。分解図を見ると、まず BasicToken.evm (ランタイム) と呼ばれる 2 番目の大きな分割ブロックを見ることができます。

実行するコードのサイズは、作成するコードの少なくとも 4 倍であるため、これは少し恐ろしいように思えるかもしれません。しかし、心配しないでください。これまでの記事で EVM コードを理解するために開発したスキルと、堅実な「分割統治」戦略の使用を組み合わせることで、この課題はより体系的になり、おそらくさらに簡単になるでしょう。これはほんの始まりにすぎません。引き続き独立した構造を特定し、解決可能な問題に分解されるまで分解を続けます。

まず、Remix オンライン エディターに戻り、ランタイム バイトコードを使用してデバッグ セッションを開始しましょう。どうやってやりますか前回は、スマート コントラクトをデプロイし、デプロイメント トランザクションをデバッグしました。今回は、関数の 1 つを使用して、デプロイされたスマート コントラクトとインターフェイスし、トランザクションをデバッグします。

まず、スマート コントラクトを思い出してみましょう。

最適化コンパイラ、v0.4.24 バージョン、および初期供給として 10000 を使用して Javascript VM を有効にしました。スマート コントラクトをデプロイすると、Remix の「実行」パネルの「デプロイされたコントラクト」セクションにスマート コントラクトがリストされるはずです。クリックして展開すると、スマート コントラクトのインターフェイスが表示されます。

このインターフェースは何ですか?これは、スマートコントラクト内のすべてのパブリックまたは外部メソッドのリストです-つまり、どのイーサリアム アカウントまたはスマート コントラクトもそれと対話できます。プライベート メソッドと内部メソッドはここでは示されません。スマート コントラクト ランタイム コードの特定の部分と対話する方法が、この記事の分解の焦点になります。

Remix の「実行」パネルにある totalSupply ボタンをクリックして、それを試すことができます。ボタンのすぐ下に応答が表示されるはずです。これは、最初のトークン供給としてスマート コントラクトをデプロイしたため、予想どおりです。ここで、[コンソール] パネルで [デバッグ] ボタンをクリックして、この特定のトランザクションでデバッグ セッションを開始します。コンソール パネルには複数の [デバッグ] ボタンがあることに注意してください。最新バージョンを使用していることを確認してください。

この場合、前回の投稿で見たように、スマート コントラクトが作成されたため、この 0x0 アドレスへのトランザクションはデバッグしませんでした。代わりに、スマート コントラクト自体、つまりそのランタイム コードに対するトランザクションをデバッグしています。

[命令] パネルがポップアップした場合は、Remix が分解グラフの BasicToken.evm (ランタイム) セクションと同じ命令をリストしていることを確認できるはずです。一致しない場合は、何か問題が発生しています。最初からやり直して、上記の正しい設定を使用していることを確認してください。

おそらく最初に気づくのは、デバッガーが命令 246 に移動し、トランザクション スライダーがバイトコードの約 60% にあることです。なぜ? Remix は非常に寛大なプログラムであるため、totalSupply 関数の本体を実行しようとしている EVM の部分に直接アクセスできます。ただし、その前に多くのことが起こったので、それをここで注目しておきます。実際、この記事では関数本体の実行については触れません。私たちの唯一の関心事は、Solidity で生成された EVM コードが受信トランザクションをどのようにルーティングするかということです。これは、コントラクトの「関数セレクター」として理解されるジョブです。

スライダーをつかんで左端までドラッグし、命令 0 から開始します。前に見たように、EVM は常に命令 0 からコードを実行し、例外は発生せず、残りのコードを流れます。これを実行するためのオペコードを見てみましょう。オペコードを実行します。

最初に現れる構造は、これまでに見たことのあるものです (実際にはたくさん見ることになります)。

図 1. 空きメモリ ポインタ

これは、Solidity で生成された EVM コードが何かを呼び出す前に常に行うことです。後で使用できるようにメモリ内にポイントを保存します。

次に何が起こるか見てみましょう:

図 2. 呼び出しデータの長さのチェック。

Remix の [デバッグ] タブで [スタック] パネルを開き、手順 5 ~ 7 をスキップすると、スタックに数字が 2 つ含まれていることがわかります。これらの非常に長い数字を読むのが難しい場合は、数字が 1 行に収まるように Remix デバッグ パネルの幅を調整するように注意してください。 1 つ目は通常のプッシュからのものですが、2 つ目は、イエローペーパーにあるように、引数をとらず、「現在の環境の入力データ」のサイズ、またはよく使用されるサイズを返すオペコードを実行した結果です。呼び出し呼び出しデータ: 4CALLDATASIZE

コールデータとは何ですか? Solidity のドキュメント ABI 仕様で説明されているように、calldata は、呼び出したいスマート コントラクト関数とそのパラメーターまたはデータに関する情報を含む 16 進数のエンコードされたブロックです。簡単に言うと、これは「関数 ID」で構成されます。これは、関数の署名 (最初の 4 バイトに切り捨てられる) をハッシュし、パラメーター データを圧縮することによって生成されます。必要に応じて、ドキュメントのリンクを詳しく調べることもできますが、このラッパーがどのように動作するかについては細部に至るまで心配する必要はありません。ドキュメントで説明されていますが、理解するのが少し難しい場合があります。実際の例を使うとより理解しやすくなります。

この calldata が何であるかを見てみましょう。 Remix のデバッガーで [Call Data] パネルを開いて、0x18160ddd を確認します。これは、keccak256 関数シグネチャにアルゴリズムを文字列として適用することによって生成される、正確に 4 バイトです。"totalSupply()"そして、上記の切り捨てを実行します。この特定の関数はパラメータを取らないので、それは 4 バイトの関数 ID だけです。 CALLDATASIZE が呼び出されると、2 番目の 4 がスタックにプッシュされるだけです。

次に、命令 8 LT を使用して、calldata サイズが 4 未満であることを確認します。 「はい」の場合、次の 2 つの命令は JUMPI 命令 86 (0x0056) を実行します。これは 4 バイト未満であるため、この場合はジャンプはなく、実行フローは命令 13 に進みます。しかし、その前に、空の calldata (つまり 0x0) でスマート コントラクトを呼び出すと仮定しましょう。 0x18160ddd の代わりに。ちなみに、Remix ではそれを行うことはできませんが、トランザクションを手動で構築することはできます。

この場合、最終的に命令 86 になります。これは基本的にいくつかのゼロをスタックにプッシュし、それらを REVERT オペコードに供給します。なぜ?そうですね、このスマート コントラクトにはフォールバック機能がないからです。バイトコードが受信データを認識しない場合、フローはフォールバック関数に迂回され、その構造が呼び出しを「キャッチ」しない場合、この回復構造はロールバックをまったく行わずに実行を終了します。返すものが何もない場合は、何もすることがなく、通話は完全に復元されます。

では、もっと面白いことをやってみましょう。 Remix の [実行] タブに戻り、アカウント アドレスをコピーし、それを引数として使用して totalSupply の代わりに BalanceOf を呼び出し、トランザクションをデバッグします。これはまったく新しいデバッグ セッションです。totalSupply のことは少し忘れてください。命令 8 に移動すると、CALLDATASIZE が 36 (0x24) をスタックにプッシュします。 calldata を見ると、0x70a0823100000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c になっています。

この新しい calldata は実際には非常に簡単に逆アセンブルできます。最初の 4 バイト 70a08231 は署名のハッシュで、その後に次のバイトが続きます。"balanceOf(address)"32 バイトには、引数として渡すアドレスが含まれます。イーサリアムのアドレスの長さが 20 バイトしかないのに、なぜ 32 バイトなのかと好奇心旺盛な読者は疑問に思うかもしれません。 ABI は、関数呼び出しで使用される引数を保持するために常に 32 バイトの「ワード」または「スロット」を使用します。

引き続き、balanceOf 呼び出し環境で、命令 13 で中断したところから始めましょう。この時点ではスタックには何もありません。次に、命令 13 は 0xffffffff をスタックにプッシュし、次の命令は 29 バイト長の 0x000000001000...000 数値をスタックにプッシュします。その理由はすぐにわかります。ここでは、一方には 4 バイトが含まれており、もう一方には 4 バイトの 0 が含まれていることに注意してください。

次の CALLDATALOAD は引数 (命令 48 でスタックにプッシュされたもの) を受け取り、その位置にある calldata から 32 バイトのチャンクを読み取ります。この場合、Yul では次のようになります。

calldataload(0)

基本的に、呼び出しデータ全体をスタックにプッシュします。ここからが楽しい部分です。 DIV はスタックから 2 つのパラメータを使用し、calldata を取得して奇妙な数値 0x000000001000...000 で除算します。これにより、calldata 内の関数シグネチャ以外のすべてが効果的にフィルタリングされ、スタック上に残ります: 0x000...000070a08231。次の命令は AND を使用しますが、これもスタック上の 2 つの要素 (関数 ID と 4 バイトの数値 f) を消費します。これは、署名ハッシュの長さが正確に 8 バイトであることを確認し、他に存在する場合にはそれをマスクするためです。 Solidity が採用しているセキュリティ対策だと思います。

つまり、calldata が短すぎるかどうかをチェックし、短すぎる場合は元に戻し、関数をスタック上に置くように少し改善します。

さらに、もうすぐ終わります。次の部分は理解しやすいでしょう。

図 3. 機能セレクター

命令 53 で、コードは 18160ddd (関数 ID totalSuppy) をスタックにプッシュし、次に DUP2 を使用して、現在スタックの 2 番目の位置にある着信 calldata 値 70a08231 を複製します。なぜコピーするのでしょうか? EQ 命令 59 のオペコードはスタックから 2 つの値を消費するため、せっかく calldata から抽出した 70a08231 値を保持したいからです。

コードは、calldata 内の関数 ID を既知の関数 ID の 1 つと照合しようとします。 70a08231 が入ってくるので、18160ddd と一致せず、命令 63 で JUMPI をスキップします。しかし、次のチェックで一致し、命令 74 で JUMPI にジャンプします。

これらの等価性チェックがスマート コントラクトの各パブリック機能または外部機能に対してどのように実行されるかを観察してみましょう。これは関数セレクターの中核であり、単にコードの正しい部分に実行をルーティングする一種の switch ステートメントとして機能します。ここが私たちの「ハブ」です。

したがって、最後のケースは一致するため、実行フローは位置 130 の JUMPDEST に到達します。これは、このシリーズの次のパートで説明するように、balanceOf 関数の ABI "ラッパー" です。後で説明するように、このラッパーは、関数本体で使用するためにトランザクションのデータをラップ解除する役割を果たします。

引き続き、このデバッグ機能の転送を試みます。機能セレクターにはまったく謎はありません。これは、すべてのコントラクト (少なくとも Solidity からコンパイルされたすべてのコントラクト) の入り口に位置し、実行をコード内の適切な場所にリダイレクトする、シンプルですが効果的な構造です。このようにして、Solidity はスマート コントラクトのバイトコードに複数のエントリ ポイント、つまりインターフェイスをシミュレートする機能を提供します。

分解図を見ると、先ほど分解したものが次のようになります。

図 4. 関数セレクターとスマート コントラクトのランタイム コードのメイン エントリ ポイント。

全体として、みなさん、あなたは気づけば、ほとんどの人よりも堅牢性の基礎となるコードに精通しており、それに固執すれば、それを完全に開くことができるようになります。


*この記事は、Alejandro Santander によって媒体上で最初に公開され、Cheetah Blockchain によって翻訳および整理されました*

Cheetah ブロックチェーン セキュリティは、Kingsoft Internet Security のテクノロジーに基づいており、人工知能、NLP、その他のテクノロジーと組み合わせて、ブロックチェーン ユーザーに契約監査やセンチメント分析などのエコロジカル セキュリティ サービスを提供します。

レーティングトークン公式サイトhttps://www.ratingtoken.net/?from=z

猎豹区块链安全
作者文库