晴耕雨読

working in the fields on fine days and reading books on rainy days

[JavaScript] 難読化プログラムの作り方

CTFの練習問題の中に難読化されたJavaScriptコードがあったので、それを読んでみた上で、 こんな感じで難読化コードを作ればいいんじゃないかなという感想。 ちなみに解いた問題は、ksnctfの問題3です。

これは本当にJavaScriptなのか

問題のページに移動してhtmlコードを見てみると、scriptタグには次のコードがありました。

(ᒧᆞωᆞ)=(/ᆞωᆞ/),(ᒧᆞωᆞ).ᒧうー=-!!(/ᆞωᆞ/).にゃー,(〳ᆞωᆞ)=(ᒧᆞωᆞ),(〳ᆞωᆞ).〳にゃー=- -!(ᒧᆞωᆞ).ᒧうー,(ᒧᆞωᆞ).ᒧうーー=(〳ᆞωᆞ).〳にゃー- -!(ᒧᆞωᆞ).ᒧうー,(〳ᆞωᆞ).〳にゃーー=(ᒧᆞωᆞ).ᒧうーー- -(〳ᆞωᆞ).〳にゃー,(ᒧᆞωᆞ).ᒧうーー=(〳ᆞωᆞ).〳にゃーー- -!(ᒧᆞωᆞ).ᒧうー,(〳ᆞωᆞ).〳にゃーー=(ᒧᆞωᆞ).ᒧうーー- -(〳ᆞωᆞ).〳にゃー,(ᒧᆞωᆞ).ᒧうーーー=(〳ᆞωᆞ).〳にゃーー- -!(ᒧᆞωᆞ).ᒧうー,(〳ᆞωᆞ).〳にゃーーー=(ᒧᆞωᆞ).ᒧうーーー- -(〳ᆞωᆞ).〳にゃー,(ᒧᆞωᆞ).ᒧうーーー=(〳ᆞωᆞ).〳にゃーーー- -!(ᒧᆞωᆞ).ᒧうー,(〳ᆞωᆞ).〳にゃーーー=(ᒧᆞωᆞ).ᒧうーーー- -(〳ᆞωᆞ).〳にゃー,ー='',(ᒧᆞωᆞ).ᒧうーーー=!(ᒧᆞωᆞ).ᒧうー+ー,(〳ᆞωᆞ).〳にゃーーー=!(〳ᆞωᆞ).〳にゃー+ー,(ᒧᆞωᆞ).ᒧうーーー={這いよれ:!(〳ᆞωᆞ).〳にゃー}+ー,(〳ᆞωᆞ).〳にゃーーー=(ᒧᆞωᆞ).ᒧニャル子さん+ー,(ᆞωᆞᒪ)=(コᆞωᆞ)=(ᒧᆞωᆞ).ᒧうー,(ᒧᆞωᆞ).ᒧうーーーー=(〳ᆞωᆞ).〳にゃーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃー-(コᆞωᆞ)],(〳ᆞωᆞ).〳にゃーーーー=(ᒧᆞωᆞ).ᒧうーーー[(ᆞωᆞᒪ)- -(ᒧᆞωᆞ).ᒧうーー-(コᆞωᆞ)],(ᒧᆞωᆞ).ᒧうーーーー=(ᒧᆞωᆞ).ᒧうーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃーー-(コᆞωᆞ)],(〳ᆞωᆞ).〳にゃーーーー=(〳ᆞωᆞ).〳にゃーーー[(ᆞωᆞᒪ)- -(ᒧᆞωᆞ).ᒧうーー-(コᆞωᆞ)],(ᒧᆞωᆞ).ᒧうーーーー=(ᒧᆞωᆞ).ᒧうーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃーー-(コᆞωᆞ)],(〳ᆞωᆞ).〳にゃーーーー=(〳ᆞωᆞ).〳にゃーーー[(ᆞωᆞᒪ)-(コᆞωᆞ)],(ᒧᆞωᆞ).ᒧうーーーー=(〳ᆞωᆞ).〳にゃーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃー-(コᆞωᆞ)],(〳ᆞωᆞ).〳にゃーーーー=(ᒧᆞωᆞ).ᒧうーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃー-(コᆞωᆞ)],(ᒧᆞωᆞ).ᒧうーーーー=(ᒧᆞωᆞ).ᒧうーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃー-(コᆞωᆞ)],(〳ᆞωᆞ).〳にゃーーーー=(〳ᆞωᆞ).〳にゃーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃーー-(コᆞωᆞ)],(ᒧᆞωᆞ).ᒧうーーーー=(ᒧᆞωᆞ).ᒧうーーー[(ᆞωᆞᒪ)-(コᆞωᆞ)],(〳ᆞωᆞ).〳にゃーーーー=(〳ᆞωᆞ).〳にゃーーー[(ᆞωᆞᒪ)-(コᆞωᆞ)],(ᒧᆞωᆞ).ᒧうーーーー=/""ω""/+/\\ω\\/,(〳ᆞωᆞ).〳にゃーーーー=(ᒧᆞωᆞ).ᒧうーーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃー-(コᆞωᆞ)],(ᒧᆞωᆞ).ᒧうーーーー=(ᒧᆞωᆞ).ᒧうーーーー[(ᆞωᆞᒪ)- -(〳ᆞωᆞ).〳にゃーーー-(コᆞωᆞ)],(〳ᆞωᆞ).〳にゃーーーー=(ᒧᆞωᆞ).ᒧうーーーー+(〳ᆞωᆞ).〳にゃーーーー,(ᒧᆞωᆞ).ᒧうーーーーー=(〳ᆞωᆞ).〳にゃーーーー+(ᒧᆞωᆞ).ᒧうー+(ᒧᆞωᆞ).ᒧうー, ...(以下略)

これはJavaScriptなのか???と思いましたが、構文エラーもなく、他にはjQueryを使っているくらいなので、 こんなJavaScriptの書き方もあるんだなーと思い知らされました。

それでは、このコードを元にJavaScriptの難読化について紐解いていきましょう。

変数の宣言と代入

マルチバイト文字を含む変数名

この問題を通して初めて知ったのですが、JavaScriptは変数名にマルチバイト文字が使えるようです。 例えば冒頭のコードの1行目では、次のように変数が宣言されています。

(ᒧᆞωᆞ)=(/ᆞωᆞ/),

varを付けないで宣言した変数はグローバル変数になるというのはよく知られていますが、 そのグローバル変数をパーレン()で囲っても代入できるということは多分ほとんどの方が知らないと思います (私も知りませんでした)。 つまり、上のコードは次のコードと同じことをします。

ᒧᆞωᆞ = /ᆞωᆞ/,

これは、変数ᒧᆞωᆞに正規表現オブジェクト/ᆞωᆞ/を代入したということになります。

プロパティへの代入

変数名にマルチバイト文字が含まれていても問題ないように、プロパティ名にもマルチバイト文字を含むことができます。

(ᒧᆞωᆞ).ᒧうー = 0,

これは、変数ᒧᆞωᆞのプロパティᒧうーに0を代入したということになります。

カンマ演算子

カンマ演算子は、それぞれの演算対象を左から右に評価し、最後のオペランドの値を返します。

expr1, expr2, expr3...

for文とかで時々お見かけすると思います。

なぜセミコロン;の代わりにカンマ演算子,を用いているかというと、 ブラウザのデバッグ機能の1つ「ブレイクポイント」を置くときに、 ブレイクポイントは1つの行に対してセットすることができるのですが、 カンマでつながっていると途中で変数の中身が変わっていても追跡できなくなる。というのが理由だと思います。

a = 123,
a = "test",
something(a),
a = 123; // ここにブレイクポイントが置かれるため、a = 123 という結果だけが表示される。

文字列からコードへ

eval

文字列をコードとして評価する最も早い方法は、非推奨で有名なevalを使うことです。

eval("a = 1 + 1");

しかし evalを使ってしまうと、解析者に「引数の文字列が実行されますよ」と言っているようなものなので、 難読化するという点ではあまりお勧めできません。 evalconsole.logとかに置き換えて実行されたら一瞬で内容がばれてしまいます。

Function

evalを使わないで文字列をコードとして評価する方法もあります。Functionコンストラクタです。

foo = Function("return 1 + 1;");

これは次のコードと同じ意味です。

var foo = function () {
    return 1 + 1;
}

Functionコンストラクタを用いて定義した無名関数をすぐに評価したい場合は即時実行関数を使います。

Function("return 1 + 1;")(); // 2

さらに難読化することができて、Number.constructor === Function なので、次のように書くこともできます。 もちろん Number クラスだけでなく Array でも Object でも同じです。

Number.constructor("return 1 + 1;")(); // 2
Array.constructor("return 1 + 1;")();  // 2
Object.constructor("return 1 + 1;")(); // 2

思い出して欲しいのは「Javascriptのクラスは関数である」ということです。 つまり、全てのクラスはFunctionクラスから生成されたので、全てのクラスのコンストラクタはFunctionクラスということになります。

// Animalクラスの定義
function Animal(name) {
    this.name = name;
}

// Animalクラスのインスタンス
animal = new Animal("dog");
animal.name; // "dog"

さらにさらに、(0).constructor === Number なので、さらに難読化して書くことができます。 Arrayの場合は []、Objectの場合は {}です。 こちらも同様に、全ての数値は、Numberクラスから生成されたので、全ての数値のコンストラクタはNumberクラスということになります。

( (0).constructor.constructor("return 1 + 1;") )(); // 2
(  [].constructor.constructor("return 1 + 1;") )(); // 2
(  {}.constructor.constructor("return 1 + 1;") )(); // 2

次のコードは、Functionコンストラクタを使って、alertを実行させる例です。

([].constructor.constructor("alert(1)"))();

ここまでで、文字列をコードとして実行させる方法がわかったと思います。 次に、どうやって文字列を難読化させるかについて話していきたいと思います。

諸々から文字列へ

実行させたい文字列をただ貼り付けただけでは難読化とは言えないので、文字列を難読化してみましょう。 その前にいくつかのJavaScriptにまつわる簡単な復習です。

復習

1. 全てのオブジェクトは文字列にキャストできます。キャストの方法は空の文字列と足し算をするだけです。

"" + 0; // "0"
"" + 1; // "1"
"" + true; // "true"
"" + [1, 2]; // "1,2"
"" + {}; // "[object Object]"
"" + function foo() {}; // "function foo() {}"

2. 文字列のn番目を取り出すときは、添字を使います。

"false"[1]; // "a"

3. 文字列などから数字を取り出すときは、単項演算子の+-を使います。

+"123"; // 123
- -"456"; // 456
+"3.14"; // 3.14
+"foo"; // NaN
+true; // 1
+false; // 0

4. 全てのオブジェクトは二重否定!!することによって true か false のどちらかになります。

!!true; // true
!!""; // false
!!1; // true
!!undefined; // false

実践

ここでの難読化の目標は、できるだけ英文字が少なくなるようにします(記号だらけにします)。 また、説明のために変数名はわかりやすくしていますが、実際は適当な変数名にしたり、 マルチバイト文字を使った変数名などにしてください。

数値の作成

それではまず、0という数字を使わないで0という値を代入します。

d0 = +!![].$; // 0

JavaScriptでは $ は識別子として使えることに注意してください。 上の奇妙なコードは次のように展開されます。

d0 = +!![].$;
// d0 = +!!undefined;
// d0 = +!true;
// d0 = +false;
// d0 = 0;

ここで undefined を持ってくるために [].$ と書きましたが、 [].fooや、オブジェクトを使って{}.barとか、正規表現を使って/^/.baz とか書いても良いです。

同じ手順で、1も作ってみます。

d2 = +!!{}; // 1

この奇妙なコードも同じように展開されます。

d1 = +!!{};
// d1 = +!false;
// d1 = +true;
// d1 = 0;

1 を作ることができれば、2〜9 は 1 を足し合わせで作ることができます。

d2 = d1 + d1; // 2
d3 = d2 + d1; // 3
d4 = d3 + d1; // 4
d5 = d4 + d1; // 5
d6 = d5 + d1; // 6
d7 = d6 + d1; // 7
d8 = d7 + d1; // 8
d9 = d8 + d1; // 9

難読化するときは、+b- -b(マイナスの減算)に置き換えたり、左項と右項を入れ替えたり、 項の数を増やしたりして、読みにくくしていきます。

とりあえず、ここまでのコードをまとめると、次のような感じになります。

d0 = +!![].$; // 0
d1 = +!!{};   // 1
d2 = d1 + d1; // 2
d3 = d2 + d1; // 3
d4 = d3 + d1; // 4
d5 = d4 + d1; // 5
d6 = d5 + d1; // 6
d7 = d6 + d1; // 7
d8 = d7 + d1; // 8
d9 = d8 + d1; // 9

これ以外にも数値の作り方はあるので、いろいろ調べてみてください。

文字の作成

まず a という文字は、次のようにして得ることができます。

a = ("" + ![])[1]; // a

上の奇妙なコードは次のように展開されます。

a = ("" + ![])[1];
// a = ("" + false)[1];
// a = "false"[1];
// a = "a";

b という文字は、オブジェクトを文字列にしたときに出てくる [object Object]b を使います。

b = ("" + {})[2];
// b = "[object Object]"[2];
// b = "b";

d という文字は、undefined を使います。

d = ("" + {}.a)[2];
// d = "undefined"[2];
// d = "d";

e という文字は、true を使います。

e = ("" + !{}.a)[3];
// e = ("" + true)[3];
// e = "true"[3];
// e = "e";

g という文字は、String のコンストラクタを使います。

g = ("" + "".constructor)[14];
// g = ("" + String)[14];
// g = "function String() { [native code] }"[14];
// g = "g";

こんな感じで、文字を作っていくのですが、作れる文字と作れない文字があるので、下にまとめておきます。 String のところは ""['constructor']、Math のところは this['Math'] のように置き換えてください。

a = "false"[1]
b = "[object Object]"[2]
c = "[object Object]"[5]
d = "undefined"[2]
e = "true"[3]
f = "undefined"[4]
g = ("" + String)[14]     // 'function String() { [native code] }'[14]
h = ("" + Math)[11]       // '[object Math]'[11]
i = "undefined"[5]
j = "[object Object]"[3]
k = ("" + WeakMap)[12]    // 'function WeakMap() { [native code] }'[12]
l = "false"[2]
m = ("" + Number)[11]     // 'function Number() { [native code] }'[11]
n = "undefined"[1]
o = "[object Object]"[1]
p = ("" + RegExp)[14]     // 'function RegExp() { [native code] }'[14]
q
r = "true"[1]
s = "false"[3]
t = "true"[0]
u = "undefined"[0]
v = ("" + eval)[10]       // 'function eval() { [native code] }'[10]
w = ("" + DataView)[16]   // 'function DataView() { [native code] }'[16]
x = ("" + RegExp)[13]     // 'function RegExp() { [native code] }'[13]
y = ("" + Array)[13]      // 'function Array() { [native code] }'[13]
z

さらに "constructor" という文字列は、標準組み込みオブジェクトのコンストラクタを使わずに作ることができます。 したがって、コンストラクタという名前のプロパティを参照するときに、 "constructor"をそのまま書かなくても実行できるようになります。

g = ("" + ""["" + c + o + n + s + t + r + u + c + t + o + r])[14];
// g = ("" + ""["constructor"])[14];
// g = ("" + "".constructor)[14];
// g = ("" + String)[14];
// g = "function String() { [native code] }"[14];
// g = "g";

とりあえず、ここまでのコードをまとめると次のようになります。

// 数値の作成
d0 = +!![].$;
d1 = +!!{};
d2 = d1 + d1;
d3 = d2 + d1;
d4 = d3 + d1;
d5 = d4 + d1;
d6 = d5 + d1;
d7 = d6 + d1;
d8 = d7 + d1;
d9 = d8 + d1;

// 文字の作成
a = ("" + ![])[d1];
b = ("" + {})[d2];
c = ("" + {})[d5];
d = ("" + {}.a)[d2];
e = ("" + !{}.b)[d3];
f = ("" + {}.c)[d4];
i = ("" + {}.d)[d5];
j = ("" + {})[d3];
l = ("" + ![])[d2];
n = ("" + {}.e)[d1];
o = ("" + {})[d1];
r = ("" + !!{})[d1];
s = ("" + !/$/)[d3];
t = ("" + !{}.f)[d0];
u = ("" + {}.g)[d0];

// 文字列 "constructor" の作成
constructor = "" + c + o + n + s + t + r + u + c + t + o + r;

// 文字の作成2
g = ("" + ""[constructor])[d1 + d9 + d4];
m = ("" + (0)[constructor])[d1 + d9 + d1];
p = ("" + /^/[constructor])[d1 + d9 + d4];
x = ("" + /\\/[constructor])[d1 + d9 + d3];
y = ("" + [][constructor])[d1 + d9 + d3];

// 残りの文字の作成
h = "h";
k = "k";
q = "q";
v = "v";
w = "w";
z = "z";

難読コードの作成例

これまでのコードを生かして、alert(1); を難読化してみましょう。

まず、コードの実行は Function コンストラクタを使います。 Functionコンストラクタは (標準組み込みオブジェクト).constructor.constructor で参照できます。

([].constructor.constructor("alert(1)"))();

文字を難読化するために用意した文字を使って、難読化すると次のような感じになります。

d0=+!![].$,d1=+!!{},d2=d1+d1,d3=d2+d1,d4=d3+d1,
d5=d4+d1,d6=d5+d1,d7=d6+d1,d8=d7+d1,d9=d8+d1;
a=(""+![])[d1],b=(""+{})[d2],c=(""+{})[d5],d=(""+{}.a)[d2],
e=(""+!{}.b)[d3],f=(""+{}.c)[d4],i=(""+{}.d)[d5],j=(""+{})[d3],
l=(""+![])[d2],n=(""+{}.e)[d1],o=(""+{})[d1],r=(""+!!{})[d1],
s=(""+!/$/)[d3],t=(""+!{}.f)[d0],u=(""+{}.g)[d0];
constructor=""+c+o+n+s+t+r+u+c+t+o+r;
g=(""+""[constructor])[d1+d9+d4];
m=(""+(0)[constructor])[d1+d9+d1];
p=(""+/^/[constructor])[d1+d9+d4];
x=(""+/\\/[constructor])[d1+d9+d3];
y=(""+[][constructor])[d1+d9+d3];
h="h",k="k",q="q",v="v",w="w",z="z";
([][constructor][constructor](a+l+e+r+t+"("+d1+")"))();

できました!あとはお好みで変数名をデタラメに書き換えるだけで、難読化コードの完成です!

発展

JavaScriptの難読化に興味のある方は、 jjencodeaaencode あたりを見ると面白いと思います。