# WannaGame Championship 2024 Vừa qua mình cùng team UIT.0r2 đã thử sức với WannaGame Championship 2024 và mình đã solve được 2 bài Crypto. ## random ### Challenge ![image](https://hackmd.io/_uploads/rJQkZ-2VJe.png) Souce Code: ```python = import random random.seed(random.randint(0, 10000)) flag = [c for c in open("flag.txt", "rb").read()] for _ in range(1337): flag = [x ^ y for x, y in zip(flag, [random.randint(0, 255) for _ in range(len(flag))])] print(bytes(flag).hex()) # 0203e2c0dd20182bea1d00f41b25ad314740c3b239a32755bab1b3ca1a98f0127f1a1aeefa15a418e9b03ad25b3a92a46c0f5a6f41cb580f7d8a3325c76e66b937baea ``` ### Solution Khởi đầu với một bài very easy. Ta biết được rằng, khi đã có $seed$ thì các chuỗi random tạo ra sẽ luôn giống nhau. Từ đó, ta sẽ bruteforce $seed$ và tìm flag. Code: ```python= import random ct = "0203e2c0dd20182bea1d00f41b25ad314740c3b239a32755bab1b3ca1a98f0127f1a1aeefa15a418e9b03ad25b3a92a46c0f5a6f41cb580f7d8a3325c76e66b937baea" ct = bytes.fromhex(ct) for seed in range(10000): random.seed(seed) flag = list(ct) for _ in range(1337): ran = [random.randint(0, 255) for _ in range(len(flag))] flag = [x ^ y for x, y in zip(flag, ran)] flag = bytes(flag) if b"W1{" in flag: print(flag) break ``` `Flag: W1{maybe_the_seed_is_too_small..._b32fe938a402c22144b9d6497fd5a709}` ## ECB ### Challenge ![image](https://hackmd.io/_uploads/r15QGWhVJx.png) Source Code: ```python= import gostcrypto from secret import key from Crypto.Util.Padding import pad with open("flag.txt", "rb") as f: flag = bytearray(f.read()) try: plaintext = bytearray.fromhex(input("Plaintext (hex): ")) plaintext = pad(plaintext + flag, 16) cipher = gostcrypto.gostcipher.new('kuznechik', key, gostcrypto.gostcipher.MODE_ECB) ciphertext = cipher.encrypt(plaintext) print(ciphertext.hex()) except: print("Eh!") exit(0) ``` ### Solution Ta nhận thấy rằng đây là dạng mã hóa `ECB`. Mà ta đã biết thì `ECB` sẽ tách plaintext ra thành các khối, mỗi khối gồm 16 bytes để mã hóa. Sau một vài phép thử thì mình biết được `len(flag) < 32`. Ta nhận thấy rằng: - Khi ta gửi `test = b'0' * 15` lên thì `plaintext = pad(test + flag, 16)` tức là trong block đầu tiên của ECB sẽ gồm `000000000000000W` (ta biết W1{ là tiền tổ của flag). Còn block thứ 2 sẽ là `1{.....` - Và nếu ta gửi `test = b'0' * 15 + b'W'` lên thì block đầu tiên của ta cũng sẽ là `000000000000000W` nhưng block thứ 2 sẽ là `W1{.....` - Dễ thấy được với 2 cách gửi thì khối đầu tiên luôn giống nhau -> sau khi mã hóa cũng sẽ giống nhau. Từ đó ta có được thuật toán bruteforce từng kí tự của flag rồi check. Code: ```python= from pwn import * import string char_set = set(string.ascii_letters + string.digits + "{}_@") len_flag = 31 flag = b'' for i in range(len_flag): conn = remote("chall.w1playground.com", 24777) conn.recvuntil(": ") test = "0" * (len_flag - len(flag)) test_encoded = test.encode() conn.sendline(test_encoded.hex()) cur = conn.recvline() cur = bytes.fromhex(cur.decode()) conn.close() for ch in char_set: conn = remote("chall.w1playground.com", 24777) conn.recvuntil(": ") newpat = test.encode() + flag + ch.encode() conn.sendline(newpat.hex()) res = conn.recvline().decode() res = bytes.fromhex(res) conn.close() if cur[:len(newpat)] == res[:len(newpat)]: flag += ch.encode() break if flag[-1] == b"}": break print() print(f"Flag: {flag}") ``` `Flag: W1{0Ld_pr0bl3m_BUT_n3W_c1pher}` ## RAS Bài này thì mình đã làm được tầm 80% trong giải nhưng bị mắc ở một chỗ. Sau khi end giải thì mình có hỏi các anh thì mới biết được 1 kiến thức cơ bản mà mình không nghĩ đến :< ### Challenge ![image](https://hackmd.io/_uploads/rJPJDW3N1g.png) Source code: ```python= from Crypto.Util.number import * from secret import flag import random class RAS(object): def __init__(self, p, q): self.p = p self.q = q self.n = (p**3 - p)*(q**3 - q) def generate_e(self): e = random.randint(self.p * self.q, (self.p * self.q)**2) return e def encrypt(self, pt): e = self.generate_e() assert pt < self.n c = pow(pt, e, self.n) return e, c flag1, flag2, flag3 = bytes_to_long(flag[:len(flag)//3]), bytes_to_long(flag[len(flag)//3:2*len(flag)//3]), bytes_to_long(flag[2*len(flag)//3:]) # shuffle it m1 = flag1*flag2*flag3 m2 = flag1 + flag2 + flag3 m3 = flag1 * flag2 + flag2 * flag3 + flag3 * flag1 nbit = 256 menu = ''' Welcome to my RAS 1. Send primes 2. Get encrypted flag ''' b = False print(menu) while True: try: choose = input('> ') if choose == '1': primes = input(f"Send me two {nbit}-bit strong primes, separated by comma: ") try: p, q = map(int, primes.strip().split(',')) except: raise Exception("Invalid input") if (isPrime(p) and isPrime(q) and p != q and p.bit_length() == q.bit_length() == nbit): b = True ras = RAS(p, q) else: print("Primes not strong enough!") elif choose == '2': if b: print(ras.encrypt(m1)) print(ras.encrypt(m2)) print(ras.encrypt(m3)) else: raise Exception("You must send strong primes first!!!") else: raise Exception("Invalid choice!!!") except Exception as e: print(f"Error: {str(e)}") break ``` ### Solution Việc chọn p, q và tính được phi khá đơn giản nên mình sẽ skip phần đó luôn. Điểm mấu chốt của bài này, đã khiến mình làm không ra đó chính là $N$ là một số chẵn. Từ đó author có thể tạo ra các giá trị $m$ chẵn khiến cho việc decrypt bằng công thức RSA bình thường sẽ không được. `c^d = m^(ed) (mod N)` mà vì `gcd(m, N) > 1` nên ta không thể áp dụng định lý Euler (`=> m^(ed) khác m`). Vậy với trường hợp gcd(m, N) > 1 thì ta làm thế nào ? Đặt `g = gcd(m, N)`. Ta có: $c^d \equiv m^{ed}$ $(mod$ $N)$ $\equiv (m' * g) ^ {ed}$ $(mod$ $N)$ $\equiv (m')^{ed} * g^{ed}$ $(mod$ $N)$ $\equiv m' * g^{ed}$ $(mod$ $N)$. Vì biểu thức trên chỉ đúng với điều kiện `gcd(m', N) = 1` nên ta sẽ tìm các giá trị $m'$ bằng cách bruteforce giá trị $g$. Từ đó bài toán của ta sẽ trở về thành: Bruteforce giá trị $g$. Tìm tất cả các giá trị $m'$ sao cho $g*m' \equiv c^d$ $(mod$ $N)$. Và $m' \in [0..N-1]$ Ta có: - $Ax \equiv B$ $(mod$ $N)$ $<=>$ $Ax=B+Ny$ $<=>$ $Ax-Ny=B$ $(1)$ Ta thấy đây là [phương trình Diophantine tuyến tính](https://www.geeksforgeeks.org/linear-diophantine-equations/) nên điều kiện để có nghiệm của phương trình phải là $gcd(A,N)$ | $B$ - Theo Extended Euclid, ta sẽ tìm được $u, v$ sao cho: $Au+Nv=gcd(A,N)=d$ $<=>$ $Au \equiv d$ $(mod$ $N)$ $(2)$ - Giả sử $B \% d = 0$. Do đó luôn tồn tại nghiệm của phương trình $(1)$. Nhân $\frac{B}{d}$ vào 2 vế của phương trình $(2)$ ta có: $Au * \frac{B}{d} \equiv d * \frac{B}{d}$ $(mod$ $N)$ $\Rightarrow Au * \frac{B}{d} \equiv B$ $(mod$ $N)$ $<=>$ $A * u\frac{B}{d} \equiv B$ $(mod$ $N)$ . - Vậy $u\frac{B}{d}$ là một nghiệm của $(1)$. - Gọi $x_0 = u\frac{B}{d}$. Do đó các nghiệm của phương trình $(1)$ sẽ là: $x_0, x_0 + \frac{N}{d}, x_0+2\frac{N}{d},...,x_0+(d-1)\frac{N}{d}$. - $x_0 + i\frac{N}{d}$ là nghiệm của $(1)$ bởi vì: $\Rightarrow A(x_0+i\frac{N}{d})$ $mod$ $N$ = $(Ax_0 + A*i\frac{N}{d})$ $mod$ $N$ = $Ax_0$ $mod$ $N$. Sau khi tìm được $m'$ thì ta sẽ tính được $m = g * m'$. Vậy, ta sẽ tìm được các giá trị $m_1, m_2, m_3$ khả thi. Từ đó thử với mỗi bộ 3 $m_1, m_2, m_3$ và giải phương trình bậc ba $x^3 - m_2.x^2 + m_3.x - m_1$ để tìm flag. Code: ```python= from pwn import * from Crypto.Util.number import * from sage.all import * import itertools p = 110546454747203504006925729538023265156225304520988132722771250047696607274399 q = 69432444660353724912594441619180565122596810625659229683919790847518915561813 N = (p**3 - p)*(q**3 - q) phi = 8130718311181529512023061937820198911824250577656859808220234200906934536574593002998439406567360340616588872082761396586369901175200281306229826359367416808716672544100675100778004339594396045911708929205724269740521522382094059853335398782101368420506241771196284270924708276195859621696139078700006880839569915571277371872103794764324997362673688435622070419864110256852465372308815637409824647991609263674448439608843405825808056538999591789771495781171200 def ExtendedEuclidAlgo(a, b): if a == 0 : return b, 0, 1 gcd, x1, y1 = ExtendedEuclidAlgo(b % a, a) x = y1 - (b // a) * x1 y = x1 return gcd, x, y def linearCongruence(A, B, N): A, B = A % N, B % N u, v = 0, 0 d, u, v = ExtendedEuclidAlgo(A, N) res = [] if (B % d != 0): return res x0 = (u * (B // d)) % N if x0 < 0: x0 += N for i in range(d): res.append((x0 + i * (N // d)) % N) return res def find_m(e, c): m_possible = [] d = inverse(e, phi) ct = pow(c, d, N) for gcd in range(1, 100): if N % gcd: continue roots = linearCongruence(pow(gcd, e*d, N), ct, N) for x in roots: m_possible.append((x * gcd) % N) return m_possible conn = remote("154.26.136.227", 46352) conn.recvuntil("> ") conn.sendline("1") conn.recvuntil(": ") conn.sendline(str(p) + ", " + str(q)) m1, m2, m3 = [], [], [] while True: conn.recvuntil("> ") conn.sendline("2") data = conn.recvline() e1 = data.decode().strip().split(" ")[0] c1 = data.decode().strip().split(" ")[1] e1, c1 = int(e1[1:-1]), int(c1[:-1]) data = conn.recvline() e2 = data.decode().strip().split(" ")[0] c2 = data.decode().strip().split(" ")[1] e2, c2 = int(e2[1:-1]), int(c2[:-1]) data = conn.recvline() e3 = data.decode().strip().split(" ")[0] c3 = data.decode().strip().split(" ")[1] e3, c3 = int(e3[1:-1]), int(c3[:-1]) if GCD(e1, phi) == 1: m1 = find_m(e1, c1) if GCD(e2, phi) == 1: m2 = find_m(e2, c2) if GCD(e3, phi) == 1: m3 = find_m(e3, c3) m1 = list(set(m1)) m2 = list(set(m2)) m3 = list(set(m3)) print(len(m1), len(m2), len(m3)) if len(m1) and len(m2) and len(m3): for x1, x2, x3 in itertools.product(m1, m2, m3): x = ZZ['x'].gen() fx = x**3 - x2*x**2 + x3*x - x1 root = fx.roots() if len(root) == 3: flag = b"" flag += long_to_bytes(int(root[2][0])) flag += long_to_bytes(int(root[1][0])) flag += long_to_bytes(int(root[0][0])) print(flag) exit() # Flag: W1{wi3rd_ch41!En9e_n33d_4_WlErD_s0O!luti0n_6f339749663eeb3508c3b00c15872e41} ```