VolgaCTF 2021 Qualifier Writeup

この大会は2021/3/27 23:00(JST)~2021/3/28 23:00(JST)に開催されました。
今回もチームで参戦。結果は173点で231チーム中144位でした。
自分で解けた問題をWriteupとして書いておきます。

Knock-Knock (Crypto)

添付のPythonコードを見ると、処理概要は以下のようになっている。

・Mersenne Twisterの乱数で32ビットの乱数を取得
・port1: 取得した乱数の前半16bit
・port2: 取得した乱数の後半16bit
・ポートノッキングで以下の条件でsshが使用できる。
 port1, port2の順でたたく
・ポートノッキングで以下の条件でsshを閉じる。
 port2, port1の順でたたく。

pcapファイルを見ると、2222ポートの通信を除くと、連続でこの通信が走っていることがわかる。
例えば、No1,2、No13,14のパケットでは、ポート番号のみ着目すると、以下のようになっている。

(送信元ポート) → (送信先ポート)
55880 → 53177
38062 → 60973
38066 → 60973
55888 → 53177

この場合、port1は53177、port2は60973であることがわかる。この情報から乱数を復元できる。
これを繰り返し見ていけば624個の乱数を取得でき、Mersenne Twisterの性質から次の乱数を予測できる。つまり次のport1, port2がわかり、フラグが得られる。

from scapy.all import *
import random

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

packets = rdpcap('knockd.pcap')

N = 624
state = []
i = 0
for p in packets:
    if p[TCP].dport != 2222 and p[TCP].sport != 2222 and p[TCP].ack == 0:
        if i % 4 == 0:
            port1 = p[TCP].dport
        elif i % 4 == 1:
            port2 = p[TCP].dport
            number = (port1 << 16) + port2
            state.append(untemper(number))
        i += 1

state.append(N)
random.setstate([3, tuple(state), None])

number = random.getrandbits(32)
a = (number & (2 ** 32 - 2 ** 16)) >> 16
b = number & (2 ** 16 - 1)

flag = 'VolgaCTF{%d,%d}' % (a, b)
print flag
VolgaCTF{15094,7850}

Feedback (Feedback)

アンケートに答えたら、フラグが表示された。

VolgaCTF{fei8ighie1aiY5ka}