この大会は2024/9/21 15:00(JST)~2024/9/22 15:00(JST)に開催されました。
今回もチームで参戦。結果は2460点で224チーム中11位でした。
自分で解けた問題をWriteupとして書いておきます。
Welcome (sanity check)
Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。
IERAE{An_incredibly_interesting_flag}
derangement (crypto, warmup)
サーバの処理概要は以下の通りです。
・LENGTH = 15
・CHAR_SET: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
・magic_word = generate_magic_word()
・CHAR_SETからランダムに15個選択した文字を結合したものを返却
・connection_count = 0
・connection_countが300未満の間、以下を実行
・connection_count += 1
・user_input: 数値入力
・user_inputが1の場合
・output_derangement(magic_word)
・以下繰り返し処理を実行
・deranged: magic_wordの文字の順序をシャッフル
・derangedとmagic_wordの同じ位置の文字がすべて異なる場合、derangedを表示して繰り返し終了
・user_inputが2の場合
・res = guess_random(magic_word, FLAG)
・inp: 入力
・inpがmagic_wordと一致する場合、フラグを表示し、Trueを返却
・inpがmagic_wordと一致しない場合、Falseを返却
・resがTrueの場合、繰り返し終了
・resがFalseの場合、"bye!"と表示して終了
・"Connection limit reached. Exiting..."と表示1を選択すると、15種の文字の必ず異なる位置がわかる。各位置でありえない文字を消去していくと、magic_wordを割り出すことができる。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('104.199.135.28', 55555)) for _ in range(7): data = recvuntil(s, b'\n').rstrip() print(data) init = True while True: data = recvuntil(s, b'> ') print(data + '1') s.sendall(b'1\n') data = recvuntil(s, b'\n').rstrip() print(data) hint = list(data[6:]) if init: init = False chars = [[c for c in hint] for i in range(15)] for i in range(15): if hint[i] in chars[i]: chars[i].remove(hint[i]) found = True for i in range(15): if len(chars[i]) != 1: found = False break if found: break magic_word = ''.join([chars[i][0] for i in range(15)]) data = recvuntil(s, b'> ') print(data + '2') s.sendall(b'2\n') data = recvuntil(s, b'> ') print(data + magic_word) s.sendall(magic_word.encode() + b'\n') for _ in range(2): data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
/********************************************************\
| |
| Abracadabra, let's perfectly rearrange everything! |
| |
\********************************************************/
type 1 to show hint
type 2 to submit the magic word
> 1
hint: )|VgnbRwop(z4@h
type 1 to show hint
type 2 to submit the magic word
> 1
hint: npbV4go)@|Rwzh(
type 1 to show hint
type 2 to submit the magic word
> 1
hint: 4@p)V|ozb(wnhRg
type 1 to show hint
type 2 to submit the magic word
> 1
hint: hpRngb()o|V@z4w
type 1 to show hint
type 2 to submit the magic word
> 1
hint: Voz4p(g@)bwhRn|
type 1 to show hint
type 2 to submit the magic word
> 1
hint: h)VoRbzg|w4n(@p
:
:
type 1 to show hint
type 2 to submit the magic word
> 1
hint: 4p)bh@(nwRog|zV
type 1 to show hint
type 2 to submit the magic word
> 1
hint: )pgRoVn(zb4h|@w
type 1 to show hint
type 2 to submit the magic word
> 2
Oops, I spilled the beans! What is the magic word?
> obh|zp@RV4g)w(n
Congrats!
IERAE{th3r35_n0_5uch_th!ng_45_p3rf3ct_3ncrypt!0n}
IERAE{th3r35_n0_5uch_th!ng_45_p3rf3ct_3ncrypt!0n}
Weak PRNG (crypto, easy)
サーバの処理概要は以下の通り。
・ランダム32ビット整数をシードとして乱数設定する ・secret: ランダム32ビット整数 ・以下繰り返し ・choice: 入力 ・choiceが"1"の場合 ・16回ランダム32ビット整数を表示 ・choiceが"2"の場合 ・num: 数値入力 ・numがsecretと一致する場合、フラグを表示 ・繰り返し終了 ・choiceが"3"の場合、繰り返し終了
Mersenne Twisterの性質を使った問題と推測できる。
この性質の乱数は以下のように決められる。
n = st0 & 0x80000000
n += st1 & 0x7fffffff
st624 = st397 ^ (n >> 1)
if n % 2 != 0:
st624 ^= 0x9908b0dfst0を求めるためには以下の2つの情報が必要。
・st0(32bit)の最上位ビット:st1, st397, st624 ・st0(32bit)の最上位ビット以外:st396, st623の他nが2で割り切れるかどうか
2点目のnが2で割り切れるかどうかは割り出すことができないので、片方に絞り、50%程度で当てる方法を取る。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) def untemper(rand): rand ^= rand >> 18; rand ^= (rand << 15) & 0xefc60000; a = rand ^ ((rand << 7) & 0x9d2c5680); b = rand ^ ((a << 7) & 0x9d2c5680); c = rand ^ ((b << 7) & 0x9d2c5680); d = rand ^ ((c << 7) & 0x9d2c5680); rand = rand ^ ((d << 7) & 0x9d2c5680); rand ^= ((rand ^ (rand >> 11)) >> 11); return rand def temper(st): y = st y ^= y >> 11 y ^= (y << 7) & 0x9d2c5680 y ^= (y << 15) & 0xefc60000 y ^= y >> 18 return y s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('35.201.137.32', 19937)) for _ in range(2): data = recvuntil(s, b'\n').rstrip() print(data) st = [] for i in range(624 // 16): data = recvuntil(s, b'> ') print(data + '1') s.sendall(b'1\n') data = recvuntil(s, b'\n').rstrip() print(data) for _ in range(16): data = recvuntil(s, b'\n').rstrip() print(data) st.append(untemper(int(data))) x = st[0] & 0x7fffffff st624 = st[623] if x % 2 != 0: st624 ^= 0x9908b0df n1 = (st624 ^ st[396]) << 1 secret1 = n1 & 0x80000000 n2 = (st[622] ^ st[395]) << 1 secret2 = n2 & 0x7fffffff secret = temper(secret1 + secret2) data = recvuntil(s, b'> ') print(data + '2') s.sendall(b'2\n') data = recvuntil(s, b'> ') print(data + str(secret)) s.sendall(str(secret).encode() + b'\n') for _ in range(2): data = recvuntil(s, b'\n').rstrip() print(data)
成功時の実行結果は以下の通り。
Welcome!
Recover the initial output and input them to get the flag.
--------------------
Menu
1. Get next 16 random data
2. Submit your answer
3. Quit
Enter your choice (1-3)
> 1
Here are your random data:
3857571068
1347230704
2419660483
1904099943
314664810
2061819187
2102972158
1281297249
3199873884
42672974
2257650591
575215108
3756627568
1225425440
2469432545
3905551383
--------------------
Menu
1. Get next 16 random data
2. Submit your answer
3. Quit
Enter your choice (1-3)
> 1
Here are your random data:
1698100346
3455265001
3301536533
2560410083
1434844083
985345386
1594241816
2358481053
3526610350
1006774301
1783307368
2122814340
3904224989
2934319154
47638360
1196039929
:
:
--------------------
Menu
1. Get next 16 random data
2. Submit your answer
3. Quit
Enter your choice (1-3)
> 1
Here are your random data:
1816115460
433359410
3550120192
1689209375
857065635
3747679250
1208751631
913166445
3748843443
3843118731
2631779891
448114461
3492083456
3504315542
622989298
2350406822
--------------------
Menu
1. Get next 16 random data
2. Submit your answer
3. Quit
Enter your choice (1-3)
> 1
Here are your random data:
2298428930
3308323559
2880907256
3288671016
1136438426
2159643853
2160767653
184621935
2284984825
958763571
201028936
2904186372
1608128180
2698755584
4031646492
1939086711
--------------------
Menu
1. Get next 16 random data
2. Submit your answer
3. Quit
Enter your choice (1-3)
> 2
Enter the secret decimal number
> 1532413110
Correct! Here is your flag:
IERAE{WhY_4r3_n'7_Y0u_u51n6_4_CSPRNG_3v3n_1n_2024}
IERAE{WhY_4r3_n'7_Y0u_u51n6_4_CSPRNG_3v3n_1n_2024}