reverse-engineer-js について
圧縮・難読化された JavaScript を LLM で読みやすくするためのスキルです。ソースマップの確認、モジュール抽出、規模に応じたバンドル処理の手順を開発者向けに案内します。
主な機能
- 01ソースマップを先に確認
- 02LLM による識別子の改名
- 03webcrack によるモジュール抽出
- 04TTY と出力ファイルの問題対処
活用シーン
- 01有効な難読化コードの名前を復元
- 02大規模バンドルから対象モジュールを処理
- 03CI やシェルでの humanifyjs の障害診断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vladmdgolam/ agent-skills --skill reverse-engineer-jsインストール後、Claude Code で/reverse-engineer-jsを入力して読み込まれたか確認します。
詳細
JS のリバースエンジニアリング
humanifyjs と、モジュールの抽出や再統合に使う周辺ツールを組み合わせて、LLM の支援で難読化・圧縮された JavaScript を読み解きます。
ツールの概要
| ツール | 用途 | 使用するタイミング |
|---|---|---|
humanifyjs (npx) | LLM を使って難読化された変数や関数の名前を変更 | 難読化されているものの、すでに有効な JS の場合 |
prettier / js-beautify | 1 行に圧縮されたコードを整形 | 必要に応じて humanify の前 |
webcrack | webpack / browserify のバンドルをモジュールごとのファイルに展開 | バンドルが 1 つの大きなファイルの場合 |
| ソースマップの調査 | バンドルの近くに // # sourceMappingURL=... がある場合があります | 必ず最初に確認してください。LLM による名前変更より確実です |
humanify にトークンを使う前に、必ずソースマップ(bundle.js.map またはファイル内の sourceMappingURL コメント)を確認してください。見つかった場合、元のソースを無料で復元できます。
humanify のデフォルトの実行方法
特に指定がなければ Gemini を使用します。
npx humanifyjs gemini -m "gemini-3.8-flash" -o <outputDir> <inputFile>
環境変数に GEMINI_API_KEY が必要です(通常はプロジェクトのルートにある .env に設定します)。
その他のバックエンド:
# OpenAI
npx humanifyjs openai --apiKey="$OPENAI_API_KEY" --model gpt-4.1-mini bundle.js
# Local (slower, free)
npx humanifyjs local bundle.js
重要な注意点
1. 非対話型シェルでは疑似 TTY が必要です
humanify は進捗バーに process.stdout.cursorTo(...) を使用します。Claude Code、CI、パイプ経由の実行環境では stdout が TTY ではないため、呼び出しが TypeError: process.stdout.cursorTo is not a function でクラッシュします。
呼び出しを script -q /dev/null でラップします。
script -q /dev/null npx humanifyjs gemini -m "gemini-3.8-flash" -o "$OUT" "$IN"
(macOS/BSD の script 構文です。Linux では script -qc "<cmd>" /dev/null を使用してください。)
2. 出力ファイル名は固定です
humanify は入力ファイル名に関係なく、常に <outputDir>/deobfuscated.js として結果を書き込みます。module_1234.js を入力すれば module_1234.js が出力される、とは考えないでください。
3. ファイルが長いと実行時間も長くなります
humanify はトップレベルの宣言を順番に処理し、宣言ごとに LLM を 1 回呼び出します。5 MB のバンドルでは 30 分以上かかることがあります。対策:
- バンドル全体ではなく、そこから抽出した個別のモジュールに対して実行します
- バンドルが webpack 形式の場合は、最初に
webcrackで分割します - コードを適切に読み取れる範囲で、最も安価なモデルを選びます。ここでは
gemini-3.8-flashがデフォルトです。非常に大きなバンドルでは、軽量版(gemini-3.5-flash-lite)を使うと宣言ごとのコストを抑えられますが、複雑なコードでは精度が多少低下します
4. 元のファイルを保管します
書き換えを行う前に、元のバンドルを archive/bundle.original.js にコピーします。humanify の結果は非決定的なため、別のモデルで再実行したくなる場合があります。
ワークフロー A:バンドル全体の難読化解除
バンドルが小さい(500 KB 未満)場合、または時間に余裕がある場合:
mkdir -p archive out
cp bundle.js archive/bundle.original.js
script -q /dev/null npx humanifyjs gemini \
-m "gemini-3.8-flash" \
-o out bundle.js
# result: out/deobfuscated.js
その後、必要に応じて prettier --write out/deobfuscated.js を実行すると、コードを読みやすく整形できます。
ワークフロー B:単一モジュールの抽出(大きなバンドル)
fxhash / generative-art のバンドルでよくあるケースです。1 つのバンドルに多数の id: (params) => { ... } 形式のモジュールが含まれており、その中から興味のあるいくつかだけを humanify にかけます。
- モジュール ID を特定する — バンドル内を特徴的な文字列で検索し、対応する外側の
<id>: (e, t, n) => {の開始位置をたどります。 - モジュール本体を抽出する — 開始位置に一致する箇所から、文字列を考慮しながら波括弧を数え、一致する
}までを抽出します。 - 単独のアロー関数でラップする —
(e, t, n) => { <body> }を一時ファイルに書き込みます。humanify で処理するには、有効なトップレベルコードが必要です。 - humanify を実行する —
script -q /dev/null npx humanifyjs gemini -m "gemini-3.8-flash" -o "$OUT" "$IN"。 - ラップを解除する — 結果から
(e, t, n) => { ... }の外側を取り除きます。 - 保存してスタブに置き換える — 難読化解除後の本体を
modules/module_<id>_<name>.jsに書き込み、バンドル内の本体を<id>: (e, t, n) => { window.DEOBF[<id>](e, t, n); }のようなスタブに置き換えます。
プロジェクトにすでに scripts/deobfuscate.js(または同様のもの)がある場合は、それを再利用してください。波括弧のカウントや文字列のエスケープ処理は厄介なので、新しいバンドルごとに一から作り直すのは無駄です。一般的なスクリプトは、数値のモジュール ID を受け取り、bundle.js から本体を抽出し、ラップした関数を humanify にかけて、ラップを解除したうえで modules/module_<id>_<name>.js に書き込みます。
ワークフロー C:Webpack バンドル → モジュールごとのファイル
Webpack バンドルを扱う場合、webcrack を使うと、humanify を実行する前にモジュールをきれいに分割できます。
npx webcrack bundle.js -o webcracked/
# Then humanify each module of interest:
script -q /dev/null npx humanifyjs gemini \
-m "gemini-3.8-flash" \
-o humanified webcracked/modules/123.js
例
例 1:小規模な fxhash バンドルを難読化解除する
ユーザーの発言:「この fxhash プロジェクトの bundle.js を humanify してください」
操作:
- ソースマップを確認します:
head -c 4096 bundle.js | tail -c 200—sourceMappingURLを探します。見つからない場合は続行します。 .envにGEMINI_API_KEYがあることを確認します。読み込みます:set -a; source .env; set +a。- アーカイブします:
mkdir -p archive && cp bundle.js archive/bundle.original.js。 - 実行します:
mkdir -p out && script -q /dev/null npx humanifyjs gemini -m "gemini-3.8-flash" -o out bundle.js。 - 整形します:
npx prettier --write out/deobfuscated.js。
**結果:**変数名が変更され、読みやすくなった out/deobfuscated.js が作成されます。
例 2:巨大なバンドルから 1 つのモジュールを難読化解除する
ユーザーの発言:「og/bundle.js のモジュール 3658 を難読化解除して、treeGenerator という名前を付けてください」
操作:
- プロジェクトに難読化解除用のヘルパー(例:
scripts/deobfuscate.js)がすでにある場合は、実行します:node scripts/deobfuscate.js 3658 treeGenerator。 - ない場合は、波括弧の数を数えてモジュール本体を抽出し、
(e, t, n) => { ... }でラップしてから、script -q /dev/nullを使って humanify を実行します。ラップを解除し、modules/module_3658_treeGenerator.jsに保存して、バンドル内の該当部分をスタブに置き換えます。
**結果:**関数の読みやすいバージョンを含む新しいファイルが modules/ に作成され、スタブへの置き換えによって bundle.js のサイズが縮小します。
例 3:cursorTo is not a function で humanify がクラッシュする
ユーザーの発言:「humanify がすぐに終了してしまいます」
**診断:**stdout が非 TTY です(Claude Code の bash ツールではこれがデフォルトです)。
対処法:script -q /dev/null <cmd>(macOS)または script -qc "<cmd>" /dev/null(Linux)でラップして再実行します。
トラブルシューティング
cursorTo is not a function
**原因:**stdout が TTY ではありません。対処法:script -q /dev/null でラップします。
GEMINI_API_KEY が設定されていない
**原因:**humanify はプロジェクトの .env から自動的に読み込まず、プロセス環境変数を読み込みます。**対処法:**同じシェルで set -a; source .env; set +a を実行するか、インラインで渡します:GEMINI_API_KEY=... npx humanifyjs gemini ...。
出力が入力と同一(名前が変更されない)
**原因:**humanify は難読化されていると判断した識別子(短い名前や 1 文字の名前)だけを変更します。すでに読みやすいコードはそのままになります。**対処法:**入力が本当に難読化されていることを確認します。useState のような名前がそのまま残るのは想定どおりで、バグではありません。
Gemini のレート制限/クォータエラー
**原因:**ファイルが大きく、トップレベル関数が多いため、LLM 呼び出しが順次多数実行されます。**対処法:**無料でローカルの名前変更ができる local バックエンドに切り替えるか、バンドル全体ではなく単一のモジュールを抽出します(ワークフロー B)。
humanify の実行に何時間もかかる
**原因:**バンドルが大きく、関数ごとの呼び出しが順次実行されます。**対処法:**事前に webcrack で分割するか、必要なモジュールだけを humanify にかけます。
出力ディレクトリに古い deobfuscated.js が残っている
**原因:**humanify は固定のファイル名で書き込みます。2 回目の実行に失敗すると、前回の正常な出力が使われます。**対処法:**各実行の前に rm -f $OUT/deobfuscated.js を実行します。