BackdoorCTF 2022 Writeup

この大会は2022/12/17 15:30(JST)~2022/12/18 15:30(JST)に開催されました。
今回もチームで参戦。結果は310点で91チーム中21位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (misc)

Discordに入り、#rulesチャネルでリアクションすると、たくさんのチャネルが現れた。#randomチャネルのメッセージを見ると、マスクされたメッセージにフラグが書いてあった。

flag{w3lc0m3_70_b4ckd00r_2022}

Fishy (crypto)

暗号化処理の概要は以下の通り。

・modulus = pow(2, 32)
・s_boxes: 256個のランダム32ビット整数の配列の4個の配列
・s_boxes.txtにs_boxesを書き込み
・initial_sub_keys: 既知の18個の32ビット整数のhex文字列の配列
・key: 18個の32ビット整数のhex文字列の連結
・processed_sub_keys: initial_sub_keysとkeyのXOR
・pt: フラグの2進数文字列
・ptの長さが64で割り切れるように先頭に"0"を付与
・pt: フラグの16進数文字列
・ct = ''
・pt(16進数文字列)の16バイトごとに以下繰り返し
 ・xl = ptの16バイトの前半8バイト
 ・xr = ptの16バイトの後半8バイト
 ・16回以下繰り返し
  ・tmp = xl
  ・xl = bin(int(xl, 16) ^ int(processed_sub_keys[j], 16))[2:].zfill(32)
  ・xa = int(xl[:8], 2)
  ・xb = int(xl[8:16], 2)
  ・xc = int(xl[16:24], 2)
  ・xd = int(xl[24:32], 2)
  ・xa = (s_boxes[0][xa] + s_boxes[1][xb]) % modulus
  ・xc = s_boxes[2][xc] ^ xa
  ・f_out = (xc + s_boxes[3][xd]) % modulus
  ・xl = hex(int(xr, 16) ^ f_out)[2:].zfill(8)
  ・xr = tmp
 ・xrt = xr
 ・xr = hex(int(xl, 16) ^ int(processed_sub_keys[16], 16))[2:].zfill(8)
 ・xl = hex(int(xrt, 16) ^ int(processed_sub_keys[17], 16))[2:].zfill(8)
 ・ct にxl + xrを結合
・ctを出力

Mersenne Twisterの特性を使って、s_boxesの値からkeyを算出できる。またinitial_sub_keysとkeyの値からprocessed_sub_keysを算出できる。あとは暗号化データから逆算し、フラグを求めればよい。

#!/usr/bin/env python3
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

initial_sub_keys = [
    "243f6a88",
    "85a308d3",
    "13198a2e",
    "03707344",
    "a4093822",
    "299f31d0",
    "082efa98",
    "ec4e6c89",
    "452821e6",
    "38d01377",
    "be5466cf",
    "34e90c6c",
    "c0ac29b7",
    "c97c50dd",
    "3f84d5b5",
    "b5470917",
    "9216d5d9",
    "8979fb1b",
]

modulus = pow(2, 32)

with open('s_boxes.txt', 'r') as f:
    s_boxes = eval(f.read())

N = 624
state = []
_rand = []
for j in range(4):
    for i in range(256):
        if len(state) != 624:
            state.append(untemper(s_boxes[j][i]))
        else:
            _rand.append(s_boxes[j][i])
state.append(N)
random.setstate([3, tuple(state), None])

for i in range(256 * 4 - 624):
    r = random.getrandbits(32)
    assert r == _rand[i]

key = "".join([hex(random.getrandbits(32))[2:].zfill(8) for i in range(18)])
processed_sub_keys = [
    hex(int(initial_sub_keys[i], 16) ^ int(key[8 * i : 8 * (i + 1)], 16))[2:].zfill(8)
    for i in range(len(initial_sub_keys))
]

with open('enc.txt', 'r') as f:
    ct = f.read().rstrip().split(' ')[-1]

pt = ''
for i in range(0, len(ct), 16):
    xl = ct[i:i+8]
    xr = ct[i+8:i+16]
    xrt = hex(int(xl, 16) ^ int(processed_sub_keys[17], 16))[2:].zfill(8)
    xl = hex(int(xr, 16) ^ int(processed_sub_keys[16], 16))[2:].zfill(8)
    xr = xrt
    for j in range(15, -1, -1):
        xl2 = xl
        tmp = xr
        xl = tmp

        tmp = bin(int(tmp, 16) ^ int(processed_sub_keys[j], 16))[2:].zfill(32)
        xa = int(tmp[:8], 2)
        xb = int(tmp[8:16], 2)
        xc = int(tmp[16:24], 2)
        xd = int(tmp[24:32], 2)
        xa = (s_boxes[0][xa] + s_boxes[1][xb]) % modulus
        xc = s_boxes[2][xc] ^ xa
        f_out = (xc + s_boxes[3][xd]) % modulus
        xr = hex(int(xl2, 16) ^ f_out)[2:].zfill(8)
    pt += xl + xr

flag = bytes.fromhex(pt).decode()
print(flag)
flag{d0n't_u53_th3_m3r53nn3_r4nd0m_g3n3r4t0r_w1th0ut_c4ut10n_<3}

Feedback (misc)

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

flag{th4nks_f0r_pl4y1ng_b4ckd00r_c7f}