スマートコントラクトの自動監査のガイド。 パート3:ミスリル

警告



この記事は、自動分析装置の有効性の評価ではありません。 それらを自分の契約に適用し、意図的にエラーを合成し、反応を研究します。 このような調査は、「より悪い」を判断するための基礎となることはできません。このため、この種のソフトウェアの気まぐれな性質を考えると、実施が非常に難しい契約の大規模なサンプルで盲検調査を実施することは理にかなっています。 契約の小さなエラーがアナライザーロジックの大部分を無効にする可能性が非常に高く、単純なヒューリスティックサインが、競合他社が単に追加することができなかった広範なバグを見つけることによって、アナライザーに膨大な量のポイントを追加する可能性があります。 契約の準備と編集の誤りも重要な役割を果たします。 問題のソフトウェアはすべて非常に新しく、絶えず開発されているため、修復不可能な問題として批判的なコメントを受け取らないでください。







この記事の目的は、さまざまなアナライザーでのコード分析の方法がどのように機能するか、および「選択する」のではなく、それらを正しく使用する能力を読者に理解させることです。 合理的な選択は、分析された契約に最も適したものに焦点を合わせて、一度に複数のツールを使用することです。







起動のセットアップと準備



ミスリルは一度にいくつかのタイプの分析を使用しますが、これに関するいくつかの優れた記事があります。 最も重要なのはthisまたはthisです。 続行する前に、それらを読むことは理にかなっています。







まず、Mythrilの独自のDockerイメージを作成しましょう(何を変更したいかは関係ありませんか?):







git clone https://github.com/ConsenSys/mythril-classic.git cd mythril-classic docker build -t myth .
      
      





今、私たちのcontracts/flattened.sol



(はじめに説明したものと同じ契約を使用しています)で実行してみてくださいOwnable



Booking



2つの主要な契約があります。 コンパイラーのバージョンにはまだ問題があります。以前の記事と同じ方法で修正し、Dockerfileにコンパイラーのバージョンを置き換える行を追加しました。







 COPY --from=ethereum/solc:0.4.20 /usr/bin/solc /usr/bin
      
      





イメージを再構築した後、契約分析を実行してみることができます。 すぐに-v4



および--verbose-report



フラグを使用して、すべての警告を確認しましょう。 行こう:







 docker run -v $(pwd):/tmp \ -w /tmp myth:latest \ -v4 \ --verbose-report \ -x contracts/flattened.sol
      
      





ここでは、依存関係のないフラット化されたコントラクトを使用します。 別のBooking.sol



コントラクトを分析し、Mythrilがすべての依存関係をキャッチするようにするには、次のようなものを使用できます。







 docker run -v $(pwd):/tmp \ -w /tmp myth:latest \ --solc-args="--allow-paths /tmp/node_modules/zeppelin-solidity/ zeppelin-solidity=/tmp/node_modules/zeppelin-solidity" \ -v4 \ --verbose-report \ -x contracts/Booking.sol
      
      





私は、フラット化されたオプションを使用することを好みます。 コードを大幅に変更します。 しかし、 --truffle



は非常に便利なモード--truffle



、これはtruffle



すべてを単純に--truffle



truffle



、プロジェクト全体の脆弱性をチェックします。 もう1つの重要な機能は、コロンを介して分析する契約の名前を指定する機能です。それ以外の場合、Mythrilは遭遇するすべての契約を分析します。 Ownable



のOwnableは安全な契約であると考えており、 Booking



のみを分析するため、実行する最終行は次のとおりです。







 docker run -v $(pwd):/tmp -w /tmp myth:latest -x contracts/flattened.sol:Booking -v4 --verbose-report
      
      





契約の開始と展開



上記の行でアナライザーを起動し、出力を確認すると、とりわけ次の行が表示されます。







 mythril.laser.ethereum.svm [WARNING]: No contract was created during the execution of contract creation Increase the resources for creation execution (--max-depth or --create-timeout) The analysis was completed successfully. No issues were detected.
      
      





コントラクトは作成されておらず、エミュレータで「修正」されていないことがわかりました。 そのため、すべてのタイプの分析に-v4



フラグを使用して、すべてのメッセージを表示し、1つの重要なメッセージを見逃さないようにすることをお勧めします。 何が悪いのかを考えてみましょう。 この実用的な問題の解決策は、Mythrilを正しく使用する方法を理解するために非常に重要です。







だから、 It uses concolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities



について読んでいる: It uses concolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities



。 これらの用語にあまり精通していない場合は、 ここでコンコリックテストに関するwikiをお勧めしますが、 ここでは x86の汚染チェックに関する優れたプレゼンテーションを紹介します。 要するに、Mythrilはコントラクトの実行をエミュレートし、実行が進むブランチを修正し、コントラクトの「壊れた」状態を達成しようとし、パラメーターのさまざまな組み合わせをソートし、すべての可能なパスを回避しようとします。 上記の記事のサンプルアクション図を次に示します。







 1.      .   symbolic-,        . 2.      ,     ,   trace .    ,      ,    . 3.     . 4.       trace-. 5.  symbolic execution   trace,   symbolic ,    ,     ,     . 6.     ,          .    , . 7.   :   ,   ,   input-,     ,      .   input-   ,   .6    . 8.   .4
      
      





これを大幅に簡素化するために、コード内のブランチに遭遇したMythrilは、どの変数セットの下で1つのブランチと他のブランチに入ることができるかを理解できます。 各ブランチでは、 selfdestruct



assert



transfer



selfdestruct



およびその他のセキュリティ関連のオペコードにつながるかどうかを知っています。 したがって、Mythrilは、どのパラメータとトランザクションのセットがセキュリティ侵害につながる可能性があるかを分析します。 そして、Mythrilが制御を得られないブランチを切断し、制御フローを分析する方法が主なトリックです。 ミスリルの内臓とブランチウォーキングの詳細については、 こちらをご覧ください







スマートコントラクトの実行の決定性により、同じ命令シーケンスは、プラットフォーム、アーキテクチャ、または環境に関係なく、常に厳密に1セットの状態変化につながります。 また、スマートコントラクトの機能は非常に短く、リソースは非常に限られているため、シンボリック実行とネイティブ実行を組み合わせたMythrilのようなアナライザーは、スマートコントラクトに対して非常に効率的に動作できます。







その過程で、Mythrilは「状態」の概念で動作します。これは、契約のコード、その環境、現在のコマンドへのポインタ、契約の保存、スタックの状態です。 ドキュメントは次のとおりです。







 The machine state μ is defined as the tuple (g, pc, m, i, s) which are the gas available, the program counter pc ∈ P256, the memory contents, the active number of words in memory (counting continuously from position 0), and the stack contents. The memory contents μm are a series of zeroes of size 256.
      
      





状態間の遷移グラフは、研究の主な目的です。 分析を正常に起動した場合、このグラフに関する情報が分析ログに表示されます。 また、 --graph



は、 --graph



オプションを使用して、人間が読める形式でこのグラフを--graph



できます。







これで、Mythrilが何をするのかを多かれ少なかれ理解し、契約が解析されない理由と[WARNING]: No contract was created during the execution of contract creation



由来を理解し続けます。 [WARNING]: No contract was created during the execution of contract creation



。 始めるために、 --create-timeout



および--max-depth



(推奨)をひねり、結果が得られないので、コンストラクターのせいだと思いました-その中の何かが機能しませんでした。 彼のコードは次のとおりです。







 function Booking( string _description, string _fileUrl, bytes32 _fileHash, uint256 _price, uint256 _cancellationFee, uint256 _rentDateStart, uint256 _rentDateEnd, uint256 _noCancelPeriod, uint256 _acceptObjectPeriod ) public payable { require(_price > 0); require(_price > _cancellationFee); require(_rentDateStart > getCurrentTime()); require(_rentDateEnd > _rentDateStart); require(_rentDateStart+_acceptObjectPeriod < _rentDateEnd); require(_rentDateStart > _noCancelPeriod); m_description = _description; m_fileUrl = _fileUrl; m_fileHash = _fileHash; m_price = _price; m_cancellationFee = _cancellationFee; m_rentDateStart = _rentDateStart; m_rentDateEnd = _rentDateEnd; m_noCancelPeriod = _noCancelPeriod; m_acceptObjectPeriod = _acceptObjectPeriod; }
      
      





ミスリルの行動のアルゴリズムを思い出してください。 トレースを実行するには、コントラクトのコンストラクターを呼び出す必要があります。これは、以降の実行はすべて、コンストラクターが呼び出されたパラメーターに依存するためです。 たとえば、 _price == 0



でコンストラクターを呼び出すと、コンストラクターはrequire(_price > 0)



例外をスローします。 _price



が多くの_price



値を反復処理しても、たとえば_price <= _cancellationFee



場合、コンストラクターは引き続き破損します。 この契約では、厳しい制限に関連付けられた多数のパラメーターがあり、もちろん、Mythrilはパラメーターの有効な組み合わせを推測できません。 彼は、コンストラクターのパラメーターをソートして、実行の次のブランチに移動しようとしますが、実際には推測する機会がありません-パラメーターの組み合わせが多すぎます。 したがって、契約の計算はうまくいきません-すべての方法は何らかの種類のrequire(...)



に基づいており、上記の問題が発生します。







2つの方法があります。1つ目は、コンストラクターですべてのrequire



をコメント化して無効にすることです。 その後、Mythrilは任意のパラメータセットでコンストラクターを呼び出すことができ、すべてが機能します。 しかし、これは、そのようなパラメーターを使用してコントラクトを検査することにより、Mythrilは、コンストラクターに渡される誤った値で発生する可能性のあるエラーを見つけることを意味します。 簡単に言えば、契約作成者が_cancellationFee



のレンタル価格の10億倍に指定した場合に発生するバグを_cancellationFee



が発見した場合、そのようなバグでは使用できません-そのような契約はブロック_mprice



、エラーを見つけるためのリソースが消費されます。 私たちは、契約がまだ多かれ少なかれ一貫したパラメーターでスタックしていることを暗示しているので、さらなる分析のために、より現実的なコンストラクターパラメーターを指定して、Mythrilが契約が適切に閉じられた場合に決して発生しないエラーを探しないようにします。







コンストラクターのさまざまな部分を含めて、無効にすることで、展開がどこで中断するかを正確に理解しようと、何時間も費やしました。 トラブルに加えて、コンストラクターはgetCurrentTime()



使用して現在の時刻を返しますが、この呼び出しがMythrilをどのように処理するかは不明です。 ここではこれらの冒険については説明しません。 ほとんどの場合、定期的に使用すると、これらの微妙な点が監査人に知られるようになります。 したがって、2番目の方法を選択しました:入力データを制限し、コンストラクターからすべてのパラメーターを削除し、 getCurrentTime()



でさえ、必要なパラメーターをコンストラクターに直接ハードコーディングしました(理想的には、これらのパラメーターは顧客から取得する必要があります):







  function Booking( ) public payable { m_description = "My very long booking text about hotel and beautiful sea view!"; m_fileUrl = "https://ether-airbnb.bam/some-url/"; m_fileHash = 0x1628f3170cc16d40aad2e8fa1ab084f542fcb12e75ce1add62891dd75ba1ffd7; m_price = 1000000000000000000; // 1 ETH m_cancellationFee = 100000000000000000; // 0.1 ETH m_rentDateStart = 1550664800 + 3600 * 24; // current time + 1 day m_rentDateEnd = 1550664800 + 3600 * 24 * 4; // current time + 4 days m_acceptObjectPeriod = 3600 * 8; // 8 hours m_noCancelPeriod = 3600 * 24; // 1 day require(m_price > 0); require(m_price > m_cancellationFee); require(m_rentDateStart > 1550664800); require(m_rentDateEnd > m_rentDateStart); require((m_rentDateStart + m_acceptObjectPeriod) < m_rentDateEnd); require(m_rentDateStart > m_noCancelPeriod); }
      
      





さらに、すべてを開始するには、 max-depth



パラメーターも設定する必要があります。 AWSインスタンスt2.mediumで--max-depth=34



--max-depth=34



して、このコンストラクターで--max-depth=34



ました。 同時に、より強力なラップトップでは、 max-depth



なしですべてが始まります。 このパラメーターの使用から判断すると、分析のためにブランチを構築する必要があり、そのデフォルト値は無限( code )です。 したがって、このパラメーターをツイスト回転させますが、目的の契約が分析されるようにします。 次のようなメッセージでこれを理解できます。







 mythril.laser.ethereum.svm [INFO]: 248 nodes, 247 edges, 2510 total states mythril.laser.ethereum.svm [INFO]: Achieved 59.86% coverage for code: .............
      
      





最初の行は分析されるグラフを説明するだけで、残りの行を自分で読んでください。 実行可能なさまざまなブランチを分析するには、深刻な計算リソースが必要です。そのため、大規模な契約を分析する場合は、高速のコンピューターでも待たなければなりません。







エラーを検索する



ここでエラーを探し、独自のエラーを追加します。 ミスリルは、放送、自己破壊、主張、およびセキュリティの観点から重要なその他のアクションが行われるブランチを探します。 上記の指示のいずれかが契約コードのどこかにある場合、Mythrilはこのブランチに到達する方法を調べ、さらにこのブランチにつながるトランザクションのシーケンスを表示します!







最初に、Mythrilが長年のBooking



契約のために発行したものを見てみましょう。 最初の警告:







 ==== Dependence on predictable environment variable ==== SWC ID: 116 Severity: Low Contract: Booking Function name: fallback PC address: 566 Estimated Gas Usage: 17908 - 61696 Sending of Ether depends on a predictable variable. The contract sends Ether depending on the values of the following variables: - block.timestamp Note that the values of variables like coinbase, gaslimit, block number and timestamp are predictable and/or can be manipulated by a malicious miner. Don't use them for random number generation or to make critical decisions. -------------------- In file: contracts/flattened.sol:142 msg.sender.transfer(msg.value-m_price)
      
      





そしてそれは起こる







 require(m_rentDateStart > getCurrentTime());
      
      





フォールバック関数で。







getCurrentTime()



隠れていることに気づいたことに注意してください。 契約の意味は間違いではないという事実にもかかわらず、 block.timestamp



をブロードキャストに関連付けるという事実は素晴らしいです! この場合、プログラマーは、マイナーが制御できる値に基づいて決定が下されることを理解する必要があります。 また、将来、サービスのオークションまたは別のオークションが契約のこの場所で発生した場合、フロントランニング攻撃の可能性を考慮する必要があります。







次のようにネストされた呼び出しで変数を非表示にした場合、 block.timestamp



への依存関係をblock.timestamp



かどうかを見てみましょう。







 function getCurrentTime() public view returns (uint256) { - return now; + return getCurrentTimeInner(); } + function getCurrentTimeInner() internal returns (uint256) { + return now; + }
      
      





そしてはい! ミスリルは引き続きblock.timestampとブロードキャストの転送との関係を確認しています。これは監査人にとって非常に重要です。 攻撃者によって制御される変数と、契約の状態のいくつかの変更後に行われた決定との関係は、ロジックによって非常に隠されている可能性があり、Mythrilではそれを追跡できます。 すべての可能な変数間のすべての可能な接続がgetCurrentTime()



されるという事実に依存する価値はありませんが、 getCurrentTime()



関数でgetCurrentTime()



を続け、三重のネストの深さを作り続けると、警告は消えます。 Mythrilの各関数呼び出しには、新しい状態ブランチを作成する必要があるため、非常に深いレベルのネストを分析するには、膨大なリソースが必要になります。







もちろん、分析パラメーターを誤って使用したり、アナライザーの深さのどこかでカットオフが発生したりするかなり深刻な可能性があります。 私が言ったように、この製品は執筆時点で積極的に開発中であり、リポジトリにmax-depth



言及があるコミットがあるので、現在の問題を真剣に受け取らないでください.Mythrilが暗黙的な接続を非常に効果的に探すことができるという十分な証拠をすでに発見しています変数。







最初に、クライアントにブロードキャストをコントラクトに送信した後にのみ、誰にでもブロードキャストを提供する関数をコントラクトに追加します。 契約がState.PAID



状態にある場合のみ(つまり、クライアントがレンタル番号を航空で支払った後にのみ)、誰でも航空の1/5を受け取ることを許可しました。 関数は次のとおりです。







 function collectTaxes() external onlyState(State.PAID) { msg.sender.transfer(address(this).balance / 5); }
      
      





ミスリルは問題を発見しました:







 ==== Unprotected Ether Withdrawal ==== SWC ID: 105 Severity: High Contract: Booking Function name: collectTaxes() PC address: 2492 Estimated Gas Usage: 2135 - 2746 Anyone can withdraw ETH from the contract account. Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent a equivalent amount of ETH to it. This is likely to be a vulnerability. -------------------- In file: contracts/flattened.sol:149 msg.sender.transfer(address(this).balance / 5) -------------------- -------------------- Transaction Sequence: { "2": { "calldata": "0x", "call_value": "0xde0b6b3a7640000", "caller": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }, "3": { "calldata": "0x01b613a5", "call_value": "0x0", "caller": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" } }
      
      





素晴らしい、つまり ミスリルは2つのトランザクションを公開しました。これにより、契約からエーテルを取得することができます。 次のように、 State.PAID



要件をState.RENT



変更します。







 - function collectTaxes() external onlyState(State.PAID){ + function collectTaxes() external onlyState(State.RENT) {
      
      





これで、 collectTaxes()



は、契約がState.RENT



状態にある場合にのみ呼び出すことができ、現時点では残高に何もありません。 契約はすでにブロードキャスト全体を所有者に送信しています。 ここで重要なことは、Mythrilは今回はエラー==== Unprotected Ether Withdrawal ====



出力しないことです! onlyState(State.RENT)



条件下では、アナライザーは、残高がゼロでない契約からエーテルを送信するコードブランチに到達しませんでした。 State.RENT



はパラメータのさまざまなオプションを検討しましたが、すべてのブロードキャストを貸し手に送信することでのみState.RENT



できます。 したがって、バランスがゼロでないコードのこの分岐に到達することは不可能であり、Mythrilは絶対に監査人を悩ませません!







同様に、 selfdestruct



を見つけてassert



、どのアクションが契約の破壊または重要な機能の故障につながる可能性があるかを監査人に示します。 これらの例は提供せず、上の例のような関数を作成して、 selfdestruct



呼び出すselfdestruct



で、そのロジックをひねりましょう。







また、Mythrilの一部がシンボリック実行であり、このアプローチ自体が実行をエミュレートせずに多くの脆弱性を判断できることを忘れないでください。 たとえば、オペランドの1つが攻撃者によって何らかの形で制御されている場合、「+」、「-」、およびその他の算術演算子を使用すると、「整数オーバーフロー」の脆弱性と見なされます。 しかし、繰り返しますが、Mythrilの最も強力な機能は、シンボリック実行とネイティブ実行の組み合わせと、論理分岐につながるパラメータ値の定義です。







おわりに



もちろん、Mythrilが検出できる潜在的な問題の全範囲を示すには、複数の記事が必要ですが、いくつかの記事が必要です。 他のすべてに、彼は実際のブロックチェーンですべてを行う方法を知っており、署名によって必要な契約と脆弱性を見つけ、美しいコールグラフを構築し、レポートをフォーマットします。 また、Mythrilでは、独自のテストスクリプトを記述して、Pythonベースのインターフェイスをコントラクトに提供し、個々の機能をテストしたり、パラメータ値を修正したり、任意の柔軟性で逆アセンブルされたコードを操作する独自の戦略を実装することもできます。







Mythrilはまだかなり若いソフトウェアであり、これはIDA Proではなく、いくつかの記事を除いてほとんどドキュメントがありません。 多くのパラメーターの値は、 cli.pyで始まるMythrilコードでのみ読み取ることができます。 各パラメーターの操作に関する完全かつ詳細な説明がドキュメントに記載されることを願っています。







さらに、コントラクトが多かれ少なかれ、大量のエラーの出力は多くのスペースを占有しますが、見つかったエラーに関する圧縮された情報を受信できるようにしたいと思います。 Mythrilを使用する場合は、分析トレースを必ず確認し、可能な限りテストされた契約を確認し、監査人が偽陽性と見なす特定のエラーを強制的に無効にすることができます。







しかし、一般的に、Mythrilはスマートコントラクトを分析するための優れた非常に強力なツールであり、現時点では監査人の武器になるはずです。 これにより、少なくともコードの重要な部分に注意を払い、変数間の隠れた関係を検出できます。







要約すると、Mythrilの使用に関する推奨事項は次のとおりです。







  1. 調査中の契約の開始条件をできるだけ絞ります。 分析中に、Mythrilが実際には実装されないブランチに多くのリソースを費やすと、本当に重要なバグを見つけることができなくなるため、潜在的なブランチの領域を常に絞り込んでください。
  2. mythril.laser.ethereum.svm [WARNING]: No contract was created during the execution of contract creation Increase the resources for creation execution (--max-depth or --create-timeout)



    などのメッセージを見逃さないように、契約分析が開始されていることを確認してくださいmythril.laser.ethereum.svm [WARNING]: No contract was created during the execution of contract creation Increase the resources for creation execution (--max-depth or --create-timeout)



    、それ以外の場合は、バグがないと誤って考慮する可能性があります。
  3. 契約コードでブランチを任意に無効化して、Mythrilがブランチを選択してリソースを節約する際の変動を少なくすることができます。 分析を「切り落とさない」ようにmax-depth



    制限を設けないでくださいが、エラーを隠さないように注意してください。
  4. 各警告に注意してください。軽いコメントであっても、少なくとも契約コードにコメントを追加して、他の開発者が簡単にできるようにする価値がある場合があります。


次の記事では、Manticoreアナライザーを取り上げますが、ここに、執筆の準備ができている、または執筆予定の記事の目次を示します。







パート1. はじめに。 Solidityのコンパイル、フラット化、バージョン

パート2. スリザー

パート3.ミスリル(この記事)

パート4. Manticore(執筆中)

パート5.エキドナ(執筆中)








All Articles