TSG-CTF2023: beginners_rev_2023(Reversing)

東京大学のサークル主催のTSG CTF 2023に3人チームで参加した。 いつも通り開始と同時にReversingの問題をやっていたが、コンテスト自体がこれまでよりも少し難しめということもあり1問しか解けなかった🥲

ctf.tsg.ne.jp

問題の概要(beginners_rev_2023)

実行ファイル「beginners-rev-2023.exe」が与えられる。

解法

beginners-rev-2023.exeを実行すると入力受付状態になり、適当な文字列を入力すると"Wrong..."と表示されて終了した。

$ ./beginners-rev-2023.exe 
Flag> test
Wrong...

とりあえずflagのバリデーションをしているんだろうと当たりをつけて、いつものようにghidraで逆コンパイルしてみる。 "Wrong..."という文字列が格納されている場所と、それを呼びだしている場所を探すと中核となる関数をすぐに見つけることができた。

これを開くと配列に対して大量にビットシフトとXOR演算をしているのが見え若干ひるむ。

ところで、今回の実行ファイルはいつものELFファイルではなくPE32+実行ファイルであり、Windowsで実行可能である。

$ file beginners-rev-2023.exe 
beginners-rev-2023.exe: PE32+ executable (console) x86-64, for MS Windows

x64dbgでデバッグできるというヒントを問題文でくれているので、さっそくインストールする。 GUIアプリなのでデバッグしやすくて助かる。 x64dbg.com

実際に動かしながらflagのバリデーションの処理を調べていくと、MEMCMPでRCXレジスタRDXレジスタの指すメモリを比較している部分があった。 ユーザーが入力した文字列を何らかの方法で暗号化し、それをあらかじめ用意したバイト列と比べているようだった。

正解のバイト列はRDX側に入っており、

07 56 e5 58 71 89 9a ca f0 67 03 2d 49 fb 6e 86 c2 f7 48 ca 3c 43 db 8e 04 2a 56 4a 97 33 a1 a2 07 83 f0 89 19 13 77 b4 9f 7d 7b 9c dd 8e fd ad b5 e2 28 0e 06 af e5 e3 86 c3 08 ad e6 4c de 63 a3 5f 1e 96 34 7d 9d 19 f5 c8 84 7f 7b 62 2a 6b c1 28 3b 6d 09 ef fc cb a0 90 9a 3e 66 a2 4e 06 90 2c 9d ae 3c 99 40 53 4c 69 63 e7 b9 a8 b3 87 a5 97 98 fe 1f 20 51 a7 ae 0d 00 ab 16 35 59 3d 08 1b 1c 92 e2 4f 1d 86 a5 6e 0a 14 45 4d 61 08 69 c3 12 a2 eb 50 13 93 22 e2 c4 10 ca 5f b2 0b a2 30 c8 54 91 3a 37 fd d2 10 ab 5a f8 38 f3 d3 d5 85 58 de df c0 f4 17 4e f7 31 79 dd 41 2f b3 20 c7 ec 98 5e ae f7 a9 cb 27 13 72 fe ca 64 ff 43 93 80 3e 1e e5 99 bf 41 4b 9d 85 4e 0f 99 94 57 e1 63 d9 01 85 78 8a 06 fe 9d 41 32 74 55 83 b2 85 e9 9f c6 2c 4b 62 8f bf 7d 57 c8 76 3b 31 5e 87 60 89 35 41 c1 52 6c d0 0b 7d ca 60 5d 82 19 b0 96 5e 16 e7 9b 2f 37 5f c9 c5 f3 20 c3 45 cb 47 a1 cc 79 e5 b6 fb d4 55 db c1 35 9b 8b fa 38 d5 b2 b5 e0 4f 4d 6c 4f 8c 0c 42 bc 8e b3 78 48 e4 87 8e 34 a3 1d 01 53 98 71 fa 8f 2f e3 7a 6b b9 1b b6 7e 34 7f c8 c4 6c ab 45 4d 81 ef ee c3 d9 db 13 5b 63 90 fc 34 18 81 bc d1 18 48 bb 7c 24 5b 56 2b 35 6b d7 f9 d3 d5 2b e2 24 d8 50 f1 ec d5 e6 29 55 66 f2 f7 28 20 7d f3 47 40 03 11 4a 47 a5 b4 74 15 35 d0 f0 e5 4c 04 b5 59 fe fc 45 9d 3a a1 3f 1a a7 a8 51 e5 65 f1 56 ee de fc c4 87 f5 fa 79 31 07 0a 3f 41 28 d1 59 17 4d 02 e4 5a 22 3a bc d2 cd 80 bc 2a 49 f0 7f 97 a1 90 59 01 8d 25 43 d8 00 ea d8 4f e2 4e 2b 06 fd 7e 16 a9 92 c4 fd b5 6a 82 06 18 0c 0a b7 b8 29 8f 87 63 65 25 b9 7a d0 6e 30 3c f2 f7 c2 30 86

となっていた。

左下の選択部分が問題のバイト列

つまり、暗号化の処理を調べて上記のバイト列を復号できればflagが得られると考えられる。 また、試しに"TSGCTF{"で始まる文字列を入力してみると最初の4バイトが一致するので、前から順番に暗号化するタイプのアルゴリズムなのでは?とあたりを付けた。

ここからは暗号化の処理を調べていく。 暗号化の処理は3段階に分かれていて、

処理A → 処理B → 処理A

のような構造になっていた。

処理A

入力した文字列に処理Aが施されるところをデバッガを使って観察してみると前から8バイトずつ順番に暗号化されていくので、ブロック暗号であることが分かる。 このことを念頭に置いて逆コンパイルされたコードを読むと、文字列の先頭から順番に8バイトずつ取り(Aとする)

A' = A ^ (A >> 12)

という処理を行っているようだった。

これを復号するのは簡単で、

A = A' ^ (A' >> 12) ^ (A' >> 24) ^ (A' >> 36) ^ (A' >> 48) ^ (A' >> 60)

である。

処理B

処理Bはコードを見るとかなり複雑で追うのは難しいように見えるが、少し気合を入れてデバッガで処理を追っていくと元の文字列を1文字ずつ暗号化していくストリーム暗号になっている。 さらに、よく見ると複雑なのは埋め込まれたシードから乱数を生成している部分で、結局やってることは各文字に決まった値を1回ずつXOR演算しているだけである(ということに気づくのに時間を要した)

処理Bを行う前と後の文字列はそれぞれメモリから取り出すことができるので、この差分を調べれば乱数生成の処理を追わずとも何文字目に何がXORされているのか突き止めることができる。

これで暗号化のアルゴリズムが分かったので、Pythonなどで復号の処理を書いてシミュレーションすれば

TSGCTF{y0u_w0uld_und3r57and_h0w_70_d3cryp7_arc4_and_h0w_70_d3cryp7_7h3_l3ak3d_5af3_l1nk1ng_p01n73r}

を得ることができた。 Flagの内容から察するに処理BはARC4という名前の暗号だったようだ。

感想

TSG CTF 2023で正の得点を取った560チームのなかで、この問題を解いていたのは24チームだった。 他のカテゴリの1問目よりかなり少ない数字なので、少し難易度高めな問題を解けたと考えることにした。

見直してみると簡単に思えるが、コンテスト中は逆コンパイルしたコードを見たりデバッガで処理を追っている時間が大半なので所要時間としてはかなりかかってしまった。 Reversingの2問目(T the weakest)はコンテスト中に解けなかったので、後で復習したい。