ソフトウェア・インテグリティ

 

Defensics SDKを用いたビットコインのファジング、パート2:ビットコイン・プロトコルのファジング

このシリーズの第2部では、ビットコイン・ネットワーク・プロトコルのデータモデルを作成し、Defensics SDKを用いてbitcoindに対してファジングを実行する方法を説明します。

Defensics SDKを用いたビットコインのファジング

前回の記事では、bitcoindのテストベッドを設定する方法をご紹介しました。そのときは、2つのコンテナ、fleurとviktorを作成し、2つのbitcoindインスタンス間の通信を設定しました。

この記事では、ビットコイン・ネットワーク・プロトコルのデータモデルを作成し、Defensics® SDKを用いてbitcoindに対してファジングを実行する方法を説明します。

ソース:

ビットコイン・メッセージのヘッダーモデル

私はまず、ピアが自分自身をアナウンスするために別のピアに送信する最初のメッセージであるバージョン・メッセージからファジングを始めました。前回の記事のWiresharkのイメージでご紹介したように、ピアがバージョン・メッセージを送信すると、応答としてバージョン・メッセージ(version)とverackが返されます。

ビットコイン・ネットワーク・プロトコルについては、次のWebサイトで詳しく説明されています。
https://bitcoin.org/en/developer-reference#p2p-network

各ビットコイン・メッセージには、次の4つのフィールドで構成される共通ヘッダーがあります。

  • ネットワーク(mainnet、testnet、regtest)を識別するマジックナンバー
  • コマンド文字列
  • ペイロード長
  • ペイロード・チェックサム

ペイロード長とチェックサムは、メッセージ・ペイロードに基づいて計算されます。

次に、Defensics SDK BNF形式でビットコイン・ヘッダーを表す1つの方法を示します。

octet = 0x00-0xff 

 

command-chooser = !corr:target ( # Message name 

    .version-name: ‘version’ 5(octet) 

  | .verack-name: ‘verack’ 6(octet) 

  | .any: 12(octet)  

) 

 

bitcoin-header = ( 

    .magic: 0xfabfb5da # Magic number for regtest 

    .command: command-chooser 

    .size: !length32:target 0x00000000-0xffffffff 

    .checksum: !sha256x2:target 0x00000000-0xffffffff 

) 

ビットコイン・ヘッダーの基本構造はマジックナンバー、コマンド、長さ、チェックサムであることがわかります。また、長さとチェックサムには具体的なルールがあります。

ルールはJavaコードで設定しますが、ここで指定している内容(!length32:targetなど)は、length32という名前のルールがあり、結果がヘッダーのサイズフィールドに格納されることを示しています。

ヘッダー内のコマンド文字列を後で対応するメッセージ・ペイロードと照合するために相関ルールを使用します。ここでは、command-chooserを使用して、使用可能なメッセージ・コマンドの1つを選択します。versionとverackが指定されていますが、ビットコイン・プロトコルのより詳細なモデルでは、指定する項目を簡単に増やすことができます。

相関ルールを使用して、次に示すように、メッセージ・コマンドごとに1つずつ、数多いペイロード選択肢のうち1つを選択します。

bitcoin-payload = !corr:source ( 

    .version-payload: version-payload 

  | .verack-payload: verack-payload 

  | .any-payload: any-payload 

) 

(未定義のルールを使用して)これを設定すると、完全なビットコイン・メッセージの一般的な定義が設定されます。

bitcoin-message = @corr @length32 @sha256x2 ( 

  .header: bitcoin-header 

  .payload: !length32:source !sha256x2:source bitcoin-payload 

) 

@corr @length32 @sha256x2の指定は、3つの名前付きルールを使用していることを示し、そのソースとターゲットの指定は、ルールが適用される場所を示します。

後で、ルールの定義方法を見てみましょう。

ビットコインのバージョン・メッセージ・モデル

バージョン・メッセージ・ペイロードの定義も単純ですが、ここでは各フィールドについての詳細には触れません。たとえば、各フィールドに対してより具体的なモデルを作成し、より具体的な変則を適用すれば、ターゲットの徹底したテストに役立ちます。

version-payload = ( 

  .version: (0x7f110100 | 4(octet)) 

  .services: (0x0904000000000000 | 8(octet)) 

  .timestamp: (0x6ff27d5f00000000 | 8(octet)) 

  .addr-recvservices: (0x0100000000000000 | 8(octet)) 

  .addr-recvipaddress: (0x00000000000000000000000000000000 | 16(octet)) 

  .addr-recvport: 0x0000 – 0xffff 

  .addr-transservices: (0x0904000000000000 | 8(octet)) 

  .addr-transipaddress: (0x00000000000000000000000000000000 | 16(octet)) 

  .addr-transport: 0x0000 – 0xffff 

  .nonce: (0xcf7990b352cb105e | 8(octet)) 

  .user-agentbytes: (0x10 | 0x00-0xff) 

  .user-agent: (‘/Satoshi:0.20.1/’ | 0..255(octet)) 

  .start-height: (0x65000000 | 4(octet)) 

  .relay: (0x01 | octet) 

)

Defensics SDKにモデルを取り込む

BNFで規定された定義は、Defensics SDKに簡単に取り込むことができます。たとえば、すべてのBNFをresources/model.bnfファイルに定義するとします。テスト・スイートでは、これらの定義の取り込みは簡単です。

public void build(BuilderTools tools) throws Exception { 

    ElementFactory factory = tools.factory(); 

 

    // ルールの設定… 

 

    factory.readTypes(tools.resources().getPathToResource(“model.bnf”));

定義が取り込まれると、ヘッダーでコマンド名を選択することで、特定のビットコイン・メッセージを組み立てることができます。関連するペイロードの選択は相関ルールで行います。たとえば、バージョン・メッセージを作成してメッセージ・シーケンスで使用できるようにする方法を次に示します。

MessageElement version = tools.factory().getType(“bitcoin-message”); 

    version.find().mandatory(“version-name”).element().select(); 

     

    tools.messages().message(“version”, version).finish(); 

課題に対処する

今のところ、これらのモデリングはいずれも注目に値するテストケースになっていません。特に、ペイロードサイズ・フィールドとペイロードチェックサム・フィールドが正確ではありません。これはおそらく、これらのフィールドがbitcoindで検査される最初のフィールドであることが原因で、該当のテストケースはすぐに破棄されます。

信頼に足るテストケースで最大限に適確なテストを行うには、サイズフィールドとチェックサムフィールドが正確である必要があります。

Defensics SDKでは、特定のフィールドが特定の方法で動作する必要があるケースにはルールが使用されます。

相関と長さは最も単純なルールで、Defensics SDKに組み込まれているルールで実現できます。定義は、BNF定義がreadTypes()で読み込まれる前に行われます。

    RuleFactory rf = tools.rule(); 

    rf.correlate(“corr”); 

    rf.length(“length32”).format(“int-lsb-32bit”); 

最後の行では、length32という名前の長さルールを作成し、結果を32ビット整数としてフォーマットし、最下位ビットを先頭にします。

チェックサムは組み込みルールで対処できないため、もう少し難しくなります。ビットコイン・プロトコルのチェックサムは、ペイロードのSHA256ダイジェスト値のSHA256ダイジェスト値の最下位4バイトです。これはタイプミスではありません。まず、ペイロードのSHA256ダイジェスト値を計算します。その後、そのダイジェスト値のSHA256を計算します。次に、結果の最下位4バイトを受け取り、チェックサムに使用します。

以下のようにDefensics SDKでカスタムルールを定義しました。

package com.example.sdk; 

 

import java.security.*; 

import java.util.Arrays; 

 

import com.synopsys.defensics.api.message.*; 

import com.synopsys.defensics.api.message.rule.CustomChecksum; 

 

public class SHA256x2 implements CustomChecksum { 

  @Override 

  public byte[] calculate(SDKEngine engine, byte[] data) { 

    MessageDigest mDigest; 

    try { 

      mDigest = MessageDigest.getInstance(“SHA-256”); 

    } catch (NoSuchAlgorithmException e) { 

      throw new IllegalStateException(e); 

    } 

    byte[] shaTheFirst = mDigest.digest(data); 

    byte[] shaTheSecond = mDigest.digest(shaTheFirst); 

    return Arrays.copyOfRange(shaTheSecond, 0, 4); 

  } 

} 

このルールを組み込むには、カスタムルールをインスタンス化する必要があります。

    rf.checksum(“sha256x2”, new SHA256x2());

この場合も、sha256x2ルールの定義はBNFが読み込まれる前に行う必要があります。

ここでは実際にジェネレーショナル・ファジングの値を利用しています。ペイロードの一部が認識できないほど変則的な場合でも、データモデルで定義したルールによって、ヘッダーサイズとチェックサムのフィールドが正しく設定されます。bitcoindターゲットに配信されたテストケースは精査され、サイズとチェックサムの検証が完了した後、さらに解析コードに渡されます。

全体を合わせる

この記事では、ソースコード全体ではなく、特に重要な部分のみを紹介しました。ソースコード全体が揃ったら、Defensicsでテスト・スイートを読み込み、他のDefensics テスト・スイートと同様に使用することができます。

Defensicsがテストベッドの仮想マシンと同じネットワーク上にある場合、ターゲットのIPアドレスとポート番号をDefensicsに通知するだけです。ポート18444を使用すると、fleurコンテナにマッピングされます。

デフォルトでは、Defensicsは暗黙的なTCPインストルメンテーションを使用します。つまり、TCPポートを開き続けることができる限り、ターゲットは正常であると仮定します。bitcoindを停止すれば、Defensicsはポートを開くことができなくなり、エラーフラグが設定されます。

テスト中に詳細情報が必要になった場合は、次のコマンドを使用してbitcoindのデバッグログへの出力を確認します。ファジングテストと同様に、通常はメッセージの組み合わせが表示されます。

bitcoindは受け取ったテストケースを通知する場合もあれば、苦情を申し立てる場合もあります。ログの監視は、テストケースがターゲットによって受信され、処理されていることを確認するのに適した方法です。

root@fleur:~# tail -f ~/.bitcoin/regtest/debug.log 

2020-11-10T14:34:34Z connection from 172.17.0.1:56228 accepted 

2020-11-10T14:34:34Z received: version (87 bytes) peer=2088 

2020-11-10T14:34:34Z ProcessMessages(version, 87 bytes): Exception ‘CDataStream::read(): end of data: iostream error’ (NSt8ios_base7failureB5cxx11E) caught 

2020-11-10T14:34:34Z ProcessMessages(version, 87 bytes) FAILED peer=2088 

2020-11-10T14:34:34Z socket closed for peer=2088 

2020-11-10T14:34:34Z disconnecting peer=2088 

2020-11-10T14:34:34Z Cleared nodestate for peer=2088 

 

最大限に効果的なテストを行うには、-whitelistオプションを使用してbitcoindに組み込まれている保護を無効にする必要があります。

今後の指針

Defensics SDKを利用したビットコイン・プロトコルのファジングを楽しんでいただければ幸いです。Defensics SDKがあらゆる種類のソフトウェアに対してジェネレーショナル・ファジングの威力を発揮するしくみをご紹介しました。

bitcoindの個別のケースでは、このテストをさらに次のように発展させることができます。

  • データモデルを改善する。たとえば、バージョン・メッセージ内のフィールドは無作為に指定されますが、プロトコル仕様を使用すれば、より「実態」に近付けることができます。
  • ビットコイン・プロトコルで他のメッセージをモデル化し、テストする。
  • 故障監視を改善する。たとえば、ASANを使用してbitcoindターゲットを実行し、メモリー・エラーを捕捉するようにすると、DefensicsのAgent Instrumentation Frameworkとうまく連携します。

謝辞

本稿を見直し、コードを見事に改善してくれたDefensics R&DチームのAleksis KauppinenとJanne Ruotsalainenに感謝します。

ビットコインのファジングの詳細に興味がある方は

ソフトウェア・インテグリティ・コミュニティにご参加ください

 

この著者によるその他の情報