はじめに
何らかの事情で JavaScript のコードを読み、その挙動について調査しなくてはいけないということはよくあると思います。
そんなとき、難読化やMinifyなどによって複雑怪奇に見えるコードに遭遇したという経験をもつひとも多いのではないでしょうか。
この記事はそんなコードにぶつかった際に、どうすれば良いのかを自分なりにまとめてみました。
また、jjencode 作者のはせがわようすけさんのJavaScript難読化読経というスライドを見て、
作者がネタバレしてるんだからこの辺の知見について書いても大丈夫だろうという気持ちで書き始めたことも一応記しておきます。
ツールによって難読化されたコードの読み方
まずはコードリーディング出来る状態にするため、よく見る2つのツールによる難読化のデコード方法について書いておきます。
Javascript Obfuscator
Javascript Obfuscator はeval(function(p,a,c,k,e,d){...
から始まる特徴的なコードにエンコードするツールです。
このエンコードされた JavaScript コードは、先頭の eval
を console.log
にするだけでデコードすることが可能です。
パッと見でのコードリーディングを防ぎたいというくらいの効果しかありません。
jjencode, aaencode, etc.
コードを読もうとしたとき、記号プログラミングの手法によって読みにくくなっていることがあります。
そうした場合は、開発ツールのコンソールを使ってデコードするところから始めます。
難読化ツールとしては jjencode が有名です。
これらの難読化ツールは基本的に number.constructor.constructor
を使うことで Function
を引き出し、Function(実行したいスクリプト本体文字列)
とすることで任意のコードを実行しています。
(boolean
や string
を使っても同様のことが出来るので、Number
がハズレだったら String
や Boolean
を試すとよいでしょう)
蛇足かもしれませんが説明しておきますと、number.constructor
は Number
であり、Number.constructor
が Function
となります。
(0).constructor
=> Number() { [native code] }
(0).constructor.constructor
=> Function() { [native code] }
つまり、下記のように Number.constructor
を console.log
にしてやることで、実行しようとしているコードを確認することができます。
Number.constructor = console.log.bind(console);
また、コードが何らかの悪意を持っている可能性がある場合は、下記のように debugger
文を使って実行を中断しましょう。(あるいは、例外を投げて失敗させても良いでしょう)
Number.constructor = function() {
console.log(arguments);
debugger;
};
記号プログラミングによる難読化手法は、より詳細な説明をはせがわようすけさんが資料を公開しているのでそちらを参照してください。
このように、jjencode などのエンコーダによる難読化は簡単にデコードできるため、解読を防ぐ用途での効果は期待しない方がよいでしょう。
実際、jjencodeには下記のように注意書きがされています。
Be aware
Using jjencode for actual attack isn't good idea.
- Decode easily. jjencode is not utilitarian obfuscation, just an encoder.
- Too characteristic. Detected easily.
- Browser depended. The code can't run on some kind of browsers.
Minify/Compile/Transpile されたコードの読み方
ここから、本来のコードリーディングの作業に入っていきます。
Minify/Compile/Transpile(以下、まとめてMinifyと表記します) されたコードをどのように追っていくかという話題です。
当然、SourceMap は提供されてないことを前提とします。
Pretty Print ツールによるソースコードの整形
まずは、ごちゃごちゃしているコードを Pretty Print (読みやすい形に変換)します。
Google Chrome などの開発者ツールについている Pretty Print でも良いのですが、後々のために Pretty Print した別のファイルにしておきます。
例えば、下記のようなコードを
function hello(a){alert("Hello, "+a)}hello("New user");
このようにきれいに整形してくれます。(例は Closure Compiler のオンラインサービス版の初期コード)
function hello(a) {
alert('Hello, ' + a);
}
hello('New user');
Pritty Print する方法はいくつかありますが、自分は esprima と escodegen による Pretty Print スクリプトを愛用しています。
IDEによるリファクタリング
Pretty Print してしまえばあとは頑張ってコードを読んでいくだけなのですが、そのまま読んでいくのは人類には厳しいのでIDEのサポートを受けながらコードを読みやすい形に修正しながら読み進めていきます。
なぜIDEを使用するかというといくつかの理由がありますが、lintを使うなど他の環境でも同様のことができるなら問題ありません。
自分は WebStorm や Visual Studio を使っています。
unused variable の除去
未使用の変数は見つけ次第どんどん削除していきましょう。
dead code の除去
未使用の変数と同様に、到達しないコードもIDEが教えてくれるので削除していきます。
変数名/function名のリネーム
Minifyされたコードは変数やfunction名が a
とか b
といった短い名前に変更されています。
しかも、別のスコープでまた a
とか b
となっていくので、どれがどれだかややこしくなっています。
これをIDEのリファクタリング機能のリネームを活用することで分かりやすい名前にしていきます。
(どういう名前にするかは、コードをよく読んで推測するしかない)
単なる置換よりも、スコープなどを適切に判断してくれるIDEのリファクタリング機能の方が便利です。
変数の使いまわしの分離
Closure Compiler ではファイルサイズ優先の Minify を行うため、元々は別の変数だったものを同じ変数を再度利用することがあります。
(オプションで変更することは可能だが、オプションの変更を行うには自分でビルドしなおすか外部ツールを使用するしかありません)
そういった変数を見つけ出して、それぞれ別の変数に分離します。
簡単にですが見分けたい場合を書いておくと、数値をいれてる変数に文字列を入れるなど、別のものを同じ変数に入れていたら注意して読んだ方が良いでしょう。