Proxyman vs mitmproxy: Protobuf WebSocket メッセージのデコード

Proxyman vs mitmproxy: Protobuf WebSocket メッセージのデコード のカバー画像

WebSocket の接続をキャプチャできたものの、メッセージがランダムなバイナリデータに見えることがあります。アプリが有用な情報を受信しているはずなのに、デバッグツールでは値を読み取れません。

メッセージは Protocol Buffers(一般に Protobuf)を使用している場合があります。Protobuf は高速で小さいサイズを実現できますが、JSON のように人が読めるように設計されてはいません。

mitmproxy と Proxyman はどちらも、Protobuf の WebSocket メッセージのデコードに役立ちます。mitmproxy には Protobuf のコンテンツビューがあり、.proto 定義ファイルを指定できます。Proxyman には、視覚的なメッセージリスト、手軽に使える Raw Mode、macOS アプリ内でのディスクリプタを使ったデコード機能があります。

このガイドでは、次の内容を学びます:

  • WebSocket と Protobuf とは何か
  • Protobuf のメッセージが読めなく見える理由
  • 現在の mitmproxy による解決方法の仕組み
  • スキーマの有無にかかわらず、Proxyman でメッセージをデコードする方法
  • よくあるデコードの問題を解決する方法

WebSocket とは?

通常の HTTP のやり取りは、次のような単純な流れに従うことがよくあります:

  1. アプリがリクエストを送信する。
  2. サーバーがレスポンスを返す。
  3. やり取りが終了する。

WebSocket は 1 本の接続を開いたままにします。アプリとサーバーは、いつでもお互いにメッセージを送信できます。

WebSocket が役立つ場面は次のとおりです:

  • チャットメッセージ
  • ライブ価格
  • ゲームのイベント
  • 配達や配車の状況更新
  • 通知
  • 共同編集
  • ライブダッシュボード

WebSocket をデバッグする際は、通常まず接続を確認し、送受信された各メッセージを順番に読みたくなるものです。

Protobuf とは?

Protobuf は、Google が作成したバイナリのデータ形式です。JSON などの一般的なテキスト形式より少ないバイト数で、構造化データを格納できます。

JSON のメッセージは、次のような形になることがあります:

{
  "userId": 123,
  "name": "Taylor",
  "online": true
}

同じデータを Protobuf でエンコードすると、バイナリになります。テキストとして開くと、四角い記号や意味のわからない記号、長いバイト列が表示されることがあります。

これはメッセージが壊れているわけではありません。デバッグツールが Protobuf のルールに従ってデコードする必要があるだけです。

なぜ Protobuf は読みにくいのか?

Protobuf のメッセージは、フィールド番号と値を格納します。わかりやすいフィールド名は、通常、別のスキーマに定義されています。

例えば、スキーマが次のように定義されているとします:

message UserStatus {
  int64 user_id = 1;
  string name = 2;
  bool online = 3;
}

バイナリメッセージにはフィールド 1、2、3 が含まれていても、user_id、name、online という名前をメッセージごとに繰り返す必要はありません。

この設計によりメッセージは小さくなります。一方で、デバッグ上の問題も生じます。正しいスキーマがなければ、ツールはフィールド番号と、考えられる値の型を表示することはできても、元のフィールド名を確実に知ることはできません。

mitmproxy による現在の解決方法

mitmproxy は WebSocket トラフィックに対応しています。また、バイナリデータを整形して表示できる Protobuf のコンテンツビューも含まれています。

名前付きのフィールドを表示するには、mitmproxy の protobuf_definitions オプションを使用します。このオプションに .proto ファイルのパスを設定します:

mitmweb --set protobuf_definitions=/path/to/messages.proto

トラフィックをキャプチャしたら、WebSocket のフローを見つけ、メッセージを開きます。自動で選択されていない場合は、Protobuf のコンテンツビューを選択してください。

これは mitmproxy に標準で搭載されている機能です。通常の Protobuf の表示のために、カスタムの Python アドオンを書く必要はありません。

mitmproxy は、次のような場合に適しています:

  • すでにトラフィックの確認に mitmproxy を使っている
  • ターミナルのオプションに慣れている
  • .proto ファイルを開発プロジェクトで管理している
  • ワークフローをより多く自動化したい、またはスクリプト化したい
  • 無料でオープンソースのプロキシが必要

mitmproxy のワークフローで難しい点は?

初心者にとっての主な課題は、各要素をつなげることです。

1. WebSocket メッセージはスキーマと別々に存在する

バイト列をキャプチャするのは最初のステップにすぎません。わかりやすいフィールド名を表示するには、正しい .proto 定義も必要です。

大規模なプロジェクトには、多数の .proto ファイル、インポート、パッケージ、メッセージ型が含まれることがあります。古いバージョンのアプリがまだ古い形式でメッセージを送信している間に、スキーマが変わることもあります。

2. 設定にはオプションとコンテンツビューを使う

オプション名を把握し、正しいファイルパスを指定し、WebSocket メッセージを見つけ、適切なコンテンツビューを選択する必要があります。

一度覚えてしまえば効率的です。ただし、ネットワークデバッグに慣れていない開発者は、今見ているメッセージから操作を始められる、目に見えるメニューの方を好むかもしれません。

3. 1 件のメッセージを探すのに時間がかかる

混み合った WebSocket では、数百から数千件のメッセージが送信されることがあります。必要なデータは、ハートビートやステータス更新、その他のイベントの種類に混ざっています。

送信と受信のメッセージを時系列で明確に表示できると、アプリでの 1 回の操作と、それに対応する 1 件のバイナリメッセージを結びつけやすくなります。

4. スキーマが手元にない場合がある

アプリのデバッグは許可されていても、.proto のソースファイルが手元にないことがあります。その場合でも、フィールド番号や値といった基本的な情報を安全に確認する方法が必要です。

Raw デコードでは元のフィールド名を復元できませんが、ペイロードがおそらく Protobuf かどうかを判断でき、必要な値を見つけるのに役立ちます。

Proxyman による視覚的な解決方法

Proxyman の macOS 版 6.5.0 以降では、WebSocket メッセージの Protobuf ペイロードをデコードできます。

役立つモードは 2 種類あります:

  • Raw Mode は、ディスクリプタファイルなしで動作します。フィールド番号と推測された値を表示します。
  • Message Type は、インポートした .desc ディスクリプタファイルを使用します。スキーマで定義されたフィールド名と型を表示できます。

このため、キャプチャしたメッセージから始めて、手元にあるスキーマ情報に応じて方法を選べます。

ステップ 1: WebSocket 接続をキャプチャする

Proxyman for macOS をダウンロードしてセットアップします。セキュアな wss:// 接続を復号する必要がある場合は、証明書をインストールして信頼してください。

Google Chrome で開かれた WebSocket をキャプチャするには、Proxyman を起動してから Web サイトを開きます。Proxyman は、Chrome から送られる通常の本番環境の WebSocket トラフィックを、自動でキャプチャできます。

メッセージを送信する操作を行います。たとえば、チャットメッセージを 1 件送信したり、ライブ設定を 1 つ変更したりします。テストを小さく保つと、該当するフレームを見つけやすくなります。

Google Chrome からキャプチャした Proxyman の WebSocket メッセージ
Google Chrome からキャプチャした Proxyman の WebSocket メッセージ

WebSocket のフローを選択します。WebSocket タブでは、クライアントが送信したメッセージと、サーバーから受信したメッセージを確認できます。

テストが許可されている WebSocket トラフィックのみを確認してください。メッセージには、アカウントの詳細、セッショントークン、個人データ、非公開のビジネス情報が含まれている場合があります。

ステップ 2: スキーマなしで Raw Mode を試す

Raw Mode は、Protobuf ディスクリプタがない場合にメッセージを確認する、最も手軽な方法です。

  1. WebSocket メッセージを選択します。
  2. メッセージを右クリックします。
  3. Protobuf → Decode を選択します。
  4. Raw Mode を選択します。
Proxyman で WebSocket メッセージに Protobuf の Raw Mode を選択する
Proxyman で WebSocket メッセージに Protobuf の Raw Mode を選択する

Proxyman はバイナリペイロードを読み取り、推測できるフィールドを表示します。

Proxyman で WebSocket メッセージからデコードされた Raw Protobuf フィールド
Proxyman で WebSocket メッセージからデコードされた Raw Protobuf フィールド

Raw Mode では、次の内容を確認できます:

  • フィールド番号
  • 数値
  • テキストの値
  • 真偽値のような値
  • ネストされたバイナリの値

Raw Mode では、フィールドが元々 user_id や message_text という名前だったことはわかりません。これらの名前を知るには、スキーマが必要です。

ステップ 3: フィールド名を表示するためのディスクリプタを準備する

Proxyman は、.desc 拡張子の Protobuf File Descriptor を使用します。ディスクリプタには、デコーダーが必要とするコンパイル済みのスキーマ情報が含まれています。

最も簡単な方法は、バックエンドチームかアプリチームに、最新のディスクリプタファイルを依頼することです。

.proto ソースファイルがある場合は、protoc を使って 1 つのディスクリプタを作成できます。この例では、インポートされている定義も含めています:

protoc \
  --descriptor_set_out=output.desc \
  --include_imports \
  -I=/path/to/proto/files \
  /path/to/proto/files/*.proto

macOS に protoc がインストールされていない場合は、まず Protobuf のコマンドラインツールをインストールしてください:

brew install protobuf

ディスクリプタは、安全な開発用フォルダに保管してください。スキーマには内部のメッセージ名やデータ構造が含まれている可能性があるため、チームで許可されていない限り公開しないでください。

ステップ 4: ディスクリプタを Proxyman にインポートする

次を開きます:

Tools → Protobuf Schema

+ ボタンをクリックし、.desc ファイルを選択します。Proxyman は、利用可能なパッケージ名とメッセージ型を読み込みます。

WebSocket メッセージから始めて、Protobuf → Decode → Message Type を選択することもできます。期待する型が見つからない場合は、インポートボタンを使用します。

Proxyman で WebSocket ペイロードの Protobuf メッセージ型を選択する
Proxyman で WebSocket ペイロードの Protobuf メッセージ型を選択する

ステップ 5: 正しいメッセージタイプを選択する

ペイロードで使われているルートのメッセージタイプを選択します。スキーマでパッケージ名が定義されている場合は、そのパッケージ名も含めてください。

例えば:

chat.events.NewMessage

正しい型を選択すると、Proxyman はフィールド名付きでペイロードをデコードします。

Proxyman でフィールド名付きでデコードされた Protobuf WebSocket メッセージ
Proxyman でフィールド名付きでデコードされた Protobuf WebSocket メッセージ

これで、バイナリの値とアプリの動作を結びつけられるようになります。Raw バイトの中に隠れていた ID、タイムスタンプ、enum 値、ネストされたメッセージ、テキストフィールドなどが確認できます。

Protobuf の WebSocket メッセージにおける Proxyman と mitmproxy の比較

作業mitmproxyProxyman
WebSocket トラフィックのキャプチャ対応対応
組み込みの Protobuf ビュー対応対応
スキーマなしで確認Protobuf コンテンツビューで raw フィールドを表示可能メッセージメニューの Raw Mode
スキーマの形式.proto ファイルコンパイル済みの .desc ディスクリプタ
設定スタイルオプションとコンテンツビューによるワークフロービジュアルメニューとスキーママネージャー
メッセージの確認コンソールまたは mitmweb インターフェースネイティブの送受信メッセージリスト
最適な用途ターミナル、オープンソース、自動化されたワークフローmacOS でのインタラクティブなデバッグ

どちらを選ぶかは、一方が Protobuf に対応していて、もう一方が対応していないという問題ではありません。両方とも対応しています。違いはワークフローにあります。

ターミナルや Web インターフェースでの作業に慣れている開発者にとって、mitmproxy は強力な選択肢です。キャプチャしたフレームをクリックして Raw Mode を試し、そのままメッセージのタイムラインを離れずにディスクリプタを追加したい場合は、Proxyman が役立ちます。

よくある問題と解決方法

接続は見えるのに、メッセージが読めない

まず、WebSocket のホストで SSL Proxying が有効になっているか確認してください。既存のセキュアな接続はまだ暗号化されたままの場合があるため、有効にした後は再接続してください。

次に、ペイロードが本当に Protobuf を使用しているか確認してください。バイナリの WebSocket データは、圧縮や暗号化されている場合や、MessagePack などの別の形式を使用している場合もあります。

Raw Mode で数値は表示されるが、フィールド名が表示されない

これは想定どおりの動作です。Protobuf では、元のフィールド名はバイナリメッセージごとに含まれていません。最新の .desc ファイルをインポートし、正しい Message Type を選択してください。

デコードされた値がおかしい

ルートのメッセージタイプが間違っている可能性があります。その特定のイベントに使われている型を試してください。クライアントからサーバーへのメッセージと、サーバーからクライアントへのメッセージでは、異なる型が使われる場合があります。

また、ペイロードには Protobuf のバイトの前に独自のラッパーが含まれている場合もあります。最初のバイトがメッセージ長、イベントタイプ、圧縮のいずれを表しているかについては、アプリチームに確認してください。

一部のフィールドが表示されない

ディスクリプタがアプリやサーバーより古い可能性があります。古いスキーマを削除し、現在の .proto ファイルから新しい .desc ファイルを生成して、再度インポートしてください。

また、Protobuf はデフォルト値を持つフィールドを送信しないことも覚えておいてください。オプションのフィールドが省略されているのは、正しい動作の場合があります。

iOS アプリから WebSocket をキャプチャできない

一部の iOS の WebSocket API は、通常の HTTP プロキシ設定に従いません。アプリで URLSessionWebSocketTask を使用している場合は、Proxyman の SOCKS Proxy のガイドに従うか、自分で開発しているアプリであれば、デバッグ専用の Atlantis フレームワークを使用してください。

メッセージリストが多すぎる

セッションをクリアして、1 つの操作を繰り返してください。操作を行った時刻をメモし、その前後に送受信されたメッセージを確認します。小さなテストの方が、ライブトラフィックを数分間記録するよりずっと確認しやすくなります。

まとめ

WebSocket はリアルタイムでメッセージのやり取りを続け、Protobuf はそのメッセージを小さく保ちます。両者を組み合わせるとアプリにとって非常に有用ですが、キャプチャしたバイナリデータは理解しにくいことがあります。

mitmproxy は、.proto 定義を使ってコンテンツビューで Protobuf をデコードできます。ターミナルのオプションや自動化されたワークフローを好む開発者にとって、強力なツールです。

Proxyman を使えば、macOS の開発者はライブの WebSocket メッセージからデコードされた値まで、視覚的に確認できます。スキーマがない場合は Raw Mode から始めてください。完全なフィールド名が必要な場合は、.desc ディスクリプタをインポートし、正しい Message Type を選択してください。

次のステップ

Noah Tran
Noah Tran

バイナリの WebSocket メッセージを読みやすく

Proxyman で WebSocket をキャプチャし、Raw Mode でメッセージをデコードするか、Protobuf ディスクリプタをインポートしてフィールド名を表示できます。

Proxyman for macOS をダウンロード