こんにちは。ネットエージェント株式会社、研究開発部の長谷川です。先日、弊社の愛甲、サイバー大学の園田道夫氏とともに「非公式セキュリティキャンプ・キャラバン」と称して北海道情報セキュリティ勉強会(セキュポロ)に講師として参加してきました。例年であれば、セキュリティ&プログラミングキャンプの事業の一環として公式なキャラバンが開催されるのですが、今年は様々な事情で公式なキャラバンの開催が難しく講師としても心苦しく思っていたところ、ちょうどセキュポロのみなさんから勉強会の打診を頂きましたので、機会に乗じて非公式ながら「セキュリティキャンプ・キャラバン」という形を取らせて頂いたのでした。
-----
さて、今日のテーマは流行りの node.js です。node.js は高効率な Web アプリケーションを、書き慣れた JavaScript で実現するための、サーバサイドで動く JavaScript 実装です。「最近、node.js も話題なので少しは触っておかないとな...」などと考えていたところに、愛甲から突然電話がかかってきました。
電話の内容は「今、node.js で []()+! の6種類の記号だけの JavaScript を書こうとしてるんだけど、うまく動かないんだよね」というものでした。愛甲といえば、普段から「0と1しか興味ない」と公言しているほどのバイナリアンであり、そんな彼が0も1も使わない JavaScript を書いているということに少し衝撃を受けた私は、全く node.js を触ったことがないにも関わらず「いや、多分なんとかなると思うので、やってみます」と返事をしてしまい、node.js の世界に飛び込んだのでした。
とりあえず、記号だけで JavaScript を動かす基本形として、まずは以下のようなコードを考えます。
Function( Function( "return\"console.log(1)\"" )() )()
これは、無名関数内で console.log を使って「1」というメッセージを表示するコードですが、Number.constructor が Function であることを利用すると、以下のように書き変えられます。
(0).constructor.constructor(
(0).constructor.constructor(
"return\"console.log(1)\""
)()
)()
さらに、メソッドの呼び出しを連想配列形式に置き換えます。
(0)["constructor"]["constructor"](
(0)["constructor"]["constructor"](
"return'console.log(1)'"
)()
)()
数字の 0 は、+[] で生成できます。また、「constructor」「return」という文字列は以下のように各文字を生成して + で連結します。
"c" : ([].filter + [])[3] // "function filter() {
// [native code] }"[3]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"n" : (undefined+[])[1] // "undefined"[1]
"s" : (false + [])[3] // "false"[3]
"t" : (true + [])[0] // "true"[0]
"r" : (true + [])[1] // "true"[1]
"u" : (undefined + [])[0] // "undefined"[0]
"c" : ([].filter + [])[3] // "function filter() {
// [native code] }"[3]
"t" : (true + [])[0] // "true"[0]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"r" : (true + [])[1] // "true"[1]
"r" : (true + [])[1] // "true"[1]
"e" : (true + [])[3] // "true"[3]
"t" : (true + [])[0] // "true"[0]
"u" : (undefined + [])[0] // "undefined"[0]
"r" : (true + [])[1] // "true"[1]
"n" : (undefined+[])[1] // "undefined"[1]
「filter」という文字列やtrue、false、undefinedといった値、数値なども、同様な方法で生成します。
"f" : (false + [])[0] // "false"[0]
"i" : (false + undefined)[10] // "falseundefined"[10]
"l" : (false + [])[2] // "false"[2]
"t" : (true + [])[0] // "true"[0]
"e" : (true + [])[3] // "true"[3]
"r" : (true + [])[1] // "true"[1]
true : !![]
false : ![]
undefined : [][[]]
0 : +[]
1 : +!+[]
2 : !+[]+!+[]
3 : !+[]+!+[]+!+[]
10 : +!+[]+[+[]]
「constructor」「return」の各文字列や数値が []()!+ の記号だけで生成できましたので、あとは「'console.log(1)'」という、実行するコード部分を記号で生成すれば任意のコードを記号だけで書けそうです。
JavaScript では、文字列定数は "\101\102\103" のように ASCII コードを \ に続けて8進数で記述することで、US-ASCII の範囲内の文字であれば表現できます。これを利用すると、「'console.log(1)'」という部分は「'\143\157\156\163\157\154\145\56\154\157\147\050\061\051'」になります。任意の数字は []()!+ の6種の記号から自由に生成できますので、あとは「\」と「'」の2文字を生成すればいいことになりますが、さてどうしよう...、というところで実は行き詰ったわけですが、node.js をいろいろ触っていると、console.dir が利用できることに気が付きました。console.dir( console.dir + []) を実行してみると、次のような文字列が返ってきます。
function (object) {
var util = require('util');
process.stdout.write(util.inspect(object) + '\n');
}
うまい具合に、(consile.dir+[])[97] と (console.dir+[])[41] に「\」と「'」が存在していました。そして、「console」「dir」は次のように記号だけで書けます。
"c" : ([].filter + [])[3] // "function filter() {
// [native code] }"[3]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"n" : (undefined+[])[1] // "undefined"[1]
"s" : (false + [])[3] // "false"[3]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"l" : (false + [])[2] // "false"[2]
"e" : (true + [])[3] // "true"[3]
"d" : (undefined+[])[2] // "undefined"[2]
"i" : (false + undefined)[10] // "falseundefined"[10]
"r" : (true + [])[1] // "true"[1]
これで、任意のコードを []()!+ の6種類だけで書けることがわかりましたので、実際に「console.log(1)」を記号だけで書いてみると、こんなふうになります。たった1行が4万文字を超えるスクリプトになってしまいました(笑)。
この方法で、node.js らしくhttpサーバも記号だけに変換してみようと思ったのですが、require や module などが Function() 内ではうまく動かず結局断念しました。require のみグローバルなコンテキストから引数で渡してやることで、ほとんどの部分を記号だけで書いた JavaScript による http サーバはこちらになります(ブラウザによっては表示に時間がかかりますのでご注意ください)。require なども記号だけで実現する、何かよい方法があればこっそり教えてください。
ということで、記号だらけですっかり混乱してしまった頭をすっきりさせるために、今日も飲みに行くのでした。