## **epsilonDH** ![image](https://hackmd.io/_uploads/BJZT71371x.png) :::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 ![image](https://hackmd.io/_uploads/S1muSkhQyg.png) :::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() ``` ::: ![image](https://hackmd.io/_uploads/Bk6Tyl3mJg.png) ## Just Lattice ![image](https://hackmd.io/_uploads/S1pY5EnQke.png) :::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} ```