reversing-obfuscated-javascript について
セキュリティ研究者向けの JavaScript 難読化解除スキルです。変換方式の判別、ソース構造の復元、実行時の重要な処理の特定を、ソースマップ、ツール、AST 処理、ブラウザデバッグを使って説明します。
主な機能
- 01バンドル、難読化、デバッグ対策の判別
- 02ソースマップの確認とソース復元
- 03webcrack などによる展開と難読化解除
- 04Babel AST による変換処理の作成
- 05DevTools で実行時処理を特定
活用シーン
- 01サイトや拡張機能の圧縮 JavaScript 分析
- 02リクエスト署名や暗号処理の復元
- 03クライアント側アルゴリズムやライセンス確認の調査
- 04本番環境で公開されたソースマップの評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill reversing-obfuscated-javascriptインストール後、Claude Code で/reversing-obfuscated-javascriptを入力して読み込まれたか確認します。
詳細
難読化された JavaScript のリバースエンジニアリング
クライアント側の JavaScript はプログラム全体を読み手に渡します。ここにあるのは削除されたバイナリではなく、意図的に読みにくくされたコードだけです。そのため、作業内容が変わります。失われた情報を取り戻すことはほとんどなく、変換を元に戻すのが目的です。まず変換方法を特定し、その変換に対応したツールで元に戻します。既製のツールでは対応できない独自のレイヤーに限り、手書きの AST パスを使用してください。
使用する場面
- 読み解く必要のある、大規模な圧縮またはバンドル済みの
.jsをページや拡張機能が配信している - コードに
_0x1234のような識別子、大きな文字列配列、先頭の回転 IIFE が含まれている(obfuscator.io の特徴です) .js.mapのソースマップにアクセスできる、または webpack が//# sourceMappingURLを残している- 実行中のバンドル内で、リクエスト署名、HMAC、トークン、暗号鍵が計算される箇所を特定する必要がある
- 配信されたコードをもとに、クライアント側のアルゴリズム(ボット対策シグナル、ライセンスチェック、ペイウォールのゲートなど)を再現する
使用しない場面
- React Native / Hermes のモバイルバンドル(
index.android.bundle、main.jsbundle、Hermes のマジックバイト)—reversing-react-native-appsを使用してください。 - 制御フローが単に平坦化されているのではなく、解釈実行されるカスタムバイトコード VM や仮想化 JavaScript 難読化ツール — これは非仮想化の領域です。VM リフティングの手法には
unpacking-protected-binariesを使用してください。 - 侵害されたサイトや npm パッケージ内の悪意ある JavaScriptで、目的が可読性ではなく IOC と動作の把握である場合 — 埋め込まれた Web ペイロードには
hunting-web-backdoors、悪意あるパッケージにはauditing-supply-chainを使用してください。 - Web アプリ自体のテスト(JavaScript が呼び出すエンドポイント、XSS、認証)—
testing-web-applicationsを使用してください。ここに戻るのは、必要なクライアント側アルゴリズムを復元するときだけです。 - ページが読み込む WASM モジュール — これは
analyzing-binariesの領域です。
ツールに触れる前にレイヤーを特定する
誤った逆難読化ツールを実行すると、もっともらしいだけのゴミが生成されます。最初の数百バイトを読み、分類してください。
| 見えるもの | 変換 | 元に戻す方法 |
|---|---|---|
| 短い名前、空白なし、読みやすい文字列 | 圧縮のみ | 圧縮解除ツール、または prettier + 名前の変更 |
webpackChunk、モジュールマップ {123: function(e,t,n){…}} | Webpack/Rollup によるバンドル | モジュールごとのファイルに分割 |
//# sourceMappingURL=… またはアクセス可能な .map | なし — ソースがそのまま存在します | ソースマップから復元 |
_0x 形式の 16 進数名+大きな文字列配列 1 つ+回転 IIFE | obfuscator.io の文字列配列 | webcrack / synchrony / restringer |
入れ子の三項演算子、while(true){switch(_0x..)} ディスパッチャー | 制御フローの平坦化 | switch を再接続する AST パス |
setInterval 内の debugger、自己防御関数 | デバッグ対策 / 自己防御 | 他の処理より先にガードを取り除く |
実際のバンドルの多くは複数のレイヤーで構成されています。外側に webpack があり、一部のモジュールが obfuscator.io で難読化され、肝心の関数に独自の文字列暗号が施されている、といった具合です。最も外側から順に剥がしてください。
ソースマップ:必ず最初に試してください
アクセス可能なソースマップがあれば、作業を始める前に解決できます。元の変換前のソースが含まれているためです。「難読化された」本番用バンドルの多くでソースマップが配信または漏えいしているので、毎回確認してください。
- バンドル末尾の
//# sourceMappingURL=コメント、およびコメントが削除されている場合でも同じディレクトリにある.js.map(<bundle>.mapを試してください)。 - webpack の開発用成果物と、マップ内の
webpack://パスにあるソース。 - ソースマップコンシューマーを使ってファイルを復元します。
unwebpack-sourcemapや短いsource-mapスクリプトでsourcesContentをたどり、ディレクトリツリーに戻せます。
アプリを評価している場合、本番用ソースマップの漏えいは単なる利便性ではなく、それ自体が発見事項となるため、そのように扱ってください。
バンドルの分解と難読化解除
バンドルにはまず webcrack を使います。一度に3つの処理を行うためです。つまり、ミニファイの解除、webpack/browserify の展開によるモジュール単位のファイルへの分割、そして obfuscator.io の文字列配列と制御フローの難読化解除です。実行したら、単一の巨大なコードではなく、生成されたモジュールツリーを確認します。
obfuscator.io だけで難読化されたバンドルには、専用の難読化解除ツールのほうがすっきり処理できることがよくあります。synchrony と restringer はどちらも文字列配列を解決し、ローテーションを元に戻し、デコーダー呼び出しをインライン化して、単純な制御フローを平坦化します。両方の出力を比較してください。異なるエッジケースで失敗します。
カスタムレイヤーをどのツールでも完全に処理できない場合は、Babel で AST パスを作成します。AST にパースし、@babel/traverse でパターン(特定のデコーダー呼び出し、定数畳み込みされた式、平坦化されたディスパッチャーなど)を見つけて、ノードを変換し、コードを再生成します。これは長く役立つスキルです。難読化ツールは変化しても、文字列配列のデコード、定数畳み込み、デッドブランチの除去は、毎回同じ AST 操作だからです。スクリプトを書く前に、実際のコードを AST Explorer で使ってマッチャーを試作してください。
次の2つの失敗モードに注意してください。
- 文字列デコーダーを「評価」するためにコードを実行する難読化解除ツールは、ペイロードを実行する可能性があります。信頼できないバンドルや悪意のあるバンドルでは、文字列の解決は静的に行うか、隔離されたサンドボックス内で行ってください。内容を確認するために、未知のバンドルをホストマシン上で
evalしてはいけません。 - 自己防衛機能/デバッグ保護機能は、改変されると再び難読化したり、
debuggerループを回したりします。まずガード関数を取り除きます(ノードを削除するか、setIntervalをオーバーライドします)。そうしないと、その後のすべての処理が妨げられます。
実行中のバンドルからルーチンを見つける
静的解析に時間がかかる場合(たとえば、数メガバイトのモジュールに埋もれた署名関数を見つける場合)は、Chrome DevTools で実行中のページを操作します。
- コードではなく動作にブレークポイントを設定します。 XHR/fetch のブレークポイントは署名付きリクエストが送信されると停止し、コールスタックをたどると署名関数に直接戻れます。DOM とイベントリスナーのブレークポイントも、UI がきっかけとなるロジックに対して同様に機能します。
- Sources パネルの
{}で整形すると、ミニファイされた関数もオフラインでの作業なしにステップ実行できます。 - コンソールからプロパティアクセスをフックします。 値を計算するメソッドを持つオブジェクトに対して
Object.definePropertyを使うか、Proxyを設定すると、スタックトレースとともにすべての呼び出しと引数が記録されます。数式を読み解かなくても、入力と出力を確認できます。 - ファイルをローカルのコピーで上書きします。 整形し、
console.logを埋め込んだコピー(DevTools Local Overrides)を使って、実行時に実際の値がどう流れるかを確認します。
実行時の観察と静的な解析は相互に補完します。DevTools で重要な関数を特定し、AST を使った作業でその関数が何を計算するかを解明すれば、オフラインで再現できます。
範囲と許可
適法に取得したバンドルを読み取り、難読化を解除することは解析です。ただし、注意が必要な点が2つあります。リクエスト署名ルーチンを再現してオリジンに対して 再送 することは(通常、ボット対策シグナルをリバースする理由です)、そのサービスに対する行為であり、他のテストと同様に許可が必要です。テストを許可されたシステムに限定し、testing-web-applications を参照してください。また、サードパーティーのサイトから取得したソースマップやバンドルは、その当事者のコードです。依頼を受けたセキュリティ評価のために復元することは問題ありませんが、独自アルゴリズムを流用するのは別の問題です。
却下すべき合理化
- 「難読化されているから、手作業で時間がかかる。」 まず分類しましょう。参照可能なソースマップや、正常に実行できる
webcrackがあれば、数時間の作業を数分に短縮できます。手作業で読むのは最後の手段であり、最初にすることではありません。 - 「webcrackの出力に変な箇所があるから、失敗した。」 多層難読化では、1つのツールで外側の層を解除しても、カスタムの内側の層が残ることがあります。その残りこそが興味深い関数です。やり直すのではなく、その部分にASTパスを適用しましょう。
- 「文字列デコーダーをそのまま eval して、平文を取り出そう。」 信頼できないバンドルでこれを行うと、攻撃者のコードがホスト上で実行されます。文字列は静的に、またはサンドボックス内で解決しましょう。
- 「変数名が消えているから、ロジックも失われている。」 ミニファイや難読化は、名前を変更し、形を変えるだけで、削除はしません。ストリップされたネイティブバイナリとは異なり、プログラム全体が存在しています。失われた情報を再構築するのではなく、変換を元に戻しているのです。
- 「DevToolsはデバッグ用で、リバースエンジニアリング用ではない。」 fetchブレークポイントとプロパティフックを使えば、バンドルを読むより速く署名ルーチンを特定でき、ライブの入力と出力も手間なく得られます。
参考資料
reversing-react-native-apps— Hermes/RNのモバイルJSバンドルunpacking-protected-binaries— VMベース/仮想化された難読化hunting-web-backdoors— 悪意のあるコードが埋め込まれたWeb JStesting-web-applications— 復元したアルゴリズムが呼び出すエンドポイントのテストreviewing-cryptography— クライアント側の暗号化/署名ルーチンを復元した後