はじめに
こんにちは。部内サービスの開発やCTFなどをしている、19Bのxxpoxxです。
本日、5/1にCPCTF22、競プロとCTFの体験会が開催されました。参加してくださった皆さん、ありがとうございました。
私はForensics、OSINT、そしてMiscジャンルにおいて、10問ほど作問を担当していました。この記事では、作問者writeupを書いていきます。
writeup
以下の10問を作問しました。
- Forensics/sunset (newbie)
- Forensics/png...? (easy)
- Forensics/deeper (easy)
- OSINT/Welcome to OSINT! 1 (newbie)
- OSINT/Welcome to OSINT! 2 (easy)
- OSINT/hacker (easy)
- OSINT/illumination (easy)
- Misc/click (easy)
- Misc/the luck 1 (easy)
- Misc/the luck 2 (medium)
Forensics
Forensicsについて
個人的にhijiki51の作問したDive!は、diveというツールの紹介はもちろん、解こうと思えばtarを解凍してうまいこと解けるという点で非常に面白いと感じました。Dockerなどの新しい技術の登場によりForensicsで解析する対象はより広くなっていくので、置いていかれないようにしたいですね。
全体的にnewbieは54solved、easyは12~20solved、mediumは1solvedと難易度勾配としては(やや急ですが)程よい勾配だったのかなと思います。png...?は元から難しいことがわかっていたのでmedium、The omegaはhardだったかもしれません。png...?のような問題はどんどんヒントを開けるのも戦略の1つですね。私は1度しかCPCTFに参加者として参加していないのであてになりませんが...
sunset (10.00pt / 54 solved)
毎度おなじみ横須賀の写真です。私の地元です。
この問題はForensicsの入り口として、exiftoolの使い方を通してコマンドを用いてファイルを解析する方法を知ってもらう目的で出題しました。
wgetコマンド等を用いてファイルをダウンロードした後、exiftool flag.jpg
というコマンドを用いることで画像のexifが確認できます。exifは、写真用のメタデータ等を記録している規格です。撮影に関する情報から、コメントまで付随することができます。今回の問題ではこのCommentフィールドにフラグが記述されていました。そもそもexiftoolというコマンドまでたどり着かなかった方は、こちらの記事などを参考にしてForensicsの様々な導入を知ると良いと思います。
png...? (376.23pt / 12 solved)
pngの規格に関する問題です。このように少し非自明な問題は解きづらく、フラストレーションをためがちであることは分かっているのですが、CTFをはじめる方に、様々なファイルのバイナリのデータ構造に親しみを覚えてもらえれば、という気持ちで今回も作問しました。同じくForensicsのmedium問であるThe omegaも同様にバイナリの不備によるフラグの秘匿だったので、かぶってしまい申し訳ないです。
Forensics/sunsetの解説であげた記事に記載してあることなどを行ってもフラグが全く見当たりません。画像ファイルが与えられてこのような状況の場合、私はしばしばステガノグラフィーまたはバイナリの不備を疑います。今回はファイルを開くと真っ白な画像が見えるので、ステガノグラフィーのようにも見えますが、png...?という問題名からもわかるように、pngとしてのバイナリの不備が原因でした。pngは非常にわかりやすいデータ構造をしています。PNGヘッダーに続いて複数のチャンクが並ぶ形式です。今回の問題では、IDATチャンクのデータ長を示す部分が0x0であったために実データ長が0であると認識され、真っ白な画像が表示されていました。ここを適当に0xffffなどに修正することでフラグを見ることができます。
deeper (300.00pt / 22 solved)
Forensicsによくある様々な形式で圧縮などの変形が為されている問題です。面倒な手を動かすだけの作業ではあるのですが、退屈でないようにbase64などを取り入れ、新入生にも好奇心とともに取り組んでもらえたら、との思いで作問しました。
基本的にはfileコマンドでファイルの種類の特定、解凍などの変形の繰り返しです。最初の一歩として、png形式の画像のあとにzipファイルが埋め込まれていることを、binwalk
コマンドなどを用いて気づく必要がありました。この問題で面白いところは、そのようなpngファイルもビューワーで閲覧することができ、さらにそのままunzip
コマンドで解凍することができる点でしょうか。私の拙いメモ帳お絵描きを見ることができます。このような問題ではスクリプトを書いて自動化させることを強いてくる問題もあるので、時間のある初心者の方は取り組んでみると良いかもしれません。
OSINT
OSINTについて
OSINTは気づいたら全ての作問者がxxpoxxでした。あまりCTFらしくなく、Miscに含まれることもあるジャンルですが、好きな人は好きだと思いますし、CTFの導入として面白い!と感じてもらいやすいジャンルだとも思います。調査対象になりきってメールを送るなどのより現実に即した状況の問題であればもっと興味をひけるのでしょうか。来年の作問では、生えてる植物の種類から植生を用いて地域を推測するなどのより面白い問題を期待しています。私は家から出ないのでそのような問題が作れず...毎回地域メタ読みを通させてしまい申し訳ないと感じています。
1人で作ることができたことも影響したのか(?)、難易度調整としては良い感じになったと思っています。少し簡単な問題が多すぎて物足りなさが残るセットだったかもしれません。OSINTで難しい問題を作るとなるとどうしてもpastebinをうまくアーカイブさせて...などして、ただ作業量を増やす方向に向かいがちなので経験を積んで幅を広げていきたいと考えています。
Welcome to OSINT! 1 (10.00pt / 72 solved)
ラーメンは美味しいですよね。この俺流塩ラーメンの豚骨ラーメンは美味しかった記憶があります。期間限定だったかもしれません。
OSINTというジャンルは、Open Source Intelligenceということもあり、OpenなSourceをもとに問題を解いていきます。ここでいうOpen Source Intelligenceとはなんなのでしょうか。様々なものがあげられるでしょう。最近では、真偽、政治的主張は別として、勲章の量から死者数を予測するというものを見ました。知りたい情報に対して適切な情報源を選ぶのがこのジャンルの難しいところですが、CTFにおいては基本的に親切にとりあえず検索すればなにかしら情報が出てくるようになっているものが多いです。この問題では、画像をGoogle 画像検索にかけると、そのままの画像を用いているnm blog
というブログがヒットします。なぜ画像検索にかけるかというと、最も手っ取り早い検索方法だからでしょうか、手癖のようなものです。そのページを開くと、フラグを見ることができます。newbie問はこういうことをするジャンルですという問題を作っても、それを理解する頃には問題を解いており、本末転倒感が否めないのが難しいところだと思います。
Welcome to OSINT! 2 (414.32pt / 30 solved)
多くの人がヒント3まで開封しての解答、ヒントなしで解いた人が3人であったことに少し驚いていました。
問題文は以下のものです。
調査対象の氏名がWelcome to OSINT! 1にて明らかになった。この情報からさらに調査が進むはずだ。頼んだぞ、エージェント諸君。
氏名が明らかになったという部分から氏名をもとに調査をすることが予想でき、何らかのアカウントがあると見当をつけてnamechk.comなどを用いる想定でした。この部分が少し論理の飛躍で正答者の低下につながってしまったと思い、反省です。GitHubのアカウントを見つけると、同時にTwitterのアカウントを見つけることができ、ツイートされているフラグを得ることができます。OSINTのコンテストでは、調査対象の氏名と嗜好からアカウントを特定するなどの問題もあったので、この問題を知っているか等の背景知識の違いも悪い影響を与えてしまったように思えます。ヒントを開けてなんだそれとなった皆さん、申し訳ございません...
hacker (372.41pt / 26 solved)
この問題に関して報告なのですが、コンテスト中は私の手元で動かしていた問題サーバーをすでに落としたので、運営が対応するまでリンクを踏んでもエラーが返ってくると思います。
TorというOSINTなどに精通していないとあまり馴染みのない領域の入り口としての問題でした。なお、ダークウェブの使用は注意して行いましょう。
特徴的なドメインである.onionからTorまでは検索等でスムーズにいってほしさがあります。このサイトを開くと、Welcomeと書かれた画像と、ハッカーを探しているという内容の文章が表示されます。しかし、フラグはどこにも見当たりません。少しWeb問っぽくなるのですが、画像が少し怪しく見えます。ソースコードを見ると、画像が8000番ポートから配信されており、そこに別のサービスが立っていることが予想できます。このポートにアクセスすると、フラグを得ることができます。この問題も、ポートにアクセスする部分が論理の飛躍があり、ヒントなしでの解答者は9人、ヒント3まで開けての解答者は16人とWelcome to OSINT! 2と同じような分布になってしまいました。いわゆるエスパー問になってしまい、申し訳ないです。画像によるForensicsの匂わせもあり、easyにしては解きごたえのある問題だったと思います。
illumination (287.18pt / 25 solved)
一番好かれて一番嫌われる画像撮影地問題です。解き方は本当に自由で、ぶれていながらもダイソーとユニクロと分かった人は検索ですぐ出てくるでしょうし、そうでない人もイルミネーション、商店街、ユニクロという単語を合わせると検索結果の画像表示において答えを見つけることができるでしょう。できるだけ解きやすいように、商店街のイルミネーションとして少し知名度のある横浜・伊勢佐木町商店街が答えでした。
この問題を問題に昇華してくれたankoくんには感謝です。
Misc
Miscについて
Miscはジャンル分けに困った問題と書いてありますが、自分としては面白いと思った話題を詰め込めて作問も解くのも好きなジャンルです。実はMellonの作問にも少し関わっているのですが、問題の不備により多くの人に迷惑をかけてしまい、申し訳なく思っています。問題のチェック体制などの反省を残し、来年に生かそうと思います。Miscで衝撃的だった問題として、元traPで現TSGに所属しているmikitさんによる、TSG CTF 2021 B??e64が挙げられます。このような問題を作れるようにこれからも頑張っていきたいですね。
難易度としては、自分の作った問題群は少し難しくなってしまう傾向があるようです。もしかしたら全部mediumだったかもしれません。
click (419.03pt / 15 solved)
Unity WebGLビルドのチート問題です。ゲームというかアプリケーションとして少しおざなりなのは勘弁してください...手元で動くもののチートでもそうなのですが、基本的にメモリ上の値を取ってこれさえすればあとはどうにでもなるようなところがあります。実際、Cheat Engineを用いる問題ではそのような解法が非常に多いです。これをブラウザ上でも行いたいのですが、Cetusという非常に便利なツールによりスムーズに解くことができます。Cheat Engineでも頑張れば解けるようですが、想定解としては用意していませんでした。申し訳ないです。
解法としてはCetusを用いてメモリ上の値を検索し、それを999999999にしたあとボタンをクリックするだけという簡単なものです。新入生向けのCTFということもあり、ツールの紹介も兼ねた問題でした。
the luck 1 (319.43pt / 20 solved)
この問題の面白みはtime(NULL)はintであるという部分です。例えばUUIDv1の生成にはtime_ns
が用いられますが、Cでしばしば見られるsrand(time(NULL))
という記述ではUNIX時刻がそのままシードとして使われています。ゲームの乱数など、ある程度セキュリティ的に問題がない部分での使用は考えられますが、今回のようなセンシティブな情報がある場面では控えましょう。逆にいうと、ゲームの乱数などでは、RTAで時間を調節して乱数調整している姿をよく目にして面白いですよね。それでも今回の問題よりもっとシビアですが...
解法としては、1秒以内に接続した2つのクライアントで、片方のクライアントに返ってきたresult
をそのままもう片方のクライアントで送り返すというものでした。
the luck 2 (344.19pt / 15 solved)
この問題はthe luck 1のようにシードを指定しておらず、システムの/dev/urandomを用いて初期化されているようです。これではthe luck 1のように解くことはできません。しかし、the luck 1と違い、何度も出力結果を見ることができるようです。ところでPythonのrandomモジュールに関するドキュメントを見ると、セキュリティ目的で使用するなと書いてあります。randomモジュールの内部で用いているMersenne twisterでは、今回のように何度も出力結果を見ることができる状況、実際には624回の出力結果を見ることができる状況では、次の出力が予想できてしまうのです。具体的には以下の手順を踏みます。
Python3のrandom
はMersenne Twisterを使っています。Mersenne Twisterは内部状態として624個のunsigned longを保持しており、これに対してTempering
と呼ばれている処理を施したものが出力となります。この内部状態は624回出力が行われると全更新されます。この更新には前の624回の内部状態から漸化式で生成されており、つまり624回の出力から内部状態を生成することができます。この性質を用いてMersenne Twisterの出力を推測することができます。
まず、Tempering
を元の内部状態に戻す、Untempering
の処理を行います。Tempering
は疑似コードで、以下のような処理が行われます。
temper(x):
x <- x xor (x >> 11)
x <- x xor ((x << 7) & 9d2c5680)
x <- x xor ((x << 15) & fffaffff)
x <- x xor (x >> 18)
return x
y = x xor (x >> 11)
からx
を求めるには、y xor (y >> 11)
とy
のxorを取ると、x >> 11
の部分が打ち消され、このbitが求まります。このような操作を繰り返すことで、x
を求めることができることがわかります。これを疑似コードにすると、以下のようなコードになります。
untemper_right_shift(y, shift):
z = y
known_bits = shift
i = 0
while (i < 32) begin
z <- y xor (z >> shift)
known_bits <- known_bits + shift
i <- i + 1
end
return z
x <- x xor ((x << 7) & 9d2c5680)
の部分等も同様にしてx
を求めることができ、Untempering
の処理が完了します。すると、乱数生成器の内部状態を再現することができるため、以下のようにして出力を推測することができます。
mt_state = tuple([untemper(x) for x in v] + [624])
random.setstate((3, mt_state, None))
predicted = random.getrandbits(32)
よって、想定解は以下のコードを実行したものです。
from pwn import *
import random
def untemper(x):
x = unbitshiftrightxor(x, 18)
x = unbitshiftleftxor(x, 15, 0xefc60000)
x = unbitshiftleftxor(x, 7, 0x9d2c5680)
x = unbitshiftrightxor(x, 11)
return x
def unbitshiftrightxor(x, shift):
i = 1
y = x
while i * shift < 32:
z = y >> shift
y = x ^ z
i += 1
return y
def unbitshiftleftxor(x, shift, mask):
i = 1
y = x
while i * shift < 32:
z = y << shift
y = x ^ (z & mask)
i += 1
return y
io = remote('theluck2.cpctf.space', 30001)
print(io.recvuntil(b": "))
v = []
for i in range(624):
io.sendline(b'0')
io.readline()
result = int(io.recvline().decode(
'utf-8').rstrip('!\n').lstrip('"The result is '))
print(result)
v.append(result)
io.readline()
io.sendline(b'y')
mt_state = tuple([untemper(x) for x in v] + [624])
random.setstate((3, mt_state, None))
predicted = random.getrandbits(32)
io.sendline(str(predicted).encode('utf-8'))
print(io.readline())
print(io.readline())
最後に
問題不備等でご迷惑をおかけし、申し訳ございませんでした。今後もCPCTF22に関するブログ記事が公開される予定ですので、お楽しみに!