CyberRebeatCTF crackme writeup
はじめに
CyberRebeatCTFに参加したので、その中で僕の解いたBinaryジャンルの問題のcrackmeのwriteupを書いていこうと思います。すでに全完してるような方がwriteupを出しているのであまり需要がないかと思ったのですが、ここでは手順を事細かに書いていこうと思います。
使ったツールはradare2です。ダウンロード方法や設定などはハリネズミ本に、使い方等はググれば先人の良ブログがたくさんあるのでそちらを参照してください。僕もまだ全然使いこなせていませんが、基礎操作さえできれば問題は解けるようになっています。
crackme(難易度100)
fileコマンド
動的リンク、not stripped、やった!と思ったのですが、アーキテクチャがARMでした。
x86系じゃないアセンブリはあまり読んだことがないのですが、、、とりあえずradare2に通してみます。
not strippedなのでmain関数に飛び、visualモードに変更しpを入力しアセンブリを表示(visualモードから戻るにはqを入力)。ちなみにvisualモードでは画面のスクロールはvimと同様jとkのキーで行います。main関数はこんな感じでした。
うーん何となくしかわからんなぁ。ということでARMアーキテクチャでググり、各種命令の意味、各レジスタの名称、引数の渡し方などを軽く理解します。ARMでは第1引数から順にr0,r1,r2...と格納しているみたいです。またpcレジスタ(x86系でいうeip)が現在の命令の2個先を指していることがわかりました。それを踏まえてもう一度逆アセンブルを眺めましょう。
気になるところは0x10574から比較分岐を行なっているところですね。r3 > 1ならジャンプをしています。前の命令も見てみると[fp-0x8]にargcを、[fp-0xc]にargvを格納していてるので0x10574ではコマンドライン引数によって分岐しています。コマンドライン引数が1以下だと0x1057cから続くエラーメッセージを出力してmain関数の最後に飛んでいます。コマンドライン引数が2以上だと0x105a0に飛んでいますね。
0x105a0からの流れではcheck(argv[1])を行い、その返り値(r0に格納されている)が0だとputs("Correct")、非0だとputs("Wrong...")を行いmain関数を終了しています。main関数はこんなところでしょうか。大事なところはコマンドライン引数の値をcheck関数に渡して返り値を0になるようにすればいいということですね。
visualモードでqを入力してcheck関数に飛びまたvisualモードに戻ります。
check関数はこんな感じです。
main関数が読めればcheck関数も読めると思います。0x1050cからの流れからもわかりますが、[fp-0x8]をカウンターとして使っていて、比較するobj.flag_encrypted_lenがさす中身は0x17であるので0x17回ループを回しています。0x104dcから始まるループの中ではloc._d_15というポインタ型の変数のさすアドレスから1文字ずつ取ってきて0x104ec〜0x104f4の処理を行い、また同じ場所に戻しています。ループが終わったら操作したloc._d_15の中身と引数である、main関数のargv[1]をstrncmpで比較しその返り値をそのままcheck関数の返り値としています。
obj.flag_encrypted_lenとかloc._d_15の内容ってどこ見たらわかるの?という人はradare2の赤いコメントを見て見ましょう。0x104dcや0x1051cの横のコメントをみるとloc._d_15の中身は0x21030、obj.flag_encrypted_lenの中身は0x21048であることがわかります。
えーでもこれだとツールのコメントなかったら解けないじゃんかという方向けにもう少し補足を加えます。loc._d_15で考えると、0x104dcと0x104f8の命令を見てください。同じ命令なのにも関わらず機械語だと0x70209fe5と0x54209fe5と1バイト目が違うことがわかります。ここがポイントで、最初の1バイトは現在のpcレジスタの値に加えるオフセットのことです。なので0x104dcの時のpcレジスタの値は先述の通り2命令先なので0x104e4、これに0x70を加えると0x10554
であります。同様に0x104f8の命令の時も計算すると0x10554となります。同じ考え方をobj.flag_encrypted_lenに適用するとloc._d_15は0x10554に、obj.flag_encrypted_lenは0x10558にあるということがわかります。そのアドレスを見てみると
はい!それぞれradare2のコメント通りの値が入っていますね!(リトルエンディアンを忘れずに)
どちらもポインタ型として使われているのでそれらのさすアドレスに飛んで見ましょう(戻ってs 0x****0で飛べます)
ありましたぁ。ここはデータが格納されているだけなので、横の逆アセンブルは気にしないでくださいね。loc._15_dの指す文字列とループの処理がわかったのでCでコードを書いて見ます。
/*crackme.c*/ #include <stdio.h> int main() { char en_flag[23] = {0xb0, 0xa1, 0xb0, 0xa7, 0xb5, 0x88, 0x9b, 0x96, 0x9f, 0x9f, 0x9c, 0xac, 0xc7, 0x81, 0x9e, 0xac, 0x84, 0x9c, 0x81, 0x9f, 0x97, 0xd2, 0x8e}; int i; char tmp; for(i = 0; i < 23; i++){ tmp = en_flag[i]; tmp ^= 0xc; tmp ^= 0xff; en_flag[i] = tmp; } printf("%s¥n", en_flag); }
わーい
まとめ
- 本当はBinary問題3問全部writeup書こうと思ってたんですが、個人的に書きたかったcrackmeから書き始めたら疲れたのでこれだけにします。もしかしたら追記するかもです。
- 同学科の同期と3人で参加しました。チームとしては27位でBinary以外はほとんど触ってないので2人がよく頑張ってくれました。
- webわからんすぎるのはどうにかしたいですが、何か得意なジャンルを作っておきたいのでもう少しrev,pwnを解き続けたいと思います。このレベルなら解けるようになってきましたが、大きい大会となるとwarmupすら解けないのはしんどいです。
- キャン18の参加者が何人か全完していて、はえ〜という気持ちです。
- 問題のレベルが初心者向けが多く楽しめました。運営の方ありがとうございました。