WhiteHat Contest 13 writeup
個人で参加してました。2問解いて300pt、48位くらい(忘れた)でした。
なお、開催時間を48時間と勘違いして終了20分後くらいに3問目をsubmitしました。
あと、説明を全然読んでなかったのでフラグ形式がわからずしばらく悪戦苦闘してました。
解いた3問のwriteupを書きます。
・Mui Ne (pwn 100)
・DaNang (crypto 200)
・Cat Ba island (crypto 200)
Mui Ne (pwn 100)
入力したものをそのまま出力して終了というプログラム。
NX, canaryが有効。
問題文に書いてある通り、FSBが存在します。また、入力をgetsで受け付けているためBoFも存在します。
以下解いた流れ
その1.
まず、1回の入出力では足りないのでmain関数をループさせることを考えます。
mainからのリターンアドレス書き換えが真っ先に思いつきますが、canaryが有効な上に、下のようなよくわからない処理(espのすり替え)をしていてスタックのアドレスがわからないとうまくEIPをとれません。
leave
lea esp, [ecx-0x4]
ret
そこで、FSBによりスタックのアドレスをリークしつつ、__stack_chk_failのgotを上書きしてmainに飛ばしてやります。これにより、適当にBoFするだけでmain関数がループするようになります。
その2.
スタックのアドレスがわかったので、BoFによりmainからのリターンアドレスをうまく書き換えてEIPを奪います。
ちなみに、EIPを奪うときに__stack_chk_failが邪魔になってくるので、再びgotを書き換えてどこかのretに飛ばすことでスルーしてます。
その3.
EIPが奪えたら、あとはシェルをとるだけになります。
今回はlibcが与えられていないので、return to dl-resolveを目指します。
32bitなので、ももテクの記事を参考にstack pivotしてからreturn to dl-resolveして終わり。
flag:WhiteHat{196841c60f4408dd151af3ce7f4dc84f9583cc7a}
DaNang (crypto 200)
encryptというテキストファイルと、makefile.pycが与えられます。
pycは人間には読めないので、easy_python_decompilerでデコンパイルします。
すると、encryptの中身は以下のようになっていることがわかります。
まず、m1,m2がわかれば Common Modulus Attackによりflagがわかりそうなことが自明です。なのでm1(m2)をどうにかして求めることを考えます。
c1,c2を見てみると、平文が44444違うだけの関係であることが見てとれます。
つまり、
という関係の2つの暗号文から平文を求める必要があります。
これは、Franklin-Reiter Related Message Attack により求めることができます。
まとめると、
・Related Message Attack により m1, m2 を求める
・Common Modulus Attack により flag を求める
flag:WhiteHat{4111c011bc640806ef98a32a5caaf9d169a960a0}
Cat Ba island (crypto 200)
どうにかして500億手に入れる問題。 今流行りの5000兆円じゃなくてよかった。
なお、moneyなのかただの数値なのか混同しそうなのでwriteup中ではmoneyの単位を円として書きます。
選べるコマンドは2つで、
1: Get secret code : ランダムな秘密鍵、IVを元にしたCBCモードのAESオブジェクトを作成し、queryという文を暗号化する。 IVのみプレイヤーに対して知らされる。
2: Get money : プレイヤーが送信したIVによって1で作成した暗号文を復号する。このとき、平文に数字が含まれていればその分のmoneyが手に入る。 厳密に言えば正規表現でマッチした最初の部分を手に入れるので、xxx5xxx3xxx みたいに復号されたら5円が手に入る、はず。
これらのコマンドを選びつつ、50回以内に目標金額を貯めるゲーム。
IVをいい感じにいじってどうにかする問題ということは漠然と考えるものの、よくわからないのでまずはIVをそのまま送って復号を繰り返します。すると、必ず1円手に入ることがわかります。つまり元の平文のどこかに必ず1があるということなので、何文字目にあるのかを知りたくなります。
やり方としては、IVのnバイト目を IVn xor 0x31 xor 0x39 として送ります。
結局下位4bitだけが変化するわけですが、元の平文が1のバイトであれば0x39となるので復号結果が9になります。
一方、元の平文が1でない、つまり数字にならない箇所では、下位4bitだけが変化しても数字になることはありません。(asciiで数字の範囲は0x30~0x39なので)
この方法で確かめると、必ず6バイト目に1が来ることがわかります。
なので、この6バイト目を基準に前後1バイトずつを数字になるようにIVをいじっていきます。
数字でないということは、平文の該当バイトの上位4bitが0x3ではない・0x3a~0x3fのいずれかということになります。したがって、IVの該当バイトの上位4bitをいじってみて数字になればok、ならなければ下4bitを調整した上で再び上位4bitをいじる、ということをしてやれば1桁ずつ増やすことができます。
これを、6バイト目を基準に前後1バイトずつ順番に増やしていき、11桁くらいになれば目標金額に届きます。
あとは、なるべく試行回数が少なくなるようにPPCをして終わり。
ちなみに、桁を増やそうと適当にやってると0円を獲得することがあるので、ソルバのleft_flagとかはそれを回避して逆からループを回してるだけです。(平文が制御文字になってる?)
ひたすら読みにくいだけのソルバだけど、2回くらいまわしてたら目標金額達成しました。
flag:WhiteHat{13643c9b5ceece81bd5a6f9b2f8813e4d7b6dab1}
所感
各問題1行感想
・Mui Ne:他の人のwriteupみたらふつうにsystem呼んでた。つらい。
・Da Nang:申し訳程度のrev要素と50点くらいのcrypto
・Cat Ba island:PPC 200
完全に時間を勘違いしていたので、適当に他の問題もこれから見ていきたいなぁという感じです。
あと、昔にstack pivot + return to dl-resolveやったときにbss領域の上のほうにpivotしすぎて死んでたのに今回もやらかした。