Learning cyber security by playing and enjoying CTFs

Cyber Security関係の雑記帳です。表明されているお気持ちなどは全て個人的なものであり、筆者が所属もしくは関係する組織・団体の意向とは一切関係ありません。

zer0pts CTF 2022 参戦記

1. はじめに

 2022/3/19(土)09:00JST ~ 3/20(日)21:00:00JSTに開催された「zer0pts CTF 2022」にチーム「N30Z30N」でソロ参加しました。この記事はその参戦記*1です。

2. 開戦前

 競技時間は36時間なので、睡眠・食事・休憩を除き24時間程度はフル稼働できるよう、週前半から体調を調整・・・出来ませんでした。サーセン。実態としては、

  • 3/16 Warmupを兼ねてpicoCTF2022を始めた矢先に地震を食らう。ツラ。※幸い物的被害はほぼナシ。
  • 3/17 睡眠障害発生、競技中のもぐもぐタイム用に準備した「どらやき」を先行して食してしまう。
  • 3/18 前日の反動で寝落ち。しかし夜中に覚醒してしまい体力回復に失敗。
  • 3/19 8:30くらいからからパソコン前に座ってドキドキワクワクするつもりが起床事故で8:55に。

 という具合にgdgdでした。まぁ、いつものことなのですが。

3. 順調な?滑り出し(Crypto:Anti-Fermat)

 Crypto全完を目標(出来るとは言っていない)に、まずはWarmup問から着手。

I invented Anti-Fermat Key Generation for RSA cipher since I'm scared of the Fermat's Factorization Method.

ファイル:「task.py」

from Crypto.Util.number import isPrime, getStrongPrime
from gmpy import next_prime
from secret import flag

# Anti-Fermat Key Generation
p = getStrongPrime(1024)
q = next_prime(p ^ ((1<<1024)-1))
n = p * q
e = 65537

# Encryption
m = int.from_bytes(flag, 'big')
assert m < n
c = pow(m, e, n)

print('n = {}'.format(hex(n)))
print('c = {}'.format(hex(c)))

ファイル:「output.txt」

n = 0x1ffc7dc6b9667b0dcd00d6ae92fb34ed0f3d84285364c73fbf6a572c9081931be0b0610464152de7e0468ca7452c738611656f1f9217a944e64ca2b3a89d889ffc06e6503cfec3ccb491e9b6176ec468687bf4763c6591f89e750bf1e4f9d6855752c19de4289d1a7cea33b077bdcda3c84f6f3762dc9d96d2853f94cc688b3c9d8e67386a147524a2b23b1092f0be1aa286f2aa13aafba62604435acbaa79f4e53dea93ae8a22655287f4d2fa95269877991c57da6fdeeb3d46270cd69b6bfa537bfd14c926cf39b94d0f06228313d21ec6be2311f526e6515069dbb1b06fe3cf1f62c0962da2bc98fa4808c201e4efe7a252f9f823e710d6ad2fb974949751
c = 0x60160bfed79384048d0d46b807322e65c037fa90fac9fd08b512a3931b6dca2a745443a9b90de2fa47aaf8a250287e34563e6b1a6761dc0ccb99cb9d67ae1c9f49699651eafb71a74b097fc0def77cf287010f1e7bd614dccfb411cdccbb84c60830e515c05481769bd95e656d839337d430db66abcd3a869c6348616b78d06eb903f8abd121c851696bd4cb2a1a40a07eea17c4e33c6a1beafb79d881d595472ab6ce3c61d6d62c4ef6fa8903149435c844a3fab9286d212da72b2548f087e37105f4657d5a946afd12b1822ceb99c3b407bb40e21163c1466d116d67c16a2a3a79e5cc9d1f6a1054d6be6731e3cd19abbd9e9b23309f87bfe51a822410a62

 「q = next_prime(p)」であればFermat法が適用できますが・・・という問題です。

 しかし悲観することはなく、q≒2の1024乗-pなので、qは

x**2 - 2**1024*x + n = 0

の解x(のうちの小さいほう)で近似できることがわかります。zer0pts CTFにしては優しめ*2の問題で、Warmupとしていい感ということになります。

 実際、以下のソルバを使ってフラグをゲットできました。

ソルバ:

from Crypto.Util.number import *
import gmpy2

n = 0x1ffc7dc6b9667b0dcd00d6ae92fb34ed0f3d84285364c73fbf6a572c9081931be0b0610464152de7e0468ca7452c738611656f1f9217a944e64ca2b3a89d889ffc06e6503cfec3ccb491e9b6176ec468687bf4763c6591f89e750bf1e4f9d6855752c19de4289d1a7cea33b077bdcda3c84f6f3762dc9d96d2853f94cc688b3c9d8e67386a147524a2b23b1092f0be1aa286f2aa13aafba62604435acbaa79f4e53dea93ae8a22655287f4d2fa95269877991c57da6fdeeb3d46270cd69b6bfa537bfd14c926cf39b94d0f06228313d21ec6be2311f526e6515069dbb1b06fe3cf1f62c0962da2bc98fa4808c201e4efe7a252f9f823e710d6ad2fb974949751
c = 0x60160bfed79384048d0d46b807322e65c037fa90fac9fd08b512a3931b6dca2a745443a9b90de2fa47aaf8a250287e34563e6b1a6761dc0ccb99cb9d67ae1c9f49699651eafb71a74b097fc0def77cf287010f1e7bd614dccfb411cdccbb84c60830e515c05481769bd95e656d839337d430db66abcd3a869c6348616b78d06eb903f8abd121c851696bd4cb2a1a40a07eea17c4e33c6a1beafb79d881d595472ab6ce3c61d6d62c4ef6fa8903149435c844a3fab9286d212da72b2548f087e37105f4657d5a946afd12b1822ceb99c3b407bb40e21163c1466d116d67c16a2a3a79e5cc9d1f6a1054d6be6731e3cd19abbd9e9b23309f87bfe51a822410a62
e = 0x10001

r = gmpy2.iroot(2**2046-n,2)[0]
x = 2**1023 - r
for i in range(-10000,10000):
  q = x + i
  if n % q == 0:
    p = n // q
    break

d = inverse(e, (p-1)*(q-1))
print(long_to_bytes(pow(c,d,n)).decode())

zer0pts{F3rm4t,y0ur_m3th0d_n0_l0ng3r_w0rks.y0u_4r3_f1r3d}

 range(-10000,10000)はいささか広めですが、大勢に影響ないのでヨシ!

 この時点では(多少もたつきましたが)、まぁ順調な滑り出しかなぁ、と思っていました。

4. 行き詰まり、Idleモードに切り替え

 競技開始時点で、他にCrypto問は

が公開されていましたが、

  • Karen => 行列問なのでLLLっぽいことするのかなー
  • CurveCrypto => 既視感があるけど何処で見たか思い出せないぞ。。。

という具合でボンヤリしていたので、思考を切り替えるため休憩をとってTVをつけることに。

 しかし、今週こそ新作が放映されるであろうと期待した「ダイの大冒険」はまたしてもベストセレクション。サイバー攻撃許すまじ。ツヨツヨになったら悪・即・BANだ!果たしてレオナ姫のご尊顔を拝することができず、MP切れ。他のことをやりながら解法をゆるゆる考える「アイドリングモード」に移行。

 Karenを見ているうちに辻褄が合わないことに気づき、discordを見ると何人か運営にDMしている方がいたのでスコアサーバを確認すると修正版が出ておりDL。

 しかし1問も解けないままそのまま時間だけが溶けて行き、夕方以降

  • OK
  • EDDH

が追加されたのを確認。「OK」を見てみると・・・

「server.py」

from Crypto.Util.number import isPrime, getPrime, getRandomRange, inverse
import os
import signal

signal.alarm(300)

flag = os.environ.get("FLAG", "0nepoint{GOLDEN SMILE & SILVER TEARS}")
flag = int(flag.encode().hex(), 16)

P = 2 ** 1000 - 1
while not isPrime(P): P -= 2

p = getPrime(512)
q = getPrime(512)
e = 65537
phi = (p-1)*(q-1)
d = inverse(e, phi)
n = p*q

key = getRandomRange(0, n)
ciphertext = pow(flag, e, P) ^ key

x1 = getRandomRange(0, n)
x2 = getRandomRange(0, n)

print("P = {}".format(P))
print("n = {}".format(n))
print("e = {}".format(e))
print("x1 = {}".format(x1))
print("x2 = {}".format(x2))

# pick a random number k and compute v = k**e + (x1|x2)
# if you add x1, you can get key = c1 - k mod n
# elif you add x2, you can get ciphertext = c2 - k mod n
v = int(input("v: "))

k1 = pow(v - x1, d, n)
k2 = pow(v - x2, d, n)

print("c1 = {}".format((k1 + key) % n))
print("c2 = {}".format((k2 + ciphertext) % n))

 x1、x2の偶奇が一致していれば、v=(x1+x2)//2と指定することでkey+ciphertextを得ることができます。・・・とここまでは分ったのですが、その先に至ることは出来ませんでした。結果は「NG」。OKなのにNGなんですよ。残念。

5. 既視感により救われた(Crypto:EDDH)

 そんなこんなで(仕切りなおすため夜はしっかり寝て)2日目に突入。EDDHに着手しました。

I heard about that EdDSA is more secure than ordinal ECDSA. So ECDH on Edwards Curve too?

「server.py」

from random import randrange
from Crypto.Util.number import inverse, long_to_bytes
from Crypto.Cipher import AES
from hashlib import sha256
import ast
import os
import signal

n = 256
p = 64141017538026690847507665744072764126523219720088055136531450296140542176327
a = 362
d = 1
q = 64141017538026690847507665744072764126693080268699847241685146737444135961328
c = 4
gx = 36618472676058339844598776789780822613436028043068802628412384818014817277300
gy = 9970247780441607122227596517855249476220082109552017755637818559816971965596

def xor(xs, ys):
    return bytes(x^y for x, y in zip(xs, ys))

def pad(b, l):
    return b + b"\0" + b"\xff" * (l - (len(b) + 1))

def unpad(b):
    l = -1
    while b[l] != 0:
        l -= 1
    return b[:l]

def add(P, Q):
    (x1, y1) = P
    (x2, y2) = Q

    x3 = (x1*y2 + y1*x2) * inverse(1 + d*x1*x2*y1*y2, p) % p
    y3 = (y1*y2 - a*x1*x2) * inverse(1 - d*x1*x2*y1*y2, p) % p
    return (x3, y3)

def mul(x, P):
    Q = (0, 1)
    x = x % q
    while x > 0:
        if x % 2 == 1:
            Q = add(Q, P)
        P = add(P, P)
        x = x >> 1
    return Q

def to_bytes(P):
    x, y = P
    return int(x).to_bytes(n // 8, "big") + int(y).to_bytes(n // 8, "big")

def send(msg, share):
    assert len(msg) <= len(share)
    print(xor(pad(msg, len(share)), share).hex())

def recv(share):
    inp = input()
    msg = bytes.fromhex(inp)
    assert len(msg) <= len(share)
    return unpad(xor(msg, share))

def main():
    signal.alarm(300)

    flag = os.environ.get("FLAG", "0nepoint{frog_pyokopyoko_3_pyokopyoko}")
    assert len(flag) < 2*8*n
    while len(flag) % 16 != 0:
        flag += "\0"

    G = (gx, gy)
    s = randrange(0, q)

    print("sG = {}".format(mul(s, G)))
    tG = ast.literal_eval(input("tG = "))  # you should input something like (x, y)
    assert len(tG) == 2
    assert type(tG[0]) == int and type(tG[1]) == int
    share = to_bytes(mul(s, tG))

    while True:
        msg = recv(share)
        if msg == b"flag":
            aes = AES.new(key=sha256(long_to_bytes(s)).digest(), mode=AES.MODE_ECB)
            send(aes.encrypt(flag.encode()), share)

        elif msg == b"quit":
            quit()

        else:
            send(msg, share)

if __name__ == '__main__':
    main()

 「問題文、sage使わずに演算独自実装してるけど、シンプルなコードが売りのzer0ptsがどうした?」と違和感を覚えつつDLPの解法を調べていくうちに、「曲線外の点についても計算できる場合、GF(p)のDLPに帰着できる場合がある」に至り*3、なるほどと納得。

 ・・・で、解法はおそらく他の正解者の方々と同じです。(0, y)をセットして(0, yのs乗)を得られるようなyを見つけてサーバに投げます。p-1の素因数は小さい数なので、GF(p)でのDLPに帰着してsを計算できます。

 競技中に得られた値は

sG = (58318174546957056169432032056343059934884742163892755006813686598775591148016,822219344460106439560681399389425143876696976177303814906846974164092345893)

 でしたので、tGには

(0,4) をセットしました。

 さらに、メッセージ「flag」(666c616700ffffffffffffffffffffff)を送ります。

返ってきた値は以下のものでした。

15507b447675922a3d17b98162c415e0b7c8a184e4d03b7e254e60f5c17bd8471cd81b6e96df5104ebee44cf176fe232fbc068c30f12694c7c9ec955e7f587d6

 また、メッセージとして00000000000000000000000000000000を送ります。

返ってきた値は以下のものでした。

00000000000000000000000000000000fffffffffffffffffffffffffffffffffabd4bbc34327fafc0fa0b5909fd423f04c068c30f12694c7c9ec955e7f587d6

 以上をもとに、フラグの値を計算しました。

ソルバ(SageMath):

from Crypto.Util.number import *
from Crypto.Cipher import AES
from hashlib import sha256

def xor(xs, ys):
    return bytes(x^^y for x, y in zip(xs, ys))

p = 64141017538026690847507665744072764126523219720088055136531450296140542176327
L = 256

#sG = (58318174546957056169432032056343059934884742163892755006813686598775591148016, 822219344460106439560681399389425143876696976177303814906846974164092345893)
#tG = (0,4)
#666c616700ffffffffffffffffffffff
#15507b447675922a3d17b98162c415e0b7c8a184e4d03b7e254e60f5c17bd8471cd81b6e96df5104ebee44cf176fe232fbc068c30f12694c7c9ec955e7f587d6
#00000000000000000000000000000000
#00000000000000000000000000000000fffffffffffffffffffffffffffffffffabd4bbc34327fafc0fa0b5909fd423f04c068c30f12694c7c9ec955e7f587d6
#7175697400ffffffffffffffffffffff

_m = "00000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
_s = "00000000000000000000000000000000fffffffffffffffffffffffffffffffffabd4bbc34327fafc0fa0b5909fd423f04c068c30f12694c7c9ec955e7f587d6"
_f = "15507b447675922a3d17b98162c415e0b7c8a184e4d03b7e254e60f5c17bd8471cd81b6e96df5104ebee44cf176fe232fbc068c30f12694c7c9ec955e7f587d6"

_share = xor(bytes.fromhex(_s), bytes.fromhex(_m))
enc_flag = xor(bytes.fromhex(_f), _share)

F = GF(p)
a = F(bytes_to_long(_share[(L//8):]))
g = F(4)

s = discrete_log(a,g)
aes = AES.new(key=sha256(long_to_bytes(s)).digest(), mode=AES.MODE_ECB)
flag = aes.decrypt(enc_flag)

print(flag)

zer0pts{edwards_what_the_hell_is_this}

 サーバ繋ぐところまで自動化したソルバの方が美しいけど、フラグゲットできたのでヨシ!

 この時点で3/20 17:45。

6. 「まだだ、まだ終わらんよ!」

 競技終了まであと3時間でしたので、OKやCurveCryptoに取り組みました。

 いろいろ試行錯誤し、CurveCryptoはCoppersmithも試しました*4が正解には至らず。時間だけが溶けていくシリーズパートn。21:00、オワタ!  

7. 結果

 Welcome/Surveyのほか、2問(Warmupを含む)を解き393点を獲得、得点を得た632チーム中93位でした。Warmup+2問くらいは解きたかったのでちょっと残念な感じです。もぐもぐタイムのどら焼きを先に食してしまい逸したことが原因と考えられるため悔やまれます。

8. おわりに(お気持ち表明)

 ほとんどCrypto問しか見ていませんが、CTF TIMEでの宣言通りguessyな問題は皆無、コードも洗練されていて作問する際のお手本にしたいものばかりでした。

 問題スクリプトの差し替えも対応迅速に行われたようですし、スコアサーバや各問題サーバへの不快感も皆無でしたので、総合的に見ても文句をつける要素が思いつきません。

 ここ最近参加したClosedなCTFでは散々な目に逢っていた*5のですが、同じ「解けない」でも最後には清々しさが残る*6極めて良い大会であったと思います。

 来年開催されれば、万難を排して出場します。あと、今年もCakeCTFの開催が楽しみです。

*1:今回は(今回も?)Warmup問を含め2問しか解けておらず、特に画期的な解き方をしたわけではなく、勉強のためWriteupを読まれる方は公式やツヨツヨな人達のWriteupを読んだ方がよいので、今回は「参戦記」という形で残すことにしました。なお、CTF参戦記としてはzer0ptsのメンバーでもあるYoshikingさんの SECCON Beginners CTF 2021 参加記 | yoshikingのへや が極めて秀逸で、参考とさせていただきました。

*2:「易しめ」の誤記ではありません。

*3:過去に類題を解いたことがあり、至ることができたというわけです。

*4:xが小さいことに着目。しかし、yが小さいことを失念していたので刺さりませんでした。まぁ当てずっぽうでやったものですから。うーん、ダメダメですね。。。

*5:例1:チームで1台しか解くためのパソコンが使えない。しかもツール類の事前準備はNG。公平性と予算の問題らしいけど、ちょっとね・・・例2:半日くらいの大会で最後の1時間になるまで重たい問題(面白い問題)がオープンされない。運営の悪意すら感じた。※いずれもSurveyでお気持ち表明してしまったので、おそらく今後は出禁でしょう。

*6:もちろん、終了直後はだいぶ落ち込みますが。

2021年の振り返りと2022年への抱負

1. はじめに

 振り返りを始める前に、そういえばチーム名「N30Z30N」の由来をどこでも全く言及していなかったので、今更ですが簡単に紹介します。

 発端は、昔とある企業で研修を受けた際、トレーナーの方から「ガンダム勉強会」へのお誘い*1を受けたことです。

 当時は今よりもさらに未熟者であったが故に、この勉強会の重要性に気付かず、忙しさを理由に 1 回も参加せず研修を終えてしまいました。

 その後、経験する様々な出来事がガンダムの世界と親和性が高いことに気づき、ガンダムチャンネルやテレビの再放送枠などを活用して「履修」を重ねた結果、我々の立ち位置は「スペースノイド」であり、我々を迫害する「アースノイド」との「戦争」が不可避であるという結論*2に至ったわけです。

 その結果、CTF に本格参戦するにあたり、サイバー空間上の仮想国家 Neo Zeon (4代目*3)の軍として「N30Z30N*4」が誕生した、という流れになります*5

 なお、「Edwow Math」というハンドルは、2 代目ネオジオン総帥(キャスバル・レム・ダイクン)の別名(Edwow Mass)と、数学(Math)、両者へのリスペクトの気持ちにより付けたものです。

2. 2021年の振り返り

★CTF

・ずっと Crypto を見ていた

 「解いていた」とは言っていません。しかし、もう少し Rev とか Pwn とかにも目を向ける余裕があれば…

・初めて CTF で賞品を頂いた(CakeCTF)

 懸賞問題の 1st Blood 賞品として、マグカップ等 3 点の景品を賜りました。

 「ガチ CTF でなかなか景品ゲットできない層にもチャンスを」というコンセプトも素敵ですし、何よりセンス溢れる品物ばかりでした*6

・解きやすい CTF はそこそこ頑張った

 トップクラスには遠く及ばないまでも、総じて前年(2020年)よりも良い結果は出ているようです。

 CTFTimeのレーティングも、2020 年は 67 戦 49.289pt で国内 33 位、2021 年は 59 戦 94.741pt で国内 18 位で数字的には上がっています*7

・四半期ごとの振り返り
  • 1Q

 年初 Tet CTF( TetCTF 2021 Writeup (2021/01/03) - Qiita)から始めましたが、始まりは低調でした。

 zer0pts CTF(zer0pts CTF 2021 Writeup (2021/03/07) - Qiita)あたりからエンジンがかかってた感じです。

  • 2Q

 4/30 の Wani CTF Spring(WaniCTF'21-spring Writeup (2021/05/03) - Qiita)はとても楽しめました。

 その後の DEF CON CTF(Quals) は壊滅しました。2022 年はせめて Crypto 1 問くらいは解いて一矢報いないと。

 SECCON 4b(SECCON Beginners CTF 2021 Writeup(Cryptoのみ) - Qiita)と、その後の HSCTF 8 は少し頑張りました。

  • 3Q

 楽しい CTF がたくさん開催されたという印象です。

 Crypto CTF(3rd Crypto CTF Writeup - easy編 - Qiita)はガッツリ 24 時間やって頑張りましたけど、結果はまだまだです。

 前述した Cake CTF(CakeCTF 2021 Writeup (Crypto) - Qiita)はほとんど解けなかったけど楽しかったです。

  • 4Q

 人気 VTuberである kurenaif さんの「YouTubeチャンネル登録者数が2000人超え」記念の暗号問題(kurenaif 2000 subscribers challenge Writeup - Qiita)を解きました。

 また、「魔女のお茶会」(魔女のお茶会 (CTF勉強会) - YouTube)にも初めて参加させていただきました。

 3Q に引き続き、TSG CTF(TSG CTF 2021 Writeup(兼参戦記) - Qiita)や Wani CTF(WaniCTF 2021 (Cryptoのみの簡易)Writeup - Qiita)、BSides Ahmedabad CTF(BSides Ahmedabad CTF 2021 Writeup - Qiita)など楽しめる CTF が盛りだくさんでした。

 最後は Harekaze mini CTF と ASIS CTF Finals(ASIS CTF Finals 2021 Writeup (in Japanese) - Qiita) で締めくくりました。

★その他

・軍備増強 -- Microsoft Surface Pro 8 *8

 諸般の事情により、パソコンではなくタブレットが必要だったので、財政厳しい中無理して買いました。

 デスクトップは整理し、目立ったものは Kindle のアイコンぐらいなので、一見 Kindle 専用端末っぽく見えるので平和そうなのですが、実は赤いザクくらいの戦闘力はあると思っています。

3. 2022年に向けて

・Crypto 戦力の大幅 UP

 まずは、CryptoHack 全完を目指します*9

 数日前まで、2022 年に出場する CTF は絞るつもりでしたが方針転換します。出られる CTF にはすべて出て、Crypto 問に挑みます*10

・AI や 量子コンピュータなどの「常識」を習得する

 とりあえず積まれている本を少しずつ読みます。

・バランス良くいろいろ楽しもう

 仕事(収入源とライフワーク)や他の趣味を含め、相乗効果が発揮できるようバランスよく、なおかつ楽しみながらステップアップを図りたいと思います。

4. おわりに

 ジーク・ジオン!

*1:ざっくりいうと、業界の基本はガンダムから学べる、とのこと。

*2:一番シンプルな例では、技術者=スペースノイドニュータイプ、管理職=アースノイドみたいな構図。

*3:初代:Z/ZZに登場するハマーンネオジオン、2代目:CCAに登場するシャアのネオジオン、3代目:UCに登場する「袖付き」。

*4:Leetでは「N30230N」とする方が普通であるが、バランスを考えて 4 文字目を「Z」とした。

*5:なので時々、affiliation = 4th Neo Zeon とすることがある。

*6:返礼として、次回は(財政状況が許せば)スポンサー枠に名乗り出る予定。

*7:このレーティングは相性の良い CTF を炙りだすのに有用であるが、実力のランク付けとは必ずしも一致しないので、あくまでアリバイ的な結果。

*8:Core i5、RAM 8GB、SSD256GB。

*9:しかし、まだ 19 問も残っているorz...

*10:BGM: Climb Every Mountain.

弱小プレーヤーがムズめのCTFに立ち向かった記録

※これは、CTF Advent Calendar 2021 - Adventar 20日目の記事です。 前回は超強々プレーヤーであるだこつ(y011d4)さんの「2021年の CTF Crypto 問を振り返る | y011d4.log」でした。 (2021年Crypto問のキャッチアップをさせていただきました!)

0. はじめに

 『我々は(フラグを取れず)人権を失った。しかし、これは敗北を意味するのか?否!始まりなのだ!強大チーム比べ、我が N30Z30N の戦力は 30 分の 1 以下*1である。 にもかかわらず今日まで戦い抜いてこられたのは何故か? 我が N30Z30N の目的が正義だからだ!?』

 このところ一層の難化傾向*2にある CTF 界隈ですが、逆にそんな状況だからこそ「Babyレベル すら怪しい(弱)一人チーム(小)プレーヤー」であっても(「解けない方がメジャー」なので)気楽に参加しやすく*3、さらに問題がうまいこと得意分野とマッチすれば上位 10 ~ 20 %くらいのポジションにすんなり入れてしまう*4こともあるので、むしろチャンスであると見ることができます。

 また、初見よりも実際にトライした問題のほうが後で Writeup を読んでもシックリとくるので、解けなくてもチャレンジをする方が断然戦力強化の効果が大きいかと思います。

 ただし、解けていない問題を復習せずに放置し続けてしまうと、折角の機会を無駄にすることになります*5

 ・・・ということで、今年参加した CTF のうち、5 つの大会を取り上げ「弱小プレーヤーがムズめのCTFに立ち向かった記録」と称し振り返りを行います*6。 ※なお、ここでは「反省」を主体とすることから、心残りが少ない(後からキャッチアップできた)大会は除外されています。

1. Challenge 1: Google Capture The Flag 2021 (2021/7/17 9:00 JST - 7/18 8:59 JST

Google CTF は「ムズ系」(高難易度) CTF で、2020 年にも参加してどうにか 2 問を解いたことから、今年はさらなる飛躍を誓い参加したンゴ。

結果

1 問(Misc)だけ解いて、275th / 379 teams

Crypto を 1 問も通せなかったので惨敗認定です。

反省点

Good
  • とりあえず(一番易しかった) Misc 1 問は通せた。
Bad
  • 約 48 時間のうち、集中できた時間が少なかった(半ばあきらめてしまった)。
  • 問題として提示された比較的長めのスクリプトを読む気力がなかった。
  • 楽しむ心の余裕がなかった。

2. Challenge 2: Cypto CTF 2021(2021/7/31 1:00 JST - 8/1 1:00 JST

Crypto CTF は Crypto 問に特化した CTF で、難易度も「易」~「難」まで揃っているので、Crypto の腕試しとして参加しがいのある大会であると言えます。

なお、easy 問の Writeup は以下に残してあります。 qiita.com

※個人的には、

  • Crypto only かつ多彩な問題が出題される点
  • チームアイコン(画像)を登録できる点

が気に入っています。

結果

10 問(Warmup 1、easy 3、medium-easy 1、medium 4、hard 1)を解いて、35th / 443 teams

やり残しが多数あるので勝利という気分にはなれませんが、今年参加した CTF では一番頑張った感があるので及第点としておきましょう。

反省点

Good
  • 昨年よりも良い成績を残せた(問題も多く解いた)。
  • 24 時間本気モードで問題に取り組めた。
Bad
  • 24 時間ガッツリやったので、競技後一週間くらい意識朦朧として周囲に迷惑をかけたこと。
  • プログラミング力があれば時短できた問題が多かったこと。
  • solve 数が多い問題を落としていること(もう少し勉強していれば解けたはず!)。

3. Challenge 3: TSG CTF 2021(2021/10/2 16:00 JST-10/3 16:00 JST

qiita.com TSG CTF は東大コンピュータサークル「TSG」が主催する CTF ですが、国内主催の CTF の中でもファンが多く*7、Team N30Z30N も 2020 年から(別名での参加を含めれば 2019 年から)参加しています。

※個人的には、

  • 問題オープンのスケジュールが事前に公開されている点
  • 問題は全体的に難しいが解きやすいものもあり、かつ美しい点
  • 出題画面やスコアボードの UI

などが気に入っています。

結果

SanityCheck、Survey 以外に Crypto 2問(うち1問はBeginner)、pwn 1問(Beginner)を解いて 86th / 776 teams

CTF をよく知らない人に順位だけ言うと「やるじゃん」的な反応が返ってきますが、実態は上の如く Crypto 消化不良な状態なので、要追試といったところでしょうか。

反省点

Good
  • Writeup と称して参戦記を残したこと(プレイ時の記憶がはっきりと蘇る)。
  • Beginner 問とはいえ、比較的早期に 1 問フラグを取り士気高揚できたこと(一人チームなので、フラグを取らない気持ちがと盛り上がらない)。
  • Crypto CTF の反省を踏まえ、短時間(3時間)とはいえ寝て体力回復を図ったこと。
Bad
  • 提示ソースが ruby で書かれている問題を捨てた(毎年出題されてるんだから読めるようにしておこうよ、的な)。
  • Crypto 全完を目標に立てたものの、勉強不足で結果が遠く及ばなかったこと。

4. Challenge 4: HITCON CTF 2021(2021/12/4 11:00 JST - 12/5 23:00 JST

qiita.com HITCON は思い出深い CTF で、ムズめな CTF で最初にフラグを取れたのが HITCON 2019 でした*8

結果

SanityCheck のほか、Crypto 1 問(一番易しいやつ)を解いて 69th / 288 teams

惨敗は言い過ぎですが、やはり要追試といったところでしょう。

Good
  • とりあえず 1 問解いた。
Bad
  • 面白そうなを目にしながら、ほとんど手出しできなかった。
  • 36 時間の時間配分に失敗した。

5. Challenge 5: hxp CTF 2021(2021/12/18 0:00 JST - 12/20 0:00 JST

 hxp CTF は昨年登録するもフラグゲットできなかったので、今年はリベンジを誓い出場したンゴ。

結果

Crypto 問「gipfel」(一番易しいやつ)だけ解いて 137th /150 teams

順位的には惨敗ですが、登録チーム数は 1022 チームだったようなので今回は赦免ということで(謎)。

反省点

Good
  • とりあえず 1 問解いた。
Bad
  • SanityCheck すら通せなかった(達磨&グリコ状態*9)。
  • 体調を整えられず、1 問を解いた後はほぼグッタリとしていた*10
  • Crypto 問に限らず、旬の話題をキャッチアップできていない。

6. まとめ

振り返りを雑にまとめると・・・

Good
  • ムズい CTF でも易しめな問題には手を出せている。
  • 相性の良い CTF では実力を上回るパフォーマンスが発揮できているっぽい。
Bad
  • 出場した後の振り返りが不十分(特に解けなかった問題の補完)。
  • 24h over の大会でのパフォーマンスが低い(体力不足)。

今後の取り組み事項

技術面
  • 解けなかった問題はもちろん、解けた問題も Writeup を読んで別解法を習得する。
  • 出場する大会を絞り(原則月 2 回までくらい)、学習とプレイとのバランスを最適化。
  • 数学力(特に楕円曲線・超楕円曲線、計算機数学)、情報工学力(有用なアルゴリズム*11)の強化。
非技術面
  • 体力トレーニング(有酸素運動、筋トレ、etc...)。
  • 解けなくても前向きな気持ちを維持できるメンタルの醸成。
  • CTF やるときでも「食う」「寝る」「遊ぶ」を疎かにしない。

7. おわりに

 「弱小」は「強大」の対義語ですが、それらはいずれも相対的なもので、何をもって「強い」「弱い」とするかは主観によります。

 ただ、「自分は弱い」と感じている人は、自分がイメージする「強さ」とのギャップを感じているのでしょうから、具体的進路を見出してそれを実践することで「強さ」を(比較的容易に)手に入れる可能性を持っているはずです。つまり、この先強くなるかどうかは「やるか、やらないか」で決まるのでしょう。

さて、CTF Advent Calendar 2021 - Adventar 21日目は 過密 (@kam1tsur3) | Twitterさんによる「 (仮)流行らなくていいよmusl libc」の予定です。 このあたりのことは疎いので、記事が公開されたら勉強させていただこうと思います!

*1:お気持ち的な値。

*2:「Welcome問題だけフラグゲットしたチーム」が半数とか。とにかくヤバい。

*3:無駄に排他的な雰囲気がないところもCTFカルチャーの良いところだと思います。

*4:そうするとお気持ち的に超盛りあがってくるのでモチベーション特大UPにつながります。

*5:この点が今年の大いなる反省点でした。

*6:要するに反省文以外の何物でもないので、特にツヨツヨな方にとって有益な情報は皆無かと思われます。その点はご容赦ください。

*7:難易度詐欺(difficulty:beginnerからしてムズい)としても有名らしいのですが、それでも参加せずにはいられない魅力があります。

*8:当時は「N30Z30N」結成前でしたので、別名で出場。

*9:「手も足も出ない」&「お手上げ」。

*10:SECCON の Speedrun までには回復。

*11:LLL とか AGCD とか Half CGDとか。

雑記・メモ:弱くても解けます(Crypto)-- 導入編

0 はじめに

 以下の文章は、CTF(Crypto問)に取り組む際の一般論ではなく、また(有益な)初心者向けの教示でもありません。

 そういった類のものは、「強い」人達が書いたものが多数ありますので、そちらを読むことをお勧めします。(∵そうした方が早く確実に目的達成できると思われます。)

 この文章は、自分自身が何らかの事情でCTFから離れ、色々忘れてしまった後、再びチャレンジをしようと思った時を想定した、再スタート用メモです。(ですので、偶然そのような境遇にある方や、筆者と似た思考回路を持つ方には、もしかしたら何らかの一助にはなるかもしれません。)

1 初期にそろえておきたい道具

 1. 自由に使えるパソコンとインターネット接続環境

 2. 基本的な暗号の歴史(古典暗号から現代暗号まで)

 ※たとえば、「暗号技術のすべて」(IPUSIRON (著)、ISBN-10 ‏: 4798148814、ISBN-13‏ : 978-4798148816)を通読する。

 3. 初等整数論といくつかの代数・数論のトピックス(線形代数楕円曲線、局所体など)の知見。

 ※具体的には、Modular arithmetic、Fermat's little theorem、Chinese remainder theorem、Quadratic residue あたり。

 4. 実装する手段(たとえば、Python + SageMath)

 ※素因数分解・離散対数問題・楕円曲線などの基本的な求解アルゴリズムの実装や、各種暗号のよく知られた攻撃手法に関するsolverは一通り持っておくと便利です。

2 少し慣れてきたら入手しておきたい道具

 1. 最近の論文と、それらに付随した実装

 ※論文は、「新しい暗号方式」と「新しい攻撃手法」の双方をチェックしたいところです。

 2. CTFでの実戦経験(トレンドの把握)

 ※難易度の高いCTFでも臆せず出場し、競技中には解けなくても復習して解き方を理解することは有益であると考えます。

 3. 過去CTFのWriteupなど

 ※「強い」人たちは敵ではなく、有益な情報を公開してくださる心強い味方です。

3 問題を解く際の基本的な考え方

 G.Polya, How to Solve It(邦題:いかにして問題をとくか、柿内賢信 訳)にほぼすべて書かれています。

4 問題を解く際のアプローチ

 ・現代暗号の場合、必ず設問のどこかに「脆弱性」が潜んでいます(でなければ、解けないはず)。

 ・極端にいうと、「一見すると無理ゲー、しかしタネを知ってしまえば瞬殺」というものも少なくありません。(中級レベル問題まではそのパターンが多いです。)

 ・脆弱性の内容によって、「鍵をリークできる」類のものと、「通常の復号とは異なる手法で解読できてしまう」類のものがあります。

 ・上級レベルの問題では、最近発表された論文のネタがそのままぶっ込まれることもあるので、「論文即応能力」を身に着けておくのも手かもしれません。

 ・競技中、他分野の問題に手を出すことは(当然)悪いことではありませんが、「Crypto解けばよかった」と後悔しないことは重要です。

5トレーニン

1. CryptoHack cryptohack.org

2. CryptoPals cryptopals.com