以下の内容はhttps://yocchin.hatenablog.com/entry/2024/09/23/080322より取得しました。


GMO Cybersecurity Contest - IERAE CTF 2024 Writeup

この大会は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 ^= 0x9908b0df

st0を求めるためには以下の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}



以上の内容はhttps://yocchin.hatenablog.com/entry/2024/09/23/080322より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14