はじめに
今回、チーム「🍣SUSH1st」のメンバーとして5/4から5/6まで開催されていた大阪大学のCTFクラブであるWani Hackaseが主催しているWaniCTF2023に参加しました。 私は主にReversingとForensicsを中心にして解いたのでWriteUpをここに残します。
Reversing
Just_Passw0rd[Beginner]
渡されたファイルはELF形式の実行ファイルである。 実行すると、パスワードの入力を求められ、そのパスワードが正しかったらFlagを表示 そうでなければ、Incorrectと表示されるようである。 stringsを用いて表層解析を行なうと、Flagがそのまま埋めこまれていることが分かった。
|
|
静的解析を行なうと、次のような処理を行なっているようであった。
|
|
そのため、実行してパスワードを入力してもFlagを得ることができる。
|
|
よってFlagはFLAG{1234_P@ssw0rd_admin_toor_qwerty}
である。
javersing[Easy]
渡されたファイルはJava archive data(JAR)である。 jar形式の実行ファイルはjava decompilerを使えば簡単に逆コンパイルを行なうことができる。 JD-GUIを用いて逆コンパイルを行なうと、以下のコードが得られる。
|
|
このプログラムは次の処理を行なっている。
|
|
そのため、文字列str1
をb * 7 % 30
の順で並び直せばCorrectと表示されるような入力を求めることができる。
以下にソルバーを示す。
|
|
これを実行するとFlagが得られた。
よってFlagはFLAG{Decompiling_java_is_easy}
である。
fermat[Easy]
渡されたファイルはELF形式の実行ファイルである。
Ghidraを用いて静的解析を行ない、main
関数を確認する。
|
|
以上の処理は、次のようなことを行なっている。
|
|
そこで、check
関数を確認する。
|
|
check
関数では、$a^3 + b^3 = c^3$を満たす3以上の自然数$a$, $b$, $c$が入力されたら、1を返すという処理を行なっていた。フェルマーの最終定理によると、これを満たすような$a$, $b$, $c$は存在しないため、単純に入力してFlagを表示するようなことはできない。そこで、gdbによる動的解析で、main
関数の20行目の条件分岐で強制的にelse側を通るようにEFLAGSレジスタを書き変える。逆アセンブルを行ない、main
関数の20行目に対応する命令を見つける。
BaseImageAddressを0x00000000
としたとき、0x000014c7
でJZ命令によって条件分岐を行なっている。
JZ命令はZeroフラグが1の場合に指定されたアドレスまで飛ぶ命令である。そこで、この命令が実行される前に、
Zeroフラグを0に変更することで、飛ばずにflag_print
関数を実行することができる。
gdbを用いて、以上のことを行なう。
0x000014c7
にブレークポイントを張り、そこまで実行する。
gdbで確認すると、この位置は*main+198
と同じである。動的解析ではプログラムを実行しながら解析を行なっていくため、静的解析したアドレスにプログラムが配置されたアドレスが足されていることに注意する。(BaseImageAddressが変わる)
0x000014c7
でのEFLAGSレジスタは0x246
であった。ZeroフラグはEFLAGSレジスタの6ビット目である。(参照: Flags register(wikipedia))
EFLAGSレジスタの6ビット目を1から0に変更すると、0x206
である。
JZ命令が実行される前に、EFLAGSレジスタを0x206
に書き変えて実行する。
これにより、条件分岐はelse側を通りFlagが得られた。
以上を行なうソルバーを以下に示す。
|
|
よってFlagはFLAG{you_need_a_lot_of_time_and_effort_to_solve_reversing_208b47bd66c2cd8}
である。
theseus[Normal]
渡されたファイルはELF形式の実行ファイルである。Ghidraを用いて静的解析を行ない、main
関数を確認する。
|
|
50行目の部分でブレークポイントを張り、compare
関数の内部に入りスタックを確認すると、Flagがあった。
|
|
よって、FlagはFLAG{vKCsq3jl4j_Y0uMade1t}
である。
この解き方は自分では強引なような気がしている。
本来はこの問題は静的解析のみで、main
関数で行なわれている処理を追い、compare
関数に対してどのような処理が行なわれているのか理解し解析していかなければならなかったと思う。
web_assembly[Hard]
与えられたurlにアクセスすると、名前とパスワードを聞かれる。正しい名前とパスワードが入力できればFlagが得られるようである。このプログラムはWebAssemblyによって動作している。開発者モードから、index.wasm
をダウンロードしてこれを解析していく。
解析にあたり、WebAssemblyを解析可能にするGhidraの拡張機能を導入した。(garrettgu10/ghidra-wasm-plugin)
導入後、Ghidraを用いてindex.wasm
を静的解析した。WebAssemblyの特徴であるのか大量の関数が見つかったが、
見つかった文字列のアドレスが使われている場所から、main
関数と考えられる関数を見つけた。(解析にあたり、いくつかの関数の名前を変更した)
|
|
以上の処理では、以下のようなことを行なっている。
|
|
ここで、local_10は0x101a0
、local_1cは0x1024c
に格納されている文字列である。
そこで、これらの文字列をバイナリから探すと、local_10はckwajea
、local_1cはfeag5gwea1411_efae!!
であることが分かった。
実際に入力するとFlagが得られた。
よってFlagはFlag{Y0u_C4n_3x3cut3_Cpp_0n_Br0us3r!}
である。
Lua[Easy]
渡されたファイルは、luaのソースコードと実行するためのMakefileである。
ソースコードを読むと、難読化されていることが分かる。
なので、はじめに、CRYPTED****
となっている変数名を読みやすいものに変更していく。(今回はできるかぎり上から順にCrypted<n>
という形に変更していった)
変数名を変換していくと、上の長いソースコードは全て関数や変数の定義のみで一番下のreturn文で定義してきた関数や変数を組み合わせてプログラムを実行していることが分かった。 主要な部分を抜き出して以下に示す。
|
|
Crypted7、Crypted8ともにbase64によって文字列がエンコードされたものである。 return文では、Crypted12にCrypted8とCrypted7を入力し、この結果と標準入力で入力された文字列をCrypted5に入力し、 入力されたFlagが正しいかどうか判断していると考えられる。
return文より、Crypted12の結果が重要であると考え、Crypted12のdがbase64デコードされる前のbase64文字列を取り出し、それをCyberChefでデコードした際にどのようなものが得られるか調べた。この結果、得られたデータの中にFlagが書かれていた。
よって、FlagはFLAG{1ua_0r_py4h0n_wh4t_d0_y0u_3ay_w4en_43ked_wh1ch_0ne_1s_be44er}
である。
僕はpythonをよく書くので(luaはほとんど書かないので)、pythonを選びます。
Forensics
Just_mp4[Beginner]
渡されたファイルはmp4形式の動画である。 exiftoolでファイルのexifを調べると、Publisherの部分に、base64でエンコードされたFlagがあった。
|
|
これをbase64デコードすると、Flagが得られる。
よって、FlagはFLAG{H4v1n_fun_1nn1t}
である。
whats_happening[Beginner]
渡されたファイルはISO 9660 CD-ROM filesystem dataという形式のものであった。 ISOで標準化されたCD-ROMのファイルシステムらしい。ファイルシステムなので、マウントして中にあるファイルを見ていく。
|
|
これにより、/mnt/usb
に渡されたファイルのファイルシステムがマウントされたので、中にあるファイルを見ることができる。
|
|
FAKE_FLAG.txtとFLAG.pngというファイルが入っていた。FLAG.pngを見るとFlagが得られる。
よってFLAGはFLAG{n0th1ng_much}
である。
lowkey_messedup[Easy]
渡されたファイルはpcap形式である。wiresharkで見てみると、 USBで通信を行なっているようである。この正体はUSBで接続されたキーボードがタイピングされたときの信号であり、この信号はUSB HID Keyboardのキーコードに対応していた。(参考: HID/キーコード)
はじめに、タイピングされた信号である8byteの各データをcapture.txtに保存する。
|
|
次に、capture.txtから各キーコードに対応する文字、もしくはbackspaceなどのキースイッチに変換して入力された文字列を調べる。pythonを用いて次のソルバーを書いた。
|
|
これを実行すると、Flagが得られた。よってFlagはFLAG{Big_br0ther_is_watching_y0ur_keyb0ard}
である。
beg_for_a_peg[Normal]
渡されたファイルは上の問題と同じくpcap形式である。wiresharkで見てみると、4回HTTPリクエストが飛んでいることが分かる。 1回目のHTTPリクエストでは、server.htmlにアクセスしており、そこには3枚の画像(dum.jpg, flag.jpg, pub.jpg)があることが分かる 。 その後の3回のHTTPリクエストで全て画像ファイルのダウンロードを行なっているようである。 wiresharkでFollowを用いると、どのようなデータが通信されているのか見やすくなり、かつ、ファイルに通信されたデータを保存することもできる。
192.51.100.1:4500
から192.168.0.16:63557
の方向で通信されたデータを取り出し、Raw形式で保存する。また、
このままだと、データの始めにHTTPレスポンスのヘッダがついているので、その部分をバイナリエディタで削除すると、
通信された3つの画像を得ることができる。
よってFlagはFlag{Hug_a_pug_less_than_three}
である。
Misc
Prompt[Beginner]
与えられたurlにアクセスすると、AI(大規模言語モデル)と会話をすることができる。 問題文によると、このモデルにはFlagを教えるが誰にももらしてはいけないと事前に言ってあるらしい。 なので普通にFlagを教えてほしいといってもFlagは教えてくれない。そこで、指示再確認攻撃を行ない、上の命令がどのようなものであったか教えてもらう。結果として次の回答が返ってきてFlagを得ることができる。
|
|
よってFlagはFLAG{40w_evi1_c4n_y0u_be_aga1ns4_A1}
である。
machine_loading[Hard]
与えられたサイトでは、自分が作成したモデルのロードをしてくれるサービスのようである。 また、このサイトのサーバーのプログラムが渡される。 このプログラムから分かることは、次のことである。
- ファイルの中身がckptでなくても、拡張子が.ckptであるならmodelload関数を通ることができる。
- modelload関数ではPyTorchのtorch.loadメソッドによってモデルのロードを行なっている。
- modelload関数でモデルがロードされた後、output_dir/output.txtというファイルがあれば、それを読んで表示する。
そこで、PyTorchを用いて適当なモデルを作成し、サイトにアップロードをすると、output_dir/output.txtが存在せず、 msgになにも代入されず、msgを使用しようとしたためにエラーが表示される。
ここで、torch.loadに注目する。torch.loadのドキュメントには、このメソッドは暗黙的にpickleを使用しているため、unpickle時に任意のコードを実行することができるため注意が必要であることが書かれている(参考: torch.load (PyTorch docs))
すなわち、PyTorchで作成されたモデル中に任意のコードを書きこんでおき、torch.loadでそのモデルが読み込まれたときにそのコードが実行されるようなモデルを作ればよい。PyTorchでは、モデルはクラスとして記述することができるため、このクラス中に__reduce__
メソッドを用意し、この戻り値で任意コードを実行するような仕組みを記述する。
__reduce__
メソッドはオブジェクトのpickle/unpickleを行なう方法を記述する際に用いて、unpickleを行なったときにこの処理が実行される。
|
|
モデルロード時に実行するコードは、以下のとおりである。
- output_dirとoutput_dir/output.txtを作る
- output_dir/output.txtにflag.txtの内容を書きこむ
作成したコードを実行し、生成されたpayload.ckptをサイトにアップロードするとFlagが得られた。
よってFlagはFLAG{Use_0ther_extens10n_such_as_safetensors}
である。
Web
IndexedDB[Beginner]
与えられたサイトにアクセスすると、すでにFlagはページ内に隠したということが書かれている。 問題タイトルのとおり、FlagはIndexedDB内に隠されていた。
よってFlagはFLAG{y0uc4n_u3e_db_1n_br0wser}
である。
終わりに
今回はReversing、Forensics、Miscについて解いていきました。 Reversingについては、与えられた問題に対して全て解くことができてうれしかったです。WebAssemblyの解析はうまくできたことがなく、どのようにやるのか分からなかったのですが、問題を通してどのように解析すればいいか分かりました。 Forensicsでは、usbの通信ははじめてで、usbキーボードのキーコードもはじめて知りました。 Miscについては、最近のトレンドにあがるGPTについてや、この技術の根幹にある深層学習の問題を通して、ものすごい速さで普及してきているAIに関するセキュリティがやはり大切になってきていることをCTFの問題として出題されたことで改めて感じました。
最後に、今回のWaniCTF2023で以上のようなさまざまな発見がありました。運営してもらったWani Hackaseの方々、楽しい大会を開いていただきありがとうございました。