shuto_log.aep

自分がやったことなどを備忘録的な感じで残していこうと思います。

難読化された .jsxbin ファイルを読んでみる

はじめに

この記事はAfterEffects Advent Calendar 2016の11日目の記事です。

本エントリーでは、ExtendScriptの難読化ファイルである .jsxbin ファイルの解析方法及びその結果を扱った英語の記事を解説しつつ翻訳・紹介していきたいと思います。(99%元記事の二番煎じですが、自分もハンズオン的な感じで手を動かして試してみました)

想定している読者層は、プログラムを書ける人です。特定のフレームワークや特定の言語、に精通している必要はありませんが、AE上で何かしら自分で関数を作り、自動化処理などさせたことがある(ExtendScriptを少し書いたことがある)くらいだとより理解しやすいかもしれません。ただし、途中から思考実験のような内容になっていきます。プログラミング力というよりも、推測力が求められるかもしれません。。。 (結局11時間くらいぶっ続けで書いたのでくだらない誤字・誤りなどあるかと思います。意味わからないところなどあればTwitterかコメントなどでお知らせ・ご質問ください。)

元記事の紹介

まずはじめに元記事のリンクを紹介します。

Reversing JSXBIN File Type

元記事は、ExtendScriptの難読化された .jsxbin ファイルの解析方法と、その結果を紹介しています。
通常、AEのスクリプト.jsx という拡張子で記述し、保存します。普通に自分で書いて使ったり、そのまま人に配って共有する場合は全く問題ないのですが、極秘の技術やアルゴリズムを用いて作成したスクリプトを配る場合や、商品として販売する場合はこれだとソースコードが丸見えで困ります。(同じ方法を真似されてしまったり、改変してあたかも自分で書いたかのように販売されてしまうかもしれません。)
その為、Adobe公式のエディターであるExtendScript Toolkitには人間には読めないけど実行は可能な形式( .jsxbin )に変換する機能が備わっています。元記事では、その「 .jsxbin ファイルを無理矢理読解してやろう」という記事です。
結果としては、完全には復元は出来ないのですが、一部が復元出来てしまいます。本エントリーでは、その内容を日本語で紹介してみたいと思います。当然のことながら、実際に販売・配布されているjsxbinファイルを解析し、その内容を公表したりしてはいけません。飽くまで自分で作成したスクリプトにだけ使うようにしてください。
それでは、元記事の流れを踏襲しつつ、日本語で紹介していきたいと思います。(jsxbinだったりJSXBINだったり表記が揺れることがありますが、主に僕の言葉で書いているのはjsxbinという小文字表記です。JSXBINという大文字表記は元記事に合わせた書き方で、元記事を翻訳した箇所などで使っています。)

JSXBINファイルとは

JSXBINという拡張子はJSX、BINという文字列から分かるように、JSXファイルをバイナリ化させたものです。JSXはAdobe ExtendScriptの標準の拡張子であり、基本的にJacaScriptを拡張したものです。一方、BINはよくバイナリファイルにつけられる拡張子です。
JSXBINファイルについて、Adobe「jsxbinファイルはJavaScriptコンパイルしたものです。バイナリ形式であるために、ソースコードは分かりません。」 と言及しています。 そしてそのドキュメントでは、JSXBINファイルが出来た2つの理由が綴られています。

しかし、これから実際jsxbinファイルを解析していくにつれて、それはただ単にjsxファイルを難読化したものに過ぎないこと、いくらか頑張ればソースコードは理解できてしまうこと、そしてもっと頑張れば完璧にjsxbinファイルをjsxファイルに戻せてしまうかもしれないということが分かってきます。

難読化とコンパイルの違い、バイナリファイルとは

ここで注釈を挟みます。まず、冒頭で何度も出てきたワードを説明します。

難読化とは

難読化とはそのままの意味で、「ソースコードの意味を変えずに読みづらくすること」です。例えば、ExtendScriptでは var activeComp = app.project.activeItem; という記述をよくしますよね。これは見ただけで activeComp に今選択しているコンポジションが入っていることが分かります。しかし、これを var kdjghahkhj = app.project.activeItem; としてもスクリプトとして成立しますし、正常に動作するはずです。このように、人間が理解し難いような無意味な文字列などに置き換える事を 難読化 といいます。置き換えただけなのでプログラムが行う処理は基本的に同じで、ほぼ動作に影響はありません。

コンパイルとは

C言語JavaScriptと言った一般的な言語(高級言語と呼ばれます)では、人間が理解しやすい文法が設計されています。例えば「 forwhile と言った文字列を記述するとループ構文になる」などと決められていますよね。しかし、コンピュータはこれをそのまま理解することは出来ません。コンピュータにはコンピュータ専用の機械語というものがあります。では、その高級言語機械語の橋渡し(翻訳)は誰がするのでしょうか。それをしてくれるのがコンパイラというソフトウェアで、高級言語機械語に翻訳することを一般的に「コンパイルする」といいます。厳密には「高級言語アセンブリ言語機械語」という流れがあり、アセンブラというソフトウェアも登場するのですが、ここでは省きます。ここで重要な難読化との違いは、高級言語機械語に翻訳してしまったので、その対応は一対一では無いことです。Google翻訳で日本語を英語にして、それをまた日本語に翻訳しても一字一句同じに戻ってくれないことが多いように、 一度コンパイルされてしまうと元のソースコードを復元するのはとても難しい です。

バイナリファイルとは

バイナリファイルとは、広義的に扱われることも多く、一言で説明するのは難しいのですが、ここでは コンパイル済みの機械語をまとめたファイル ということにしましょう。Windowsでは .exe ファイルですね。機械語になっているので、もはや人間の理解できる文字列ではありません。一般的に .bin という拡張子は、WindowsMacLinuxなどコンピュータの種類に問わず使われるもので、テキストエディタ等で開いても人間には理解できないようなデータにつけられる拡張子です。
f:id:shutosg:20161210213127p:plain
↑手元にあった適当な.exeファイルをテキストエディタで開いた図。文字化けしてしまっていて、そもそもテキスト形式のデータではない事が分かる。
※注釈ここまで。話に戻ります。
つまり、Adobeは公式アナウンスとしては「jsxbinファイルはコンパイル済みのバイナリファイルである」と発表していますが、元記事の実験を進めていくと、実際は単に難読化しただけである事が分かるようです。

JSXBINファイルの作成方法

ESTKをインストールしたら、JSXファイルをバイナリ化する方法を見ていきましょう。

  1. ESTKを開いて新しいファイルを開く
  2. Ctrl+Sで保存する(任意)
  3. JSXBINをエクスポートするためにCtrl+Shift+Eを押すか、 ファイル メニューから関係するメニューを選択します。
    f:id:shutosg:20161210213233p:plain

jsxbinはバイナリではない

上述の通り、厳密にはjsxbinファイルはバイナリ形式ではありません。というのも、jsxbinファイルをテキストエディタで開くと、人間にパッと見で読めない文字列ではあるものの、文字化けは一切していません。どうやらASCIIコードで定義されている範囲の文字を用いてるようです。厳密な「バイナリ化」ではなく、Base64エンコードのように、特定の規則に従って単に難読化されたjsxファイルであるようです。進んでいくと分かるのですが、元のjsxファイルをjsxbinに変換すると、ある文字に対してはいつも必ず決まった文字での置き換えが行われるのです。

Base64について

完全な寄り道です。興味がある人以外はここは飛ばしてください。元記事でも言及されているように、jsxbinファイルのように、「ある規則に従って元のデータ(文字列や画像など)を変換する」手法にBase64というものがあります。この手法は常に「元の文字列⇔Base64で変換した文字列」の相互変換が可能です(いわゆる可逆変換)。僕がいまある文字列をBase64で変換(エンコード)しました。興味がある方はこの文字列をBase64を使って復元(デコード)してみてください。エンコード・デコードのツールはオンラインツールがたくさんありますので探してみてください。

WW91IGRpZCBpdCEhIE5vdywgeW91IGhhdmUgZGVjb2RlZCBTaHV0bydzIGJhc2U2NCB0ZXh0ISEhIQ==

開始点

さて、解析を始めていきましょう。まずは空っぽのjsxファイルをjsxbinファイルに変換してみます。中身を見てみるとこんなテキストになっています。
f:id:shutosg:20161210221157p:plain

@JSXBIN@ES@2.0@MyBn0DzABByB

次にたった一文字 a と入力してjsxbinを出力してみます。(以降画像を省きます)

@JSXBIN@ES@2.0@MyBbyBn0ABJAnAjzBjBBf0DzACByB

この差分を取れば、jsxでの a を意味するjsxbinでの文字列を推測することが出来ます。
これをたっくさんの文字に対して繰り返し行うことで、jsxbinの文字列置換のプロセスが見えてくるわけですね。ここでは、次のことが分かりました。

  • JSXBINファイルは @JSXBIN@ES@2.0@ で始まり、これは何かしらの説明用のヘッダの役割をしているようだ。(注釈:AEがこれを読み込む際、この文字列をみて「これはjsxbinファイルなんだ」と判断しているということでしょう)
  • 説明用のヘッダにつづいて、いつも MyB という文字列が続いていた。
  • 最後は必ず ByB の文字列だった。JSXBINファイルの終端を指しているようだ。

次のステップ

さて、前章で説明用ヘッダと最初の3文字、そしてJSXBINファイルの終端を表す3文字を明らかに出来ました。次のステップでは、異なる文字や記号をどのように表すのか、文字と記号の組み合わせはどのように表すのかを見ていきましょう。
例えば、上でやった一文字だけのスクリプトを拡張して、 aaaaaa などでjsxbinを書き出して比べます。その後、同じことを他の文字でやったり、それぞれの文字を異なる行に書いてみたりもします。そのようにして文字の置き換えパターンを明らかにできたら、(ExtendScriptとしての)オブジェクトの置き換えパターンや変数、関数、制御構文などについての置き換えパターンも調べていきましょう。

アルファベットの表現

上記で述べた様々な組み合わせでの文字列のjsxbinを書き出して調査した結果、下記のような文字の変換表を作り上げることに成功しました。小文字と大文字でもちろん区別がされており、小文字は j[B-Za] で表されます。(注釈:ここで、 j[B-Za] とは、正規表現を用いた表し方がされています。 j[B-Za] の意味は jB , jC , jD , jE , … jY , jZ , ja という意味です。)
つまり、小文字については a = jB から始まり、 z = ja で全てが表されます。大文字は j の代わりに i が使われます。つまり A = iBZ = ia となります。
また、文字が何文字続くのかを示す文字(カウンター値と呼ぶことにします)も明らかになりました。例えば ab のように一文字のみの場合は jBiC の直前に B というカウンター値が入ります。 b を表す場合は BjC となり、それ以降に続く文字が増えるほど、文字列の直前に付くカウンター値は CDE 、となっていきます。つまり、 Test という文字列は T = iUe = jFs = jTt = jU であり、4文字を表すカウンター値が E なので、結局 EiUjFjTjU と置換されることになります。

小文字 jsxbinでの表記 大文字 jsxbinでの表記
a jB A iB
b jC B iC
c jD C iD
d jE D iE
e jF E iF
f jG F iG
g jH G iH
h jI H iI
i jJ I iJ
j jK J iK
k jL K iL
l jM L iM
m jN M iN
n jO N iO
o jP O iP
p jQ P iQ
q jR Q iR
r jS R iS
s jT S iT
t jU T iU
u jV U iV
v jW V iW
w jX W iX
x jY X iY
y jZ Y iZ
z jz Z iz

この文字列変換表は、変数名や関数名などにおいて使えます。 String 形式のデータに対しても同じ変換表が使えますが、 Fe という文字列が直前に置かれるようです。
ここまでをまとめると、変数名や関数名に Test という名前をつけた場合、jsxbinでは上述した通り EiUjFjTjU と置換され、Stringの文字列データとしてjsxに "Test" と記述をした場合、それは FeEiUjFjTjU となるようです。つまり、 Fe という文字列が現れたら、それに続く文字列はStringデータだぞ、という目印なわけですね。

特別な記号について

ここまで文字の置換について見てきましたが、他の記号についても同様に置換が行われます。特に解説・翻訳する内容も無いので、変換表は元記事SPECIAL CHARACTERS REPRESENTATION を参照ください。

数値の表現

数値の表現も同様に解析していきます。文字の時と同じようにまずは1桁、次に複数桁、複数行、、、と調べていきます。以下は 1 だけでjsxbinを出力した例です。(jsxbinを表す説明用ヘッダは削除しています。)

MyBbyBn0ABJAnAFdB0EzABByB

上記の出力結果から、 1dB に置き換わっていることがわかります。しかし、大文字小文字の違いとは異なって、Stringデータの "1" は異なる hQ という文字列で置換されています。(ここでも変換表の掲載は省きます。見たい方は元記事DIGITS REPRESENTATION を参照ください。)
ここで分かった大きなことは、String文字列の数字 "22192" と数値としての 22192 でjsxbin表記に変換された際に、その法則に明確な違いがあるということです。
文字列の "22192" はStringを意味する Fe と5文字を表すカウンター値の F に加え、 "1" = hR "2" = hS"9" = hZ として Fe + F + "2" + "2" + "1" + "9" + "2" = FeFhShShRhZhS となります。(元記事は FeFhShShZhRhS となっていますが、1と9を逆にしてしまっているようです。こちらは実際に試してみたので確実です。)
一方で、数値としての 22192d2lQiW と表されます。(元記事では d2kAiZ とありますが、これも1と9が入れ替わった 22912 のjsxbin表記です。)
(注釈:数値として数字のjsxbin表記は少々難解で、数字が増えるに連れてjsxbin表記の文字列の桁数も増えていきます。しかし、やはり規則性を持って増加しているのは見て取れますので、頑張れば一般化して変換の処理をプログラムとして記述し、自動化できそうです。元記事でも諦めたのか、詳しくは言及されていませんでした。)
この数値としての数字は、前述の文字列や変数名、関数名の文字数を表すカウンター値や後述の行カウンターなどでもしばしば登場しますが、私(元記事の筆者)の理解では、始まりの値(注釈:先の数値の例では 1, 2, 3... = dB, dC, dD... なので dB が開始値)はそれぞれのカウンターで同じというわけではありませんでした。(注釈:始まりは違うのだけど、同じような規則性を持って増えていくということでしょう)共通点といえば、32毎に桁あふれ(つまり 31 → 32dgf → dhA のように、 dgg にならずに桁が繰り上がっている)しているという点くらいです。

さて、ここまでのことをまとめると、「単語(数字・記号を含まない純粋な英文字列)」のjsxbin表記の一般化ができそうです。元記事では相変わらず正規表現で示しています。
([A-Z]|([g-z][A-Fa-f]))[[ij][B-Za]]*
これは元記事を見てもらったほうが色付けされていて分かりやすいのですが、
([A-Z]|([g-z][A-Fa-f])) [[ij][B-Za]]*
という構造になっていて、最初の ([A-Z]|([g-z][A-Fa-f])) がカウンター値を表します。つまり、単語の最初には A, B, ...Z 一文字かgA, gB, gC, ...gF, ga, ...gf, hA, hB, ... といった2文字のカウンター値が来て、その後に大文字なのか小文字なのかによって iB, iC, ...jB, jC... といった正味の文字部分を表す文字列が来ます。2文字目以降はカウンター値いらずで文字部分だけが続きます。

文字カウンター

ここまでで既にカウンター値の話はしました。今までは 「これから続く文字の数」 を表すために カウンター値 という言葉を使ってきましたが、その概念を拡張して、今までカウンター値と読んできた値をこれからは 「文字カウンター」 と呼ぶことにしましょう。呼び方が変わるだけなので難しいことはありません。 Test という文字列を表したければ4文字なので、文字列の前に E という文字カウンターを挿入するだけです。

行カウンター

さて、別の例を見ていきましょう。同じ文字を複数行に渡って書かれたJSXBINファイルを開いて、改行を表す箇所を探してみましょう。

"line1"
"line2"
"line3"

上がjsxファイルで、下がjsxbinファイル。

@JSXBIN@ES@2.0@MyBbyBn0ADJAnAFeFjMjJjOjFhRJBnAFeFjMjJjOjFhSJCnAFeFjMjJjOjFhT0DzA
BByB

読みやすく整形します。最初と最後を消してしまって、それっぽい箇所で改行してみます。また、Stringを表す Fe などもスペースで区切ってみます。

MyBbyBn0AD
JAnA Fe FjMjJjOjFhR("line1")
JBnA Fe FjMjJjOjFhS("line2")
JCnA Fe FjMjJjOjFhT("line3")
0DzA

ここで、さらに最初と最後を削ってみます。

JAnA Fe FjMjJjOjFhR("line1")
JBnA Fe FjMjJjOjFhS("line2")
JCnA Fe FjMjJjOjFhT("line3")

ここまでくれば行頭の J[A-C]nA が怪しいことが容易に想像できますよね。元記事では更に改行をして "line33" まで調べたようです。すると、 JnA の間の文字が行番号を表しているのではないかと睨むことができました。

J Z  nA Fe GjMjJjOjFhShW("line26")
J ga nA Fe GjMjJjOjFhShX("line27")
J gf nA Fe GjMjJjOjFhThS("line32")
J hA nA Fe GjMjJjOjFhThT("line33")

次に、さらにこの間の文字に注目していくことになります。まずは、それぞれの文字を10進数のASCIIコードに変換してみます。ASCIIコードとは、(語弊がありますが)文字の機械語と想像すると良いでしょう。機械は数字(極論を言えば0と1)しか扱えないので、人間にとっての文字は機械にとっては全て数字で表されるのです。

Z(26)    90
ga(27)103 97
gf(32)103 102
hA(33)103 65

さて、どうして26(行目)を表しているZの次はASCIIコードでいう91番の [ やそれに続く92番の \ を使わずに二桁にしているのでしょうか。それはきっと単にJSXBINファイルでは[A-Za-z0-9]の値のみを使いたいからではないでしょうか。

カウンター一覧表

省略します。元記事THE COUNTER TABLE を参照ください。ここでもやはり分かるのは、32毎に桁が繰り上がっているということです。

変数

ここまでの章で、文字の違い、データ形式の違い(数値 or String)、そして変数名などの文字数や行数を示す仕組みを明らかにしてきました。
ここでは変数の宣言について見ていきましょう。

var a=2;
a=1;
a;
var b=null;
b=1;
var test=1;
test = a;

↑をjsxbinにして↓

@JSXBIN@ES@2.0@MyBbyBn0AHJAnASzBjBByBndCftJBnASByBndBffJCnAVBfyBJDnASzBjCCyBnbft
JEnASCyBndBffJFnASzEjUjFjTjUDyBndBftJGnASDyBVBfyBnffADB40BiAC4B0AiAD4C0AiAADAzA
EByB

さらに見やすく省略・整形します。

MyB byB n0AH
JAnA Sz BjB ByB ndCft
JBnA S ByB ndBff
JCnA V BfyB
JDnA Sz BjC CyB nbft
JEnA S CyB ndBff
JFnA Sz EjUjFjTjU DyB ndBft
JGnA S DyB V BfyB nff
AD B40Bi AC 4B0Ai AD 4C0Ai AAD
AzAE ByB

さらに抜き出し、記号を付けていきます。

(a) JAnA Sz BjB ByB ndCft
(b) JBnA S ByB ndBff
(c) JCnA V BfyB
(d) JDnA Sz BjC CyB nbft
(e) JEnA S CyB ndBff
(f) JFnA Sz EjUjFjTjU DyB ndBft
(g) JGnA S DyB V BfyB nff

それぞれのブロックの意味は次のような事が推測されます。

Line Block #1 Block #2 Block #3 Block #4 Block #5 Block #6
(a) J A nA Sz BjB ByB ndCft
行カウンターの A1 Sz は変数が定義されると出てくる a 一文字を表す a への参照を表すようだ dC2
(b) J B nA S ByB ndBff
行カウンターの B2 変数に値を代入するという意味 どの変数に値を代入するか(ここでは a を示す) dB1
© J C nA V BfyB
行カウンターの C3 V は変数が参照されると出てくる BfyBByB とよく似ていて、変数を参照するための文字列かもしれない
(g) J G nA S DyB V BfyB nff
行カウンターの G7 変数に値を代入するという意味 どの変数に値を代入するか(ここでは test ) V は変数が参照されると出てくる BfyB は変数 a への参照 nff は分からないまま。

最後の行の AD B40Bi AC 4B0Ai AD 4C0Ai AAD も重要な行だが、意味はわからないままだ。似たような行が変数を使う際にしばしば出てくる。

以下、しばらく注釈です。
見比べやすいように各行のjsxも再掲します。

(a) var a=2;
(b) a=1;
(c) a;
(g) test = a;

var a = 2; は行頭を抜かすと Sz BjB ByB ndCft となるわけですが、 Sz で「変数を定義」、 BjB で「変数名を a に」、「変数aへの参照を ByB 」とし、 ndCft より「 2 を代入」していると見られます。
a;test = a; のように、何か変数(ここでは a )の中身を参照する際には V と記述し、その後に参照したい変数への参照 ByBf という文字を挟んで BfyB と記述する決まりになっているようです。
また、別の例として個人的に実験もしてみました。

var a=2;
a=1;
a;
a = a + 1;
a = a + 2;
a = a + 3;
a = a + 4;
a = a - 2;
a = a * 3;
var b = 3;
b = b / 1;
b = b % 2;
b = b - 3;

これをjsxbinにして整形すると次のようになります。

JAnA Sz BjB ByB ndCft
JBnA S ByB ndBff
JCnA V BfyB
JDnA S ByB CzBhLC V BfyB nndBnff
JEnA S ByB CC V BfyB nndCnff
JFnA S ByB CC V BfyB nndDnff
JGnA S ByB CC V BfyB nndEnff
JHnA S ByB CzBhND V BfyB nndCnff
JInA S ByB CzBhKE V BfyB nndDnff
JJnA Sz BjC FyB ndDft
JKnA S FyB CzBhPG V FfyB nndBnff
JLnA S FyB CzBhFH V FfyB nndCnff
JMnA S FyB CD V FfyB nndDnff

これより、四則演算の記号も推測することが出来ます。

新出時 2度目以降
+ [C-Za-z]zBhL[C-Za-z] 両端2文字(例: +CzBhLD なら CD
- [C-Za-z]zBhN[C-Za-z] 両端2文字
* [C-Za-z]zBhK[C-Za-z] 両端2文字
/ [C-Za-z]zBhP[C-Za-z] 両端2文字
% [C-Za-z]zBhF[C-Za-z] 両端2文字

注釈ここまで。

関数

空っぽの関数から見ていきましょう。

空っぽの関数

function a(){}
function b(){}
function c(){}

jsxbinにして整形するとこんな感じ。

MyB byB nAD
MAn0 Dz BjB B0A
MBn0 Dz BjC CAB
MCn0 Dz BjD DAC
0EzAE ByB

これから分かることは、

  • 最初のブロック M[A-C]n0 は行カウンターととても似ている
  • 関数を宣言する時に Sz によく似た Dz が出て来る。怪しい。
  • Bj[B-D] は明らかに関数名である
  • 最後のブロックは部分的に関数名(最初の一文字)と何かしらのカウンターを表している可能性がある

です。次に、空っぽだけれども複数行に渡っている関数を見ていきましょう。

1) function a(){
2)    
3)    
4)    
5)    
6)    }
7) function b(){
8)    
9)    
10)    
11)    
12)   }

注意:行番号は見やすくするためにつけています。実際にJSXスクリプトに記述しているわけではありません。
そしてJSXBINにしたものがこちら。(a)、(b)もこちらでつけています。

MyB byB nAC
(a) MAn0 Dz BjB BAF
(b) MGn0 Dz BjC CAL
0EzAD ByB

(a)について最初のもの( MAn0 Dz BjB B0A )と比べてみましょう。最後の2文字が変化しています。そして2つめの(b)関数についても( MBn0 Dz BjC CAB )みてみると、2文字目と最後の2文字が変わっています。ソースコードの違いは、改行が有るか無いかだけです。
したがって、2文字目は関数が宣言され始める行( function a(){ )を表しており、最後の文字は関数定義が終わる行( } )を表しているようだ。

関数呼び出し

さらに複雑な関数宣言を見る前に、複数の関数を扱う例を見てみましょう。

function xxa(){}
xxa();
function bbb(){}
bbb();
function ccc(){}
ccc();
function ddd(){}
eee();
function eee(){}
ddd();
xxa();
bbb();
ccc();
ddd();
eee();
xxa();
bbb();
ccc();
ddd();
eee();
xxa();
bbb();
ccc();
ddd();
eee();

JSXBINにすると…

MyB byB nAF
MAn0 Dz DjYjYjB B0A
MCn0 Dz DjCjCjC CAC
MEn0 Dz DjDjDjD DAE
MGn0 Dz DjEjEjE EAG
MIn0 Dz DjFjFjF FAI 
U
JBnA EjB fnf
JDnA EjC fnf
JFnA EjD fnf
JHnA EjF fnf
JJnA EjE fnf
JKnA EjB fnf
JLnA EjC fnf
JMnA EjD fnf
JNnA EjE fnf
JOnA EjF fnf
JPnA EjB fnf
JQnA EjC fnf
JRnA EjD fnf
JSnA EjE fnf
JTnA EjF fnf
JUnA EjB fnf
JVnA EjC fnf
JWnA EjD fnf
JXnA EjE fnf
JYnA EjF fnf
0DzAG ByB

JSXBINを解析することで、関数宣言は関数呼び出しよりも先に来るように並び替えられることが分かりました。結果として、全ての関数の宣言がJSXBINファイルの最初に組み込まれるのです。( MJ の次の文字が行番号を示していることも注目しておきましょう。)
関数呼び出しの構造についても理解することが出来ました。

  • 行頭のブロックは行番号を含んでいる J[counterValue]nA
  • 2番目のブロックは関数への参照を含んでいる( Ej[reference]
  • 最後のブロックは不明
  • 一つだけ含まれる U も不明

最初の関数( xxa() )を例に、2つめのブロックに注目してみましょう。関数 xxa() の定義は MAn0 Dz DjYjYjB B0A であり、その呼出は JBnA EjB fnf です。関数宣言の最後のブロックの最初の文字( B )は、関数呼び出しの2つめのブロックの最後の文字と同じです。したがって、それらは参照用の値と思われ Ej は一般的な関数呼び出しの方法か何かであるに違いありません。

複数行関数

更に色んなコンテキストの関数を分析していきましょう。色々変な要素を挿入してみました。

1)   function a(){
2)      //test1
3)      "alpha"
4)      //test2    
5)      }

こんなJSXBINに変換されます。

MyB byB nAB
(a) MA byB n0 AB
(b) JCnA FeFjBjMjQjIjB 0 
(c) Dz BjB BAE
0EzACByB

行 (a) について最初の例( MAn0 Dz BjB BAF )と比較してみましょう。かなり違いが出てきました。
- 行頭の行番号を示す MAn0 の間に byB が打ち込まれて MA byB n0 となった - AB という新しい値が出てきた

他にも2つの変更点が - 行2)と行4)はJSXBINには登場しない。つまり、コメントはJSXからのエクスポートの際に完全に無視されるということだ。 - 行(b)は予想通り "alpha" という文字列だ

行©は最初の例の残りの関数宣言部分( MAn0 Dz BjB BAF )です。最後の F の文字は数字の 5 に等しく、関数宣言が終わった行番号を示している。

さて、関数が宣言された行と別な下の方の行で呼ばれる例を見ていきましょう。

1) function a(){
2)     //test1
3)     "alpha"
4)     //test2    
5)     }
6)
7) a();

JSXBIN変換後

MyB byB nAB
(a) MA byB n0 AB
(b) JCnA FeFjBjMjQjIjB 0
(c) Dz BjB BAE
(d) B
(e) JGnA EjB fnf 
0DzAC ByB
  • 行(a)~©は上の例と全く同じ
  • 行(d)の意味が今のところ不明
  • 行(e)が関数呼び出し。参照値の B がまたも行©の最後のブロックの最初の文字と一致している。

これまでの例はとても簡単で、複数のコンテキストを持っていませんでした。それでは、関数内で変数定義をしてみましょう。

1) function a(){
2)      //test1
3)      var non=null;
4)      //test2
5)      }

JSXBIN変換後

MyB byB nAB
(a) MA byB n0 AB
(b) JCnA Sz DjOjPjO BA nbft
(c) AB B40Bi AAB
(d) Az BjB CAE 
0EzAD ByB

行(b)が変数宣言の行です。 変数 の章でも話した通り、未だに何なのかよく分かっていない行がある。ここでは行©がよく似ている。他の違いは行(d)に見られるように、もともと Dz だった箇所が変数宣言を下とともに Az に変わってしまった。

最後の例として、他の変数を関数に追加してみよう。

1) function a(){
2)     //test1
3)     var non=null;
4)     var alpha="alpha";    
5)     //test2
6)    }

JSXBINに変換すると

MyB byB nAB
(a) MA byB n0AC
(b) JCnA Sz DjOjPjO BA nbft
(c) JDnA Sz FjBjMjQjIj BCB neFjBjMjQjIjB ft
(d) AC C 4B0Ai A B40Bi AAC 
(e) Az BjB DAF
0EzAE ByB

この例では、行©が新しく追加された変数 alpha に文字列 "alpha" を代入している。同時に行(d)にも変化があった。元々 AB B40Bi AAB だったのが、 AC C 4B0Ai A B40Bi AAC に変わった。

  • 最初のブロックの2文字目が B から C に変わった。何かのカウンターなのかもしれない。
  • そして3つの新しいブロック C 4B0Ai A が挿入された。
  • 最後のブロックの最後の文字も B から C に変わった。最初のブロックの変化と関係があるのかもしれない。

関数のまとめ

JSXからJSXBINへの変換には確かに多くのパターンが有り、全てのアルゴリズムを完璧に復元するには大変な時間がかかります。これはとりわけ変数宣言における表立たない内部的な処理に原因があります。
ここまでを要約すると、

  • 関数を識別する M[A-Z] の文字列は、ソースコードのどこで宣言が始まったかを示している
  • 最後の行で関数の名前とその参照が示された後、宣言が終わる行番号も記される
  • 関数呼び出しは常に Ej[referenceLetter] の形式である。

jsxbinファイルに含まれないもの

jsxbinに含まれずに捨てられる情報もある。

  • コメント
  • 行末のセミコロン
  • ブラケット

個人的考察

これらを理解すると、そこそこの文字列の復元は出来てしまうでしょう。あまりこういった例は少ないかと思いますが、例えばjsxbinスクリプトに何かのurlが含まれていたり、TwitterやSlackといったウェブサービスAPIキーやシークレットと呼ばれる鍵情報を含ませている場合、jsxbinファイルを開いて http を表す jIjUjUjQ だったり、 key を表す jLjFjZ などで検索を掛けるとすぐに引っかかってしまうと思います。こういったことを避けるために、万が一こういった機密な情報が含まれる場合は var url = "http://example.com" のようにべた書きするのではなく、

var ur1 = "ht";
var ur2 = "tZ" + ":";
var ur3 = "/";
var ur4 = "examination"; 
var ur5 = "apple";
var ur6 = "computer";
var uurrll = ur1 + ur2.split("Z")[0] + "p" + ur2.split("Z")[1]
+ ur3 + ur3 + ur4.substr(0, 4) + ur5.substr(2) + "." + ur6.substr(0, 3);

といった具合に無駄に文字列を分割して更に読解困難にしたり、そもそも key とか url といったワードを変数名にしないようにするのもよいかと思います。
販売されるほど複雑なスクリプトならまず復元できることは無いと思いますが、 .jsxbin ファイルは万能なバイナリ形式ではない(そもそもバイナリ形式ではないですが)ということを知って頂ければと思います。

元記事:Reversing JSXBIN File Type