記号だけのJavaScriptプログラミングの基本原理

2010-12-03

こんにちは。プログラマ定年を迎えたのであとは悠々自適に日々過ごそうと思ってるはせがわです。

JavaScriptで記号プログラミングを行う基本的な取り組を説明します。
jjencodeなどで使っているテクニックです。

まず最初は数字の作り方。

+[] // 空の配列にプラス演算子で数値の 0
~[] // 空の配列にビット反転で -1
~{} // 空のオブジェクトにビット反転で -1
-~[] // 空の配列にビット反転で-1、-1に単項マイナスで +1
-~-~[] // +1 にビット反転で -2、-2 に単項マイナスで +2

このように、空の配列や空のオブジェクトに数値用の演算子を適用することで、任意の数値を記号だけで生成することができます。

次に文字の作り方。

![]         // 空の配列に論理否定で false
!![]        // 空の配列に論理否定を2回で true
(![]+"")    // true に空文字列を連結で、文字列 "true"
(![]+"")[0] // 文字列 "true" の最初の文字 "t" 
({}+"")     // 文字列 "[object Object]"
({}+"")[1]  // 文字 "o"

このように、いくつかの文字は空の配列や空のオブジェクト、true / false などから作り出すことができます。
"false"[0] のように、文字列から配列形式で文字を切り出していますので、IE6,IE7では動きませんし、IE8でもドキュメントモードがIE8モードである必要があります。

作りだした文字を、JavaScript のコードとして実行するには eval を含めいくつかの方法がありますが、記号だけでやりやすい方法として、Function( "/* code here */" )() という形式を利用します。これは、Function オブジェクトのコンストラクタを利用し、文字列から無名関数を生成、それを実行するということです。
Function オブジェクトは、Number.constructor から取得でき、Number オブジェクトは (0).constructor のようにして取得することができます。

つまり、

(0).constructor.constructor("alert(1)")()

(function(){alert(1)})()

と同じく、alert を実行する無名関数を生成して実行するということになります。

そして、メソッドは連想配列のように表記できるJavaScriptの特性から

(0).constructor.constructor("alert(1)")()

(0)["constructor"]["constructor"]("alert(1)")()

とも同義となります。

ここまでできれば、"constructor" と "alert" の各文字が生成できれば、alert を記号だけで実行できるということがわかります。
あとは各文字を記号から生成しましょう。

({}+"")[ 5 ]     // 'c' : "[object Object]"[ 5 ]
({}+"")[ 1 ]     // 'o' : "[object Object]"[ 1 ]
({}.$+"")[ 1 ]   // 'n' : "undefined"[ 1 ]
(!{}+"")[ 3 ]    // 's' : "false"[ 3 ]
(!!{}+"")[ 0 ]   // 't' : "true"[ 0 ]
(!!{}+"")[ 1 ]   // 'r' : "true"[ 1 ]
({}.$+"")[ 0 ]   // 'u' : "undefined"[ 0 ]
({}+"")[ 5 ]     // 'c' : "[object Object]"[ 5 ]
(!!{}+"")[ 0 ]   // 't' : "true"[ 0 ]
({}+"")[ 1 ]     // 'o' : "[object Object]"[ 1 ]
(!!{}+"")[ 1 ]   // 'r' : "true"[ 1 ]

(!{}+"")[ 1 ]    // 'a' : "false"[ 1 ]
(!{}+"")[ 2 ]    // 'l' : "false"[ 2 ]
({}+"")[ 4 ]     // 'e' : "[object Object]"[ 4 ]
(!!{}+"")[ 1 ]   // 'r' : "true"[ 1 ]
(!!{}+"")[ 0 ]   // 't' : "true"[ 0 ]

これで、"constructor" と "alert" が生成できましたので

(0)["constructor"]["constructor"]("alert(1)")()

に各文字を当てはめると

(0)[
    ({}+"")[ 5 ]   + // 'c' : "[object Object]"[ 5 ]
    ({}+"")[ 1 ]   + // 'o' : "[object Object]"[ 1 ]
    ({}.$+"")[ 1 ] + // 'n' : "undefined"[ 1 ]
    (!{}+"")[ 3 ]  + // 's' : "false"[ 3 ]
    (!!{}+"")[ 0 ] + // 't' : "true"[ 0 ]
    (!!{}+"")[ 1 ] + // 'r' : "true"[ 1 ]
    ({}.$+"")[ 0 ] + // 'u' : "undefined"[ 0 ]
    ({}+"")[ 5 ]   + // 'c' : "[object Object]"[ 5 ]
    (!!{}+"")[ 0 ] + // 't' : "true"[ 0 ]
    ({}+"")[ 1 ]   + // 'o' : "[object Object]"[ 1 ]
    (!!{}+"")[ 1 ]   // 'r' : "true"[ 1 ]
]
[
    ({}+"")[ 5 ]   + // 'c' : "[object Object]"[ 5 ]
    ({}+"")[ 1 ]   + // 'o' : "[object Object]"[ 1 ]
    ({}.$+"")[ 1 ] + // 'n' : "undefined"[ 1 ]
    (!{}+"")[ 3 ]  + // 's' : "false"[ 3 ]
    (!!{}+"")[ 0 ] + // 't' : "true"[ 0 ]
    (!!{}+"")[ 1 ] + // 'r' : "true"[ 1 ]
    ({}.$+"")[ 0 ] + // 'u' : "undefined"[ 0 ]
    ({}+"")[ 5 ]   + // 'c' : "[object Object]"[ 5 ]
    (!!{}+"")[ 0 ] + // 't' : "true"[ 0 ]
    ({}+"")[ 1 ]   + // 'o' : "[object Object]"[ 1 ]
    (!!{}+"")[ 1 ]   // 'r' : "true"[ 1 ]
](
    (!{}+"")[ 1 ]  + // 'a' : "false"[ 1 ]
    (!{}+"")[ 2 ]  + // 'l' : "false"[ 2 ]
    ({}+"")[ 4 ]   + // 'e' : "[object Object]"[ 4 ]
    (!!{}+"")[ 1 ] + // 'r' : "true"[ 1 ]
    (!!{}+"")[ 0 ]   // 't' : "true"[ 0 ]
    +"(" + 1 + ")"
)(1)

となります。あとは、数値を記号に置き換えるだけです。

$ = +[];          // $ = 0
$ = {
     ___ : $++,  // 0
     __$ : $++,  // 1
     _$_ : $++,  // 2
     _$$ : $++,  // 3
     $__ : $++,  // 4
     $_$ : $++,  // 5
     $$_ : $++,  // 6
     $$$ : $++   // 7
};
( $.___ )[
    ({}+"")[ $.$_$ ]   + // 'c' : "[object Object]"[ 5 ]
    ({}+"")[ $.__$ ]   + // 'o' : "[object Object]"[ 1 ]
    ({}.$+"")[ $.__$ ] + // 'n' : "undefined"[ 1 ]
    (!{}+"")[ $._$$ ]  + // 's' : "false"[ 3 ]
    (!!{}+"")[ $.___ ] + // 't' : "true"[ 0 ]
    (!!{}+"")[ $.__$ ] + // 'r' : "true"[ 1 ]
    ({}.$+"")[ $.___ ] + // 'u' : "undefined"[ 0 ]
    ({}+"")[ $.$_$ ]   + // 'c' : "[object Object]"[ 5 ]
    (!!{}+"")[ $.___ ] + // 't' : "true"[ 0 ]
    ({}+"")[ $.__$ ]   + // 'o' : "[object Object]"[ 1 ]
    (!!{}+"")[ $.__$ ]   // 'r' : "true"[ 1 ]
]
[
    ({}+"")[ $.$_$ ]   + // 'c' : "[object Object]"[ 5 ]
    ({}+"")[ $.__$ ]   + // 'o' : "[object Object]"[ 1 ]
    ({}.$+"")[ $.__$ ] + // 'n' : "undefined"[ 1 ]
    (!{}+"")[ $._$$ ]  + // 's' : "false"[ 3 ]
    (!!{}+"")[ $.___ ] + // 't' : "true"[ 0 ]
    (!!{}+"")[ $.__$ ] + // 'r' : "true"[ 1 ]
    ({}.$+"")[ $.___ ] + // 'u' : "undefined"[ 0 ]
    ({}+"")[ $.$_$ ]   + // 'c' : "[object Object]"[ 5 ]
    (!!{}+"")[ $.___ ] + // 't' : "true"[ 0 ]
    ({}+"")[ $.__$ ]   + // 'o' : "[object Object]"[ 1 ]
    (!!{}+"")[ $.__$ ]   // 'r' : "true"[ 1 ]
](
    (!{}+"")[ $.__$ ]  + // 'a' : "false"[ 1 ]
    (!{}+"")[ $._$_ ]  + // 'l' : "false"[ 2 ]
    ({}+"")[ $.$__ ]   + // 'e' : "[object Object]"[ 4 ]
    (!!{}+"")[ $.__$ ] + // 'r' : "true"[ 1 ]
    (!!{}+"")[ $.___ ]   // 't' : "true"[ 0 ]
    +"(" + $.__$ + ")"
)( $.__$ )

あとは、コメントや空白を消せば、ほらこのとおり、見なれた記号だけのJavaScriptになりました。

$=+[];$={___:$++,__$:$++,_$_:$++,_$$:$++,$__:$++,$_$:$++,$$_:$++,$$$:$++
};($.___)[({}+"")[$.$_$]+({}+"")[$.__$]+({}.$+"")[$.__$]+(!{}+"")[$._$$]+
(!!{}+"")[$.___]+(!!{}+"")[$.__$]+({}.$+"")[$.___]+({}+"")[$.$_$]+
(!!{}+"")[$.___]+({}+"")[$.__$]+(!!{}+"")[$.__$]][({}+"")[$.$_$]+({}+"")
[$.__$]+({}.$+"")[$.__$]+(!{}+"")[$._$$]+(!!{}+"")[$.___]+(!!{}+"")[$.__$]+
({}.$+"")[$.___]+({}+"")[$.$_$]+(!!{}+"")[$.___]+({}+"")[$.__$]+(!!{}+"")
[$.__$]]((!{}+"")[$.__$]+(!{}+"")[$._$_]+({}+"")[$.$__]+(!!{}+"")
[$.__$]+(!!{}+"")[$.___]+"("+$.__$+")")($.__$)

今回は記号だけで alert を実行するJavaScriptのコードについて解説しました。alert 以外の任意のコードを実行する場合には、任意の文字も記号から作り出す必要がありますが、それについてはまた後日。Enjoy !