全完して7位。1位のst98氏は午前中に終えるという驚異的なスピード。すげえよ。
全体的に難易度は低めで初~中級者向けだった。前回よりも簡単に感じた。
いつも通りwriteupを書いていく。できるだけ初学者にも解く手順がわかるように書いているつもり。わからない点があったら答えるので何らかの形で聞いてほしい。

PGカテゴリ(プログラミング)
縮めるだけじゃダメ [100pt, xx solves]
添付のExcelファイルからフラグを読み取ってください。
マクロ付きのExcelファイルが与えられるので開いて表示を調整してみるとこんな感じ。

何かフラグが書かれているが {
と }
の間の数字がわからなくなっている。マクロを見るとこんな感じになっている。

セルの色を設定してお絵描きしている模様。実行してみても特に変化は無いように見える。よくわからんのでデバッグしてみようとステップ実行していったところ、あるところまで行くとこのように。これより先に進むと数字が潰されて初期状態と同じになる。

Flag: flag{268653}
暗算でもできるけど? [100pt, xx solves]
添付のソースコードを実行した際の出力値の68番目の値と、このソースコードから推測される314番目の値を足した数を答えてください。
ソースコードが改行されてないので読みにくいが、1000以下の素数を求めるものになっている。68番目の素数は337、314番目の素数は2083で、足して2420。
Flag: flag{2420}
formjacking [200pt, xx solves]
添付のファイルは「Card Stealer」と呼ばれるフォームからの入力値を外部へ送信するJavaScriptです。 カード情報が妥当な場合、その値は外部へ送信されるようなので追跡したいです。
与えられたファイルは難読化されたJavaScriptファイル。解読するのは自分には無理なので box-js を使って解析してみる。

アクセス先のURLが得られた。cardnumber
にはテスト用のカード番号、exp-date
には未来の日付、cvc
には適当な値を入れてリクエストを出してみるとフラグが得られた。

Flag: flag{f1iping_de0bfuscat0r}
loop in loop [300pt, xx solves]
以下の要件を満たすプログラムを作成してください。 プログラムの言語は問いません。
1.引数として以下の値を指定できる。
- 第一引数:文字列
- 第二引数:文字列
2.プログラム内部で引数に以下の処理を加える。
- それぞれの引数のハッシュ値を求める。ハッシュ関数にはRIPEMD160を使用する。
- 第一引数のハッシュ値の1文字目と第二引数のハッシュ値の1文字目を抜き出し、それらの値が両方数値だった場合、それらのXORを求める。そうでない場合は何も処理しない。
- 続いて、第一引数のハッシュ値の1文字目と第二引数のハッシュ値の2文字目を抜き出し、それらの値が両方数値だった場合、それらのXORを求める。そうでない場合は何も処理しない。
- 同様に、3文字目、4文字目と続け、と第二引数のハッシュ値の最後の文字まで行う。
- 続けて第一引数のハッシュ値の2文字目に対して第二引数のハッシュ値の1文字目から同様の処理を行う。
- 同様に第一引数のハッシュ値の3文字目、4文字目と続け、と第一引数のハッシュ値の最後の文字まで行う。
- それぞれの値を加算する。
- 加算された値を10進数で出力する。
このプログラムに下記の引数を与えた時に出力される値を答えてください。
- 第一引数:Phoenix
- 第二引数:Messiah
問題文が長い。ちゃんと読まずにChatGPTに突っ込んだら答えが得られた。プログラム作成と引数を与えるのを別々に入力すると正しい答えを出してきたが、問題文全部をまとめて入力したら間違った答えを出してきた。コードは合ってように見えるんだが謎。
https://chatgpt.com/share/679f287a-36b4-8004-8655-1196df750e62
Flag: flag{5785}
NWカテゴリ(ネットワーク)
頭が肝心です [100pt, xx solves]
添付したメールファイルからフラグを探してください。 フラグはこのメールが届くまでに経由した2番目のメールサーバのIPアドレスとします。
メールをエディタで開くとこんな感じ。ISO-2022-JPがちゃんと解釈されてないけどまあ読めないことはない。
Received
の間に To
や Subject
が挟まっている不思議な形式だが、そこには目をつぶって Received
を見ていく。Received
はサーバを経由するごとに上に追加されるので、下から2番目の Received
の by
の後に続くのが2番目のメールサーバで mx.example.com
であるとわかる。そのIPアドレスはもう一つ上の Received
に書いてある。
Flag: flag{172.16.25.39}
3 Way Handshake? [200pt, xx solves]
添付したのはTCPポートスキャン時のパケットログです。 オープンポートを見つけてください。 オープンしているポート番号を小さい順に「,(カンマ)」で区切って答えてください。
ポートがオープンしている場合には SYN+ACK
が返ってくるのでこれを抽出するフィルタを作る。

info
列に書いてある矢印の左側の数字が開いているポート。
Flag: flag{21,23,37,70,79,98,109,110,111,113,143,513,514,1025,50506}
さあ得点は? [200pt, xx solves]
添付されたパケットファイルから攻撃を特定し、その攻撃のCVEを調べてください。 その攻撃のCVSS Version2.0のBaseScoreがフラグです。 CVSSのスコアはNISTで公開されている値とします。 https://nvd.nist.gov/
最初のTCPストリームを見てみると、見るからに怪しい Range
や Request-Range
ヘッダが見える。
“HTTP Range 脆弱性”といったキーワードで検索すると問題のCVEがCVE-2011-3192とわかる。NISTのページでこの脆弱性を調べるとCVSS 2.0のBaseScoreが7.8とわかる。

Flag: flag{7.8}
decode [300pt, xx solves]
添付のパケットファイルからフラグを探してください
配布されたZipファイルを展開するとpcapファイルがたくさん。どうやら細切れにしたファイルのようなので、Wireshark付属の mergecap
で一つにまとめて見てみる。
HTTP通信を見てみると、image.php
にGETリクエストを送って画像を取得している。画像データはレスポンスのJSONデータの image
キーの値。

値をコピーしてCyberChefを使って処理すると画像の確認までできる。かわいい猫ちゃんですね。

200 OK
が返ってきているのは全て猫ちゃんの画像で特にフラグに繋がる情報は無かった。レスポンスが正しく取れておらず、”Continuation”となっているものがいくつかあるので、TCPストリームの追跡をするとそれらでもレスポンスのJSONデータを得られる。そのうちの一つにフラグが書かれていた。

Flag: flag{c4ptur3_cat}
WEカテゴリ(Webアプリケーション)
簡単には見せません [100pt, xx solves]
アクセスしても「このページにフラグはありません」と出るのみ。おもむろに dirb
を仕掛けてみると、blue
というフォルダがあり、ファイルリストが見えることがわかった。

辿ると「このページにフラグがあります」と出る。はい、2000年代を生きたインターネット老人会員には簡単ですね。ソースを見ればいいのです。

Flag: flag{TakeMeToTheFlag}
試練を乗り越えろ! [100pt, xx solves]
下記のURLからフラグを入手してください。
フラグを得るには1万問の質問に答えないといけないらしい。これは大変だ。

問題は「今は何問目?」で常に同じ模様。1万問目の問題の答えもわかる。リクエストをいじって1万問目の問題に答えているようにすればよい。
私はFirefoxを使っていて、Firefoxの開発者ツールでは過去のリクエストのパラメータを編集して再送信することができるのでその機能を使って攻略する。下図の左下の「ボディ」にある qCount
と answer
で今何問目か、それに対する回答は何かを表しているのでそこをいじって10000にしてやるとフラグが得られる。

Flag: flag{WinThroughTheGame}
直してる最中なんです [200pt, xx solves]
下記のサイトから脆弱性のあるアプリケーションを特定し、その脆弱性を利用してフラグを入手してください。
https://we3-prod.2025winter-cybercontest.net
フラグが記載されているファイルは下記の通りです。 /etc/WE-3

ソースを見るとダウンロードの仕組みとやらがわかる。

download.js
にある dlFile
という関数を使ってファイルをダウンロードできるらしい。
download.php
にファイル名をPOSTしてファイルを取得する方式の模様。こういう時に試すのはディレクトリトラバーサルですね。これで目的のファイルをゲット。

Flag: flag{fGrantUB56skBTlmF14mostFP}
直接聞いてみたら? [200pt, xx solves]
下記のURLはAPIテストのためのフォームです。 ここからフラグを入手してください。

問い合わせボタンを押してみて開発者ツールで見てみると、Base64エンコードされたデータを送って、求める情報を返してもらっている。
送ったデータをデコードしてみるとどのフィールドが欲しいかを示したデータになっている。

今回欲しいのはフラグなので、flag
をリクエストしてみるとフラグが得られた。


Flag: flag{ParameterHandlingError}
整列! [300pt, xx solves]
旗の下に必要な者だけが正しく並べばいいのです。
アクセスするとリストが出てくる。
IDの横にあるUp/DownボタンでIDの値の昇順/降順に並べ替えができる。この時、アドレスバーを見ると sort=id+ASC
というクエリ文字列がくっついている。これはSQL文のorder句の値だと思われる。

クエリ部分を書き換えて sort=flagSec+ASC
にするとフラグ文字列が見えるが頭の方しか見えていない。order句の後ろで件数制限をしていると推測して、order句でSQL文を終わらせてみると全件出てきた。

Flag: flag{6f24d2267d87b7b232ed0d6ed3ad2924}
CYカテゴリ(暗号)
エンコード方法は一つじゃない [100pt, xx solves]
以下の文字列をデコードしてFlagを答えてください。
%26%23%78%35%35%3b%26%23%78%36%33%3b%26%23%78%36%31%3b%26%23%78%36%65%3b%26%23%78%34%32%3b%26%23%78%37%64%3b%56%6d%46%79%61%57%39%31%63%30%56%75%59%32%39%6b%61%57%35%6e%63%77%3d%3d%36%36%36%63%36%31%36%37%37%62
URLエンコードされているのでCyberChefでデコードしてみる。

HTMLの数値文字参照とBase64、文字列を16進表記したものの3つのパートに分けられるので、それぞれデコードして並べ替えてフラグを獲得。



Flag: flag{VariousEncodingsUcanB}
File Integrity of Long Hash [100pt, xx solves]
添付のZIPファイルの中から下記のファイルを探してください。 フラグはそのファイルの中に書かれています。
189930e3d9e75f4c9000146c3eb12cbb978f829dd9acbfffaf4b3d72701b70f38792076f960fa7552148e8607534a15b98a4ae2a65cb8bf931bbf73a1cdbdacf
この長い文字列はSHA512のハッシュ値と思われるので該当するファイルを探すと一致するものがあったのでそのファイルに書かれているものが正解のフラグ。

Flag: flag{346D895B8FF3892191A645}
Equation of ECC [200pt, xx solves]
楕円曲線のパラメータは以下の通りとします。
a=56,b=58,p=127
基準点(42,67)と設定した場合、公開鍵の値が下記になる秘密鍵の最も小さい値を答えてください。
公開鍵(53,30)
これもChatGPTに聞いたら正解を出してくれた。正解者数が多いところを見ると他の人も同じようにしたのではないかと思われる。
https://chatgpt.com/share/679f4155-efa8-8004-82f7-05484973fba6
Flag: flag{16}
PeakyEncode [300pt, xx solves]
文字化けした文が送られてきました。送信者によるとこの文字化けはインターネットから探してきたロジックを使って暗号化を施したかったそうです。 暗号化した際の環境が送られてきているので復号ができないでしょうか。
配布ファイルの script.rb
は以下のプログラム。この出力が encryption
ファイル。Dockerfile
もあるけど script.rb
を実行するだけで特に追加の情報はない。
require './encode.rb'
flag = File.open("flag", "r").read()
generate = PeakeyEncode.new.generate(flag)
generate = generate.gsub(">", "🚒")
generate = generate.gsub("<", "😭")
generate = generate.gsub("+", "😡")
generate = generate.gsub("-", "🙌")
generate = generate.gsub(".", "🌺")
generate = generate.gsub(",", "✍️")
generate = generate.gsub("[", "😤")
generate = generate.gsub("]", "🐈")
sjis = generate.force_encoding(Encoding::SJIS)
p sjis.encode(Encoding::UTF_8)
encode.rb
でフラグを何らかの方式でエンコードしているようだがこのファイルは配布されていないので処理内容は謎。encryption
の中身を見る感じ普通の文字は含まれていないので、ここで変換されている記号だけが使われているのではないかと推測。
その後にやっていることは、「UTF-8だと各絵文字として解釈されるバイト列を強引にSJISとして解釈、その後UTF-8に変換」である。
処理がどうであれ、一定の記号のみ使われるのであれば各記号と最終的な出力の対応表を作れば逆変換ができるので、対応表を出力するプログラムを作成した。
for c in ['>', '<', '+', '-', '.', '[', ']', ','] do
print c
print "\t"
c = c.gsub(">", "🚒")
c = c.gsub("<", "😭")
c = c.gsub("+", "😡")
c = c.gsub("-", "🙌")
c = c.gsub(".", "🌺")
c = c.gsub("[", "😤")
c = c.gsub("]", "🐈")
c = c.gsub(",", "✍")
print c
print "\t"
sjis = c.force_encoding(Encoding::SJIS)
utf8 = sjis.encode(Encoding::UTF_8)
print utf8
print "\t"
puts "#{utf8.bytes.map{|b|b.to_s(16)}}"
end
実行結果は以下。

カンマに対応する絵文字の変換時にエラーになっている。これは、他の絵文字はUTF-8では4バイトであるところ、カンマに対応する絵文字だけ3バイトであることが原因。SJISはASCII文字以外1文字2バイトなので変換できなくてエラーになっている。script.rb
と同じ処理をしてエラーになるということは実際の実行時にはカンマは表れてこないということになるので気にしないでいい。
この結果をもとに逆変換をするプログラムを作成。もうちょっとスマートな書き方はないものか。
d = open("encryption", "rb").read()
for i in range(int(len(d) / 6)):
s = i * 6
e = i * 6 + 6
if d[s:e] == b'\xee\x81\x9e\xe5\x9b\x83':
print('>', end = "")
elif d[s:e] == b'\xee\x81\x9e\xe4\xba\x8a':
print('<', end = "")
elif d[s:e] == b'\xee\x81\x9e\xe4\xb8\x95':
print('+', end = "")
elif d[s:e] == b'\xee\x81\x9e\xe5\x89\x8f':
print('-', end = "")
elif d[s:e] == b'\xee\x81\x9e\xe7\x8e\x84':
print('.', end = "")
elif d[s:e] == b'\xee\x81\x9e\xe4\xb8\xb6':
print('[', end = "")
elif d[s:e] == b'\xee\x81\x9e\xe7\xb2\x8b':
print(']', end = "")
else:
print(',', end = "")
print()
これを実行して以下の出力を得る。
これは何かというと Brainf*ck のプログラム。使われている記号の時点で推測していた。Brainf*ckでのカンマは標準入力からの読み込みであり今回の処理では表れてくることはないのでエラーになっても問題はないというわけであった。
手元に処理系がないのでオンラインBrainf*ckデバッガを使って実行。フラグを得る。

Flag: flag{you_know_bra1n}
FRカテゴリ(フォレンジック・バイナリ解析)
ここまで書いた時点で問題リストが見られなくなってしまっていたのでこれ以降は問題文無し。
露出禁止! [100pt, xx solves]
ログを見ると mypage.php
へのアクセスにセッションIDがくっついているのでそれをそのまま使ってアクセスしてみると、有効期限切れと言われる。

セッションIDはBase64に見えるのでCyberChefでデコードする。

カンマで区切られた3つのフィールド。1番目はUNIX Timestampと思われる。2番目は不明。最後はユーザ名だろう。1番目を理解しやすい日付に変換してみる。

これが恐らく有効期限だと思われるので、未来の日付にしてセッションIDを作り直してアクセスするとエラーにはならなかったが、「このユーザにはフラグは表示されません」と出る。

ユーザ名をadminにしてみてもダメだった。セッションIDを構成する3つのフィールドのうちの2番目の数字をいじってみたところ、これを1にすることでフラグが表示された。

Flag: flag{SessionIDsCarefully}
成功の証 [200pt, xx solves]
与えられたpcapファイルを見てみると、FTP通信が見えるのでftpでフィルタをかけてみると、ログイン試行を繰り返している様子が見える。”Login incorrect.”ばかりなので”success”で検索をすると”Login successful.”が1つだけ見つかる。同じタイミングで複数のセッションが動いている関係でどのパスワードが正解かわからないのでTCPストリームの追跡を行うことでそのセッションのパスワードを得た。

Flag: flag{zyyzzyzy}
犯人はこの中にいる! [200pt, xx solves]
グローバルIPアドレスを偽装していたとしても、MACアドレスまでは偽装していない(はず、していると問題にならない)。グローバルIPアドレスを偽装しているパケットからSrcのMACアドレスを抜き出してそのアドレスを使ってフィルタしてやると、同じMACアドレスを持つローカルIPアドレスが特定できる。


Flag: flag{192.168.204.137}
chemistry [300pt, xx solves]
何度か FR-4
を実行して、ある数字を入力したときの出力は固定で、単発で実行しようとも前後に他の数字があろうとも変わらないことを確認した。では対応表を作ってやればよい、と以下のようなコマンドを実行して対応表を得た。

出力が正しく返ってくるのは118までだった。それだけならそれほど難しいことはないので、出力が”FLAG I AM LUCKY”になるような入力を、手動でパズルを解いて求めた。

Flag: flag{114,47,0,53,0,95,0,71,6,19,39}
InSecureApk [300pt, xx solves]
APKのデコンパイルはjadxを使えば簡単にできる。展開してファイルを取り出してなどなどいろいろやらなければならなかった昔と比べると格段に楽になり申した。
FR-5.apk
を開いて見てみると、MainActivity
内で入力のチェックをしている。この比較文字列がフラグかと思ったけどそうではなかった。

比較の1行上で SecretGenerator.deoce
で入力文字列を処理している。ではこの処理を見てみようとするも、実際に処理をしている checkNative
の実装が書かれていない。

その下に System.LoadLibrary("insecureapp");
とあるので調べてみると、Androidアプリではネイティブライブラリをロードして使うことができる模様。そのライブラリはAPKをZipとして展開した時の lib
フォルダに配置されている。x86_64用の libinsecureapp.so
をIDAで読み込んでみるとExportsに checkNative
を見つけたので見てみる。
最初のブロックに気になる文字列を発見。でもこれはフラグではなかった。

APKで比較してた文字列とこの文字列、どちらも16文字なので、じゃあXORだろ!と雑に考えてやってみたらフラグが出てきた。

Flag: flag{AppNoGuard}
一応ループしているコードの中にXORは出てきていたので根気よく読んでいけば正攻法で解けたかもしれないが、XOR前後のcallが何だか不明でめちゃくちゃ時間がかかっていたものと思われる。
PWカテゴリ(PWN)
CVE-2014-7169他 [100pt, xx solves]
CVE-2024-7169はShellShock。使いやすいから気持ちはわからないでもないけど、古すぎませんか。
与えられたログファイルを見ると、User-AgentにShellShockのexploitを埋め込んでいる。また、ステータスコードより n.cgi
だけが存在していることがわかるのでこれに対してexploitを投げてやればよい。
ちなみに、n.cgi
に直接アクセスすると”shellshockable”と教えてくれる。親切。
Firefoxの開発者ツールを使ってUser-Agentを書き換えてリクエストを送ることでフラグを取得。

Flag: flag{>:(!shellshock!}
問題名にある「他」は何だったのだろう?
認可は認証の後 [200pt, xx solves]
与えられたURLにアクセスするとログイン画面が出てくる。

アカウントに”12345678″、パスワードに”password”を入れて送信した際の通信をBurp Suiteで見てみた。

ログインページにはJavaScriptでValidatorがあってめんどいのでBurpのRepeaterに送ってリクエストをいじってみると、name
のところにSQLインジェクションの脆弱性がありそう。

定番のクエリを入れてみると、何やら認証が成功したようでCookieでセッションIDが返ってきた。

出てきたCookieをセットして mypage.php
にアクセスしてみる。「フラグを表示」ボタンを押すと管理者でないとダメと出る。


mypage.php
を表示したときのソースを見てみると、「フラグを表示」ボタンでは flag.php
にPOSTしているが、追加のデータとして admin=0
としている。これを admin=1
としてみたところ、フラグが表示された。

Flag: flag{DoNotUseParameter2Auth}
おまけ。sqlmap
でデータベースをダンプしてみた。データベースにテーブルはこの1つだけ。
管理者フラグは無し。secret
が”No flag”だと管理者じゃない判定なのかと思い適当な値を設定した行を UNION SELECT
で追加してもダメだった。であればあの admin=0
はどこから持ってきているというのか。こういう理不尽さは無くしてほしかった。
formerLogin [200pt, xx solves]
与えられたPowerPointファイルはこのようなもの。特に裏に隠されたオブジェクトなどは無し。

指定のURLにアクセスするとこれ。

社員IDと氏名としてイニシャルを入れればいいらしい。しかしPowerPointに見える人物のどれを見てもダメ。当然そんな簡単なわけがない。
というわけでPowerPointファイルの中身を見てみる。PowerPoint(というかOffice)のファイルの実体はZipファイルなので展開してみる。すると docProps/core.xml
に新たな人名 ytanaka
と htahakashi
を見つけた。

また、ppt/slides/slide1.xml
には TGT260
という社員IDを見つけた。

ytanaka
の y
と htahakashi
の h
はいずれも名前の頭文字だろうと推測して社員ID TGT260
に対応するイニシャルとして入れてみてもダメ。
そもそもイニシャルの表記には複数ある。姓と名のどちらを先にするか、ピリオドはつけるか、姓名の間にスペースをいれるかで単純に一人あたり8パターンある。
社員IDも TGT260
が正解とも限らないので、TGT000
~ TGT999
まであり得る。
全パターンを作成して ffuf
でBrute forceするも成功せず。
イニシャルとは書いてあるものの違うかもと思って ytanaka
形式の氏名も試してみたがやはりダメ。
ここで、ずっと頭にひっかかっていた hta
ha
kashi
を hta
ka
hashi
にしてみたところ、TGT260
との組み合わせで通った。
これが最後の1問だったのでよっしゃ!という気持ちと、ふざけんな!という気持ちが溢れた。イニシャルじゃない上に自分の名前typoしてんのかよ。この問題は今回随一のクソ問としてマークされた。次点は「認可は認証の後で」。どっちもPWカテゴリかい。
Flag: メモし忘れた
overmeow [200pt, xx solves]
プログラム overmeow
が渡され nc
での接続先も与えられる。脆弱性を見つけてExploitしろということだ。overmeow
をIDAで見てみる。
_gets
の入力先のバッファは var_20
で 'mdow'
と比較しているのは var_8
。24バイト適当な値を入れてから 'mdow'
を入れればよい。エンディアンに気を付けて入力するとフラグが返ってきた。(画像のpwdがおかしいが気にするな)

Flag: flag{I_will_Golondon}
heapmeow [300pt, xx solves]
今度はソースコードが与えられる。2. Allocate Cat
すると鳴き声を聞いてくるくせに says
メンバに書き込まない不思議実装。
初期状態で1つ Cat
がallocateされた状態だが、 4. Free cat
した後に 2. Allocate Cat
するとFreeしたのと同じアドレスに確保されるようで、24バイト適当な値の後に meow
を入れると条件を満たしてフラグが得られた。

Flag: flag{cat_g0es_me0w}
TRカテゴリ(その他・トリビア)
合体はロマン [100pt, xx solves]
4つに分割されたQRコードの画像が渡される。以下の画像は5倍に拡大したもの。




QRコードの基本についてはQRコードのしくみ | バーコード講座 | キーエンスを参照。
QRコードには白か黒かが確実に決まっている領域がある。左上・右上・左下にあるファインダパターン、右下にあるアライメントパターン、タイミングパターンである。
アライメントパターンがあるのは TR-1_3
のみなので、TR-1_3
が右下部分なのは確定。白黒が反転していることに注意。他の3つは白が背景色だがこれだけは黒が背景色になっている。また、タイミングパターンがファインダパターンから2方向に出ているのは TR-1_4
のみなので、これが左上部分であることが確定する。タイミングパターンの残りの部分に相当する箇所は TR-1_1
と TR-1_2
のどちらにもある。
4つのパーツを本来のQRコードの位置に配置するためには回転や反転の操作が必要になる。まずは反転の可能性を考えず、回転だけで配置を試みると、TR-1_1
が左下部分、TR-1_2
が右上部分となる。この配置で4パーツの結合を試みたところ、不整合なく綺麗にパーツが一部重なりつつQRコードが完成された。これを読み取ると、ThisCodeIsLevelH
と出る。
Flag: flag{ThisCodeIsLevelH}
余談。TR-1_3
以外の3つは、セルの色が単色ではなく同系統の色複数によるまだら模様になっている。セルの色を変えるだけなら4パーツの識別をしやすくするためにありだと思うのだが、複数色使っているのは単純な色の置き換えができないようにすることが目的なのではないかと勘繰ってしまう。そんなのこの問題の本質的なポイントじゃないんだからわざわざ処理しにくくする必要ないだろと。こういうのは嫌い。
Windowsで解きましょう [200pt, xx solves]
与えられたバッチファイルを中身を見ずに実行すると、90個のファイルが作成された。

どれが正解のフラグか。「Windowsで」と言っているのだからWindows固有の何かを使っているはず。ファイル関連でWindows(というかNTFS)固有と言えば代替データストリーム(ADS, Alternative Data Stream)。dir
コマンドだと /r
スイッチで確認できる。

flags_25.txt
が”TrueFlag”らしいのでこのファイルの中身が正しいフラグとわかる。

Flag: flag{7525252364612534937244}
別解。ADSとか知らなくてもバッチファイルの中身を見れば flags_25.txt
にだけ何か特別なことをしていることがわかる。VHDXを配るとか、もうちょっとなんとかならなかったのかな…

ところで、flags_25.txt:TrueFlag
には何のデータも書き込まれていないんだけど、dir /r
の結果ではサイズが22になってる。これは何でだろう?
排他的論理和 [300pt, xx solves]
3つのファイルと比較対象のファイルが与えられる。ファイルサイズは全部10バイト。
XORを取ってみる。



pattern3
の時だけフラグの形式になっている。カッコ内は4バイトのデータでASCIIではないが、IPアドレスを回答しろということだったのでこの4バイト(ac
, 1d
, ef
, fd
)をIPv4アドレスの表記にするとフラグになる。
Flag: flag{172.29.239.253}
おわりに
問題数が多かったので長くなった。最初にも書いたが難易度は低めでCTFをやってみようという人のステップアップにちょうどいいのではないかと思う。一方で質の悪い問題もあったが、30問中1,2問であれば許容範囲か。あと脆弱性に関する問題があるのはいいんだけど、ちょっと古すぎ。使いやすいのはわかるが。
運営の姿勢には疑問が残った。当初の募集段階では禁止事項に入っていなかったWriteupの公開を禁止するようなルールを通知したり(これは撤回された)、アンケートの有効期限が競技終了1時間後の22時までだったり。翌6時までとかでもいいものを1時間しか猶予与えていないのはそんなにアンケートに回答されたくないのか、と思ってしまう。
ともあれ、たくさん解けるコンテストは楽しい。来年もあることを期待したい。