はじめに
SECCON Beginners CTF 2022のReversingのWriteupです。今年はReversingは全問といたのでReversing全問書きます。ちなみにLinuxのバイナリは動かしてないですので、こういう動きするんだろうなぁとは思いながら解いていますが、実際に動的解析しながら動かすとどうなるかとかは知りません。(Linuxバイナリ解析する環境を整えてないのでGhidraとIDA Freeで戦いました。)
Quiz
GhidraでStringsを見たらflagが書いてある。(stringsコマンドでも十分)
ctf4b{w0w_d1d_y0u_ca7ch_7h3_fl4g_1n_0n3_sh07?}
WinTLS
TLSはHTTPS通信のほうではなく Thread Local Storage。
GhidraでStringsを見ながら、Referenceをたどるとメインの処理をしている関数が見つかる。(わかりやすいように変数名とかは変更している)
メイン処理ではt1
とt2
関数に対し、入力した文字列を引数として渡し、スレッドを実行している。また、t1
とt2
のリターン結果によって、正解か間違いかを判定している。
t1
関数の動作を見てみると次のようになっている
t1
関数の動作は次の通り
- 入力した文字列が3もしくは5で割り切れるとき、その文字をbufferに追加
- check関数で、TlsSetValueで設定した
c4{fAPu8#FHh2+0cyo8$SWJH3a8X
とbufferをチェック
逆にt2
関数の動作は次のようになっていた。
- 入力した文字列が3もしくは5で割り切れないとき、その文字をbufferに追加
- check関数で、TlsSetValueで設定した
tfb%s$T9NvFyroLh@89a9yoC3rPy&3b}
とbufferをチェック
ここまで動作が分かったのでpythonでソルバを書く
s1 = "c4{fAPu8#FHh2+0cyo8$SWJH3a8X" s2 = "tfb%s$T9NvFyroLh@89a9yoC3rPy&3b}" ans = "" c1 =0 c2 = 0 for i in range(60): if i % 3 == 0 or i % 5 == 0: ans += s1[c1] c1 = c1 + 1 else: ans += s2[c2] c2 = c2 + 1 print(ans) #ctf4b{f%sAP$uT98Nv#FFHyrh2o+Lh0@8c9yoa98$ySoCW3rJPH3y&a83Xb}
ctf4b{f%sAP$uT98Nv#FFHyrh2o+Lh0@8c9yoa98$ySoCW3rJPH3y&a83Xb}
Recursive
Ghidraで見ると、問題名の通り再帰的にcheck関数を読んでおり、入力した文字列をチェックしている。
読めば簡単だが、文字列を半分にどんどん区切っていき、文字列が1文字となったときにtableにあるデータと一致しているかを確認している。
今回はtableデータを使っているのでGhidraスクリプトを使う。
dat = getBytes(toAddr(0x00104020),512) res = [] def check(number , offset): if number == 1: res.append(dat[offset]) else: half = number / 2 check(half, offset) if number % 2 == 0: check(half, half * half + offset) else: check(half + 1, half * half + offset) check(0x26,0) print("".join(res)) #ctf4b{r3curs1v3_c4l1_1s_4_v3ry_u53fu1}
ctf4b{r3curs1v3_c4l1_1s_4_v3ry_u53fu1}
Ransom
Ghidraでコードを読むと次の処理になっている。
- RC4のカギを作成
- ctf4b_super_secret.txt ファイルの内容を読み込む
- rc4で読み込んだファイルの内容を暗号化し、ctf4b_super_secret.txt.lockに書き込む
- 192.168.0.225:8080にRC4のカギを送付する
そのためpcapファイルからRC4のカギを入手し、復号すればよい。
カギはrgUAvvyfyApNPEYg
なお、ctf4b_super_secret.txtに書き込むときに%02x
の書式で書き込んでいるので、そこだけ注意する。私はバイナリエディタでファイルを開いてコピーした
enc=b'\x2b\xa9\xf3\x6f\xa2\x2e\xcd\xf3\x78\xcc\xb7\xa0\xde\x6d\xb1\xd4\x24\x3c\x8a\x89\xa3\xce\xab\x30\x7f\xc2\xb9\x0c\xb9\xf4\xe7\xda\x25\xcd\xfc\x4e\xc7\x9e\x7e\x43\x2b\x3b\xdc\x09\x80\x96\x95\xf6\x76\x10' key = b"rgUAvvyfyApNPEYg" cipher = ARC4.new(key) cipher.decrypt(enc) #b'ctf4b{rans0mw4re_1s_v4ry_dan9er0u3_s0_b4_c4refu1}\n'
ctf4b{rans0mw4re_1s_v4ry_dan9er0u3_s0_b4_c4refu1}
please_not_debug_me
Ghidraで見ると、データを0x16
でxorを取り、そのデータを実行していることがわかる。
適当にGhidraのPythonスクリプトを使って書き出しておく。
length = 0x41a0 dat = getBytes(toAddr(0x00104020), length) with open('unpack', 'wb') as f: f.write(bytearray([(i ^ 0x16) & 0xff for i in dat]))
書き出したファイルはLinuxの実行ファイルだがGhidraでELF Formatで読み込もうとするとエラーがでてうまく読み込んでくれなかったのでIDA Freeで読み込んでもらった。
IDAで読み込むと気になる文字列が見当たるのでそのあたりの関数を見ていく。
Correctといった文字列の直前にsub_17d1関数を呼び出しており、ここでフラグ文字列を参照していると思われることがわかる(Usage: %s path_to_flag_file
という文字列もあるので)
ここの処理を追っていくと、"b14be7`2i<hoj;mnq&#+#-!$,//xy$)/D"の文字を文字列の順番でxorした値を鍵としたRC4の処理が見つかる。(正直またRC4かと思った)
復号するデータはunk_4020のデータというのがわかる。これで復号したところflagが得られた。
from Crypt.Cipher import ARC4 rc4_key_enc=[0x62,0x31,0x34,0x62,0x65,0x37,0x60,0x32,0x69,0x3C,0x68,0x6F,0x6A,0x3B,0x6D,0x6E,0x71,0x26,0x23,0x2B,0x23,0x2D,0x21,0x24,0x2C,0x2F,0x2F,0x78,0x79,0x24,0x29,0x2F,0x44,0x11,0x16,0x45,0x10,0x10,0x1F,0x43] enc_data=[0x27,0x0D9,0x65,0x3A,0x0F,0x25,0x0E4,0x0E,0x81,0x8A,0x59,0x0BC,0x33,0x0FB,0x0F9,0x0FC,0x5,0x0C6,0x33,0x1,0x0E2,0x0B0,0x0BE,0x8E,0x4A,0x9C,0x0A9,0x46,0x73,0xB8,0x48,0x7D,0x7F,0x73,0x22,0x0EC,0xDB,0x0DC,0x98,0x0D9,0x90,0x61,0x80,0x7C,0x6C,0x0B3,0x36,0x42,0x3F,0x90,0x44,0x85,0x0D,0x95,0x0B1,0x0EE,0x0FA,0x94,0x85,0x0C,0x0B9,0x9F] key=bytearray([i ^ rc4_key_enc[i] for i in range(len(rc4_key_enc))]) cipher = ARC4.new(key1) print(cipher.decrypt(bytearray(enc_data))) b'ctf4b{D0_y0u_kn0w_0f_0th3r_w4y5_t0_d3t3ct_d36u991n9_1n_L1nux?}'
ctf4b{D0_y0u_kn0w_0f_0th3r_w4y5_t0_d3t3ct_d36u991n9_1n_L1nux?
感想
個人的にReversingは去年よりも初心者がとっつきやすい問題で、Reversing初心者におすすめな難易度と思い、自分も参考にしたい内容でした。解いてて楽しいReversing問題でした
RC4は2回連続したのは「またRC4か」というのが正直な感想。ただ、マルウェアもよく使うし見慣れて損はない暗号だし、そういう意図があったのかなとは思いました。