たのしい難読化のお話
まえがき
20歳になりました
はじめに
これは香川大学工学部サークルSLPのアドベントカレンダー15日目の記事です。
SLP KBIT Advent Calendar 2017 - Adventar
みんなだいすき難読化のおはなし
難読化ってなんだろ?
難読化コード 難読化コード(Obfuscated code)とは、コンピュータプログラムにおいて、その内部的な動作の手続き内容・構造・データなどを人間が理解しにくい、あるいはそのようになるよう加工されたソースコードやマシンコードのこと。 wikipediaより引用
JavaScriptなどのスクリプト言語では、ソースコードが簡単に見えてしまうので、まずいよねってこと。
できるだけクライアントサイドの処理を減らすか、今回の難読化という方法が取られます。
難読化コードってどんなの?
難読化には様々な手法がありますが、おもにJavaScriptでは次の3つの手法が取られます。
- 変数名を変数名や値をわかりづらくする
- ダミーのコードを埋め込む
- Ajaxなどで動的にコードを生成する。
この内、3つ目のAjaxは、クライアントだけで完結するのは難しいです。
今回は1つ目にしぼって難読化コードを考えてみます。
変数名の難読化
JavaScriptの変数名には「_」や「$」が使用できるのはご存知かと思われますが、
じつは一部の2バイト文字も利用することができます。
var ぞば=5; console.log(ぞば)//->5
漢字も対応してますので「鬱」や「呪」とかの漢字を使い、 読む人の精神を攻撃するのもいいかもしれませんね
あとは、1つのオブジェクトに変数をまとめるのも難読化では用いられます。
var 呪 = {呪:{呪:{呪:{呪:'呪'}}}}; console.log(呪.呪.呪.呪.呪)
うわぁ・・・
ドン引きですね、こんなのがコード中にあると、一体どんな気持ちでコードを書いていたのか心配になります。
さて、JavaScriptではオブジェクトのキーへのアクセスに[]を使用することができます。
window.document.bodyにアクセスするときに、
var a = "document" var b = "body" window[a][b]
とも書くことができます。これはショートコーディングにも使われる機能です。
後述する「値の難読化」と組み合わせると恐ろしい効力を持ちます。
変数名の難読化の面白いアプローチとしては、読む相手が人間ということを利用します。 変数名と内容を一致させないというのも難読化としては有効になります。
値の難読化
変数名の難読化ができても、値がそのままだとちょっと読みづらい程度で終わってしまいます。
ソースコードに文字列や数字がそのまま書いてあるのではあまり良くありません。
しっかり値も隠しちゃいましょう
数字の難読化
ここでは整数の場合に絞って考えてみます。
0以外の整数が1つ以上あれば、すべての整数を表現することができます。
ここでは、3の場合で考えてみます。
3-3 = 0
3/3 = 1
が出せます。あとはビットシフトなどをつかえば全ての数字を表現することができます。
例えば15なら
(3<<3/3+3/3)+3
などの表現を使用することができます。
ホーナー法を使えば自動で変換もできますね。
ホーナー法 - Wikipedia
また、~[]が-1になることを利用して、ソースコードから数字を消し去ることもできます。
文字列の難読化
難読化のキモです。文字列をどう処理するかで難読化の質が変わってくると言っても過言ではありません。
主に
- decodeURIでパーセントエンコードされた文字を戻す
- 「true」、「false」などを文字列に変換して使う
- String.fromCharCodeで文字コードを文字に戻す
- 「\uxxxx」を使う
があります。
1つずつ解説していきます。
decodeURIでパーセントエンコードされた文字を戻す
パーセントエンコードとは「%E3」など、URLなどで用いるためのエンコード形式です。
普通は、2バイト文字のエンコードに使用しますが、実は英数字もエンコードできます。
例えば「Gatrin」は「%47%61%74%6C%69%6E」とかけます。
変換は、以下のコードを参考にしてください。
"%"+"Gatlin".split('').map(w=>w.charCodeAt(0).toString(16).toUpperCase()).shift('%').join('%')
「true」、「false」などを文字列に変換して使う
これが一番強力です。こいつをうまく使うとコードからアルファベットが消えます。
JavaScriptでは「""+」と書くことで文字列に変換することができます。
これをうまく使い、「constructor」などの一部の文字を生成することができます。
(""+{})[5]+(""+{})[1]+(""+undefined)[1]+(""+false)[3]+(""+true)[0]+(""+true)[1]+(""+true)[2]+(""+{})[5]+(""+true)[0]+(""+{})[1]+(""+true)[1]
これは数値の難読化と合わせると非常に強力な難読化手法になります。
String.fromCharCodeで文字コードを文字に戻す
これは簡単です。String.fromCharCodeを呼び出すと文字コードを文字似直すことができます。
String.fromCharCode自体を1つ前の型変換を利用して隠してしまうと、すべての文字を難読化できます。
「\uxxxx」を使う
JavaScriptの文字列では、\uから始めることによって文字コードに対応した文字を作ることができます。
ただし、"\"+"uxxxx"などで連結してもタダの「\uxxxx」という文字にしかなりません。
evalなどで連結した文字をコードとして実行してやることによって目的の文字として取り出すことができます。
難読化の自動化
さて、これらの難読化を手動でやるのはさすがに無理です。
ある程度自動化する必要があります。
コードの変換には2つ方法があって
- 構文解析を使う
- コードを文字列として難読化する
構文解析を使う
Esprimaという構文解析用のモジュールがあります。
構文解析木をなぞりながら変数名、値を難読化していきましょう。
Esprima: Parser
実際に作ってみたのがこちら
GitHub - pekko1215/mochiscript: なんどくかだー
node index.html 入力ファイル 出力ファイル
で実行できます。
難読化始めた初期に作ったので、String.fromCharCodeの難読化が不十分ですね
コードを文字列として難読化する
こっちは簡単です。コード自体を生成するJavaScriptのコードをつくり、
evalやsetTimeoutなどで実行します。
実際に作ってみたのがこちら
GitHub - pekko1215/TrieteliScript
これはソースを「3」と記号に置き換えて難読化します。
なんで3なのかというと3周年だからです
eval、setTimeroutの代わりに「(3).constructor.constructor(コード)()」を利用してます。
最後に
いかがでしたか?JavaScriptのような規則が比較的ゆるい言語はいろいろな書き方をすることができます。
上に上げた以外にもいろいろな難読化の手法があります。難読化コードは作るのも楽しいですが読むのも楽しいです。
たまにはWebページのソースコードを覗いてみるのもいいかもしれません(๑•﹏•๑`)ぷえ~っ