こんにちは。ネットエージェント株式会社、研究開発部の長谷川です。本年もよろしくお願い致します。今回は、昨年11月に行いました NetAgent Security Contest 2010 の Level7 JavaScript で使用した JavaScript の難読化に関連するトピックスについて書かせていただこうと思います。
-----
今回の問題は、難読化されたJavaScriptコード中に埋め込まれた未使用変数に代入される文字列が解答となる、というものでした。
JavaScriptの難読化手法については、実際の攻撃コードに使用されるものから、非実用的な研究目的のものまで様々な方法があります。今回の問題を作成するにあたっては、公知の難読化手法だけでは面白みに欠ける一方、特定のブラウザを使用していなければ解読さえできない、というほど凝ったものにならないよう、バランスに苦慮しながら出題しました(編注:実際、Level7 については難易度調整のため、2度の作り直しが行われました)。
難読化は、32種類の記号のみで構成される JavaScript を生成する jjencode と呼ばれるエンコーダと、さらにそれを改良した JSF*ck とを組み合わせて、難解な記号だけの JavaScript コードを HTML 内に埋め込みました。
JSF*ck は、任意のJavaScriptを [ ] ( ) ! + の6種類の記号だけに変換するエンコーダであり、今回の NetAgent Security Contenst 2010 に合わせて開発しました。例えば、alert(1) という JavaScript コードを、 [ ] ( ) + ! の6文字だけに変換すると以下になります。今回は、このコードの動作原理についての説明は割愛しますが、興味のある方はコードを追いかけてみるのも面白いかと思います。
<script>
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]
]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+
!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])
[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]
)[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+
[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])
[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!
+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![
]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+
[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[]
)[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+
!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!
+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]
+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+
[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[
])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][
[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+
[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[]
[(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[
])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![
]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[!+[]+!+[]+
!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][([][(![]+[])[+[]]+([![]]+[][
[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+
[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+
([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[
])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+
(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+
[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+
(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+
[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+
[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[
]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!
+[]+!+[]]]+[+!+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(
![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[
+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[
]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+
(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+
!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[
]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[
]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[
+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+
!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[
]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]])()
</script>
さて、前述のように記号だけの JavaScript は比較的簡単に書けますが、せっかくの機会なので、それ以外にもマイナーなテクニックを使っておきたいということで、文字列を JavaScript として実行するために、通常なら eval 関数を使うところを、Firefox 独自実装の crypto.generateCRMFRequest メソッドを利用しました。
<script>
crypto.generateCRMFRequest(
'CN=0',0,0,null,'alert("Hello, World")',384,null,
'rsa-dual-use')
</script>
このように、crypto オブジェクトを経由することで eval のように文字列として与えた引数を JavaScript のコードとして実行できます。これは、Firefox 独自の機能ですが、Firefox 以外のブラウザにおいても引数部分の文字列を解析すれば解答を導き出せるということで、今回の NetAgent Security Contenst 2010 において使用することにしました。
このように、JavaScript において「文字列を JavaScript として実行する」という機能は、実は様々な方法があり、よく知られている代表的なものだけでも以下のようなものがあります。
var js = "alert(1)";
eval(js); // もっともオーソドックスな方法
window[ "eval" ](js); // windowオブジェクトのevalメソッド
// evalを文字列として表記
Function(js)(); // 文字列から無名関数を生成して実行
alert.constructor("alert(1)")(); // alert.constructor==Functionを利用
Number.constructor("alert(1)")(); // Number.constructor==Functionを利用
setTimeout(js);
setInterval(js);
これら以外にも、DOM を経由して <script> 要素などを追加する方法や、location に "javascript:" スキームを代入する方法もあります。さらに、Internet Explorer では JavaScript 内の文字列を別の言語(VBScript等)として実行する機能も備わっています。execScript の第2引数、setTimeout、setIntervalの第3引数は実行する言語です。
var vbs = "MsgBox(1)"
execScript(vbs, "vbscript");
setTimeout(vbs, 0, "vbscript");
setInterval(vbs, 0, "vbscript");
さらにさらに、JavaScript、VBScript 以外にも、それらをMicrosoft製の難読化エンコーダである Windows Script Encoder でエンコードした JScript.Ecnode や VBScript.Encode も指定可能です。
// 難読化されたJScript "alert(1)"
var js = "#@~^CAAAAA==C^+.D`8#mgIAAA==^#~@";
// 難読化されたVBScript "MsgBox(1)"
var vbs = "#@~^CQAAAA==\\ko$K6vF#0gIAAA==^#~@";
javascript:execScript(js, "jscript.encode");
javascript:execScript(vbs, "vbscript.encode");
javascript:setTimeout(js, 0, "jscript.encode");
javascript:setInterval(vbs, 0, "vbscript.encode");
Windows Script Encoder による難読化は、デコーダの実装も複数存在するため、元のコードを復元することもできますが、JavaScript 中に VBScript のような他の言語が混在することや、それを呼び出す JavaScript 自体を他の手法で難読化可能なことなどを踏まえると、実際の解読には相応の手間がかかります。
ただ、このような凝った難読化手法というのはブラウザに依存してしまうため、一般的なソフトウェア開発において簡単に利用するのは難しいですが、研究目的、あるいは今回の NetAgent Security Contest 2010 での出題のようなパズル的な楽しみとしての使用であれば、まだまだ開拓の余地がある分野です。みなさんも、おもしろい JavaScript の書き方を見つけた場合にはぜひ教えて頂けると幸いです。