どのようにして5,000ドルのGoogleマップXSSを発見したか

数ヵ月前、私はGoogleマップを、もっと正確に言うとGoogleストリートビューを利用しました。Googleストリートビューは子供の頃に思い描いた未来的なテレポートみたいで、とても気に入っています。私は、普段そうするように、その時もアドレスバーを見ました。2014年のいつ頃からか、パラメータは単なるクエリの文字列ではなくなり、その代わりに感嘆符で区切られた英数字の奇妙な寄せ集めになったようです。

難解で、現在のところ公開されたドキュメンテーションもなく、多くの人々に毎日使用され、リバースエンジニアリングが可能なプロトコル。こういうコードを目の前にすると、私は解読したくてウズウズしてきます。

0-YzYMmvZFvmwq_vJI

私はブラウザのWebコンソールも見てみました。AJAX APIへのリクエストが同じようにエンコードされていただけではなく、もしレスポンスの一部が画像だった場合、その他のレスポンスは暗号を用いたバイナリデータの新たなシグネチャになっていました。深まる謎に比例して、私のやる気も大きくなっていきます。

蓋を開ける

Googleマップは、約3.2MBの縮小化されたJavaScriptです。一方、Google Earthは重く感じられます。つい最近までは、Googleマップは間抜けでグダグダな地図だと思っていたのに、気付いたらEarthモードが搭載されていました。Google Earthは、WebGLを使って3Dグラフィックスを描画し、大きなタイルサイズを持ち、JavaScriptに実装されたキャッシング、スケジューリング、プランニングモジュールを要求して表示します。その他、詳しくはこちらをご覧ください。

このサイズのコードベースだと、組織立てて考えなければなりません。幸運にも、ここ数年の間に全ての(両方の)主要なブラウザには、ブレークポイントを任意の関数で設定したりAJAXのリクエストやDOMイベントが発生した場合に設定したりする目的で、独自のデバッガが搭載されました。ほとんどのリバースエンジニアリングは静的または動的な解決策をもちますが、私はどちらかというと静的な側で、動的な分析を用いるのは極端な場合のみです。細かいことが好きで、アセンブリにハマりがちになってしまいますが、これは後ほど役に立ってきます。

では、コードを見ていきましょう。Googleマップは、大体8つのJavaScriptコードファイルを(一部はXMLHttpRequestを介してワーカで)読み込みます。コードファイルには、モジュールを分割するための改行しかありません。JavaScriptのモジュールは、2文字または3文字のコードでURL内に指定されており、Googleの圧縮・軽量化ツールであるClosure Compilerが、コードを軽量化しています。この軽量化ツールは、関数をインライン化したり、関数のステートメントの半分を条件式に入れたり、単純なカンマで区切ったり、その他多くのことをします。コードを(インデントして)十分に読めるように元に戻すのは、ブラウザのWebコンソール内やhttp://jsbeautifier.org/のようなシンプルなツールで実行可能です。

1-sr5P-BC-z1bWB15I_eIX5A
注釈: Googleマップのソースを読み込むリクエストと、軽量化されたコードの一部

この種の出力を読みなさいと言われれば、一部の人はひるむかもしれませんが、それほど難しくはありません。アセンブリを読む時と同じように、直接ポイントを突けばいいだけです。つまり、文字列と定数をたくさん検索し、参照されている関数から別の関数にジャンプし、txtでメモを取り、折に触れて、見た目からその関数がどのように機能するのかを推測したりします。

例として、感嘆符の文字列の検索をした場合はどうでしょうか。

0-Gbvn1KjMvShpPgnG
注釈: 明らかに、謎のURLエンコーディングを行う一部の関数のコード

見てみると、URLのパラメータの文字列(以下のスクリーンショットで再び図示)が、無限に繰り返されていることが分かります。

  • 感嘆符:セパレータ
  • 数字:キー/値のペアの整数キーのように見える
  • 単一の文字:後続を決定する型。”s”は文字列、”i”は整数、およびその他…
  • 数字あるいは文字列:キー/値のペアの値

0-CXFIgVKUTRVsGlIj
注釈: AJAXの各種エンドポイントへのリクエストを示すChromeのWebコンソールのスクリーンショット

これは新しいシリアライゼーションの形式でしょうか。いや、実際は違いました。シリアライゼーションについての自分の記憶を整理すると、どうやらProtobufのようです。

ProtobufはGoogleで(保存や通信に)使われている形式です。2001年にバージョン1が開発されました。2008年にはXMLの問題に対応するバージョン2が公開され、2016年には更に改良が施されたバージョン3が発表されています。今や、 “Googleの新しいAPIプラットフォーム” と通信するための主な基盤となるもので、WebにおけるGoogle製品のフロントエンド部分でますます使用されています。またAndroidでもかなり普及しています。

Protobufはテキストではなく、大抵(ネットワーク上で)バイナリデータとして転送されます。

Protobufを使ってプログラムを作成する時は、最初に(ややCのような構文に似た.protoという形式で)キー/値フィールドのペアを定義します。各フィールドは型、名前、数値を持ちます。そして、これらのフィールドをメッセージと呼ばれる名前のブロックに入れます。フィールドは、整数、文字列、または別のメッセージ(ネストされたメッセージと言います)で構成可能です。この他にも違うタイプのものがありますが、ここでは触れません。

1-XZessS-mdIShW6Buyvx7pA
注釈: Protobufのメッセージ定義例(.proto形式)

次に、.protoをコードにコンパイルします。そうです。定義したProtobufメッセージを読み書きすることができる任意の言語(多くのバインディングがあります)のコードです。

1-agknrovsda_RkiBnKCN2fQ
注釈: この例では、 “Person” のProtobufメッセージは、Javaの “Person” クラスに変化

次に、プログラマは(前の手順で生成された呼び出しコードを使用して)Protobufフィールドをそのデータに設定し、それが完了すると、バイナリ形式でシリアライズするようにライブラリに指示します。

1-DSiQlt936t_sCLVIFKnrLw
注釈: プログラマは “Person” クラスを呼び出し、データを設定

結果のデータは次のようにエンコードされます。5ビットのフィールド番号に8ビットタグ、フィールドデータ型に3ビット、そして整数や文字列、バイトやメッセージなどの実際のデータ、または…(12種類の整数型があり、ネットワーク上でそれらをエンコードする方法がその半分ほどあります)。

1-y9ObXPjpL2l1lOqlsoCvjA

つまり、URLのパラメータデータは、テキストを代わりに使ってエンコードされたものと同じでしょうか?

1-cgMYPoOjiUtxDv86CJ0kwA
注釈: URL情報をエンコードするために使用される形式で、ほとんど同じ意味を持つ

結果的に分かったことは、バイナリ形式には5種類のデータ型(0:可変長整数、1:固定64ビット、2:文字列、バイトまたはメッセージ、3と4:基本的にメッセージを定義する方法としては非推奨のグループ-望まないコンテンツをスキップするのに十分な情報、あるいはそれを解釈せずに読む方法を知っている情報)しかない一方で、GoogleマップのURLには、文字を指定する型として18種類の文字があるということです。

// This function, found in Google Maps' code, says which type character should map to what default field value:
dba = function(a) {
    switch (a) {
        case "d":
        case "f":
        case "i":
        case "j":
        case "u":
        case "v":
        case "x":
        case "y":
        case "g":
        case "h":
        case "n":
        case "o":
        case "e":
            return 0; // Those are integers (and enum)...
        case "s":
        case "z":
        case "B":
            return ""; // Those are string/bytes...
        case "b":
            return !1; // This one is boolean...
        default:
            return null // And there's also "m" for messages, referenced in further serialization code
    }
};

これはおおむね、.protoでフィールドを定義する時に使える型の数です。ただ、どれがどれにマッピングされているのか分かりません。また、メッセージの編集や再生をしたり、隠しフィールドを見つけたりできるよう、GoogleマップのURLで使用される全てのフィールドを、理解しやすい.proto定義に再構築することはできるのか、そしてどのアプローチを使うべきなのかについても不明です。

これについては後ほど触れたいと…いや、今ここで考えましょう。Protobufメッセージとそのフィールドを定義する関数呼び出しは、JavaScriptのコードの至る所で、複数の関数の周りに散らばっているように見えます。正規表現の繰り返しでそれらをまとめるのは難しいでしょうし、何より面倒です。

1-_P_LFvybjQWNSSomrrnI3g
注釈: これを解読するのは避けたいですね

では、動的なアプローチについて考えてみましょう。 “!” で区切られたURLデータをシリアライズする関数の前に配置し、構造の情報が来るのを待って、何らかの形でデバッガの使用を自動化できないでしょうか。コードという、シリコンの世界を横断できる魔法の接着剤を理解することで、人間と接するように機械と話すことができないでしょうか。

最初の魔法のスクリプト

幸運にもChromeには、デバッガも作動させるデバッガAPI(デバッガ自体は、WebSocketを介してこのAPIと通信するHTMLとJavaScriptです)があります。それにFirefoxやSafari、Edgeなども同様です。ただし、完全にポータブルなわけではありません。それはともかく、これらの中ではChromeのデバッガAPIが優れていると思うので、ここではChromeのものを使用します。実際の問題として、 “ヘッドレスChrome” が登場すれば、ほとんどのPhantomJSの機能は置き換えられることになるかもしれません。

接続の手始めとして、--remote-debugging-port=<port number>フラグを指定して通常のChromeを起動します。指定したポートに接続するとHTTPサーバが表示されるので、 “/json” エンドポイントへのリクエストを行い、それぞれのデバッガAPIに接続するためにアクティブなタブとWebSocketのURLをリストにします。次に、簡単な方法でソケット上のJSONリクエスト/レスポンス/イベントメッセージを交換します。リクエストは一定のIDを持ち、レスポンスまたはエラーのいずれかにマップされます。そしてイベントは、特定のリクエストを用いて有効化できます。

1-F237kFl7CKSUDzwnXhk_pA
ChromeとChromeデベロッパツール間のWebSocket通信の一部(Chromeデベロッパツールを使用してキャプチャ)

実際にやってみましょう。まず、 “Runtime.enecutionContextCreated” イベントをキャッチするために “Runtime.enable” を呼び出します。これで、ページのJavaScript実行コンテキストが作成された時にイベントが発生します。次に、スクリプトがロードされると “Debugger.scriptParsed” イベントを有効にする”Debugger.enable”が実行されるので、これを見てブレークポイントを入れたい関数を見つけます。そして、JavaScriptのレベルでインターセプトした “!” エンコードデータがどれに含まれているのかを見つけるため、 “Network.enable” にして全てのHTTPリクエストについて通知を受けるようにします。.proto定義とHTTPエンドポイント間の直接リンクを作成し、最後に “Page.navigate” を実行して、ブラウザにGoogleマップまでナビゲートするよう命じます。

1-H5WYUZ5-SjJWMsydYVHL-g
該当箇所のコード:WebSocket作成時に送られるコマンドとイベントループの開始

新しいスクリプトで “Debugger.scriptParsed” イベントがトリガされたら、 “Debugger.pause” (競合状態を回避するため)と “Debugger.getScriptSource” を呼び出します。これで正規表現や文字列のシグネチャを使って関連する関数を見つけることができるようになります。その後
Debugger.setBreakpoint” を呼び出してブレークポイントを設定し、スクリプトID、行および列番号を指定します。

以下は、割り込みたいJavaScriptの関数です。

_.Eqa.prototype.H = function(a, b) {
    var c = Array(Fqa(a, b));
    Gqa(a, b, c, 0);
    return c.join("")
};

これは2つの引数を取ります。1つ目は “b” で、Protobufメッセージの構造を定義するJavaScriptのオブジェクトです。2つ目は “a” で、この構造体でシリアル化される、メッセージデータを定義するもう1つのよりシンプルなオブジェクトです。そして、 “!” で区切られた生のデータ文字列、 “c” を返します。

“Fqa” は、出力配列のサイズを計算します(4 * フィールドの数。各フィールドは結合される4つの部分列、セパレータ、フィールド番号、文字型、フィールド値で構成されています)。 “Gqa” が書き込み、String.prototype.joinが文字列に変換します。

“b” を確認して判読可能な.protoにし、後ほど “c” を覚えておくようにすれば、どのリクエストで送信されたかを確認することができます。このことはつまり、最後の行にブレークポイントを置くことを意味します。デバッガから見た引数のデータは次の通りです。

1-09D6r40xmNoTrfphx_mosw
左から順に、 “a” 、 “b” 、 “c” ローカル変数のサンプルデータ

ある程度の推定後、 “b” の構造は以下のようになります。


ここでは判読しやすいバージョンを示しましたが、他の場合もテキスト名とコメントの代わりにランダムな文字を使えば同じように計算できます。ほとんどのオブジェクトの属性はミニファイされる(ミニファイツールの実行に応じて変化する)ので、どの属性がどの情報に対応しているかを知るには、コード上で正規表現を使用しなければなりません。

このデータの意味を理解したら、次に実際の.protoを生成します。デバッガAPIを使用して変数とオブジェクトの属性に直接アクセスできますが、これだと非常に遅くなるので現実的ではありません。また、JSONに変換することもできますが、ネストされたメッセージはその親を参照できるため機能しないことが多く、結果的に循環参照となる恐れがあります(子オブジェクトから親オブジェクトへの参照)。正しいのは次の解決策です。JavaScriptを挿入して操作させ、任意の媒体(私は故あってコンソールAPIを使用)を介して文字列を返すというものです。

JavaScriptは、ローカルでは “Debugger.evaluateOnCallFrame” の呼び出しで、グローバルでは “Runtime.evaluate” の呼び出しで評価することができます。 “Runtime.consoleAPICalled” イベントを通じてコンソールメッセージが通知されるので、とても便利です。ネットワークリクエスト( “Network.requestWillBeSent” イベント)のキャプチャに加えて、history.replaceState()のAPI関数をフックするために別のJavaScriptも挿入すると、 “!” 区切りの状態データを含むURLをメインページがいつ変更するかを知ることができます。

もう少しコードを記述してChromeのインスタンスを制御するスクリプトを起動すれば、Googleマップとの連携でデータを収集することが可能です。それにより必要な2つの要素(再構成された.proto構造体とシリアライズされたデータを有するURLのサンプル)が得られます。前者は通常の形でディスクに書き込まれ、後者はリクエストのサンプルを含む便利なJSONとして、全てのエンドポイントの配列に置かれます。

1-jXGixADisIDU0tM6ELx5_w
注釈: 再構成された.proto構造体の一部

1-nuJ8B6ixGJShVdzxHTrigg
注釈: キャプチャされたデータサンプルを有するネットワークエンドポイントの一部

いいですね。では、これをどのように使えばいいでしょうか。

2番目の効果的なプログラム

ボタンだらけの大きなコントロールパネルを皆さんが好きかどうかは分かりませんが、可能な時にそれらをランダムに選べるとしたら、それは素晴らしいことだと思いませんか。ここからは、前述のデータセットを利用してそれを構築していきます。Protobufのメッセージはツリービューと呼ばれるGUI要素としてレンダリングされるので、操作の利便性を上げるのはコマンドラインだけでは難しいでしょう。

私たちが見た、構造から推定されたProtobufフィールドには名前がないので、ミニファイされたコードとして1文字の名前を割り当てました。名前を付ける時には、名付け先の実際の特性に応じたものにしたいので、まずは観察します。観察が速くできる便利な方法(例えば、マウスホイールを使用して整数フィールドを変更する)があれば素晴らしいですね。

また、全ての値のテストもします。それにより基本的には、そのフィールドの特性を知ることになるので、例えばリバースエンジニアリングされた構造を他の場所に統合して、全く新しいものを構築できるようになったり、フィールドの一部がセキュリティに関して興味深い振る舞いを引き起こすかどうかを観察できるようになったりもします。

私は、全てにおいてPythonを使うのが好きです。最初のスクリプトはPythonを使用し、2番目のスクリプトはQtでPythonを使いました。ソリューションはシンプルです。QTreeWidgetウィジェットを使ってメッセージをレンダリングすると、(テキストの場合はQLabel、整数の場合はQSpinBoxなどのような子ウィジェットと共に)各フィールドがサブクラス化されたQTreeWidgetItemとなります。繰り返しフィールド(最初にフィールドに記入する際にUI上で複製される)やチェック中あるいはリクエスト中のフィールドの状態などのケースを処理することになるので、コードは最終的に多少ややこしく見えますが、それでも全体的には読みやすく、理解しやすいと思います。

出力ページはQtWebEngineを用いてライブでレンダリングされます。QtWebEngineはQtWebKitに取って代わる便利なQtモジュールであり、Blink(実際のところ、Chromiumから分離したかなりの部分)をアプリケーション内でフレームとして統合することができます。また、応答ハッシュ、サイズ、応答コード、MIMEタイプなどのサイド情報を表示するためのテキスト領域としても統合可能です。

相手がボタンに触れたら、こちらはライブでレスポンスしたいですよね。きっと素晴らしい体験となるはずです。あと数百のソースコードで、それが実装されます。

0-FNQItzUm3sBMRHmU
注釈: Protobufエディタ(2番目の成果)、前の手順でキャプチャしたネットワークデータのスムーズな編集と再生が可能

ゼロデイ

エンドポイントに対してGoogleマップのタイル
https://www.google.com/maps/vt/pb)を要求するリクエストは全部で約730フィールドあり、約125のメッセージに散在しています。これはセキュリティの観点から言えば、いいことです。ここがアタック・サーフェス(攻撃対象範囲)になりますが、到達するのにリバースエンジニアリングが必要な暗黙かつ隠しフィールドで、パブリックに試されていないのはその程度だからです。

私は手動で多くのフィールドをテストし、折に触れ動作に関して文書化してきました。ほとんどが私が試した現状のセットアップとは無関係のようでした。最終的に、リクエストのうち重要な部分を整理してみました。

まず、地図上の座標です。これを表現するには標準の10進数のWGS84や、衛星画像に便利な、より大きく正確な座標の単位などいろいろな方法があります。また、例えばある正確な区域を、与えられたピクセルサイズで表示するマップが欲しい時など、ビューポートを定義する方法は他にもあります。マップを描画するにはあらゆる方法があります。マップのレイヤにも種類が多くあります。マップ上にラベルや位置マーカを表示する、ある位置から他の位置への経路を描画する、サーバ側に描かれた全てのGoogleマップの機能をカバーするオプションです。

しかしここで重要なのは、ビットマップだけではなく、ベクタもレンダリングされるということです。Androidアプリはずいぶん前から、最近のWebや一部のリクエストでもベクタを扱ってきました。ベクタには複数の形式があり、全てがプロプライエタリです。

前者は標準のバイナリ形式(シグネチャは “DRAT” )で、マップのレンダリングデータの保存に便利なバイナリ構造で、いつフィールドが現れるか、意味が変わるか、消えるかを決定するバージョン番号を備えていました。タイル座標から成るRC4キー、固定のキーとノンス(論文の71ページに、期限切れの復号キーと共に短く説明されているのを見つけました)とZLIBによってデータは難読化され、今でもAndroidアプリによって使われています。

1-cf7O1tssQp-ACptSDQi6bQ
注釈: マップをベクタ形式でAndroidクライアントに転送するにはプロプライエタリな
“DRAT” 形式が使われる

もっと新しい、Webアプリに統合されたものはバイナリのProtobufを使います。繰り返しますが、これはWebアプリのProtobufの2番目の形式です。1バイトのXORがRC4の代わりに使われます。かなり効率性が良くなりましたよね。これは先ほど言及した、AJAXを経由して送られたバイナリデータで、単なるLength-valueコンテナ形式(シグネチャは “XHR1” )でカプセル化されています。転送されるデータは古いものとほとんど同じ意味を持っています(暇潰しにSVGにレンダリングする小さなスクリプトを書きました。うまくいったのですが、多くの人にとっては利用価値がありません。ただしC&Dの興味を引くかもしれないので、読者へのエクササイズとして残しておきます)。

12種類以上あるうちの、どの形式のバージョンを選んでも構いません。1つが特に興味深いものでした。どんな風に興味深いかと言うと、 “Content-Type: text/html” のヘッダを備えた出力だったのです。それでいて、プレーンなバイナリデータでした。新しい形式と同様Ptotobufベースでしたが、以前のヘッダを持っていたので、同じRC4+ZLIBの処理方式でした。しかし、多くのオプションがあるということはセキュリティの観点からは興味深いものです。そして、暗号が無効化されたフィールドと、圧縮用のフィールドがありました。それから、位置とマーカのためのraw文字列を含むrawマップデータがあり、ブラウザのtext/htmlとしての役割を果たしていました。

任意の文字列をマップに挿入するいい方法は何でしょうか? マーカを設置すればいいのです。他のリクエストフィールドを編集すればできます。

1-V2xVJeVUpEDXVMwlNJ0AeQ
注釈: リクエスト情報を編集することによって、任意の名前を付けたマーカをマップ上に設置している。このスクリーンショットは、ラスタ形式(PNG)でマップタイルをリクエストしている最中に撮られたもの

私たちが見つけた文書化されていない形式を有効にするようフィールドを設定して試してみると、JavaScriptの警告メッセージが勢いよく出現します。

1-mZk-TMjYr9Hqf-k8bB6Q1A
注釈: 私たちの元々のXSSペイロードはこのようになっていた????

もう少し賢い方法で、HTMLに関連したキャラクタを使わずにどのフィールドがエンコードできるのかを見てみましょう。Chromeなどのブラウザは、HTMLがリクエストとレスポンスの両方に存在している時は、XSSを検知してブロックするフィルタを持っています。NoScriptなどの拡張機能を備えたFirefoxでも同じです。フィルタ回避の知恵の出番です。

例え元々1つのProtobufタイプだったとしても、 “!” で区切られたデータ復号ルーチンが、文字列に対応した2つのデータタイプを受け入れます。 “s” タイプの文字は出力文字列の一部として文字列をraw形式のままエンコードさせてくれます。 “!” と “*” だけは “*21” と “*2A” (対応する16進コード)としてエスケープされます。 “z” のキャラクタタイプではbase64(パディングなしのシンプルなバージョン)として文字列をエンコードすることができます。フィルタベースのXSS防御は無意味になります。

1-Qi9zDPIf7GMiDoJj6fqygg
注釈: HTML文字列がbase64にエンコードされたので、ペイロードは変わらない

サーバがオンザフライで “!” で区切られたデータをrawバイナリのProtobufへと変換するので、更に難読化されます。ほとんどの型はチェックなしでキャストされます。文字列とメッセージは同じ5つのデータ型をバイナリ形式で共有しているということを覚えておいてください。ネストされたメッセージを単純にバイナリに変換し、文字列として渡すことで、内部の処理を人目にさらすことなく、かつクールに見せることができます。

1-50kmKgdtHTWeH20hgSFOBA
注釈: ネストされたメッセージをワイヤ形式に変換した後、base64文字列にキャストすれば、同じペイロードでも判読不可能になる

0-wgUyKuSNmCWip6sb
注釈: これで私たちのXSSペイロードはChrome、Firefox、Safariなど、JavaScriptを受け入れるものなら何とでも問題なく稼働する。ただしこれはhttp://www.google.com/ドメイン上でのことで、以前のhttp://maps.google.com/上では、処理がメインドメインへ戻されてしまう

もっと賢い方法も検討してみましょう。誰でもGoogleマップに新しい位置を投稿できることが分かりましたね。マップ上に(後天的に)控えめに表示されます。<script>alert(1)</script>という位置をマップに送信した人はいるでしょうか? そうですね、中国とインドのハッカーが送信したようです。そのうち1人は恐ろしい顔すらもさらしています。

0-fTckOn6aTs3KIdai
注釈: Googleマップであなたの地域の話題のハッカーを探そう

1-1EO_bjGsF0OJ5n_sO3OHWQ
注釈: ペイロードがマップ上の既存の地点を利用している

さて、このクールなゼロデイを突破するワクワクする方法を4つ紹介しました。もう十分ですね。次へ行きましょう。

ベンダの対応

Googleはほとんどのセキュリティバグ発見に対して標準化された報奨を提供しています。これについては脆弱性に対する報奨プログラムのこのページで説明されています。私たちのケースでは、www.google.comドメイン上での適格なXSSは5000ドルの報奨金をもたらします。

0日目 報告書式に記入
0.54日目 報告がすでにトリアージされている
0.64日目 私の報告が検証され、バグが登録される
1日目(程度) 修正完了
1.84日目

0-Ls2s0023emmUg86Z
注釈:こんにちは。バグを報告してくださってありがとうございます。審査の結果、Googleの脆弱性報奨プログラムの一環として、5000ドルの報奨金を支払う決定をいたしました。
高額の報奨金がもらえればやる気が起きるし、やる気はオープンソースに反映される

プロジェクト

この調査にリンクされたコードはもう少し量があったようです。少し調査してみたところ、Androidプラットフォームの多くのアプリケーション(Googleマップも含む)がProtobufを使っていることを発見しました。そして、これらの構造(いくつかのローカライズされたスクリプトは存在していましたが、特定のアプリや実装バージョンを想定していて、ほとんどがProguardをサポートしていません)を抽出する統一されたツールがなさそうだということにも気付きました。デスクトップアプリでは、モバイルJavaの他にはC++がほとんどでした。

そこでいくつかの機会に書いてきたスクリプトをつなげ、同じGUIにして、いくつか機能を追加したところ、それらしく見えてきました。

ここから始めたプロジェクトの現在の状況はhttps://github.com/marin-m/pbtkで見られるようになっています。上述したコードの主要な部分を下記にリストします。

  • extractors/web_extract.py: これが主要な抽出の.protoスクリプトで、Chromeを起動し、深部に接続し、驚くべき仕事をします。引数としてURLを渡せば、個別でも使えます。
  • views/fuzzer.py: これは小さなGUI編集とリプレイのリクエストです。現在では大きなアプリに統合されています。GUI(gui.py)を起動したり、“Step 3…”をクリックしたりして遊べます。インターフェースに説明があるように、ホームディレクトリのフォルダ保存されている.protosを探しにいきます。
  • それから、utils/pburl_decoder.py.にバンドルされているURLのパラメータをエンコードしたり戻したりするのに必要なコード。以前から含まれています。

これで終わりです。楽しんでいただけましたか? 更なるリバースエンジニアリングの冒険の旅でまたお会いしましょう。