# Perfect day (easy)
## Description
**Challenge code**
```python=
import random
from Crypto.Util.number import getPrime, bytes_to_long
soundtrack = [
b"House Of The Rising Sun",
b"the Dock of the Bay",
b"(Walkin' Thru The) Sleepy City",
b"Redondo Beach",
b"Pale Blue Eyes",
b"Brown Eyed Girl",
b"Feeling Good",
b"Aoi Sakana",
b"Perfect Day"
]
print("Welcome to the song guessing game!")
for i in range(32):
p = getPrime(512)
k = random.randint(1, 4096)
song = random.choice(soundtrack)
padded_song = song + b"0xff"*(256 - len(song))
hint = pow(1 + k*p, bytes_to_long(padded_song), p**2)
print(f"p = {p}")
print(f"hint = {hint}")
ans = input("Guess the song: ")
if ans == song.decode():
print("Correct!")
else:
print("Wrong! Try again.")
exit(0)
FLAG = open("flag.txt", "r").read()
print("Congratulations! You've guessed all the songs correctly.")
print(f"Flag: {FLAG}")
```
Trước hết thì cũng phân tích challenge code để ta tìm được hướng giải mã
```python=
for i in range(32):
p = getPrime(512)
k = random.randint(1, 4096)
song = random.choice(soundtrack)
padded_song = song + b"0xff"*(256 - len(song))
hint = pow(1 + k*p, bytes_to_long(padded_song), p**2)
print(f"p = {p}")
print(f"hint = {hint}")
ans = input("Guess the song: ")
if ans == song.decode():
print("Correct!")
else:
print("Wrong! Try again.")
exit(0)
```
Ở đoạn code này thì ta thấy được là cần đoán đúng tên bài hát 32 lần thì ta sẽ nhận về **flag**
Ngoài ra thì sau mỗi lần đoán đúng thì ta sẽ được cung cấp giá trị $hint$ và $p$ mới trong đó giá trị $hint$ được tính như sau:
```
m = bytes_to_long(padded_song)
```
$$
hint = (1 + k.p)^m \mod p^2
$$
Bằng cách sử dụng khai triển nhị thức Newton thì ta có được phương trình mới
$$
hint ≡ 1 + m.k.p \mod p^2
$$
$$
(hint - 1) / p ≡ m.k \mod p
$$
Đặt `r = (hint - 1) / p`
Dựa vào điều kiện có ở `k = random.randint(1, 4096)`, thì với mỗi round ta sẽ có cặp $(p,hint)$ và khi đấy ta sẽ xem bài hát nào cho ra giá trị $k$ thoả mãn `1 <= k <= 4096` thì bài hát đấy là đáp án đúng cho round đó:
$$
k = r.m^{-1} \mod p
$$
## Code
```python=
from pwn import *
from Crypto.Util.number import *
soundtrack = [
b"House Of The Rising Sun",
b"the Dock of the Bay",
b"(Walkin' Thru The) Sleepy City",
b"Redondo Beach",
b"Pale Blue Eyes",
b"Brown Eyed Girl",
b"Feeling Good",
b"Aoi Sakana",
b"Perfect Day"
]
def recover_song(p, hint, soundtrack):
r = ((hint - 1) // p) % p
for song in soundtrack:
padded = song + b"0xff" * (256 - len(song))
m = bytes_to_long(padded)
try:
inv_m = inverse(m, p)
except ValueError:
continue
k = (r * inv_m) % p
if 1 <= k <= 4096:
return song.decode()
return None
host = '36.50.177.41'
port = 5001
r = remote(host,port)
for i in range(32):
p_line = r.recvline_contains(b'p = ').strip()
hint_line = r.recvline_contains(b'hint = ').strip()
p = int(p_line.split(b'=')[1])
hint = int(hint_line.split(b'=')[1])
print(f"[Round {i+1}] p = {p}\n[Round {i+1}] hint = {hint}")
song = recover_song(p,hint,soundtrack)
r.sendline(song)
rep = r.recvline().decode().strip()
print(rep)
if 'Wrong' in rep:
break
flag = r.recvall().decode()
print(flag)
```
## Flag
```python=
KMACTF{did_you_enjoy_soundtrack_perfect_days}
```
# Make some noise (medium)
## Description
**Challenge code**
```python=
from random import randint
from re import search
flag = b"KMACTF{fake_flag}"
flag = flag[7: -1]
p = random_prime(2**1024)
setup = [randint(0, 2**32) for _ in range(len(flag))]
setup = [f ^^ ((pad >> 8) << 8) for f, pad in zip(flag, setup)]
output = []
for _ in range(len(flag)^2):
noise1 = [randint(0, 2**32) for _ in range(1000)]
noise2 = [randint(0, len(setup)-1) for _ in range(1000)]
noise3 = [randint(0, len(setup)-1) for _ in range(1000)]
noise4 = [randint(0, 2**32) for _ in range(1000)]
noise5 = [randint(0, 2**32) for _ in range(1000)]
output.append(noise1)
output.append(noise2)
output.append(noise3)
output.append(noise4)
output.append(noise5)
s = 0
for i in range(1000):
s += (noise5[i] * (noise1[i] + pow(setup[noise2[i]], 1337, p) * pow(setup[noise3[i]], 1663, p)) + noise4[i]) % p
output.append(s % p)
f = open("out.txt", "w")
f.write(f"{p = }\n")
f.write(f"{output = }\n")
```
**File [out.txt](https://docs.google.com/document/d/1jgpvEIuXZ9Jw42IRz8U-ecwRZjBePB4DZQY46YrSkus/edit?usp=sharing)**
Bắt đầu với `setup`, thì nó ban đầu là 1 danh sách chứa các giá trị ngẫu nhiên 32 bit trong đó số lượng giá trị bằng với độ dài của flag `flag = flag[7: -1]` . Sau đó
```
setup = [randint(0, 2**32) for _ in range(len(flag))]
```
Mỗi kí tự `f` trong `flag` được `xor` với một giá trị được tạo từ `pad `tương ứng:
- `pad >> 8`: dịch phải pad 8 bit, lúc này sẽ loại bỏ 8 bit thấp của pad
- `(pad >> 8) << 8`: tiếp tục dịch trái 8 bit, lúc này pad sẽ được thêm 8 bit 0 vào cuối
- Như vậy có nghĩa là 8 bit thấp của `f` sẽ không bị thay đổi khi thực hiện `xor` mà chỉ có các bit cao mới có thể bị thay đổi.
Tiếp đến với phần tạo `output`, thì vòng lặp chính chạy `len(flag)^2` lần mà trong mỗi lần lặp thì:
- 5 danh sách chứa các giá trị ngẫu nhiên `noise1`, `noise2`, `noise3`, `noise4`, `noise5` được tạo. Trong đó noise 1, 4 và 5 chứa các số nguyên ngẫu nhiên 32 bit còn noise 2 và 3 là các chỉ số của setup.
- Mỗi giá trị $s$ được tính toán theo công thức sau:
$$
s
\;\equiv\;
\sum_{i=0}^{999}
\Bigl(
\text{noise5}_i \,\cdot\,
\bigl(
\text{noise1}_i
\;+\;
\bigl(\text{setup}[{\text{noise2}_i}]\bigr)^{1337} \bmod p
\;\times\;
\bigl(\text{setup}[{\text{noise3}_i}]\bigr)^{1663} \bmod p
\bigr)
\;+\;
\text{noise4}_i
\Bigr)
\;\pmod{p}
$$
Từ phần phân tích `setup`, thì ta có thể suy ra được `setup[i] = flag[i] ^^ (pad[i] & 0xFFFFFF00)`, tức là 8 bit thấp của `setup[i]` là 8 bit thấp của `flag[i]`. Do đó có thể suy ra là `setup[i] & 0xFF = flag[i]`.
Vậy để tìm được flag thì ta cần tìm được `setup[i]`
Từ phương trình của $s$ ta có thể suy ra được như sau:
$$
s
\;-\;
\sum_{i=0}^{999}
\bigl(\,
\text{noise5}_i \,\cdot\, \text{noise1}_i
\;+\;
\text{noise4}_i
\bigr)
\;\equiv\;
\sum_{i=0}^{999}
\bigl(
\text{noise5}_i \,\cdot\, X_{\text{noise2}_i,\;\text{noise3}_i}
\bigr)
\pmod{p}
$$
Trong đó $X_{\text{noise2}_i,\;\text{noise3}_i}$
= $\bigl(\,(\text{setup}[noise2_i])^{1337} \bmod p \;\times\; (\text{setup}[noise3_i])^{1663} \bmod p\bigr)
\;\pmod{p}$
= $\bigl(\,(\text{setup}_j)^{1337} \bmod p \;\times\; (\text{setup}_k)^{1663} \bmod p\bigr)
\;\pmod{p}$
Lúc này ta sẽ giải phương trình `A.X = b` trong trường $p$ để tìm các giá trị $X$.
Tìm được $X_{j,k}$ thì để tìm lại các thành phần của `setup` bằng cách xét trường hợp $j=k$ thì ta có được như sau:
$$
X_{j,k} = \bigl(\,(\text{setup}_j)^{1337} \bmod p \;\times\; (\text{setup}_k)^{1663} \bmod p\bigr)
\;\pmod{p}
$$
$$
X_{j,j} = \bigl(\,(\text{setup}_j)^{1337} \bmod p \;\times\; (\text{setup}_j)^{1663} \bmod p\bigr)
\;\pmod{p}
$$
$$
X_{j,j} = \bigl(\,(\text{setup}_j)^{3000} \bmod p )
\;\pmod{p}
$$
Trong đó $j$ chạy từ $0$ đến $L-1$, tức là ta sẽ tìm được từ $setup[0]$ đến $setup[L-1]$, ghép lại là có được $flag$ :broken_heart:
## Code
```python=
from sage.all import *
import ast
def infer_L(output):
n = len(output)
if n % 6 != 0 or n == 0:
raise ValueError(f"Chiều dài output_data ({n}) không hợp lệ.")
L2 = n // 6
L = Integer(sqrt(L2))
if L * L != L2 or L == 0:
raise ValueError(f"L^2={L2} không phải số chính phương >0.")
return L
def find_A_b(f, output, L):
m = len(output) // 6
A = Matrix(f, m, L * L)
b = vector(f, m)
for k in range(m):
block = output[6*k : 6*k + 6]
noise1, noise2, noise3, noise4, noise5, s = block
s = f(s)
const = sum((f(n5) * f(n1) + f(n4)) for n1, n4, n5 in zip(noise1, noise4, noise5))
b[k] = s - const
for u, v, w in zip(noise2, noise3, noise5):
idx = int(u) * L + int(v)
A[k, idx] += f(w)
return A, b
def recover_flag(sol, p, L):
chars = []
for j in range(L):
Vj = sol[j*L + j]
roots = Vj.nth_root(3000, all=True)
ch = '?'
for r in (roots if isinstance(roots, list) else [roots]):
r = Integer(r)
if 0 <= r < 2**32:
c = int(r & 0xFF)
if 32 <= c <= 126:
ch = chr(c)
break
chars.append(ch)
return ''.join(chars)
with open("out.txt") as f:
p = Integer(f.readline().split('=')[1].strip())
output = ast.literal_eval(f.readline().split('=')[1].strip())
f = GF(p)
L = infer_L(output)
A, b = find_A_b(f, output, L)
sol = A.solve_right(b)
flag = recover_flag(sol, p, L)
print(f"KMACTF{{{flag}}}")
```
## Flag
```python=
KMACTF{aw3s0me_s0lut1on}
```
# Stranger things (hard)
## Description
**Challenge code**
```python=
import os
from Crypto.Util.number import bytes_to_long, getPrime
#p = getPrime(64)
p = 12267883553178394373
Fp = GF((p, 3))
FLAG = b'KMACTF{fake_flag}'
s = Fp.from_integer(bytes_to_long(FLAG))
set_random_seed(1337)
out = []
for i in range(15):
a, b = Fp.random_element(), Fp.random_element()
s = a * s + b
out.extend(s)
print([x >> 57 for x in out])
[43, 80, 59, 18, 3, 12, 77, 20, 25, 68, 68, 30, 47, 73, 78, 52, 62, 68, 84, 39, 32, 16, 1, 55, 39, 58, 48, 8, 72, 13, 63, 34, 19, 44, 45, 56, 82, 76, 10, 46, 69, 28, 69, 78, 29]
```
Cùng phân tích source code:
`Fp = GF((p, 3))`: tạo ra một trường mở rộng $F_{p^3}$ mà trong đó các phần tử trong trường này là các đa thức có bậc nhỏ hơn 3.
Để tìm được $flag$ ta cần tìm được $s_0$ hay chính là `s = Fp.from_integer(bytes_to_long(FLAG))`
Vì đã có $seed$ nên ta có thể dễ dàng khôi phục được các cặp giá trị $(a,b)$. Ngoài ra hệ thống cũng có ta biết được phần thông tin rõ rỉ là 7 bit cao nhất của mỗi hệ số.
## Code
```python=
from sage.all import *
set_random_seed(1337)
p = 12267883553178394373
Fp = GF((p, 3))
trunc = 57
leak = [43, 80, 59, 18, 3, 12, 77, 20, 25, 68, 68, 30,
47, 73, 78, 52, 62, 68, 84, 39, 32, 16, 1, 55,
39, 58, 48, 8, 72, 13, 63, 34, 19, 44, 45, 56,
82, 76, 10, 46, 69, 28, 69, 78, 29]
n = len(leak) // 3
a_list = []
b_list = []
for i in range(n):
a_list.append(Fp.random_element())
b_list.append(Fp.random_element())
c = [Fp(1)]
d = [Fp(0)]
for i in range(1, n):
c.append(c[-1] * a_list[i])
d.append(a_list[i] * d[-1] + b_list[i])
def embed(M):
M = matrix(M)
R = zero_matrix(ZZ, M.nrows(), 3 * M.ncols())
R[:, 0::3] = M.apply_map(lambda z: z[0])
R[:, 1::3] = M.apply_map(lambda z: z[1])
R[:, 2::3] = M.apply_map(lambda z: z[2])
return R
def unembed_vec(v):
return vector([
v[i] + v[i+1] * Fp.gen() + v[i+2] * Fp.gen()**2
for i in range(0, len(v), 3)
])
As = matrix(Fp, [[c_i for c_i in c]])
L = embed(As.stack(As * Fp.gen()).stack(As * Fp.gen()**2))
L = L.stack((identity_matrix(3 * n) * p)[3:])
B_ZZ = vector(embed([[d_i] for d_i in d]))
lb = vector([x << trunc for x in leak]) - B_ZZ
ub = vector([(x + 1) << trunc for x in leak]) - B_ZZ
mid = (lb + ub) / 2
def cvp(B, t):
t = vector(ZZ, t)
B = B.LLL()
S = B[-1].norm().round() + 1
L2 = block_matrix([
[B, zero_matrix(ZZ, B.nrows(), 1)],
[matrix(t), matrix(1, 1, S)]
])
for v in L2.LLL():
if abs(v[-1]) == S:
return t - v[:-1] * sign(v[-1])
raise ValueError("CVP failed")
v = cvp(L, mid) + B_ZZ
seq = unembed_vec(v)
s0 = (seq[0] - b_list[0]) / a_list[0]
flag_int = s0.to_integer()
flag_bytes = flag_int.digits(256)[::-1]
flag = ''
for i in flag_bytes:
flag += chr(i)
print(flag)
```
## Flag
```python=
KMACTF{y0u_ar3_4mazing}
```
## Note
- Bài này trong thời gian giải diễn ra thì mình chưa làm được và sau giải thì mình được a Tuệ hint mới giải ra :broken_heart: