# Crypto CTF 2025 write-up
這次 38 名,其實沒打太好,不過挺好玩的
## Warm-up
### Welcome
就 base64,沒什麼好說的
## Easy-Peasy
### Vinad
```python
def parinad(n):
return bin(n)[2:].count('1') % 2
def vinad(x, R):
return int(''.join(str(parinad(x ^ r)) for r in R), 2)
def genkey(nbit):
while True:
R = [getRandomNBitInteger(nbit) for _ in range(nbit)]
r = getRandomNBitInteger(nbit)
p, q = vinad(r, R), getPrime(nbit)
if isPrime(p):
e = vinad(r + 0x10001, R)
if GCD(e, (p - 1) * (q - 1)) == 1:
return (e, R, p * q), (p, q)
def encrypt(message, pubkey):
e, R, n = pubkey
return pow(message + sum(R), e, n)
nbit = 512
pubkey, _ = genkey(nbit)
m = bytes_to_long(flag)
assert m < pubkey[2]
c = encrypt(m, pubkey)
```
這題是一個 RSA,特別的是它生成 `n` 和 `e` 的方式是使用 `vinad` 這個函式。做的事情是把一個數字和陣列 `R` 每個數字 xor 之後計算是 1 的數量的奇偶數,再以二進位制的方式變成整數。它會給 `R` 、`e` 、`n` 和 `c`。會發現,在計算 `vinad(x, R)`,的時候如果 `x` 變了一個 bit,那麼結果上每個計算結果奇偶性都會變,所以就是逐位元翻轉。結論是,`vinad` 的結果在 `R` 相同的情況下只會有兩種,而且他們兩個逐位元互補。求其中一個的方式是發現任兩個 `R` 中元素如果 xor 的結果 1 數量是奇數的話那兩個位置的 bit 就不同,否則相同。
```python
from Crypto.Util.number import *
def parinad(n):
return bin(n)[2:].count('1') % 2
def vinad(x, R):
return int(''.join(str(parinad(x ^ r)) for r in R), 2)
R = list(R)
RR = [parinad(R[i] ^ R[i - 1]) for i in range(1, len(R))]
k1 = "0"
for i in RR:
if i == 1:
k1 += "1" if k1[-1] == "0" else "0"
else:
k1 += "1" if k1[-1] == "1" else "0"
k2 = "".join(["1" if i == "0" else "0" for i in k1])
k1 = int(k1, 2)
k2 = int(k2, 2)
q = k1
p = n // k1
assert(p * q == n)
e = k1
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
m = pow(c, d, n)
m = m - sum(R)
print(long_to_bytes(m))
```
### Interpol
```python
#!/usr/bin/env sage
from sage.all import *
from Crypto.Util.number import *
from flag import flag
def randpos(n):
if randint(0, 1):
return True, [(-(1 + (19*n - 14) % len(flag)), ord(flag[(63 * n - 40) % len(flag)]))]
else:
return False, [(randint(0, 313), (-1) ** randint(0, 1) * Rational(str(getPrime(32)) + '/' + str(getPrime(32))))]
c, n, DATA = 0, 0, []
while True:
_b, _d = randpos(n)
H = [d[0] for d in DATA]
if _b:
n += 1
DATA += _d
else:
if _d[0][0] in H: continue
else:
DATA += _d
c += 1
if n >= len(flag): break
A = [DATA[_][0] for _ in range(len(DATA))]
poly = QQ['x'].lagrange_polynomial(DATA).dumps()
f = open('output.raw', 'wb')
f.write(poly)
f.close()
```
它用拉格朗日差值法計算出一個多項式,包括有 flag 資訊的點和一些隨機點,就枚舉一些 flag_len,看看是不是每個 x 對應到的 y 都符合條件就好。
```python
from sage.all import *
import string
with open("output.raw", "rb") as f:
# poly = QQ["x"].loads(f.read())
poly = loads(f.read())
# [19, 23, 62]
# n = 0
for flag_len in range(5, 100):
flag = bytearray(b"\x00"*flag_len)
n = 0
fail = False
for i in range(flag_len):
k = poly(-(1 + (19*n - 14) % flag_len))
if k >= 256 or k < 0 or chr(k) not in string.printable:
fail = True
break
flag[(63 * n - 40) % flag_len] = k
n += 1
if fail:
continue
if bytes(flag[:5]) == b"CCTF{":
print(flag)
```
### Mechanic
```python
from quantcrypt.kem import MLKEM_1024
from quantcrypt.cipher import KryptonKEM
from random import randint
from pathlib import *
from os import urandom
from flag import flag
kem = MLKEM_1024()
kry = KryptonKEM(MLKEM_1024)
pt = Path('/Mechanic/flag.png')
f = open('output.raw', 'wb')
m = randint(2 ** 39, 2 ** 40)
B, c = bin(m)[2:], 0
for b in B:
if b == '1':
pkey, skey = kem.keygen()
ct = Path(f'/flag_{c}.enc')
kry.encrypt(pkey, pt, ct)
pt = ct
c += 1
else:
pkey, skey = urandom(kem.param_sizes.pk_size), urandom(kem.param_sizes.sk_size)
f.write(skey)
f.close()
```
它會去一直加密,並且把私鑰寫到一個檔案裡面,我們有的是 `flag_22.enc` 和私鑰的檔案。只要枚舉私鑰的位置,再一路解密回去就好了。
```python
from quantcrypt.kem import MLKEM_1024
from quantcrypt.cipher import KryptonKEM
from pathlib import *
# kem = MLKEM_1024()
kry = KryptonKEM(MLKEM_1024)
sz = 3168
# print(kem.param_sizes.sk_size)
with open("output.raw", "rb") as f:
data = f.read()
data = [data[i:i+sz] for i in range(0, len(data), sz)]
cnt = 22
for i in data[-1::-1]:
try:
kry.decrypt_to_file(i, Path(f"./flag_{cnt}.enc"), Path(f"./flag_{cnt-1}.enc"))
cnt -= 1
print(data.index(i))
except:
pass
```
## Getting There
### Ikkyu San
```python
def Ikkyu(nbit):
p = getPrime(nbit)
while True:
a, b = [randint(1, p - 1) for _ in range(2)]
E = EllipticCurve(GF(p), [a, b])
G, H = [E.random_point() for _ in range(2)]
try:
I = E.lift_x(1)
except:
if legendre_symbol(b - a - 1, p) < 0:
return p, E, G, H
def fongi(G, H, P):
try:
xG, xP, yP = G.xy()[0], P.xy()[0], P.xy()[1]
except:
xP = 1337
return int(xP) * G + int(yP) * H + int(xG) * P
```
這題是橢圓曲線,係數生成的部份沒什麼好說的。這題支援以下幾個操作:
- fongi
給一個在曲線上的點 `P`,它會計算 `fongi(G, H, P)`
- random point
給你一個橢圓曲線上的隨機點
- encrypt
給你 `m * G.xy()[0] * H.xy()[1]`
首先,可以透過隨機點把橢圓曲線的參數算出來。
然後,注意到一件事情是如果在進行 fongi 的時候,輸入的 `P` 的階很小的話,那麼乘一個小倍數就能把它全部消掉了,所以我先生一個點,方便起見用生成元,並且將它乘上橢圓曲線的階除以 2(如果橢圓曲線的階是奇數就重來),並且 `fongi(GG * (oord//2))`。會發現一件事情:$2(P_xG+P_yH+G_xP)=2P_xG+2P_yH$(因為 $2P=0$)
那如果再計算 `fongi(-GG * (oord//2))` 會得到 $2P_xG-2P_yH$,於是兩個相加相減之後再分別乘上 $P_x$ 和 $P_y$ 的逆元就可以得到 $4G$ 和 $4H$,然後接下來會去開根號。
注意的一點是 $P_x$ 必須要互質於橢圓曲線的階,才有逆元,這樣先計算出所有的 $G$ 的可能。然後,代隨機點去算出 $H$。最後,用 encrypt 得到資訊解出 `m`
```python
from sage.all import *
from Crypto.Util.number import *
from pwn import *
import ast
from functools import reduce
def Ikkyu(nbit):
p = getPrime(nbit)
while True:
a, b = [randint(1, p - 1) for _ in range(2)]
E = EllipticCurve(GF(p), [a, b])
G, H = [E.random_point() for _ in range(2)]
try:
I = E.lift_x(1)
except:
if legendre_symbol(b - a - 1, p) < 0:
return p, E, G, H
def fongi(P):
r.sendlineafter(b"uit", b"g")
r.sendlineafter(b": ", f"{P[0]}, {P[1]}".encode())
print(r.recvuntil(b"= "))
point = r.recvline().strip(b"()\n").decode()
return tuple(list(map(int, point.split(" : ")))[:2])
r = remote("91.107.252.0", 37773)
# r = process(["python", "Ikkyu_san.py"])
def get_random_point():
r.sendlineafter(b"uit", b"r")
r.recvuntil(b" = ")
point = r.recvline().strip(b"()\n").decode()
return tuple(list(map(int, point.split(" : ")))[:2])
points = [get_random_point() for i in range(10)]
yee = [((points[i-1][1]**2-points[i-1][0]**3)-(points[i][1]**2-points[i][0]**3),points[i-1][0]-points[i][0]) for i in range(1, 10)]
yee2 = [yee[i][0]*yee[i-1][1]-yee[i][1]*yee[i-1][0] for i in range(1,9)]
p = reduce(GCD, yee2)
print("yee")
a = yee[0][0] * pow(yee[0][1], -1, p) % p
x1, y1 = points[0]
b = (y1**2 - (x1**3 + a * x1)) % p
print(a, b)
E = EllipticCurve(GF(p), [a, b])
# f1 = y1**2 - (x1**3 + a*x1 + b)
# f2 = y2**2 - (x2**3 + a*x2 + b)
oord = E.order()
assert(oord % 2 == 0)
GG = E.gens()[0]
xG = 0
# print(oord % 3)
k = 2
print("GG")
# print(GG)
# print(-GG)
res1 = E(fongi(GG * (oord//2)))
print(res1 * 2)
res2 = E(fongi(-GG * (oord//2)))
print(res2 * 2)
P = GG*(oord//2)
G4 = (res1*2+res2*2)*pow(int(P.xy()[0]), -1, oord)
roots = G4.division_points(4)
while True:
R = E.random_point()
xP, yP = R.xy()
try:
pow(int(yP), -1, oord)
break
except:
pass
res3 = E(fongi(R))
sols = []
for G in roots:
xP, yP = R.xy()
xG, yG = G.xy()
H = (res3 - (int(xG) * R) - (int(xP) * G)) * pow(int(yP), -1, oord)
sols.append((G, H))
r.sendlineafter(b"uit", b"e")
r.recvuntil(b"= ")
c = int(r.recvline().strip().decode())
for G, H in sols:
x = GF(p)(int(G.xy()[0]))
y = GF(p)(int(H.xy()[1]))
m = GF(p)(c) * x**(-1) * y**(-1)
try:
print(long_to_bytes(int(m)))
except:
pass
r.interactive()
# CCTF{prOm!nEn7_Z3n_Ikkyu_p03t?!}
```
### ASIS Primes
```python
if ans == 'e':
m = bytes_to_long(flag)
c = pow(m, e ^ 1, p * q)
pr(f'{c = }')
elif ans == 's':
pinit = f'CCTF{{7H!S_iZ_th3_f1RSt_pRim3__P_f0R_oUr_{nbit}-bit_m0DulU5_{rand_str(randint(5, 40))}'.encode()
qinit = f'CCTF{{7H!S_iZ_th3_s3c0Nd_pRim3_Q_f0R_oUr_{nbit}-bit_m0DulU5_{rand_str(randint(5, 40))}'.encode()
pr(border, f'the condition for the first prime is: {pinit}')
pr(border, f'the condition for the second prime is: {qinit}')
pr(border, f'Please submit the primes p, q: ')
inp = sc().decode().strip()
try:
_p, _q = [int(_) for _ in inp.split(',')]
_pbytes, _qbytes = [long_to_bytes(_) for _ in (_p, _q)]
if (
isPrime(_p) and isPrime(_q)
and _pbytes.startswith(pinit) and _qbytes.startswith(qinit)
and _pbytes.endswith(b'}') and _qbytes.endswith(b'}')
and is_valid(_pbytes) and is_valid(_qbytes)
and (9 * _p * _q).bit_length() == 2 * nbit
):
p, q = _p, _q
except:
pr(border, f'The input you provided is not valid! Try again!!')
nbit += 1
```
這題有個 RSA 加密,然後可以傳入 p 和 q,只是要符合一些條件。他們必須要有固定前綴 `pinit` 和 `qinit`(其中有一段是隨機的),固定後綴 `"}"`,字元在特定字元集裡,並且位元數滿足 `(9 * p * q).bit_length() == 2 * nbit`,然後 nbit 是可控的,但只能越來越大。
如果去暴搜的話,會發現固定前後綴的情況下,隨機塞一些東西後是質數的情況其實是不罕見的。那我們要做的是先估算出 bit 數後在兩個數塞東西,而因為我們是塞在低位,所以只要位數對了就基本問題不大,做些調整就好。另外是,`nbit` 的選擇原則上是 4 的倍數越大越好。
```python
from Crypto.Util.number import *
from sage.all import *
import string
import itertools
from pwn import *
import ast
charset = string.printable[:63]+'_{-}'
# print(list(map(ord, charset)))
k=2
def rand_str(l):
charset = string.printable[:63] + '_'
return ''.join([charset[randint(0, 63)] for _ in range(l)])
def find_prime_k(pre: bytes, k: int):
for item in itertools.product(charset, repeat=k):
res = pre + "".join(item).encode() + b"}"
p = bytes_to_long(res)
if isPrime(p):
return res
return None
# pre = b"CCTF{7H!S_iZ_th3_f1RSt_pRim3__P_f0R_oUr_512-bit_m0DulU5_d5OtXLlQpW3"
def gen_prime(pre1: bytes, pre2: bytes, nbits):
if (int(bytes_to_long(pre1) * bytes_to_long(pre2) * 9).bit_length() - 2 * nbits) % 8 != 0:
return None
l = (2*nbits - int(bytes_to_long(pre1) * bytes_to_long(pre2) * 9).bit_length()) // 8
# print(l)
if l < 0:
return None
for i in range(l):
res1 = find_prime_k(pre1, i)
if not res1:
continue
lim = max((2*nbits - int(bytes_to_long(res1) * bytes_to_long(pre2) * 9).bit_length()) // 8 - 1, 0)
res2 = find_prime_k(pre2, lim)
if not res1 or not res2:
continue
p = bytes_to_long(res1)
q = bytes_to_long(res2)
# print((9 * p * q).bit_length()-2 * nbits)
if (9 * p * q).bit_length() != 2 * nbits:
continue
return res1, res2
r = remote("65.109.189.98", 13737)
nbit = 512
def add_four():
global nbit
for i in range(4):
r.sendlineafter(b"uit", b"s")
r.recvuntil(b":")
r.recvuntil(b":")
r.sendlineafter(b":", b"yee")
print(r.recvuntil(b"Try again"))
nbit += 1
print(nbit)
while True:
print(r.recvuntil(b"uit"))
r.sendline(b"s")
# r.sendlineafter(b"uit", b"s")
r.recvuntil(b": ")
pinit = ast.literal_eval(r.recvline().strip().decode())
r.recvuntil(b": ")
qinit = ast.literal_eval(r.recvline().strip().decode())
res = gen_prime(pinit, qinit, nbit)
print(res)
if not res:
r.sendlineafter(b":", f"{bytes_to_long(pinit)},{bytes_to_long(qinit)}".encode())
add_four()
continue
res1, res2 = res
p = bytes_to_long(res1)
q = bytes_to_long(res2)
assert((9 * p * q).bit_length() == 2 * nbit)
r.sendlineafter(b":", f"{p},{q}".encode())
break
r.sendlineafter(b"uit", b"e")
r.recvuntil(b"= ")
c = int(r.recvline().strip())
print(f"{p=}")
print(f"{q=}")
print(f"{c=}")
r.close()
n = p * q
phi = (p - 1) * (q - 1)
d = pow(65537, -1, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
```
### Sobata
```python
def gen_params(nbit):
while True:
p = getPrime(nbit)
if p % 6 == 1:
F = GF(p)
R = [F.random_element() for _ in '01']
a, b = [R[_] ** ((p - 1) // (3 - _)) for _ in [0, 1]]
if a != 1 and b != 1:
c, d = [F.random_element() for _ in '01']
E = EllipticCurve(GF(p), [0, d])
return (p, E, a, b, c)
def walk(P, parameters):
p, E, a, b, c = parameters
x, y = P.xy()
Q = (a * x, b * y)
assert Q in E
return int(c) * E(Q)
def jump(P, n, parameters):
_parameters = list(parameters)
# c = pow(c, n, E.order())
# c^n * E(Q)
_parameters[-1] = pow(int(_parameters[-1]), n, _parameters[1].order())
return walk(P, _parameters)
```
這題是橢圓曲線,首先會生成以些參數,`p`、`E`、`c` 沒什麼好說的,`a` 和 `b` 分別是 `(p-1)/3` 和 `(p-1)/2`。它的 flag 加密的方式是 `walk(P, parameters)`,其中,`walk` 會把 `x` 和 `y` 分別乘上 `a` 和 `b`,很簡單的可以知道它依然會在這個橢圓曲線上,並且把這個點再乘上 `c`。另外,`jump` 是可以先將 `c` 進行特定次方後,再進行 `walk`,其中 `c` 模的是橢圓曲線的階。
首先,用 encrypt 獲得一個點後,就可以生出更多點,然後求出橢圓曲線 `E`。
然後,發現 `jump` 其實是可以讓 `c` 先做 0 次方的,也就是說輸入 `(x, y)` 可以得到 `(a*x, b*y)`,於是我們就能得到 `a` 和 `b`。假如我們有一個 $P$ 點 $(x,y)$,如果我們先把它變成 $(a^2x,by)$ 再進行 `jump` 會發生什麼事呢?它第一個步驟會先把它變成 $(a^3x,b^2y)$,其中,$a^3=1$ 且 $b^2=1$(模 p 下)也就是說他計算的直接就是 $c^kP$。利用這個特性,我們把密文的 `x` 和 `y` 經過這樣的處理後去進行 `jump`,次數是 `-1`,就會得到 $(ax_s,by_s)$。再分別乘上 $a^2$ 和 $b$ 就能求出點了。
```python
from pwn import *
from sage.all import *
from Crypto.Util.number import *
from functools import reduce
import ast
# r = process(["python", "sobata.py"])
def get_enc(rm):
rm.sendlineafter(b"uit", b"e")
rm.recvuntil(b": ")
enc = ast.literal_eval(r.recvline().strip().decode())
return enc
def jump(rm, Q, n):
rm.sendlineafter(b"uit", b"j")
rm.sendlineafter(b": ", f"{Q[0]},{Q[1]}".encode())
rm.sendlineafter(b": ", str(n).encode())
rm.recvuntil(b": ")
res = ast.literal_eval(r.recvline().strip().decode())
return res
data = []
cnt = 0
r = remote("91.107.133.165", 11177)
enc = get_enc(r)
print(enc)
p1 = jump(r, enc, 1)
p2 = jump(r, enc, 2)
p3 = jump(r, enc, 3)
p4 = jump(r, enc, 4)
p5 = jump(r, enc, 5)
b1 = p1[1]**2-p1[0]**3
b2 = p2[1]**2-p2[0]**3
b3 = p3[1]**2-p3[0]**3
b4 = p4[1]**2-p4[0]**3
b5 = p5[1]**2-p5[0]**3
p = reduce(GCD, [b1 - b2, b1 - b3, b1 - b4, b1 - b5])
print("yee")
# assert(isPrime(p))
d = b1 % p
E = EllipticCurve(GF(p), [0, d])
G = tuple(E.random_point().xy())
p0 = jump(r, G, 0)
r3 = p0[0] * pow(G[0], -1, p) % p
r2 = p0[1] * pow(G[1], -1, p) % p
print(r2, r3)
x, y = enc
x, y = x * r3 * r3 % p, y * r2 % p
res = jump(r, (x, y), -1)
x = res[0] * r3 * r3 % p
# x = 118843442403488878017662153140228666578021080047461066797802948818418999679
for i in range(10):
print(long_to_bytes(int(x - i)))
```
### Mancity
```python
#!/usr/bin/env python3
from Crypto.Util.number import *
from flag import flag
def man(n):
B = bin(n)[2:]
M = ''
for b in B:
if b == '0':
M += '01'
else:
M += '11'
return int(M, 2)
def keygen(nbit):
while True:
p = getPrime(nbit)
r = man(p)
B = bin(p)[2:] + '1' * nbit
q = int(B, 2)
if isPrime(q) and isPrime(r):
return q, r
nbit = 256
p, q = keygen(nbit)
m = bytes_to_long(flag)
assert m < n
e, n = 1234567891, p * q
c = pow(m, e, n)
print(f'n = {n}')
print(f'c = {c}')
```
這題直接 z3:
```python
from Crypto.Util.number import *
from z3 import *
n = 147170819334030469053514652921356515888015711942553338463409772437981228515273287953989706666936875524451626901247038180594875568558137526484665015890594045767912340169965961750130156341999306808017498374501001042628249176543370525803456692022546235595791111819909503496986338431136130272043196908119165239297
c = 77151713996168344370880352082934801122524956107256445231326053049976568087412199358725058612262271922128984783428798480191211811217854076875727477848490840660333035334309193217618178091153472265093622822195960145852562781183839474868269109313543427082414220136748700364027714272845969723750108397300867408537
e = 1234567891
# def man(n: BitVec) -> BitVec:
# res = Concat(Extract(0, 0, n), BitVecVal(1, 1))
# for i in range(1, 256):
# b = Extract(i, i, n)
# res = Concat(b, BitVecVal(1, 1), res)
# return res
# p = BitVec("p", 256)
# r = man(p)
# q = BitVecVal(int("1"*256, 2), 256)
# q = Concat(p, q)
# r = ZeroExt(512, r)
# q = ZeroExt(512, q)
# print(q.size())
# print(r.size())
# s = Solver()
# s.add(q * r == n)
# s.check()
# print(s.model()[p].as_long())
def man(n):
B = bin(n)[2:]
M = ''
for b in B:
if b == '0':
M += '01'
else:
M += '11'
return int(M, 2)
p = 97918363698716947075658593730252813578192444725864192759071101318494207364607
r = man(p)
B = bin(p)[2:] + '1' * 256
q = int(B, 2)
print(q * r == n)
phi = (q - 1) * (r - 1)
d = pow(e, -1, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
```
### Matemith
```python
from Crypto.Util.number import *
from flag import flag
l, flag = 14, flag.lstrip(b'CCTF{').rstrip(b'}')
FLAG = [flag[l * i:l * (i + 1)] for i in range(len(flag) // l)]
M = [bytes_to_long(_) for _ in FLAG]
p = getPrime(313)
R.<u, v, w, x, y, z> = PolynomialRing(QQ)
COEFS = [getRandomRange(1, p - 1) for _ in range(21)]
f = COEFS[0] * u * v + COEFS[1] * u + COEFS[2] * v
g = COEFS[3] * u * x * y + COEFS[3] * x + COEFS[5] * y + COEFS[6] * v
h = COEFS[7] * u * w + COEFS[8] * w + COEFS[9] * u
i = COEFS[10] * v * y * z + COEFS[11] * y + COEFS[12] * z + COEFS[13] * w
j = COEFS[14] * v * w + COEFS[15] * v + COEFS[16] * w
k = COEFS[17] * w * z * x + COEFS[18] * z + COEFS[19] * x + COEFS[20] * u
f, g, h, i, j, k = R(f), R(g), R(h), R(i), R(j), R(k)
CNST = [_(M[0], M[1], M[2], M[3], M[4], M[5]) for _ in [f, g, h, i, j, k]]
f, g, h, i, j, k = [[f, g, h, i, j, k][_] + (p - CNST[_]) % p for _ in range(6)]
print(f'{p = }')
print(f'{f = }')
print(f'{g = }')
print(f'{h = }')
print(f'{i = }')
print(f'{j = }')
print(f'{k = }')
```
這題是有一堆多項式,然後他們的根是 flag。
首先是,它給多項式的方式是這樣的,$f_i'=f_i+(p-f_i(M_i))(\text{mod p})$,可以注意到 $(p-f_i(M_i))(\text{mod p})$ 的部份是沒有變數的,而 $f_i$ 部份是沒有常數的,所以我們可以輕易的切割兩者,然後用後者的資訊去列方程式,這樣我們獲得了 6 條方程式,接下來就是去消方程式。從 Maple's blog 偷學了一些 sage 中多項式環的方便技巧,這就可以輕鬆解出來了:
```python
from sage.all import *
from Crypto.Util.number import *
from coppersmith import *
from sage.matrix.matrix2 import Matrix
def resultant(f1, f2, var):
# Copied from https://jsur.in/posts/2021-07-19-google-ctf-2021-crypto-writeups#h1
return Matrix.determinant(f1.sylvester_matrix(f2, var))
p = 9892984422801315119260311427714389408772405421306235794826917610128461644036928139298330716261
f = lambda u,v,w,x,y,z:8593371583346286129538282168765198524220954884352992069219549555526097253129502925759872761483*u*v + 8192555264287905175212103898575474256555217842060435386769432116145712989123062847161390929397*u + 9598573789403814092125115160545174167539204328557118715540593719644188998531033259685435430387*v
g = lambda u,v,w,x,y,z:7737077144206080155196706693824644356475708615710271404071364943161652008584970269394416250641*u*x*y + 6282097687310252658473848438985225466620614743750918909885172321224925965646628839166491648752*v + 7737077144206080155196706693824644356475708615710271404071364943161652008584970269394416250641*x + 3354788147890488743832873565215769634619909759459203496980671578348799162553954862104978291860*y
h = lambda u,v,w,x,y,z:6107224904478508858527197508483774405356161856691777460732363192128980355274418091837270668258*u*w + 3584245173493717638976874408629921683995390608944250077841702023698807664457252845973088744491*u + 5646173287331462026544218972062953582608380797148923127395811758145598594972832047259631339566*w
i = lambda u,v,w,x,y,z:7622670835797214156123791992548663880284352234566921286637648219243086701251627093499322050472*v*y*z + 6026769215097777844835562389865313764490318485655789123763637718591748620654875700763740623760*w + 8145050175261359549200629067766090532616263522561328878195831921153188650784907223634130346224*y + 3622105614070476540808786980829452605696331317022729645355376801209444137548670550164418237117*z
j = lambda u,v,w,x,y,z:1912186465211454827473018892315659311053527670028135595953520151335825509122313783795561869379*v*w + 6246883466276200389231653597272295993565421216541002743075041326054203024921176043191679609212*v + 4002308425802254921531592700910138281674785127934610897914017993007060136199147207365547047048*w
k = lambda u,v,w,x,y,z:1423338294606985951732736428034353751447528399559929388138157330118213387990891693204997290038*w*x*z + 784018806462384388182217012266169299116410899849461442885543245867941419322406775218178098109*u + 7684681843989505989596042520590550892565982707534588920361260899638313817214040416765327284778*x + 4982848574842913858489870338816729222210785430242027484672099513487039514577513464674726403409*z
c1 = 5738603225260621554442220996093767502015758942320213371600986432070445300427944977409453429117
c2 = 2560270290674636359252235177920929027441112715609783111306743340637878970846852799006820932563
c3 = 1994681139685786114971936867358158466232859433926848067961874687630342141141862187589124089741
c4 = 4800360746061605999597274870855047707130861888252519642520437605796496240599924899885487900040
c5 = 973159800079995512996976852328990077106942094656694887771601292254542762394381629810393447820
c6 = 7781690757622738625626304200561818137843970209349935834539461705684625161407233281360563620790
P = PolynomialRing(GF(p), "u,v,w,x,y,z")
u, v, w, x, y, z = P.gens()
p1 = f(u,v,w,x,y,z)+c1
p2 = g(u,v,w,x,y,z)+c2
p3 = h(u,v,w,x,y,z)+c3
p4 = i(u,v,w,x,y,z)+c4
p5 = j(u,v,w,x,y,z)+c5
p6 = k(u,v,w,x,y,z)+c6
# yee = P.remove_var(u)(resultant(p1, p3, u))
# yee = P.remove_var(v)()
yee = resultant(p5, resultant(p1, p3, u), v)
print(P.remove_var(u,v,x,y,z)(yee).roots())
w_val = P.remove_var(u,v,x,y,z)(yee).roots()[1][0]
# w_val = 805420393927477744859034020467169441858066504651770630409897314678880218582728551254748219321
print(w_val)
Pw = w - w_val
yee = P.remove_var(u,w,x,y,z)(resultant(p5, Pw, w))
print(yee.roots())
v_val = yee.roots()[0][0]
print(v_val)
Pv = v - v_val
yee = P.remove_var(v,w,x,y,z)(resultant(p1, Pv, v))
u_val = yee.roots()[0][0]
print(u_val)
Pu = u - u_val
p2 = resultant(resultant(resultant(p2, Pv, v), Pu, u), Pw, w)
p4 = resultant(resultant(resultant(p4, Pv, v), Pu, u), Pw, w)
p6 = resultant(resultant(resultant(p6, Pv, v), Pu, u), Pw, w)
yee = P.remove_var(u,v,w,x,y)(resultant(resultant(p2, p4, x), resultant(p2, p6, x), y))
z_val = yee.roots()[1][0]
Pz = z - z_val
yee = P.remove_var(u,v,w,x,z)(resultant(resultant(p2, p4, x), Pz, z))
y_val = yee.roots()[0][0]
Py = y - y_val
print(y_val)
yee = P.remove_var(u,v,w,y,z)(resultant(resultant(p2, p4, y), Pz, z))
x_val = yee.roots()[0][0]
print(x_val)
flags = list(map(int, [u_val, v_val, w_val, x_val, y_val, z_val]))
flag = "".join([long_to_bytes(i).decode() for i in flags])
print("CCTF{" + flag + "}")
```
## Tough Cookie
### Toffee
```python
def sign(msg, skey):
global k
h = bytes_to_long(sha512(msg).digest())
k = toffee(u, v, k)
P = k * G
r = int(P.xy()[0]) % _n
s = inverse(k, _n) * (h + r * skey) % _n
return (r, s)
def toffee(u, v, k):
return (u * k + v) % _n
```
這題太簡單了,感覺不該放在這裡。它是一個 $k$ 有線性關係的 DSA,所以可以很簡單的計算出 $sk$:
```python
from pwn import *
from sage.all import *
from Crypto.Util.number import *
from hashlib import sha512
from sage.matrix.matrix2 import Matrix
def resultant(f1, f2, var):
# Copied from https://jsur.in/posts/2021-07-19-google-ctf-2021-crypto-writeups#h1
return Matrix.determinant(f1.sylvester_matrix(f2, var))
r = remote("91.107.188.9", 31111)
r.sendlineafter(b"uit", b"g")
r.sendlineafter(b":", b"0")
r.recvuntil(b"= ")
v = int(r.recvline().strip().decode())
r.sendlineafter(b"uit", b"g")
r.sendlineafter(b":", b"1")
r.recvuntil(b"= ")
u = int(r.recvline().strip().decode()) - v
def sign(msg: bytes):
r.sendlineafter(b"uit", b"s")
r.sendlineafter(b":", msg)
r.recvuntil(b"= ")
rr = int(r.recvline().strip().decode())
r.recvuntil(b"= ")
s = int(r.recvline().strip().decode())
return (rr, s)
p = 0xaeaf714c13bfbff63dd6c4f07dd366674ebe93f6ec6ea51ac8584d9982c41882ebea6f6e7b0e959d2c36ba5e27705daffacd9a49b39d5beedc74976b30a260c9
a, b = -7, 0xd3f1356a42265cb4aec98a80b713fb724f44e747fe73d907bdc598557e0d96c5
n = 0xaeaf714c13bfbff63dd6c4f07dd366674ebe93f6ec6ea51ac8584d9982c41881d942f0dddae61b0641e2a2cf144534c42bf8a9c3cb7bdc2a4392fcb2cc01ef87
x = 0xa0e29c8968e02582d98219ce07dd043270b27e06568cb309131701b3b61c5c374d0dda5ad341baa9d533c17c8a8227df3f7e613447f01e17abbc2645fe5465b0
y = 0x5ee57d33874773dd18f22f9a81b615976a9687222c392801ed9ad96aa6ed364e973edda16c6a3b64760ca74390bb44088bf7156595f5b39bfee3c5cef31c45e1
F = FiniteField(p)
E = EllipticCurve(F, [a, b])
r1, s1 = sign(b"yeee")
r2, s2 = sign(b"yeee")
h1 = bytes_to_long(sha512(b"yeee").digest())
h2 = h1
P = PolynomialRing(Zmod(n), "k1,k2,sk")
k1,k2,sk = P.gens()
f = k1 * s1 - (h1 + r1 * sk)
g = k2 * s2 - (h2 + r2 * sk)
h = k2 - (u * k1 + v)
f = resultant(f, h, k2)
g = resultant(g, h, k2)
f = P.remove_var(k1,k2)(resultant(f, g, k1))
flag = f.roots()[0][0]
print(long_to_bytes(int(flag)))
```