スクリプト難読化と闘ってみる

Windows Script Encoder なるものがある。これは、クライアントサイドで実行される JavaScriptVBScriptエンコードするもの。


http://www.microsoft.com/downloads/details.aspx?FamilyID=2976ee94-bec5-4314-84fd-8d7ec891c1c5&displaylang=ja


スクリプトソースコードが第三者から丸見えだ。つまり大事な知的財産がダダ漏れとなる。知的財産っていってしまうと大袈裟かもしれないが、大事なノウハウを隠せず、勝手に改変されてしまう可能性もある。そこで、ソースコードの露出度を抑えようということで開発されたのがこの仕組み。なのだが、最近では悪性コードにこのエンコードがかけられている。解析を困難にさせるような流行りの難読化ってやつだ。このエンコード自体はけっこう昔からあるものなのだが、最近になってよく聞くようになった。

このエンコードは「暗号化」とまではいえないものらしい。まぁ広義でいえば「暗号化」といえるかもしれないが、昨今の暗号強度の発展からいえば、とてもその強度は暗号と呼べるものではない。(あくまで「crypt」や「cipher」ではなく「encode」なのだと勝手に思ってみる)。まぁ、そんなに重くもできるわけないけどね。

MSのサイトにも以下のような文言がある。

エンコードされた内容が悪意のあるユーザーによって解読されるのを防ぐことを目的としているものではありません。


なるほど。じゃ、自分みたいなプログラマのハシクレでもあっさりデコードできるんだろうか。まぁ、Web上を探せばデコードツールなんて山ほどあるんだろう思うけど、ここはあえて、まず自力でトライ。自力で解析できれば目や勘を養うこともできるだろうと。


Windows Script Encoderの使い方はこのあたりを見てもらうこととして、


WSHスクリプト・コードを暗号化する
http://www.atmarkit.co.jp/fwin2k/win2ktips/443wshenc/wshenc.html


ツールの動きを見てみる。以下のスクリプト部分を Script Encoder にかけてみる。

<SCRIPT LANGUAGE="VBScript">
    Dim x
    x = "aa"
    MsgBox x
</SCRIPT>


見事に一見では無意味な形にしてくれる。

<SCRIPT LANGUAGE="VBScript.Encode">#@~^MAAAAA==@#@&d7ifb:PX@#@&7di6~',JCCr@#@&i7dt/o~GaPX@#@&d7VAcAAA==^#~@</SCRIPT>


たぶんシンタックスまで見てエンコードしてないだろう(単にスクリプト部分を文字列としてエンコードしているだけ)と思うので、以下のように単純なパターンを変えてエンコードしてみる。

<SCRIPT LANGUAGE="VBScript">a</SCRIPT>
↓
<SCRIPT LANGUAGE="VBScript.Encode">#@~^AQAAAA==CYQAAAA==^#~@</SCRIPT>

<SCRIPT LANGUAGE="VBScript">b</SCRIPT>
↓
<SCRIPT LANGUAGE="VBScript.Encode">#@~^AQAAAA==8YgAAAA==^#~@</SCRIPT>

<SCRIPT LANGUAGE="VBScript">c</SCRIPT>
↓
<SCRIPT LANGUAGE="VBScript.Encode">#@~^AQAAAA==^YwAAAA==^#~@</SCRIPT>

<SCRIPT LANGUAGE="VBScript">ab</SCRIPT>
↓
<SCRIPT LANGUAGE="VBScript.Encode">#@~^AgAAAA==C(wwAAAA==^#~@</SCRIPT>

<SCRIPT LANGUAGE="VBScript">abc</SCRIPT>
↓
<SCRIPT LANGUAGE="VBScript.Encode">#@~^AwAAAA==C(mJgEAAA==^#~@</SCRIPT>

<SCRIPT LANGUAGE="VBScript">abcd</SCRIPT>
↓
<SCRIPT LANGUAGE="VBScript.Encode">#@~^AwAAAA==C(mJgEAAA==^#~@</SCRIPT>


頭の「#@~」から「==」まではの12バイトはヘッダっぽい。末尾にもフッタのようなものがついている。これも12バイトだとしてこれらを無視すると、「a」→「C」、「b」→「B」、「c」→「^」と単純な1バイトごとの変換か?いや、「ab」→「C(」、「abc」→「C(m」となっているので、そんな単純な話ではなさそうね。

abcde → C(m[
abcdef → C(m[0
abcdefghijklmn → C(m[0TtkN3^hx


こう見えると全体の長さは変えてないようで何となく規則性は見受けられる。でも「a」→「C」、「b」→「(」という単純な変換ではない。

bca → 81l
cba → ^(l
cab → ^m4


になるから。先頭に来る文字は1文字で変換したものと同じだ (「a」→「C」、「b」→「8」、「c」→「^」)。「b」が2文字目にくる場合は「(」だ。「a」が2文字目にくる場合は「m」だ。では.....

aaa → Cml
bbb → 8(4
ccc → ^1m


お、やはり。もしかして、何か決まったパターンと論理演算しているのでは?引き続き...

aaaaaa → CmlCml
bbbbbb → 8(48(4
cccccc → ^1m^1m
aaaaaaaaa → CmlCmlmll
bbbbbbbbb → 8(48(4(44
ccccccccc → ^1m^1m1mm
aaaaaaaaaaaaaaaa → CmlCmlmllmlmClml
bbbbbbbbbbbbbbbb → 8(48(4(44(4(84(4


やはりそうっぽい。ではマスクしているビットパターンは何だろう。

「a」→「0x61」→「0110 0001」
              XOR  0010 0010
「C」→「0x43」→「0100 0011」
        
「b」→「0x62」→「0110 0010」
             XOR   0001 1010
「8」→「0x38」→「0011 1000」

「c」→「0x63」→「0110 0011」
             XOR   0011 1100
「^」→「0x5E」→「0101 1111」


あああああ....一致しない......微妙にマスクするビットパターンを変えているのか。