Shape では、不完全な JavaScript に多く遭遇します。 ページに悪意を持って挿入されたスクリプトが見つかることがあります。これは、顧客からアドバイスを求めて送信されたものである可能性があります。また、セキュリティ チームが、当社のサービスの一部の側面を具体的に参照していると思われる Web 上のリソースを見つけることもあります。 私たちは日々の業務の一環として、まずスクリプトを詳しく調べて、スクリプトが何を実行し、どのように動作するかを理解します。 これらは通常、縮小され、難読化されており、詳細な分析を行う準備ができるまでには、常に複数レベルの変更が必要になります。
最近まで、この分析を行う最も簡単な方法は、手動編集を可能にするローカルにキャッシュされた設定を使用するか、プロキシを使用してコンテンツをオンザフライで書き換えることでした。 ローカル ソリューションは最も便利ですが、Web サイトは必ずしも他の環境に完全に変換されるとは限らず、生産性を上げるためにトラブルシューティングの迷路に陥ってしまうことがよくあります。 プロキシは非常に柔軟性がありますが、通常は扱いにくく、移植性もあまりありません。誰もが自分の環境に合わせて独自のカスタム設定を行っており、あるプロキシよりも別のプロキシに慣れている人もいます。 私は、リクエストとレスポンスの発生時にそれをフックし、その場で変更するために、Chrome とその devtools プロトコルを使い始めました。 これは Chrome を搭載したあらゆるプラットフォームに移植可能で、さまざまな問題を回避し、一般的な JavaScript ツールと適切に統合されます。 この記事では、 Chrome の devtools プロトコルを使用して JavaScript をオンザフライでインターセプトして変更する方法について説明します。
ここでは node を使用しますが、開発ツール フックに簡単にアクセスできる場合は、コンテンツの多くは選択した言語に移植可能です。
まず、Chrome のスクリプトを一度も試したことがない場合は、Eric Bidelman が書いた優れたHeadless Chrome のスタートガイドを参照してください。 ここで紹介するヒントは、ヘッドレス Chrome と GUI Chrome の両方に適用されます (ただし、1 つの奇妙な点については次のセクションで説明します)。
これを簡単にするために、npm のchrome-launcherライブラリを使用します。
chrome-launcher はまさにあなたが期待する通りの動作をし、ターミナルから使い慣れたのと同じコマンド ライン スイッチを変更せずに渡すことができます (すばらしいリストがここに保持されています)。 以下のオプションを渡します:
–ウィンドウサイズ=1200,800
–auto-open-devtools-for-tabs
–user-data-dir=/tmp/chrome-testing
スクリプトを実行して、Chrome を開けるかどうかを確認してください。 次のような画面が表示されます。
これは「Chrome デバッガー プロトコル」とも呼ばれ、Google のドキュメントでは両方の用語が同じ意味で使用されているようです。 まず、npm 経由でchrome-remote-interfaceパッケージをインストールします。これにより、devtools プロトコルと対話するための便利なメソッドが提供されます。 さらに深く掘り下げたい場合は、プロトコルのドキュメントを手元に用意しておいてください。
CDP を使用するには、デバッガー ポートに接続する必要があります。chrome -launcherライブラリを使用しているため、 chrome.port経由で簡単にアクセスできます。
最初にプロトコル内の多くのドメインを有効にする必要があり、コンソール API に接続してブラウザー内のすべてのコンソール呼び出しをコマンド ラインに配信できるように、ランタイムドメインから開始します。
これで、スクリプトを実行すると、完全に機能する Chrome ウィンドウが表示され、そのすべてのコンソール メッセージがターミナルに出力されます。 それ自体が素晴らしいですが、特にテスト目的の場合、素晴らしいです。
まず、 RequestPatternsのリストをsetRequestInterceptionに送信して、インターセプトする対象を登録する必要があります。 「リクエスト」ステージまたは「HeadersReceived」ステージのいずれかでインターセプトすることができ、実際に応答を変更するには、「HeadersReceived」を待つ必要があります。 リソース タイプは、開発ツールのネットワーク ペインでよく表示されるタイプにマップされます。
上記のRuntimeの場合と同じように、同じ配列にNetwork.enable()を追加して、 Networkドメインを有効にすることを忘れないでください。
イベント ハンドラーの登録は比較的簡単で、インターセプトされた各リクエストには、リクエストに関する情報を照会したり、最終的に継続を発行したりするために使用できるinterceptionIdが付属しています。 ここでは、インターセプトしたすべてのリクエストをターミナルに記録するだけです。
リクエストを変更するには、base64 文字列をエンコードおよびデコードするヘルパー ライブラリをインストールする必要があります。 利用できるライブラリは多数ありますので、自由に選択してください。 atobとbtoa を使用します。
応答を処理するための API は少し扱いにくいです。 レスポンスを処理するには、リクエストのインターセプトにすべてのレスポンス ロジックを含める必要があります (たとえば、レスポンスを単にインターセプトするのではなく)。その後、インターセプト ID で本文を照会する必要があります。これは、ハンドラーが呼び出された時点で本文が利用できない可能性があるためです。これにより、探しているものだけを明示的に待機できます。 本文は base64 でエンコードされている場合もあるので、盲目的に渡す前に確認してデコードする必要があります。
この時点では、JavaScript を自由に使用できます。 コードにより、応答の途中に配置され、要求された完全な JavaScript にアクセスし、変更した応答を返すことができます。 素晴らしい! 変更したコードがブラウザで実行されたときにターミナルにメッセージが表示されるように、JS の最後にconsole.logを追加して調整します。
コンテンツが元のリソースとともに送信されたヘッダーと競合する可能性があるため、変更された本文だけを単純に渡すことはできません。 積極的にテストと調整を行っているため、伝える必要のある他のヘッダー情報についてあまり心配する前に、基本から始めることをお勧めします。 必要に応じて、イベント ハンドラーに渡されるresponseHeadersを介して応答ヘッダーにアクセスできますが、ここでは、後で簡単に操作および編集できるように、配列内に独自の最小限のセットを作成します。
新しい応答を送信するには、完全な base64 エンコードされた HTTP 応答 (HTTP ステータス行を含む) を作成し、 continueInterceptedRequestに渡されるオブジェクトのrawResponseプロパティを介して送信する必要があります。
ここで、スクリプトを実行してインターネットを閲覧すると、スクリプトが JavaScript をインターセプトし、変更した JavaScript がブラウザーで実行され、チュートリアルの冒頭で作成したフックを通じてconsole.log()がバブルアップするため、ターミナルに次のようなものが表示されます。
基本的な例の完全な動作コードはここにあります:
ソース コードをきれいに印刷することから始めることができます。これは、リバース エンジニアリングを開始するときに常に便利な方法です。 はい、もちろん、ほとんどの最新ブラウザでこれを行うことができますが、ブラウザ間およびブラウザ バージョン間で一貫性を保ち、ソースを分析する際に点と点をつなげることができるように、変更の各ステップを自分で制御する必要があります。 外国の難読化されたコードを詳しく調べているとき、変数や関数の目的を理解し始めたら、それらの名前を変更するのが好きです。 JavaScript を安全に修正するのは簡単な作業ではなく、これについてはブログ記事で詳しく説明する必要がありますが、現時点では、一般的な縮小化および難読化手法を元に戻すには、 unminifyのようなものを使用できます。
npm 経由でunminify をインストールし、新しい JavaScript 本体をunminifyの呼び出しでラップして、動作を確認できます。
次の投稿では、変換についてさらに詳しく説明します。 ご質問、ご意見、その他の便利なトリックなどがありましたら、 Twitter からご連絡ください。