2016.08.12
Amazing Language(ジャンル:Web) 第2回
コンピュータセキュリティ技術の人気競技CTF(キャプチャー・ザ・フラッグ)にどんな問題が出されているのか?取り組み方・解き方について解説していきます。
はじめに
こんにちはこんにちは、『CTF for ビギナーズ』運営のやぎはしゅです。
今回は前回に引き続き、2015年度のセキュリティキャンプ全国大会での出題をベースにした問題に取り組みたいと思います。
問題
Amazing Language
ジャンル:Web、得点:400(最高500)
とある経路で入手したこの2つのファイル(problem.zip:クリックでダウンロード)、何やら重要な情報が隠されているようなのだが、どこに隠されているのかさっぱりわからない。隠された情報を見つけ出して欲しい。
解いてみよう
前半部分については前回の記事を御覧ください。
5. problem.pngを確認してみる
さて、それではproblem.png
が先述の変換ツールによって変換されたJavaScriptのソースコードなのかを確認していきましょう。
もし自己解凍機能を備えたPNGファイルであればファイル内にHTMLタグが含まれているはずです。
PNGファイルはバイナリファイルなので、本来であればバイナリエディタで確認するところなのですが、筆者はものぐさなのでcat
コマンドで確認してしまおうと思います。
HTMLタグを構成する文字は印字可能文字なので、cat
コマンドでもきちんと表示されるはずです。
以下のようなそれらしいHTMLタグが含まれているのがわかるかと思います。
念のためsample.png
も確認してみますが、同様にHTMLタグが含まれているのがわかりますね。
<canvas id=c><img onload=for(w=c.width=59,h=c.height=58,a=c.getContext('2d'),a.drawImage(this,p=0,0),e='',d=a.getImageData(0,0,w,h).data;t=d[p+=4];)e+=String.fromCharCode(t);(1,eval)(e) src=#>
さて、画像ファイルの正体がやっと判明しました。
JavaScriptとして実行可能なこともわかっているため、次はWebブラウザで実際に実行してみましょう。
6. Webブラウザの世界へ
先の記事にも記載がありますが、今回の問題ファイルをJavaScriptとして実行するためには、当該ファイルをHTMLとしてWebブラウザに読み込ませる必要があります。
HTMLとして読み込ませるための一番シンプルな方法は、拡張子の書き換えです。
problem.png
のファイル名末尾に.html
を追記してproblem.png.html
とします。
このHTMLファイルをWebブラウザで開きます。(今回はFirefoxを例として用います。)
Firefoxで当該ファイルを開くと以下のような画面が表示されます。
このようにして画像ファイルを開いた時点で元のJavaScriptコードが実行されるはずなのですが、画面上にFLAGらしき文字列は表示されていません。
どうやら実行するだけでは不十分で、もう少しこのファイルを調べてみる必要がありそうです。
7. ソースを読む
今開いている画像ファイル(拡張子を変更したのでHTMLファイルといった方が適切かもしれませんが…)、WebブラウザはどのようなHTMLファイルとして解釈しているのでしょうか。
「ソースを表示」でソースコードを見てみます。
元々は画像ファイルなので意味不明な文字の羅列になっています。
このままでは読みにくいので、Webブラウザに付属の開発者ツールを使って、整形された状態のHTMLソースを読んでみましょう。
command + option + I
(WindowsではCtrl + Shift + I
)で開発者ツールのインスペクタを開きます。
インスペクタではHTMLソースを整形した状態で表示します。
今回の問題ファイルではcanvasタグに画像ファイル自身を読み込んで、色情報を読み取って実行する、という自己解凍方式を取っているため、インスペクタに表示されているcanvasタグ左の三角形をクリックしてみます。
imgタグのonload属性に自己解凍のためのJavaScriptコードが記述されています。
色情報から一文字ずつ元のソースコードを復元して、最後にeval関数を呼び出して、元のソースコードを実行する、といったコードです。
コード末尾の(1,eval)(e)
がeval関数を呼び出す箇所です。eval関数の引数として指定されるのは元のソースコードと推測されますので、引数eの中身を見れば、元のソースコードがわかるはずです。
Firefoxの開発者ツールでは、JavaScriptコンソールというものが提供されています。
簡単にJavaScriptのコードを実行することができるため、CTFでは非常に重宝するツールです。
今回もこのツールを使って、eval関数の引数の中身を確認してみようと思います。
開発者ツール上部の「コンソール」タブに移ります。
eval関数の引数として指定されている変数eはグローバル変数なので、コンソールに単にe
と打てば、中身が表示されるはずです。
やっとソースコードに辿り着けると思ったのに何やらわけのわからない文字列が表示されています。
心が折れそうになりますが順に読み解いていきましょう。
8. 難読化されたソースコードを読み解きFLAGを入手する
ついに姿を表したJavaScriptのソースコードですが、何やらわけのわからない文字列になっています。
ですが、JavaScriptのeval関数で実行できている以上、このわけのわからない文字列はあくまでJavaScriptのコードです。
このような形でソースコードを人間が読みにくい形に変換することを「難読化」と呼びます。
JavaScriptの難読化について詳しく説明し始めると連載が終わってしまうので、ここでも詳細は割愛しますが、難読化されたJavaScriptのソースコードの復元方法は大きく分けて2つあります。
メジャーな難読化手法の大まかな原理は突き詰めていくとひとつの方法に行き着くのですが、それらを復元するときのシンプルな解読手法はだいたい2種類に分けられます。
ひとつはFunctionオブジェクトのコンストラクタに難読化した文字列を渡して作成した関数オブジェクトを実行するもの、もうひとつはeval関数等に難読化した文字列を渡して実行させるものです。
・eval関数等を別の関数に置き換える
まずひとつめはeval等の引数として指定された文字列をコードとして実行する性質のある関数を利用したものです。
一見意味不明な文字列である難読化されたJavaScriptコードも、実行する過程で文字列に変換されていきます。
最終的に変換された文字列をeval等の関数に渡すことで実行することになるので、今回の問題でPNGファイルの自己解凍機能を確認したときと同様、eval関数に渡される引数の中身が難読化される前のソースコードということになります。
とはいえ、難読化されたコードでは関数自体も難読化して表現されるため、どこからどこまでが引数なのかが非常にわかりにくいです。
そんなときに使えるテクニックとして、eval等の関数自体を別の関数に置き換えてから難読化されたコードを実行してみるというものがあります。
先ほどのJavaScriptコンソールに以下のようなコードを1行ずつ順に打ち込んでみてください。
eval("alert('本来はこのalert関数を呼び出すコードが難読化されています。')"); // => evalを置き換える前の実行 eval = function(e) { console.log(e); }; eval("alert('本来はこのalert関数を呼び出すコードが難読化されています。')"); // => evalを置き換えた後の実行
1回目のeval関数の実行ではダイアログボックスが表示されます。
eval関数を置き換えた後の2回目の実行では、コンソールログにeval関数の引数の内容が表示されているのがわかるかと思います。
この方法によって、難読化されていたとしても、eval関数にコードが渡される限りは元のソースコードを復元することができます。
実際に難読化されたコードでこの方法を試す場合は、上記の例でeval関数を使っている行で、難読化されたJavaScriptコードを丸ごとペーストすれば同様に復元できます。
また、もしeval関数の置き換えでうまくいかないようであれば、次に説明する方法を試すか、setTimeOutなどのその他のevalに相当する関数で同じことを試してみてください。
・関数オブジェクトに対してtoString()メソッドを実行する
eval関数に難読化されたコードを渡す以外の方法として、Functionオブジェクトのコンストラクタに難読化されたコードを渡して、元のコードを実行する関数を生成するというものがあります。
この手法で難読化されているかどうかは、難読化されたコードの末尾に関数呼び出しのための「()」が付いているかで判別できます。(例外はありますよ!)
今回の問題ではこちらのパターンで難読化されているようです。
この手法の難読化はevalのときよりも容易に解読可能です。
JavaScriptの関数オブジェクトはtoString()メソッドというものを備えています。
このメソッドは関数オブジェクトに対して実行されると、その関数の定義時の文字列を返します。
以下のようなイメージです。
function sample() { alert('foo'); } console.log(sample.toString()); /*************** 「function sample() { alert('foo'); }」 という文字列がコンソールログに表示される。 ***************/
この方法で今回の問題の難読化されたコードを復元してみましょう。
画像ファイルを開いているFirefoxのコンソールで以下のコードを実行します。
copy(e) // => copy()は引数の中身をクリップボードにコピーするデバッグ用の関数です。 /*************** コピーした難読化されたコードの末尾の「()」を消し、代わりに「.toString()」を入力して実行します。 ***************/ $=~[];$={___:++$,$$$$:(![]/* ???長いので省略??? */();\\"+$.__$+$._$_+"\"")()).toString();
実行した際のイメージは下記の通りです。
以下の関数が表示されていますね。
function anonymous() { (function(){alert.givemeflag=function(){var a=[0x46,0x4c,0x41,0x47,0x7b,0x6a,0x61,0x76,0x61,0x73,0x63,0x72,0x69,0x70,0x74,0x2c,0x61,0x6d,0x61,0x7a,0x69,0x6e,0x67,0x5f,0x6c,0x61,0x6e,0x67,0x75,0x61,0x67,0x65,0x7d];var s="";for(var i=0;i<a.length; i++){s+=String.fromCharCode(a[i]);}this(s);};})(); }
少し整形して見やすくしてみます。
function anonymous() { (function() { alert.givemeflag = function() { var a = [0x46,0x4c,0x41,0x47,0x7b,0x6a,0x61,0x76,0x61,0x73,0x63,0x72,0x69,0x70,0x74,0x2c,0x61,0x6d,0x61,0x7a,0x69,0x6e,0x67,0x5f,0x6c,0x61,0x6e,0x67,0x75,0x61,0x67,0x65,0x7d]; var s = ""; for (var i = 0; i < a.length; i++) { s += String.fromCharCode(a[i]); } this(s); }; })(); }
どうやらalert.givemeflag
というメソッドを作成しているようです。
どんな動作のメソッドなのかは置いておいて、おそらくこのメソッドを実行すればFLAGが手に入りそうです。
JavaScriptコンソールに以下のように入力してみます。
alert.givemeflag()
FLAG{javascript,amazing_language}
というFLAGが表示されました。
長くなりましたがやっとFLAGを見つけることができました!
まとめ
今回取り扱った問題は、一見するとWebとはあまり関係がなさそうに見えますが、実はJavaScriptを利用しているという問題でした。
やっと元のJavaScriptコードを実行するところまで辿り着いたと思ったら、そこには難読化されたコードが…、という初心者にとってはなかなか骨の折れる問題でもありましたね。
解き進めていく中で、前半部分では画像ファイルが一体どんな性質のものなのかを推測し、情報を収集する発想力、後半にはJavaScriptやWebブラウザの機能に関する知識が求められる等、単純な問題ではありますがいろいろな要素が詰まっていたりもします。
特にWebブラウザに備わる開発者ツールは、Webジャンルの問題を解く際は非常に有用なものです。ぜひ使い方を覚えて、日頃から使いこなす練習をするようにしてみてください。
後半で取り扱ったJavaScriptの難読化は、CTFの外の現実世界では、マルウェアが利用したりすることもあり、実際にインシデントハンドリングを行うような方であれば解析しなければならないこともあるかもしれません。(実際にはもっと複雑な難読化が施される場合も多く、今回紹介した方法だけでは終わりませんが…。)
今回紹介した方法は単なるエンコーディングに近いものがあり、復元が容易なため実際のCTFで見かけることはあまりありませんが、作法として読めるようになっておくことは大事です。
ぜひJavaScriptの難読化手法についても一度調べてみてください。