affine.group home writeups about

TetCTF 2022 - all crypto challenges

Published on 02 Jan 2022
Writeups

Setup:

In [1]:
from sage.all import *
import ast
from binteger import Bin
from sock import Sock

Shares

In this challenge, we have a linear secret sharing scheme, based on random matrices modulo $P=37$. The secret password $s$ is 16 symbols long. In the $i$-th query, we are given $$(A_i, B_i, c_i = A_i\times s + B_i \times r_i),$$ where $A_i,B_i \in \mathbb{F}_P^{16\times16}$ are random matrices and $r_i \in \mathbb{F}_P^{16}$ is a random vector.

If the matrix $B_i$ is always invertible, then $c_i$ is useless, as it contains full entropy from $r_i$. However, the coefficients are sampled uniformly at random, and so, the matrix can be singular with probability about $1/P=1/37$. In this case, there exists at least one bector $b_i$ such that $b_i\times B = (0,\ldots,0)$. Then, $$ b_i\times c_i = b_i\times A_i\times s + b_i\times B_i \times r_i = (b_i\times A_i) \times s. $$ We get a pure linear equation on $s$!

Collecting enough equations (16), we can solve for $s$. For this, we need about $16 \times 37 = 592$ queries.

In [2]:
import string

ALLOWED_CHARS = string.ascii_lowercase + string.digits + "_"
P = len(ALLOWED_CHARS)
INT_TO_CHAR = {}
CHAR_TO_INT = {}
for _i, _c in enumerate(ALLOWED_CHARS):
    INT_TO_CHAR[_i] = _c
    CHAR_TO_INT[_c] = _i

In [3]:
F = GF(P)

known = []
target = []
f = Sock("139.162.61.222 13371")
f.send("x\n" * 1000)
for i in tqdm(range(1000)):
    entry = ast.literal_eval(f.read_line().decode())
    mat = matrix(F, [
        [CHAR_TO_INT[v] for v in row]
        for row in entry
    ])
    A = mat[:16,:16]
    B = mat[:16,16:32]
    c = mat.column(-1)
    if B.rank() != 16:
        for ker in B.left_kernel().matrix():
            known.append(ker * A)
            target.append(ker * c)
            if matrix(known).rank() >= 16:
                sol = matrix(F, known).solve_right(vector(F, target))
                pw = "".join(INT_TO_CHAR[int(v)] for v in sol)
                print("got pw!", pw)
                f.send_line(pw)
                f.shut_wr()
                print(f.read_all().splitlines()[-1])
                break
        else:
            continue
        break
 57%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████▉                                                                                    | 573/1000 [00:02<00:01, 317.42it/s]
got pw! 4epfhiql50wgj2so
 60%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▊                                                                               | 598/1000 [00:03<00:02, 171.46it/s]
b'TetCTF{m0r3_sh4r3s____m0r3_m0r3_m0r3_fun}'

Shares2

In the second variant of the challenge, the password length is 32, there are no random symbols added and we are given all 32 equations (i.e., the matrices $A_i$ are 32x32 each). However, the output $c_i$ is masked by adding a random binary vector (multiplied by $P//2=18$).

I misread the challenge details, and assumed that the setting is the same as in Shares, except that the binary vector is added. That is, I assumed that the password is 16 symbols long and 16 other symbols are random each time. I will describe how to solve this variant, as it is harder and can anyway be used to solve the correct variant.

Assume that in the $i$-th query, we are given $$(A_i, B_i, c_i = A_i\times s + B_i \times r_i + 18\cdot e_i),$$ where $A_i,B_i \in \mathbb{F}_P^{32 \times16 }$ are random matrices, $r_i \in \mathbb{F}_P^{16}$ and $e_i \in \mathbb{F}_2^{32}$ are random vectors.

Similarly to Shares, we can eliminate $r_i$. Since we have 32 equations now, we get 16+ equations on each query: let $\tilde{B_i} = \text{left ker}(B)$, then $$\begin{split} c'_i &= \tilde{B}_i\times c_i \\ &= \tilde{B}_i\times A_i\times s + \tilde{B}_i\times B_i \times r_i + \tilde{B}_i \times 18\cdot e_i \\ &= (\tilde{B}_i\times A_i) \times s + \tilde{B}_i \times 18\cdot e_i \\ &= A'_i \times s + C'_i \times e_i. \end{split}$$

We have linear equations with a small error vector. Clearly, we should try LLL.

We will use two queries and build the following lattice:

$$ \left( \begin{array}{rrrrrr} \color{gray}c'_1 & \color{gray}c'_2 & \color{gray}s & \color{gray}e_1 & \color{gray}e_2 & \color{gray}* \\ -&-&-&-&-&- \\ A_1'^T & A_2'^T & 1 & & & \\ C_1'^T & & & 2 & & \\ & C_2'^T & & & 2 & \\ P &&&& & \\ & P &&& & \\ c'_1 & c'_2 & & 1 & 1 & D \\ \end{array} \right) $$ Here, the last row corresponds to the target vector. We are doing CVP but using the Kannan embedding method to reduce it to SVP, with an appropriate bound $D$.

In [4]:
def split(mats):
    As, Cs, cs = [], [], []
    for mat in mats:
        A = mat[:,:16]
        B = mat[:,16:32]
        C = identity_matrix(GF(P), 32) * (P // 2)
        c = mat[:,32:]
        ker = B.left_kernel().matrix()
        A, C, c = ker * A, ker * C, ker * c
        As.append(A.transpose())
        Cs.append(C.transpose())
        cs.append(c)
    return As, Cs, cs

In [5]:
f = Sock("139.162.61.222 13372", timeout=100000)
f.send("x\n" * 5)
data = []

for i in range(5):
    entry = ast.literal_eval(f.read_line().decode())
    mat = matrix(F, [
        [CHAR_TO_INT[v] for v in row]
        for row in entry
    ])
    data.append(mat)

In [6]:
def solve(data):
    t = len(data)
    I = ZZ(1)
    O = ZZ(0)
    As, Cs, cs = split(data)
    bm = []
    bm.append(As[:t] + [I] + [O] * t)
    for i, C in enumerate(Cs[:t]):
        row = [O] * t
        row[i] = C
        row2 = [O] * t
        row2[i] = 2*I
        bm.append(row + [O] + row2)
    for i in range(t):
        row = [O] * t
        row[i] = P*I
        bm.append(row + [O] * (1 + t))
    target = []
    for c in cs[:t]:
        target.extend(c.list())
    target.extend([0] * 16 + [1] * 32 * t)
    target = vector(target).change_ring(ZZ)

    m = block_matrix(ZZ, bm)
    assert m.ncols() == 16 * t + 16 + 32 * t

    weights = [10**20] * 16*t + [1] * 16 + [10**10] * 32*t + [1]

    D = ZZ(t * 32 * 10**8)
    m = m.stack(matrix(ZZ, [target]))
    m = m.augment(matrix(ZZ, [O] * (m.nrows()-1) + [D]).transpose())
    assert len(weights) == m.ncols()

    # make server busy to keep connection
    f.send("a\n" * 1000)

    for i, wt in enumerate(weights):
        m.rescale_col(i, wt)
    print("BKZ...")
    m = m.BKZ(block_size=24).change_ring(QQ)
    print("BKZ done")
    for i, wt in enumerate(weights):
        m.rescale_col(i, QQ(1)/wt)
    m = m.change_ring(ZZ)

    for row in m:
        if row[:16*t] == 0 and row[16*t+16:-1] != 0 and row[-1] != 0 and row[-2] % 2 and abs(row[-1]) == D:
            print(row[-1], D)
            vals = row[16*t+16:-1]
            print(min(vals), max(vals))
            if -1 <= min(vals) <= max(vals) <= 1:
                if row[-1] == -D:
                    row = -row

                sol = -row[16*t:16*t+16]

                pw = "".join(INT_TO_CHAR[int(v) % P] for v in sol)

                print("pw", pw)
                return pw
    Fail

In [7]:
mat1, mat2 = data[:2]
pw1 = solve([mat1, mat2])

mat1 = mat1[:,16:32].augment(mat1[:,:16]).augment(mat1[:,32:])
mat2 = mat2[:,16:32].augment(mat2[:,:16]).augment(mat2[:,32:])
pw2 = solve([mat1, mat2])

f.send_line(pw1 + pw2)
f.shut_wr()

print(f.read_all().splitlines()[-1])
BKZ...
BKZ done
6400000000 6400000000
-1 1
pw sxaxwxjjkkv71zv4
BKZ...
BKZ done
6400000000 6400000000
-1 1
pw d9hf5ctbub0av3bi
b'TetCTF{but_th3_m4st3r_sh4re_1s_n0t_fun_4t_4ll}'

Algebra

Here, we are given some group implementation, and we need to find an efficient homomorphism to $\mathbb{F}_p^*$.

In [8]:
p = 50824208494214622675210983238467313009841434758617398532295301998201478298245257311594403096942992643947506323356996857413985105233960391416730079425326309
C = 803799120267736039902689148809657862377959420031713529926996228010552678684828445053154435325462622566051992510975853540073683867248578880146673607388918

INFINITY = "INF"

def op(x1, x2):
    """Returns `(x1 + x2 + 2 * C * x1 * x2) / (1 - x1 * x2)`."""
    if x2 == INFINITY:
        x1, x2 = x2, x1
    if x1 == INFINITY:
        if x2 == INFINITY:
            return (-2 * C) % p
        elif x2 == 0:
            return INFINITY
        else:
            return -(1 + 2 * C * x2) * pow(x2, -1, p) % p
    if x1 * x2 == 1:
        return INFINITY
    return (x1 + x2 + 2 * C * x1 * x2) * pow(1 - x1 * x2, -1, p) % p

assert op(op(2020, 2021), 2022) == op(2020, op(2021, 2022))

Confusingly, INFINITY here is a usual group element, not the group identity (which is 0).

Further, note that op computes something very similar to the tangengt of the sum: $$ \tan(x+y) = \frac{\tan(x) + \tan(y)}{1-\tan(x)\tan(y)}, $$ $$ op(x,y) = \frac{x + y + 2\cdot C \cdot x\cdot y}{1-x \cdot y}. $$ Note that the tangent formula can be interpreted simply as adding angles, which clearly form a group.

Usually, homomorphisms from curves to $\mathbb{F}_p^*$ are linear rational maps. Let's try to check this here directly.

We want a non-degenerate rational map $\pi(x)=\frac{x+a}{x+b}$ such that $\pi(op(x, 2022)) = g\cdot \pi(x)$. That is, multiplying by $2022$ in the group is equivalent to multiplying by some $g$ in $\mathbb{F}_p^*$.

In [9]:
n = 10
a, b, g = GF(p)['a,b,g'].gens()
x = 2022
y = op(x, x)
eqs = []
for i in range(n):
    x = op(x, 2022)
    y = op(y, 2022)
    # (y + a) / (y + b) = g (x + a) / (x + b)
    eq = (y + a) * (x + b) - g * (x + a) * (y + b)
    eqs.append(eq)
Ideal(eqs).variety()
verbose 0 (3837: multi_polynomial_ideal.py, groebner_basis) Warning: falling back to very slow toy implementation.
verbose 0 (1080: multi_polynomial_ideal.py, dimension) Warning: falling back to very slow toy implementation.
ValueError: The dimension of the ideal is 1, but it should be 0

Ooh, the system is underdetermined! But why?

It turns out, that there exists a 1-dimensional solution space: $g=1, a=b$ leads to $$(y+a)/(y+a)=1\cdot(x+a)/(x+a) = 1,$$ which is obviously true.

How can we encode the fact that we want $a \ne b$? Here, we can do a simple trick. We multiply the variable $g$ of the equation by $a-b$: it corresponds to a simple change of variables, but makes $a=b$ a wrong solution!

In [10]:
eqs = []
for i in range(n):
    x = op(x, 2022)
    y = op(y, 2022)
    # (y + a) / (y + b) = g (x + a) / (x + b)
    eq = (y + a) * (x + b) - (a - b) * g * (x + a) * (y + b)
    eqs.append(eq)
sols = Ideal(eqs).variety()
sols
verbose 0 (3837: multi_polynomial_ideal.py, groebner_basis) Warning: falling back to very slow toy implementation.
verbose 0 (1080: multi_polynomial_ideal.py, dimension) Warning: falling back to very slow toy implementation.
verbose 0 (2270: multi_polynomial_ideal.py, variety) Warning: falling back to very slow toy implementation.
verbose 0 (3837: multi_polynomial_ideal.py, groebner_basis) Warning: falling back to very slow toy implementation.
verbose 0 (3837: multi_polynomial_ideal.py, groebner_basis) Warning: falling back to very slow toy implementation.
Out [10]:
[{a: 22028781193260601150215372852743243072534054369034373355826026755141703972839605913903845829450804221333172177888152011390316978343515577032315535048272856,
  b: 30403025541489493604800988683343385662063299229646452236323267699080879682775308287796866138143113667746438130490796553103815494624941972144707891591831289,
  g: 45402017495049849725596416041010660568087572919781453522482002870895129994557342220899801511502853600885466780271951723888133792819135097058341909311476364},
 {a: 30403025541489493604800988683343385662063299229646452236323267699080879682775308287796866138143113667746438130490796553103815494624941972144707891591831289,
  b: 22028781193260601150215372852743243072534054369034373355826026755141703972839605913903845829450804221333172177888152011390316978343515577032315535048272856,
  g: 3778598816224028730365547047535206079472473753181851253732207806576394233991895639470685374537933502872454119905634788555826768305217156939182119999868005}]

In [11]:
a, b, g = sols[0][a], sols[0][b], sols[0][g]
g /= (a - b)  # undo variable replacement

In [12]:
proj = lambda x: (x+a) / (x+b)
proj(0)
Out [12]:
37291755603778762952446086021127083965370188491320744451482250160284045677238313859983256323428449624529668045685695172227464589652991653757342205880081966

Note that we solved the multiplicative effect, but we didn't solve the base case. For this, we simply need to always divide by the image of the group element 0.

In [13]:
pie = lambda x: proj(x) / proj(0)

In [14]:
f = Sock("139.162.61.222 13374")
q = int(f.read_line())
w = int(f.read_line())
e = int(f.read_line())
f.send_line(str(pie(q)))
f.send_line(str(pie(w)))
f.send_line(str(pie(e)))
print(f.read_line())
b'TetCTF{1_just_l0v3_th3_un1t_c1rcl3_s0_much}\n'

Fault

In this challenge, we are given access to a faulty RSA decryption oracle. It always decryps encrypted flag with the secret 128-bit exponend $d$, but it also introduces a random 128-bit fault $e$ which is xored to the exponent before the decryption. In other words, we obtain $$ (e, b=c^{d\oplus e} \mod{n}) $$ in one query.

We are not even given the value of $n$ here.

I didn't notice that we can ask to decrypt a constant instead of the flag. In this case, sending $c=-1$ reveals $n-1$ if the exponent is odd.

Instead, I will describe how to recover $n$ using only the flag decryption oracle.

The main observation for this challenge is that each bit of $e$ simply defines a multiplier to the final product. Let $c^d$ to be the "base" value. If the $i$-th least significant bit of $d$ is 0, the resulting product contains $c^{2^i e_i}$. Otherwise, the resulting product contains $c^{-2^i e_i}$, as flipping the 1 bit to 0 corresponds to dividing by $c^{2^i}$. This can be written as: $$ b = c^{d\oplus e} = c^d \cdot \prod_i c^{(-1)^{d_i}2^i e_i} = a \cdot \prod_i c_i'^{e_i}. $$

Now, we can let all 128 (unknown) values $c'_i$ to be the multiplicative basis, together with the base value $a=c^d$. Multiplication corresponds to addition of exponents, so we have linear algebra in the exponents on the known vectors $e$. Since we don't know the group order, we have to work over $\mathbb{Z}$ (or $\mathbb{Q}$ in intermediate steps).

First, in order to recover $n$, we need to find different ways to obtain the same value modulo $n$. Since we can collect more than 129 samples, there is linear redundancy, which can be used to find zero-vectors in the exponent: $$\frac{b_1^{k_1}\cdot\ldots b_t^{k_t}}{b_{t+1}^{k_{t+1}}\cdot\ldots\cdot b_{t+t'}^{k_{t+t'}}} \equiv 1 \pmod{n},$$ $$\Rightarrow b_1^{k_1}\cdot\ldots b_t^{k_t} \equiv b_{t+1}^{k_{t+1}}\cdot\ldots\cdot b_{t+t'}^{k_{t+t'}} \pmod{n}.$$ Unfortunately, if we simply perform Gauss elimination over $\mathbb{Q}$, the coefficients (= powers) will become huge. Since we don't know $n$, we can not afford huge exponents.

So, we need small solutions in a linear system, pointing to... LLL!

In [15]:
f = Sock("139.162.61.222 13373")
data = []
f.send("c\n" * 250)
for i in tqdm(range(250)):
    data.append(ast.literal_eval(f.read_line().decode()))
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 250/250 [00:01<00:00, 188.09it/s]

In [16]:
mat = matrix(ZZ, [
    Bin(e, 128).tuple + (1,)  # 1 for the base b = c^d
    for e, c in data
])
m = mat.augment(identity_matrix(mat.nrows()))
W = 2**64  # we want it zero
for i in range(129):
    m.rescale_col(i, W)
m = m.LLL()
# not need to scale back since those are zeroes
m[0]
Out [16]:
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -6, 0, -3, -12, 2, 11, -4, -2, -1, 1, 4, 13, 2, -3, 8, 1, -4, -1, 10, -5, -3, 0, -2, -2, -7, 3, 8, 10, -9, 6, 3, -4, 2, -12, -4, -8, -3, -3, 2, 2, -3, 2, -5, -2, 5, 4, 0, -4, 1, -2, 1, -6, -4, 10, -7, 5, -6, -1, -9, -1, 2, 2, 6, 5, 3, -1, -9, -1, -7, -1, -2, 1, 7, 3, -6, 5, 1, 2, 10, -7, 0, 6, 3, -7, 6, -6, -8, 4, 3, 3, -4, 4, 4, 0, -3, 2, 2, 1, -2, -3, 8, -2, -1, 6, 9, -1, -3, 1, 7, 1, 2, -2, 1, -3, -1, -1, -1, 4, 2, 0, 1, 1, 4, -1, 4, -12, 7, -4, 5, 5, -2, -1, 0, 0, 6, -11, 7, -6, -1, -5, 0, 3, 1, 6, 1, 1, -2, 1, 5, 0, 3, -3, -4, -13, -3, 6, 5, 0, 0, -1, 1, -9, 2, -4, -2, -2, -2, 7, -2, 11, -3, 9, -7, 13, 9, 1, 1, -9, -1, 1, 7, -3, 4, 0, -11, -1, -3, -4, -4, -1, -6, -3, -3, -3, -1, -1, -3, -4, 5, 2, 3, 2, -1, -2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

In [17]:
n = 0
for row in m:
    if row[:129] != 0:
        continue

    take = row[129:]
    assert take * mat == 0

    total = sum(map(abs, take))
    print("total exp", total)
    if total > 1000:
        continue

    l = 1  # positive exponent
    r = 1  # negative exponent
    for i, coef in enumerate(take):
        if coef < 0:
            r *= ZZ(data[i][1])**int(-coef)
        elif coef > 0:
            l *= ZZ(data[i][1])**int(coef)

    was0 = n == 0
    n = gcd(n, l - r)
    if was0:
        for d in primes(10**6):
            while n % d == 0:
                n //= d
print(int(n).bit_length, "bits")
n
total exp 788
total exp 956
total exp 992
total exp 940
total exp 1020
total exp 1134
total exp 1012
total exp 1010
total exp 964
total exp 956
total exp 1174
total exp 964
total exp 866
total exp 1066
total exp 1162
total exp 1212
total exp 1058
total exp 1076
total exp 1108
total exp 968
total exp 916
total exp 1088
total exp 1038
total exp 1024
total exp 1150
total exp 1106
total exp 874
total exp 1014
total exp 864
total exp 968
total exp 954
total exp 1030
total exp 988
total exp 1066
total exp 956
total exp 972
total exp 1122
total exp 940
total exp 920
total exp 878
total exp 892
total exp 972
total exp 874
total exp 1002
total exp 850
total exp 1000
total exp 822
total exp 1010
total exp 1006
total exp 1032
total exp 994
total exp 826
total exp 946
total exp 1006
total exp 766
total exp 972
total exp 1000
total exp 916
total exp 1006
total exp 880
total exp 948
total exp 994
total exp 908
total exp 946
total exp 1020
total exp 964
total exp 1066
total exp 1046
total exp 1026
total exp 776
total exp 872
total exp 1098
total exp 1016
total exp 1172
total exp 984
total exp 1062
total exp 1084
total exp 954
total exp 1092
total exp 924
total exp 1004
total exp 892
total exp 1144
total exp 1162
total exp 1004
total exp 956
total exp 1060
total exp 1162
total exp 1006
total exp 878
total exp 892
total exp 1056
total exp 950
total exp 934
total exp 982
total exp 1136
total exp 900
total exp 1046
total exp 976
total exp 944
total exp 980
total exp 870
total exp 1106
total exp 1082
total exp 990
total exp 1126
total exp 950
total exp 1090
total exp 894
total exp 962
total exp 892
total exp 1104
total exp 964
total exp 1126
total exp 1044
total exp 1064
total exp 910
total exp 1002
total exp 1198
total exp 1022
total exp 1176
<built-in method bit_length of int object at 0x7f3829afedf0> bits
Out [17]:
124217875017693981438373889994606129923304968946265334335977917823588507715221733762831898609731538619253209136618903634779460007760209936134075290899530703320032114878851957436031470983264365504490701904507160973127049540844631212367371313377707894526617065409095533368660793349328274804862198629524390018631

Now, to recover the flag, we want to recover $b=c^d$, which corresponds to the vector $(0,\ldots,0,1)$ in the exponent. We can repeat the procedure but allowing small values in the last component. Then, either we hit 1 by chance or we can use extended GCD to get 1 from several coprime exponents.

In [18]:
m = mat.augment(identity_matrix(mat.nrows()))
W = 2**64  # we want it zero
V = 10 # smaller

for i in range(128):
    m.rescale_col(i, W)
m.rescale_col(128, V)

m = m.LLL()

m = m.change_ring(QQ)
m.rescale_col(128, QQ(1)/V)
m = m.change_ring(ZZ)

In [19]:
for row in m:
    if row[128] == -1:
        row = -row
    if row[:128] == 0 and row[128] == 1:
        print(row)
        msg = 1
        for i, v in enumerate(row[129:]):
            msg *= pow(int(data[i][1]), int(v), n)
        print(Bin(msg).bytes)
        break
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 6, -1, 1, -4, 10, 0, 1, -1, -1, -2, -1, -2, -3, -3, -10, -5, 4, -4, -3, 0, -5, -3, -4, -5, 1, 4, 8, -12, 12, -11, -7, -4, 4, 5, 4, 2, -5, -3, 0, -12, 4, -4, 6, 8, 12, -8, -9, -8, -8, 10, -7, -9, -10, 12, 4, -3, 11, 5, -6, -8, 1, 7, 3, 2, 1, 11, 0, -6, 0, 7, -15, -3, -1, -9, -2, 6, 3, -4, 1, 11, 11, 7, -8, -12, 9, 1, -11, -5, -4, 5, -7, -7, -2, 6, 4, -4, 3, 4, -1, 5, -10, 1, -4, -6, 4, 20, 11, -3, 7, -5, 7, 0, -11, 3, -3, 15, 2, -4, -5, -4, -6, 1, 2, 9, 3, 2, -8, 18, -2, 0, 6, -2, -9, -7, 3, -4, -4, 0, -2, -11, -1, 5, 6, 6, -6, 9, 0, -2, -11, -5, -5, 1, 3, 7, 0, 2, -1, 1, 0, 1, -3, -7, -8, 3, 3, -1, 1, 11, 14, 2, 0, 8, 8, -7, 8, 3, 6, 6, -13, -8, 10, -3, -2, -3, 0, -4, -2, 1, -1, 8, -2, 0, 0, -4, 1, 5, 0, 3, 1, 0, -1, 0, 1, -1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
b'TetCTF{4n_unr34l1st1c_f4ult____1_th1nk}'

Site version #146 from 2023-11-26 21:16:45 (CET)