Harekaze CTF 2019 Write-Up

概要

2019年5月18日~5月19日にかけて開催された、Harekaze CTFのWrite-Upです。

所属しているチーム「生活習慣崩壊ズ」は7問通して910点を獲得し、28位となりました。そのうち、自分は4(+1)問通し、500(+100)点を獲得しました。

ここでは、自分の通した問題についての解法を記したいと思います。

ONCE UPON A TIME(Crypto 100pts)

鍵となる行列keyがあり、フラグを5×5行列に整形した行列flagについてflag×key又はkey×flagが与えられる(これをcryptoとする)。この演算は251(素数)で割った剰余環上で行われる。

素数mod上での積には逆元があるので、当然逆行列を考えることもできる。 逆行列の求め方はいろいろあるが、余因子行列の各要素を行列式で割る求め方が最も良いと思う(逆元を適用させやすいため。)

嬉しいことに、keyとなる行列には逆行列が存在した。これをkey^-1とすると、crypto×(key^-1)又は(key^-1)×cryptoのどちらかがflagとなることがわかる。これを計算すると、求めたいflagが出てくる。

Encode & Encode(Web 100pts)

  JSON形式で指定したファイルパスからfile_get_contentsで読み込んだファイルがクライアントに渡される。file_get_contentsにパス文字列には以下のようなバリデーションが掛けられていて、さらにクライアントにファイルを渡す前に正規表現でフラグに該当する部分が削除されるようになっている。フラグは/flagにあるので、ここをなんとかして読み込めたら勝ち。

f:id:keymoon:20190519144301p:plain

まず、/flagを読み込むことを考える。JSON形式の文字列を一度パーサに通しているため、おそらく何らかのエスケープをしておいてもエスケープを戻してくれるだろうと推測する。いろいろと実験をすると、unicodeエスケープ(\u0000)を戻してくれそうだと分かる。よって、{page:"\u002f\u0066\u006c\u0061\u0067"}のようなクエリを投げる。すると、{"content":"HarekazeCTF{<censored>}"}と検閲済みのflagが帰ってくる。元の情報を損なわない形で整形なり切り取りなりをして正規表現/HarekazeCTF\{.*}\/をすり抜けるようにすれば良いと分かる。

PHPにはフィルタと呼ばれる機能があり、php://filter/convert.base64-encode/resource=[resource]とするとリソースをbase64エンコードして返してくれる。これを使い、php://filter/convert.base64-encode/resource=/flagunicodeエスケープしたものを投げつけて終わり。

[a-z().](Misc 200pts)

JSFuck等の縛りJS系問題。制約は問題名の通りで、英小文字と()、ピリオドのみ使用可能。

この制約の下、(数字の)1337と評価されるJavaScriptを200字以内で書けたら勝ち。

まず使えるオブジェクトを列挙する。JavaScriptは悪魔的な言語なので、起点となるオブジェクトがあれば大抵なんでもできる(要出典)。よって、起点となるオブジェクトを探す。グローバルが空になっているため、通常の環境のようにいろいろと使えるわけではない。調査の結果、eval,console,escape,unescapeあたりが使えそうだと分かった。

1337を生成する方針として、文字列としての"1337"から生成することとする。これはNumberのコンストラクタに入れることで実現でき、コンストラクタはその型のオブジェクトから取得することができるからである。

次に、文字列を生成する方法を考える。流石に1337という文字列が落ちている筈もないため、一文字ずつ生成してから空文字列に対して.concatをしていくアプローチを取りたい。

空文字列はs.substr(s.length)とすることによって取得できるため、どこかにある文字列を使えば良い。これには、eval.nameが最も短く済みそうである。よって、空文字列はeval.name.substr(eval.name.length)として取得できる。

次に、concatする各"1","3","7"について探す。JavaScriptはキャストを結構緩めにやってくれるので、これらはStringでなくNumberでも良い。様々な調査の結果、1eval.length3console.log.name.length7console.context.name.lengthを使うこととした。 最後に、今までの結果を纏めてあげる。

eval.length.constructor(eval.name.substr(eval.name.length).concat(eval.length).concat(console.log.name.length).concat(console.log.name.length).concat(console.context.name.length))

これは180byteなので、制約に収まる。

Avatar Uploader 1(Misc 100pts)

finfo_filepngかつgetimagesizeのimagetypeがpngでないようなものを出せば良い。 finfo_fileシグネチャのみを判定しているため、シグネチャだけ合っていてimagetypeがバグるような「画像」を突っ込めば良い。 具体的には、以下のようなバイナリを突っ込んだ。

f:id:keymoon:20190519032438p:plain

Twenty-five

ppencodeのコードが換字式暗号でスクランブルされた状態で渡されるので、デコードしなさいという問題。換字式暗号パートの本質部分は自分ではなく漁師が片付けたが、なぜか実行しても上手く実行されなかった。

ということでppencodeに掛けた状態のものをデコードする作業をすることになった。まず、ppencodeのエンコーダを探してエンコード方法を確認する。少し探すと、

shinh.skr.jp

ここによって実行されていることがわかった。ソースを読む。すると、"$_="";v112.114.105.110.116.32.49"といったソースに整形されるような式を評価し、もう一度評価することによって実行を実現しているようだ。

f:id:keymoon:20190519054803p:plain

置き換え部分のロジックを読むと、ピリオドの部分と数字の部分が交互に出てきていることが分かる。数字は個別に置き換わっているようなため、置き換えている配列を確認する。すると、綺麗に文字数が1ずつ増えていることがわかる。

f:id:keymoon:20190519054305p:plain

数字と何文字差があるかは実行時の乱数依存だが、コードに必ずといってもいいほど含まれる空白はコードポイントが32で、これは通常のコードに含まれる文字のうち最小であることから差分が推測できる。

このようにしてコードを復元すると、

my $flag="ViZGUiyGEON{Gq.zeUeQGtei.FZd/zeUe/NZGToGqvR_iqipRheh}";$flag=~y/raEKcNnVMSwJgCbLAfmOuIsBWliXvtGxdHekUpjqFQTZhPoDzYRy ouvqriklpzawthxgfnbjcmdsye/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/;print $flag;

となる。これを実行し、フラグHarekazeCTF{en.wikipedia.org/wiki/Frequency_analysis}を得た。

感想

今回はそこまで真面目に参加できなかったが、様々な問題を解けてよかった。特に、縛りJSの問題は前から挑んでみたいと思っていたためとても面白かった。相変わらずだが、binary方面に全く手を出せないのが悲しいのでそこの力もつけていきたいと思った。