ぺぺっぱーの日記帳

なんか そういうかんじで

#TsukuCTF 2025 writeup

しばらくCTFをやっていなかったので久しぶりにやりました

 

始まってから主戦場であるReverseがない事に気づきこれやれるか?となったもののまあまずまずという感じでした

 

osint

一番専門外

Casca

海が綺麗なこの日本の街は、かつてポルトガルのリゾート地との交流がありました。」とのことで、そのままググると熱海っぽいことがわかります。

 ついでにcasca.jpgを検索にかけるとこのサイトによれば該当の場所はジャカランダ遊歩道の付近にあるようです。

 ということでそのあたりでこの特徴的なちょっと小高いところを探すのですが、ちょうどGoogleストリートビュー範囲外です(位置はここ)

 仕方ないので「ジャカランダ 熱海 記念碑」で検索すると記念碑がよく写ってる写真があるサイトが引っかかり、2014年6月6日であることがわかります。

 

Curve

写真を見るとカーブしてそうなエスカレーターです。珍しいことで有名ですね。

検索結果でランドマークタワーの主張が強いので見比べてみると似てそうな雰囲気です。

 

destroyed

ウクライナでのミサイル(ракет)攻撃の被害の様子のようです。一旦日本語に翻訳させるとステプノ(Степненської)という地域のギムナジウム(гімназію 体育館と翻訳されますがギムナジウムのようです)であるということがわかります。

ミサイル ステプノ ギムナジウム で更にウクライナ語で検索していくと、ステプノの中でもレジノ村(Лежино)という場所にあるようです。

レジノ村はそこまで広くないのであとは写真と見比べながらそれっぽい場所を探します。大体このあたりのようです。

 

rider

目立つのは右端の"OTi? Fried Chicken"です。検索するとインドネシアの一部地域に展開しているチェーン店のようです。 数は多くないので検索に引っかかるものを一つ一つ道や横のパンダの看板と見比べて一致するものを探します。"OTI Fried Chicken Salatiga"が該当の店舗です。


schnee

上の"SKI RENTAL GRINDELWALD"の幟が目立ちます。GRINDELWALDはスイスの山のようです。

幟の下部の"Buri Sport"がどうも店の名前のようです。これで検索すると複数店舗ありますが数が少ないので全部見ます。これが該当の店舗です

 

power

ほとんど点字しか見えないので点字を訳すと下の文章っぽいところに”きゅうせきまさかど”とあります。検索すると将門塚が出ます。

 

hidden_wpath

解けてないです が貼られている画像からパスを推測するとリサイズされていないTsukuCTFのロゴ画像が落とせて、binwalkしまくると謎の画像が手に入ります。

よくわかりません まず代表というのは・・・?

ちなみに色を弄ったりとか輝度を弄ったりとかもしましたが特にわかりませんでした。

Crypto

これも専門外です

PQC0

システムの更新をサボっていたのでOpenSSLが3.5.0まで上がってませんでした。(ArchLinuxなので逆に言えば更新するだけで3.5系になります)

基本的にはoutput.txtの内容を適切に切り出して保存してprob.pyを参考にpublic key

の生成、secretのdecapを行い、その結果でAESのdecryptをするだけです。

 

a8tsukuctf

ヴィジュネル暗号を使った暗号です。tsukuctfの部分が変わらないということはその部分の鍵がaaaaaaaaになってしまっているということです。

暗号化部分を読むとヴィジュネル暗号を変形していて、鍵長を超えた部分は暗号文を鍵として使用しています。さらに暗号文の中にはスペースを無視するとちょうどaaaaaaaaになる部分が存在します。

なのでtsukuctfの部分が暗号文のaaaaaaaaと重なることより、元の鍵長は8であることがわかり、復号できます。

すると"????????joy this problem or tsukuctf, or both? the flag is concatenate the seventh word in the first sentence, the third word in the second sentence, and 'fun' with underscores."という文字列が得られます。文字数と復号できた部分から推測するに"please en"から始まるので(嘘っぽい)、後は復号文の指示通りにflagを組み立てます。

 

PQC1

秘密鍵が途切れているのでそのままだと使えません。

一旦PQC0の鍵を見てみましょう。

"-----BEGIN PRIVATE KEY-----"から始まる鍵はPEM形式と呼ばれるもので、データがBase64エンコードされているものです。

なので一旦Base64エンコードをほどきhexにします。

ついでに openssl pkey -inform PEM -in "PQC0の鍵ファイル" -text もします。

これら2つの情報を見比べるとなんとなく鍵データの構造がわかります。

どうやらseed, 復号用のdk, 暗号化用のekの3つのデータがあるようです。

ではPQC1の鍵データはどうなっているかというと、seedの途中で切れてしまっています。

これは大変困ります。seedが欠落していたら鍵の復元もできないように思えます。

 

ところで、PythonでのML-KEM-768の実装で、指定されたseedから鍵を生成するこのコードを見てみましょう。

このコードでは64Byteのseedを32Byteの2つのデータd,zに分割しています。そして実際にekとdkを生成する_keygen_internalに渡しています。この中でdとzの値の使われ方を見ると、dは積極的に使用されているのに対し、zはdkの末尾にくっつけられているだけです。ekに対してzは関与していないので、zが異なっていてもdが同じseedから生成されたdkならば正しく復号できないとおかしいですね。(そうでないとekが魔法を使ってzを知っていることになりますからね)

 

さて、PQC1の鍵データに戻りましょう。先程seed値がちぎれているということでしたが、どうも32Byte以上は存在しているようです。つまりdは確保できるというわけです。

ということで先程のPythonのライブラリを使用して、zの欠落部分は0埋めしたseedからek,dkを生成し、このdkでsecretをdecapすることで、PQC0と同様にAES decryptをしてフラグを手に入れることができます。

 

PQC2

解けてないです。が、PQC1と同様にすると先頭が切れているdkと完全なek(とseedのz)は手に入ります。でもekからdkが生成できると公開鍵暗号方式としてはマズイんですよね。dk欠落部分かseedのdを総当りしていたら時間終わっちゃいますし。d->(ek,dk)も途中でハッシュ関数が挟まっているので一方向ですし。

 

web

やっぱり専門外です

len_len

JavaScriptは変数になんでも代入できてしまうのでパースした結果うっかりオブジェクトが代入されることがあるというわけです。これでlengthプロパティを上書きしてしまうというわけです。

array={"length":-1} をPOSTするとフラグが帰ってきます。

 

flash

フラッシュ暗算みたいなアプリですが途中の数値が消えます。

しかし、出てくる数値はセッションIDから生成されているので、Cookieを控えた上で一度正解を取得し、2回めで結果送信前にCookieを書き戻し、取得しておいた正解を送信することで正解することができます。

 

pwn

行けるかと思ったら一つも解けませんでした。(1つ目が解けないならどうにもならんので それはそう)

easy_kernel


試行錯誤の跡

おわりに

おわりです

専門外しかない割には頑張ったんじゃないでしょうか 一人だし