Crypto CTF 2021 | Hyper Normal

#cryptoctf2021

#!/usr/bin/env python3

import random
from flag import FLAG

p = 8443

def transpose(x):
    result = [[x[j][i] for j in range(len(x))] for i in range(len(x[0]))]
    return result

def vsum(u, v):
    assert len(u) == len(v)
    l, w = len(u), []
    for i in range(l):
        w += [(u[i] + v[i]) % p]
    return w

def sprod(a, u):
    w = []
    for i in range(len(u)):
        w += [a*u[i] % p]
    return w

def encrypt(msg):
    l = len(msg)
    hyper = [ord(m)*(i+1) for (m, i) in zip(list(msg), range(l))]
    V, W = [], []
    for i in range(l):
        v = [0]*i + [hyper[i]] + [0]*(l - i - 1)
        V.append(v)
    random.shuffle(V)
    for _ in range(l):
        R, v = [random.randint(0, 126) for _ in range(l)], [0]*l
        for j in range(l):
            v = vsum(v, sprod(R[j], V[j]))
        W.append(v)
    random.shuffle(transpose(W))
    return W

enc = encrypt(FLAG)
print(enc)

フラグからなる対角行列 Vを作って行シャッフルしたあと、以下のように Wを作 り、列シャッフルする

 W_0 = (R_0V_{0,0} + R_1V_{1,0} + R_2V_{2,0} + \dots, R_0V_{0,1} + R_1V_{1,1} + R_2V_{2,1} + \dots, \dots)

 W_1 = (R'_0V_{0,0} + R'_1V_{1,0} + R'_2V_{2,0} + \dots, R'_0V_{0,1} + R_1V_{1,1} + R'_2V_{2,1} + \dots, \dots)

 Vは行シャッフルされた対角行列であったことを思い出すと、シャッフル前の Wについて、次のように簡略化できる

 W_0 = (R_iV_{i, 0}, R_jV_{j, 1}, \dots)

 W_1 = (R'_iV_{i,0}, R'_jV_{j,1}, \dots)

 Rの値は小さいので共通の V_{x,y}が復元される R_i, R'_i, \dots(例えば)を探索すれば V_{i,0}の候補がでる。あとは hyperが順序付けされていそうなことを考えて順番を復元すればOK

import ast
from collections import Counter

with open("output.txt") as f:
    W = ast.literal_eval(f.read().strip())

p = 8443
n = len(W)


chars = []
for x in range(n):
    appears = []
    for y in range(n):
        for r in range(1, 126):
            v = W[y][x] * pow(r, -1, p) % p
            if v == 0:
                continue
            appears.append(v)

    counter = Counter(appears)
    chars.append(counter.most_common()[0][0])

print(bytes([c // (i + 1)for i, c in enumerate(chars)]))
# used = set()
# # chars = sorted(chars)
# flag = []
# for i in range(len(chars)):
#     for j in range(len(chars)):
#         if j in used:
#             continue
#         if chars[j] % (i+1) == 0:
#             used.add(j)
#             flag.append(chars[j] // (i + 1))
#             break
#     else:
#         flag.append(0)
#         # raise ValueError("warikirenai")
# print(bytes(flag))