## **epsilonDH**

:::spoiler **chall.py**
```python
from Crypto.Util.number import getStrongPrime, getRandomNBitInteger, bytes_to_long, long_to_bytes
import os
p = getStrongPrime(1024)
flag = b"fake+flag"
class Epsilon:
def __init__(self, a, b):
self.a, self.b = a, b
def __add__(self, other):
if type(other) == int: other = Epsilon(other, 0)
return Epsilon(self.a + other.a, self.b + other.b)
def __radd__(self, other): return self.__add__(other)
def __mul__(self, other):
if type(other) == int: other = Epsilon(other, 0)
return Epsilon(self.a * other.a, self.a * other.b + other.a * self.b)
def __rmul__(self, other): return self.__mul__(other)
def __mod__(self, other: int):
return Epsilon(self.a % other, self.b % other)
def __repr__(self): return f"{self.a} + {self.b}ɛ"
@staticmethod
def getRandomBits(n):
return Epsilon(getRandomNBitInteger(n), getRandomNBitInteger(n))
def powm(b, e, m):
r = 1
while e > 1:
if e & 1:
r = (r * b) % m
b = (b * b) % m
e >>= 1
return (b * r) % m
ɛ = Epsilon(0, 1)
g = ɛ.getRandomBits(1024)
m = bytes_to_long(flag)
assert m < p
A = powm(g, m, p)
print(f"{p = }")
print(f"{g = }")
print(f"{A = }")
```
:::
:::spoiler **output.txt**
```
p = 173924944755645003178406095718617168013285320974193311533464918516351624141198287888308296721497553891802368640344837769848433705383843820088678374708528763495103734488139368870389319280613181418960926879728892929013723036956818870578758055144789952650214552781344528622703875374067812710366180881422848078127
g = 153222010878956025592659771364999461265827693159532862299380012549533704470078014065110463612108844661289052080113198166134196684645743591092035461757997498335465019478118882739217108862526250347939116529661007420054504044554198442479469991584947626223020239910145162698053768142977329057860163194054350707249 + 172891042743500566967040288858220451145776247635832845268756172370398885506225014595399937064138727095012954778403481826951857306135326675358326250562011754152669045113179084291737802426967956129601732530346663460456772733886633658030480267226610996560624379249886941665142384623344612516572694197005870648544ɛ
A = 111358852433093434730054197107140594544307303976075171645711474646957878889456280742672183479878216988442037548855367380131019369757301409440037291726826948896290153981733240859717678222235993520619121828503359668550259222802623131077882382174117682287404839404525320091636778728025592053329591735052259548204 + 45354415949210290456746549581237886628185346518296265188224888250560968013577364380436312628842962917052795341010011570997705657164666282067908433629612354440756444902895692385443905786057605548586533595209894409891955713650856537806150873335015064767898088756645985769501205659617651498842444073593828856739ɛ
```
:::
Challenge này thực hiện mã hóa dựa trên **epsilon number** trong đó $\epsilon$ là một giá trị "siêu nhỏ". Đây là một cấu trúc quen thuộc trong các hệ số đại số phi tiêu chuẩn hoặc số siêu thực.
Các phép toán đã được định nghĩa lại để phù hợp với epsilon number.
Flag được mã hóa như sau: $A = g^{flag} \pmod p$
Mình có thấy p-1 factor được khá nhiều prime nhỏ nhưng ko biết áp dụng Polig-Hellman vào chỗ này làm sao. Sau đó mình phải đi xin hint =))))))
Ta nhận thấy:
$$
\begin{aligned}
(x + y\epsilon)^2 &= x^2 + 2xy\epsilon \\
(x + y\epsilon)^3 &= x^3 + 3x^2y\epsilon \\
(x + y\epsilon)^4 &= x^4 + 4x^3y\epsilon
\end{aligned}
$$
Theo quy luật này ta sẽ có:
$$
\begin{aligned}
&A = (g_x + g_y\epsilon)^{flag} = g_x^{flag} + flag*g_x^{flag - 1}*g_y\epsilon \\ &\Rightarrow A_y = flag*g_x^{flag - 1}*g_y = flag*(A_x/g_x)*g_y \\ &\Rightarrow flag = (A_y * g_x) / (A_x * g_y) \pmod p
\end{aligned}
$$
:::spoiler **Script**
```python
p = 173924944755645003178406095718617168013285320974193311533464918516351624141198287888308296721497553891802368640344837769848433705383843820088678374708528763495103734488139368870389319280613181418960926879728892929013723036956818870578758055144789952650214552781344528622703875374067812710366180881422848078127
g = [153222010878956025592659771364999461265827693159532862299380012549533704470078014065110463612108844661289052080113198166134196684645743591092035461757997498335465019478118882739217108862526250347939116529661007420054504044554198442479469991584947626223020239910145162698053768142977329057860163194054350707249,172891042743500566967040288858220451145776247635832845268756172370398885506225014595399937064138727095012954778403481826951857306135326675358326250562011754152669045113179084291737802426967956129601732530346663460456772733886633658030480267226610996560624379249886941665142384623344612516572694197005870648544]
A = [111358852433093434730054197107140594544307303976075171645711474646957878889456280742672183479878216988442037548855367380131019369757301409440037291726826948896290153981733240859717678222235993520619121828503359668550259222802623131077882382174117682287404839404525320091636778728025592053329591735052259548204 ,45354415949210290456746549581237886628185346518296265188224888250560968013577364380436312628842962917052795341010011570997705657164666282067908433629612354440756444902895692385443905786057605548586533595209894409891955713650856537806150873335015064767898088756645985769501205659617651498842444073593828856739]
x, y = g
mx, my = A
m = (my * x * pow(mx * y, -1, p)) % p
print(long_to_bytes(int(m
```
:::
## Twister

:::spoiler **deploy.py**
```python
from dataclasses import dataclass
from cmath import exp
import secrets
import time
import os
FLAG = os.getenv("FLAG") or "test{flag_for_local_testing}"
@dataclass
class Wave:
a: int
b: int
def eval(self, x):
theta = x / self.a + self.b
return ((exp(1j * theta) - exp(-1j * theta)) / 2j).real
ALL_WAVES = [Wave(a, b) for a in range(2, 32) for b in range(7)]
class MaximTwister:
"""
Next-generation PRNG with really **complex** nature.
More reliable than /dev/random cuz doesn't block ever.
"""
def __init__(self, state=None):
if state is None:
state = (1337, [secrets.randbits(1) for _ in ALL_WAVES])
self.point = state[0]
self.waves = [wave for wave, mask in zip(ALL_WAVES, state[1]) if mask]
def get_randbit(self) -> int:
result = 0
for wave in self.waves:
# you would never decompose a sum of waves 😈
result += round(wave.eval(self.point))
# especially if you know only the remainder, right? give up
result %= 2
self.point += 1
return result
def get_randbits(self, k: int) -> int:
return int("".join(str(self.get_randbit()) for _ in range(k)), 2)
def get_token_bytes(self, k: int) -> bytes:
return bytes([self.get_randbits(8) for _ in range(k)])
print("*** BUG DESTROYER ***")
print("You encounter: 😈 SEGMENTATION FAULT 😈")
opponent_hp = int(time.time()) * 123
days_passed = 0
random = MaximTwister()
while True:
print(
f"🕺 You ({10-days_passed} days till release) -- 😈 SEGMENTATION FAULT ({opponent_hp} lines)"
)
print(f"Day {days_passed + 1}. You can:")epsilonDH
image
chall.py
output.txt
Challenge này thực hiện mã hóa dựa trên epsilon number trong đó
là một giá trị "siêu nhỏ". Đây là một cấu trúc quen thuộc trong các hệ số đại số phi tiêu chuẩn hoặc số siêu thực.
Các phép toán đã được định nghĩa lại để phù hợp với epsilon number.
Flag được mã hóa như sau:
Mình có thấy p-1 factor được khá nhiều prime nhỏ nhưng ko biết áp dụng Polig-Hellman vào chỗ này làm sao. Sau đó mình phải đi xin hint =))))))
Ta nhận thấy:
Theo quy luật này ta sẽ có:
Script
p = 173924944755645003178406095718617168013285320974193311533464918516351624141198287888308296721497553891802368640344837769848433705383843820088678374708528763495103734488139368870389319280613181418960926879728892929013723036956818870578758055144789952650214552781344528622703875374067812710366180881422848078127
g = [153222010878956025592659771364999461265827693159532862299380012549533704470078014065110463612108844661289052080113198166134196684645743591092035461757997498335465019478118882739217108862526250347939116529661007420054504044554198442479469991584947626223020239910145162698053768142977329057860163194054350707249,172891042743500566967040288858220451145776247635832845268756172370398885506225014595399937064138727095012954778403481826951857306135326675358326250562011754152669045113179084291737802426967956129601732530346663460456772733886633658030480267226610996560624379249886941665142384623344612516572694197005870648544]
A = [111358852433093434730054197107140594544307303976075171645711474646957878889456280742672183479878216988442037548855367380131019369757301409440037291726826948896290153981733240859717678222235993520619121828503359668550259222802623131077882382174117682287404839404525320091636778728025592053329591735052259548204 ,45354415949210290456746549581237886628185346518296265188224888250560968013577364380436312628842962917052795341010011570997705657164666282067908433629612354440756444902895692385443905786057605548586533595209894409891955713650856537806150873335015064767898088756645985769501205659617651498842444073593828856739]
x, y = g
mx, my = A
m = (my * x * pow(mx * y, -1, p)) % p
print(long_to_bytes(int(m
Twister
image
deploy.py
from dataclasses import dataclass
from cmath import exp
import secrets
import time
import os
FLAG = os.getenv("FLAG") or "test{flag_for_local_testing}"
@dataclass
class Wave:
a: int
b: int
def eval(self, x):
theta = x / self.a + self.b
return ((exp(1j * theta) - exp(-1j * theta)) / 2j).real
ALL_WAVES = [Wave(a, b) for a in range(2, 32) for b in range(7)]
class MaximTwister:
"""
Next-generation PRNG with really **complex** nature.
More reliable than /dev/random cuz doesn't block ever.
"""
def __init__(self, state=None):
if state is None:
state = (1337, [secrets.randbits(1) for _ in ALL_WAVES])
self.point = state[0]
self.waves = [wave for wave, mask in zip(ALL_WAVES, state[1]) if mask]
def get_randbit(self) -> int:
result = 0
for wave in self.waves:
# you would never decompose a sum of waves 😈
result += round(wave.eval(self.point))
# especially if you know only the remainder, right? give up
result %= 2
self.point += 1
return result
def get_randbits(self, k: int) -> int:
return int("".join(str(self.get_randbit()) for _ in range(k)), 2)
def get_token_bytes(self, k: int) -> bytes:
return bytes([self.get_randbits(8) for _ in range(k)])
print("*** BUG DESTROYER ***")
print("You encounter: 😈 SEGMENTATION FAULT 😈")
opponent_hp = int(time.time()) * 123
days_passed = 0
random = MaximTwister()
while True:
print(
f"🕺 You ({10-days_passed} days till release) -- 😈 SEGMENTATION FAULT ({opponent_hp} lines)"
)
print(f"Day {days_passed + 1}. You can:")
print("1. Make a fix")
print("2. Call a senior")
choice = input("> ").strip()
if choice == "1":
damage = random.get_randbits(32)
opponent_hp -= damage
if opponent_hp <= 0:
print(
f"You commited a fix deleting {damage} lines. Miraculously, it worked!"
)
break
else:
print(f"You commited a fix deleting {damage} lines. The bug remained 😿")
elif choice == "2":
print("You called a senior. It's super effective! The bug is destroyed.")
break
else:
print(
f"You spent {random.get_randbits(4)} hours doing whatever {choice} means."
)
print("A day has passed. You couldn't fix the bug.")
days_passed += 1
if days_passed == 10:
print("It's release date! The bug is still there. You're fired.")
exit()
print("1. Make a fix")
print("2. Call a senior")
choice = input("> ").strip()
if choice == "1":
damage = random.get_randbits(32)
opponent_hp -= damage
if opponent_hp <= 0:
print(
f"You commited a fix deleting {damage} lines. Miraculously, it worked!"
)
break
else:
print(f"You commited a fix deleting {damage} lines. The bug remained 😿")
elif choice == "2":
print("You called a senior. It's super effective! The bug is destroyed.")
break
else:
print(
f"You spent {random.get_randbits(4)} hours doing whatever {choice} means."
)
print("A day has passed. You couldn't fix the bug.")
days_passed += 1
if days_passed == 10:
print("It's release date! The bug is still there. You're fired.")
exit()
print("The bug is gone! You got a raise.")
print(
"In your new office you see a strange door. It is locked. You try to guess the password from the digital lock:"
)
password = input("> ")
if bytes.fromhex(password) == random.get_token_bytes(16):
print("Somehow, you guessed the password! The room opens before you.")
print("You see a mysterious text:", FLAG)
print(
"What could it mean?... You turn around and see your boss right behind you..."
)
print("BAD ENDING")
else:
print("Incorrect. Well, let's get back to work...")
print("GOOD ENDING")
```
:::
Chall này có 2 chức năng chính:
- option 1: trả về một giá trị random
- option 2: người dùng predict được giá trị random tiếp theo thì sẽ nhận được flag.
Cơ chế tạo ra số random như sau:
- State được random là các mảng bao gồm các bit 0 và 1 và state này không được cập nhật như hàm random thông thường.
```python!
state = (1337, [secrets.randbits(1) for _ in ALL_WAVES])
```
- Các ==waves== được chọn dựa vào state
```python!
waves = [wave for wave, mask in zip(ALL_WAVES, state[1]) if mask]
```
- Hàm get_ranbit()
```python!
def get_randbit(self) -> int:
result = 0
for wave in self.waves:
# you would never decompose a sum of waves 😈
result += round(wave.eval(self.point))
# especially if you know only the remainder, right? give up
result %= 2
self.point += 1
return result
```
Các bit tạo thành dựa trên **point** và **wave** sau mỗi lần gọi hàm thì `point+=1`.
$result = \sum_{i=0}^{k} W_i(point) \bmod 2$
với `W_i(point) = round(waves[i].eval(point))`
Nếu ta có thể khôi phục lại state thì có thể predict được các số random tiếp theo.
Gọi A là ma trận có dạng như sau:
$$
M = \begin{bmatrix}
W_0(point) & W_1(point) & ... & W_n(point)\\
W_0(point+1) & W_1(point+1) & ... & W_n(point+1) \\
\vdots \\
W_0(point+k) & W_1(point+k) & ... & W_n(point+k)
\end{bmatrix}
$$
$\Rightarrow state * M = [bits]$
Ta nhận thấy len(state) = 210 nên chương trình có giới hạn 10 lần gửi thì ta vẫn tạo ra một ma trận hoàn chỉnh để tìm lại state.
:::spoiler **Script**
```python
from sage.all import *
from dataclasses import dataclass
from cmath import exp
import secrets
import time
import os
@dataclass
class Wave:
a: int
b: int
def eval(self, x):
theta = x / self.a + self.b
return ((exp(1j * theta) - exp(-1j * theta)) / 2j).real
ALL_WAVES = [Wave(a, b) for a in range(2, 32) for b in range(7)]
def gen_base_matrix():
point = 1337
res = []
tmp = []
while point < 9*32 + 1337:
for wave in ALL_WAVES:
# you would never decompose a sum of waves 😈
tmp.append(int(round(wave.eval(point))))
# especially if you know only the remainder, right? give up
res.append(tmp)
tmp = []
point += 1
return res
def solver(arr):
res = []
for i in range(len(arr)):
res += [0]*(32 - arr[i].bit_length()) + list(map(int, bin(arr[i])[2:]))
res = vector(GF(2), res[:210])
print(len(res))
base = Matrix(GF(2), gen_base_matrix()[:210])
new_state = base.inverse() * res
return new_state
from pwn import *
numbers = []
io = remote("twister.chal.wwctf.com", 1337)
for i in range(9):
io.recvuntil(b'> ')
io.sendline(b'1')
io.recvuntil(b'You commited a fix deleting ')
number = int(io.recvuntil(b' ').decode())
numbers.append(number)
print(numbers)
state = (1337, [int(i) for i in solver(numbers)])
class MaximTwister:
"""
Next-generation PRNG with really **complex** nature.
More reliable than /dev/random cuz doesn't block ever.
"""
def __init__(self, state=state):
print(state)
if state is None:
state = (1337, [secrets.randbits(1) for _ in ALL_WAVES])
self.point = state[0]
self.waves = [wave for wave, mask in zip(ALL_WAVES, state[1]) if mask]
def get_randbit(self) -> int:
result = 0
for wave in self.waves:
# you would never decompose a sum of waves 😈
result += int(round(wave.eval(self.point)))
# especially if you know only the remainder, right? give up
result %= 2
self.point += 1
return result
def get_randbits(self, k: int) -> int:
return int("".join(str(self.get_randbit()) for _ in range(k)), 2)
def get_token_bytes(self, k: int) -> bytes:
return bytes([self.get_randbits(8) for _ in range(k)])
ran = MaximTwister()
print([ran.get_randbits(32) for i in range(9)])
predict = ran.get_token_bytes(16)
io.recvuntil(b'> ')
io.sendline(b'2')
io.recvuntil(b'> ')
io.sendline(predict.hex().encode())
io.interactive()
```
:::

## Just Lattice

:::spoiler **chall.py**
```python!
import numpy as np
from secret import flag
def gen(q, n, N, sigma):
t = np.random.randint(0, high=q//2, size=n)
s = np.concatenate([np.ones(1, dtype=np.int32), t])
A = np.random.randint(0, high=q//2, size=(N, n))
e = np.round(np.random.randn(N) * sigma ** 2).astype(np.int32) % q
b = ((np.dot(A, t) + e).reshape(-1, 1)) % q
P = np.hstack([b, -A])
return P, s
def enc(P, M, q):
N = P.shape[0]
n = len(M)
r = np.random.randint(0, 2, (n, N))
Z = np.zeros((n, P.shape[1]), dtype=np.int32)
Z[:, 0] = 1
C = np.zeros((n, P.shape[1]), dtype=np.int32)
for i in range(n):
C[i] = (np.dot(P.T, r[i]) + (np.floor(q/2) * Z[i] * M[i])) % q
return C
q = 127
n = 3
N = int(1.1 * n * np.log(q))
sigma = 1.0
P, s = gen(q, n, N, sigma)
def prep(s):
return np.array([int(b) for char in s for b in f'{ord(char):08b}'], dtype=np.int32)
C = enc(P, prep(flag), q)
P = P.tolist()
C = C.tolist()
print(f"{P=}")
print(f"{C=}")
```
:::
:::spoiler output.txt
P=[[54, -22, -60, -8], [45, -26, -16, -62], [83, -8, -32, -30], [106, -46, -25, -21], [23, -22, -20, -25], [102, -40, -46, -42], [111, -16, -60, -50], [35, -9, -4, -41], [22, -61, -22, -7], [46, -10, -35, -20], [89, -54, -12, -3], [58, -43, -4, -10], [18, -56, -47, -52], [16, 0, -54, -31], [45, -28, -41, -40]]
C=[[18, 33, 115, 66], [52, 88, 94, 117], [111, 44, 32, 14], [0, 33, 10, 56], [54, 34, 107, 121], [50, 69, 33, 29], [70, 116, 96, 17], [67, 80, 59, 21], [58, 49, 73, 27], [5, 47, 29, 87], [9, 30, 70, 70], [97, 18, 24, 47], [105, 31, 92, 66], [7, 12, 45, 17], [26, 78, 63, 69], [48, 37, 23, 31], [105, 22, 44, 48], [36, 44, 13, 89], [0, 100, 102, 50], [24, 11, 28, 34], [89, 64, 71, 75], [64, 101, 75, 10], [29, 95, 96, 16], [34, 71, 40, 4], [52, 7, 64, 57], [117, 94, 101, 30], [20, 97, 15, 37], [108, 36, 66, 118], [104, 91, 85, 78], [73, 38, 74, 93], [115, 120, 53, 96], [50, 21, 42, 11], [29, 120, 105, 63], [51, 88, 85, 100], [9, 70, 76, 65], [111, 85, 112, 55], [18, 42, 44, 110], [71, 97, 14, 110], [101, 57, 119, 100], [38, 106, 117, 18], [7, 77, 22, 92], [103, 31, 81, 106], [58, 48, 89, 49], [116, 43, 51, 83], [15, 51, 6, 26], [18, 105, 118, 123], [0, 18, 108, 83], [106, 63, 10, 17], [11, 33, 53, 87], [66, 43, 39, 54], [15, 64, 121, 79], [28, 0, 2, 110], [38, 110, 37, 98], [31, 37, 122, 34], [40, 42, 32, 56], [90, 40, 105, 90], [65, 39, 96, 52], [56, 111, 111, 66], [77, 24, 75, 91], [60, 26, 73, 38], [102, 5, 40, 7], [41, 10, 66, 116], [119, 18, 41, 23], [28, 102, 71, 37], [78, 99, 70, 105], [100, 4, 80, 47], [52, 105, 43, 104], [54, 83, 74, 113], [12, 87, 84, 20], [24, 96, 39, 63], [17, 44, 72, 124], [77, 121, 28, 100], [32, 94, 88, 114], [77, 51, 116, 63], [69, 125, 69, 45], [11, 108, 105, 57], [105, 15, 123, 19], [43, 17, 33, 75], [99, 116, 97, 106], [2, 82, 9, 6], [4, 21, 47, 39], [35, 23, 62, 110], [9, 77, 114, 45], [37, 112, 120, 92], [10, 115, 85, 90], [72, 96, 25, 15], [57, 59, 68, 121], [66, 33, 30, 91], [57, 111, 75, 78], [11, 29, 22, 120], [102, 20, 31, 46], [23, 41, 99, 109], [37, 88, 36, 88], [73, 33, 27, 16], [96, 70, 4, 121], [44, 33, 59, 28], [77, 112, 80, 34], [6, 6, 84, 122], [118, 64, 49, 68], [69, 4, 22, 96], [45, 6, 59, 73], [71, 98, 105, 3], [106, 118, 23, 26], [106, 60, 27, 116], [8, 38, 112, 63], [38, 123, 125, 108], [56, 57, 102, 20], [70, 92, 105, 95], [98, 51, 94, 62], [59, 75, 86, 26], [104, 125, 54, 37], [33, 24, 2, 46], [48, 37, 8, 113], [30, 6, 37, 21], [81, 104, 34, 83], [115, 117, 59, 3], [71, 41, 60, 105], [7, 31, 44, 7], [19, 4, 7, 56], [86, 70, 120, 112], [13, 70, 32, 1], [59, 23, 47, 75], [78, 125, 113, 111], [66, 54, 61, 122], [75, 41, 91, 89], [66, 82, 72, 1], [3, 73, 59, 19], [105, 81, 20, 42], [46, 75, 105, 101], [21, 106, 67, 7], [21, 99, 52, 21], [35, 119, 86, 60], [34, 104, 109, 86], [81, 92, 16, 70], [72, 16, 54, 94], [89, 21, 12, 99], [20, 119, 94, 95], [60, 26, 97, 114], [63, 35, 122, 66], [57, 33, 81, 47], [48, 101, 99, 9], [11, 6, 85, 118], [120, 88, 108, 8], [22, 36, 88, 64], [90, 103, 50, 11], [65, 21, 27, 73], [84, 85, 125, 92], [79, 45, 80, 1], [14, 25, 7, 59], [107, 16, 86, 109], [75, 106, 38, 30], [81, 74, 9, 81], [38, 13, 95, 41], [53, 46, 99, 121], [26, 103, 50, 11], [31, 34, 64, 68], [114, 104, 43, 60], [54, 50, 120, 113], [24, 8, 42, 116], [11, 1, 87, 92], [15, 114, 93, 108], [6, 96, 44, 4], [28, 100, 46, 59], [31, 76, 26, 46], [85, 67, 74, 122], [27, 3, 5, 41], [81, 102, 51, 85], [104, 124, 84, 25], [114, 58, 73, 62], [45, 30, 1, 101], [3, 91, 49, 6], [104, 87, 113, 21], [118, 0, 46, 57], [50, 62, 107, 32], [64, 124, 100, 79], [27, 101, 76, 110], [120, 20, 7, 124], [81, 118, 84, 126], [85, 96, 117, 100], [98, 44, 10, 42], [81, 5, 4, 122], [94, 46, 64, 28], [83, 0, 105, 32], [115, 56, 117, 46], [39, 108, 119, 107], [90, 77, 95, 102], [70, 18, 32, 91], [88, 57, 64, 24], [61, 99, 32, 56], [122, 16, 92, 114], [113, 113, 23, 10], [39, 89, 94, 65], [1, 25, 122, 117], [63, 8, 122, 64], [22, 8, 1, 66], [51, 49, 85, 119], [91, 121, 126, 75], [125, 107, 126, 31], [101, 48, 18, 4], [7, 50, 67, 106], [40, 126, 36, 50], [60, 26, 73, 38], [106, 3, 17, 27], [100, 42, 85, 21], [22, 5, 8, 63], [3, 11, 50, 35], [57, 61, 68, 85], [43, 31, 120, 0], [9, 101, 35, 72], [68, 105, 105, 121], [82, 120, 82, 53], [117, 70, 9, 80], [0, 58, 70, 17], [39, 83, 96, 111], [47, 78, 105, 120], [85, 57, 122, 40], [64, 13, 2, 60], [81, 120, 0, 25], [99, 80, 14, 31], [1, 60, 86, 25], [35, 107, 115, 28], [52, 101, 39, 72], [6, 77, 58, 80], [27, 124, 29, 87], [30, 46, 106, 22], [99, 77, 51, 93], [18, 95, 20, 99], [47, 102, 126, 35], [29, 80, 112, 120], [80, 9, 36, 44], [74, 14, 44, 103], [31, 66, 99, 20], [108, 76, 102, 112], [84, 105, 110, 14], [113, 41, 104, 123], [22, 15, 25, 94], [62, 8, 89, 90], [3, 83, 1, 93], [74, 96, 49, 30], [37, 112, 15, 48], [62, 52, 98, 60], [120, 50, 55, 37], [46, 25, 94, 26], [102, 41, 71, 36], [23, 82, 104, 91], [67, 78, 83, 83], [95, 123, 58, 106], [111, 92, 70, 96], [16, 99, 118, 88], [101, 31, 17, 76], [105, 67, 76, 111], [67, 60, 78, 123], [33, 50, 63, 20], [110, 3, 126, 37], [65, 73, 43, 113], [109, 88, 71, 104], [34, 39, 122, 75], [108, 102, 15, 93], [23, 62, 97, 108], [49, 50, 121, 35], [125, 61, 92, 119], [106, 37, 41, 21], [116, 60, 64, 75], [81, 36, 28, 28], [110, 120, 27, 105], [115, 11, 44, 13], [57, 13, 115, 89], [8, 43, 45, 79], [23, 116, 74, 110], [97, 33, 44, 116], [103, 19, 4, 42], [118, 104, 2, 54], [116, 92, 97, 125], [107, 5, 64, 41], [69, 20, 42, 71], [78, 74, 65, 107], [83, 50, 38, 114], [29, 93, 19, 119], [91, 58, 29, 21], [54, 30, 45, 65], [52, 91, 123, 39], [123, 122, 17, 55], [56, 54, 4, 56], [89, 76, 50, 122], [126, 100, 78, 35], [3, 111, 124, 57], [25, 48, 56, 104], [63, 59, 33, 96], [22, 87, 46, 25], [8, 56, 26, 36], [103, 96, 101, 80], [53, 3, 64, 5], [11, 79, 24, 18], [74, 94, 89, 1], [12, 120, 3, 42], [33, 1, 106, 58], [123, 63, 37, 4], [121, 126, 112, 88], [112, 36, 108, 87], [109, 23, 104, 95], [36, 111, 99, 80], [88, 65, 120, 71], [88, 106, 76, 112], [52, 57, 120, 126], [64, 22, 42, 46], [51, 35, 5, 33], [59, 41, 83, 33], [76, 31, 36, 91], [12, 49, 102, 92], [29, 3, 70, 70], [63, 84, 126, 1], [24, 23, 43, 44], [107, 9, 28, 47], [43, 91, 22, 14], [90, 0, 83, 84], [108, 108, 12, 73], [92, 86, 100, 77], [50, 56, 80, 12], [111, 36, 27, 25], [21, 20, 72, 58], [9, 79, 112, 107], [110, 74, 19, 38], [8, 38, 79, 6], [90, 13, 122, 123], [45, 115, 47, 14], [8, 12, 0, 45], [58, 117, 25, 68], [120, 47, 21, 67], [55, 13, 22, 54], [43, 108, 91, 87], [70, 97, 15, 29], [62, 74, 58, 122], [73, 88, 90, 78], [31, 13, 116, 23], [65, 75, 79, 123], [22, 61, 15, 80], [70, 101, 34, 26], [98, 5, 9, 23], [45, 68, 18, 92], [114, 112, 14, 40], [2, 82, 9, 6], [68, 108, 30, 48], [5, 80, 14, 49], [51, 123, 112, 61], [38, 45, 18, 21], [68, 59, 29, 0], [70, 101, 86, 11], [0, 58, 57, 125], [78, 56, 55, 38], [81, 40, 19, 5], [70, 50, 66, 82], [52, 0, 14, 96], [96, 49, 106, 107], [83, 13, 17, 71], [20, 32, 26, 0], [3, 109, 35, 107], [48, 86, 19, 6], [93, 85, 73, 51], [60, 33, 5, 124], [38, 43, 64, 28], [35, 38, 94, 105], [82, 87, 99, 106], [18, 74, 97, 79], [101, 20, 61, 89], [116, 79, 19, 62], [4, 79, 115, 116], [52, 99, 77, 125], [31, 49, 80, 47], [92, 82, 35, 32], [23, 59, 101, 102], [119, 56, 3, 98], [26, 89, 39, 72], [84, 68, 106, 83], [55, 55, 32, 73], [14, 61, 54, 88], [54, 11, 39, 34], [10, 90, 116, 47], [26, 12, 68, 68], [90, 94, 18, 15], [115, 124, 3, 59], [32, 0, 112, 53], [123, 38, 112, 11], [3, 57, 109, 124], [11, 108, 105, 57], [73, 126, 112, 21], [110, 70, 13, 19], [103, 126, 50, 10], [48, 106, 15, 51], [79, 2, 73, 10], [22, 89, 40, 52], [71, 75, 111, 81], [107, 22, 102, 50], [15, 6, 0, 120], [69, 69, 63, 63], [93, 39, 108, 101], [97, 39, 6, 49], [49, 92, 57, 22], [70, 61, 103, 123], [116, 64, 37, 73], [116, 79, 94, 16], [7, 117, 18, 35], [22, 47, 107, 38], [118, 10, 63, 96], [11, 69, 103, 14], [84, 99, 84, 98], [30, 92, 51, 97], [120, 121, 9, 76], [73, 25, 116, 14], [21, 21, 52, 78], [35, 124, 20, 89], [49, 29, 15, 39], [17, 4, 87, 108], [22, 43, 100, 72], [105, 104, 8, 88], [114, 71, 1, 7], [62, 46, 55, 112], [92, 44, 118, 81], [3, 67, 22, 5], [30, 35, 119, 70], [22, 3, 95, 18], [9, 94, 97, 52], [87, 53, 61, 66], [2, 68, 73, 24], [41, 126, 51, 56], [116, 124, 76, 45], [37, 79, 99, 26], [69, 23, 17, 109], [14, 82, 93, 32], [52, 119, 107, 41], [50, 58, 73, 62], [34, 79, 27, 42], [22, 48, 14, 55]]
:::
Vì không gian khóa khá nhỏ nên ta có thể brute-force được s. Khi đó ta có thể lấy được flag dễ dàng.
**Script**
```python!
import numpy as np
import itertools
from binteger import Bin
P=...
C=...
def dec(C, s, q):
n = C.shape[0]
M = np.zeros(n, dtype=np.int32)
for i in range(n):
inner_product = np.dot(C[i], s) % q
M[i] = 1 if inner_product > q // 4 and inner_product < 3 * q // 4 else 0
return M
q = 127
n = 3
N = int(1.1 * n * np.log(q))
sigma = 1.0
P = np.array(P, dtype=np.int32)
C = np.array(C, dtype=np.int32)
for ss in itertools.product(range(0, q // 2), repeat=3):
s = np.concatenate([np.ones(1, dtype=np.int32), ss])
e = P @ s % q
e[e > q // 2] -= q
if (e >= -2).all() and (e <= 2).all():
print(s)
break
M = dec(C, s, q)
print(Bin(M.tolist()).bytes)
# wwf{1f_y0u_5qu33z3_17_h4rd_3n0u6h_ju1c3_w1ll_c0m3_0u7}
```