### lalala ``Source.py`` ```python from random import randint from re import search flag = "bi0sctf{ %s }" % f"{randint(2**39, 2**40):x}" p = random_prime(2**1024) unknowns = [randint(0, 2**32) for _ in range(10)] unknowns = [f + i - (i%1000) for i, f in zip(unknowns, search("{(.*)}", flag).group(1).encode())] output = [] for _ in range(100): aa = [randint(0, 2**1024) for _ in range(1000)] bb = [randint(0, 9) for _ in range(1000)] cc = [randint(0, 9) for _ in range(1000)] output.append(aa) output.append(bb) output.append(cc) output.append(sum([a + unknowns[b]^2 * unknowns[c]^3 for a, b, c in zip(aa, bb, cc)]) % p) print(f"{p = }") print(f"{output = }") ``` Bài này flag được ẩn trong **unknowns** bao gồm 10 giá trị với 100 vòng for : Ta hãy để ý ``` output.append(sum([a + unknowns[b]^2 * unknowns[c]^3 for a, b, c in zip(aa, bb, cc)]) % p) ``` Ở vòng for đầu tiên ta sẽ có coefficient $coeff_i$ là: $$coeff_0 * unknown_{b0}^2 * unknown_{c0}^3 + coeff_0 * unknown_{b1}^2 * unknown_{c1}^3 + coeff_0 * unknown_{b2}^2 * unknown_{c2}^3 + ... + coeff_0 * unknown_{b9}^2 * unknown_{c9}^3 + sum(aa_0) = output_0 $$ Với $b, c \in [0,9]$ Ta sẽ dựng ma trận như sau: $$ \begin{equation*} \begin{bmatrix} coeff_0.b_0.c_0 & coeff_0.b_0.c_1 & ... & coeff_0.b_9.c_9 \\ coeff_1.b_0.c_0 & coeff_1.b_0.c_1 & ... & coeff_1.b_9.c_9 \\ \vdots & \vdots & & \vdots \\ coeff_{99}.b_0.c_0 & coeff_{99}.b_0.c_1 & ... & coeff_{99}.b_9.c_9 \\ \end{bmatrix} = \begin{bmatrix} output_0 - sum(aa_0) \\ output_1 - sum(aa_1) \\ \vdots \\ output_{99} - sum(aa_{99}) \end{bmatrix} \end{equation*} $$ Giải ma trận bằng sage ta thu được 100 nghiệm của $unknown_0^2 * unknown_0^3$ đến $unknown_9^2 * unknown_9^3$ Thu được 10 giá trị của $unknown_0^5$ đến $unknown_9^5$ ta chỉ cần căn bậc 5 của $unknown$ và chia dư cho 1000 để khôi phục lại flag. ``solved.py`` ```python from sage.all import * p = ... output = ... m = [] y = [] vars = [[i, j] for i in range(10) for j in range(10)] for i in range(0, len(output), 4): aa = output[i] bb = output[i+1] cc = output[i+2] rs = output[i+3] coeffs = [0]*100 # sum([aa[i] + unknowns[bb[i]]^2 + unknowns[cc[i]]^3 for i in range(1000)]) sum = 0 for j in range(1000): sum = (sum + aa[j]) % p temp = [bb[j], cc[j]] coeffs[vars.index(temp)] += 1 y.append((rs-sum) % p) row = coeffs m.append(row) l = len(m) m = Matrix(GF(p), m) y = vector(GF(p), y) ans = list(m.solve_right(y)) print(ans) flag = [] for i in range(100): u = gmpy2.iroot(int(ans[i]), 5) print(u) if u[1] == True: u = u[0] % 1000 flag.append(u) print(u) print(flag) print("".join([bytes([c]).decode() for c in flag])) ``` ### challengename ``chall.py`` ```python from ecdsa.ecdsa import Public_key, Private_key from ecdsa import ellipticcurve from hashlib import md5 import random import os import json flag = open("flag", "rb").read()[:-1] magic = os.urandom(16) p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff a = ###REDACTED### b = ###REDACTED### G = ###REDACTED### q = G.order() def bigsur(a,b): a,b = [[a,b],[b,a]][len(a) < len(b)] return bytes([i ^ j for i,j in zip(a,bytes([int(bin(int(b.hex(),16))[2:].zfill(len(f'{int(a.hex(), 16):b}'))[:len(a) - len(b)] + bin(int(b.hex(),16))[2:].zfill(len(bin(int(a.hex(), 16))[2:]))[:len(bin(int(a.hex(), 16))[2:]) - len(bin(int(b.hex(), 16))[2:])][i:i+8], 2) for i in range(0,len(bin(int(a.hex(), 16))[2:]) - len(bin(int(b.hex(), 16))[2:]),8)]) + b)]) def bytes_to_long(s): return int.from_bytes(s, 'big') def genkeys(): d = random.randint(1,q-1) pubkey = Public_key(G, d*G) return pubkey, Private_key(pubkey, d) def sign(msg,nonce,privkey): hsh = md5(msg).digest() nunce = md5(bigsur(nonce,magic)).digest() sig = privkey.sign(bytes_to_long(hsh), bytes_to_long(nunce)) return json.dumps({"msg": msg.hex(), "r": hex(sig.r), "s": hex(sig.s)}) def enc(privkey): x = int(flag.hex(),16) y = pow((x**3 + a*x + b) % p, (p+3)//4, p) F = ellipticcurve.Point('--REDACTED--', x, y) Q = F * privkey.secret_multiplier return (int(Q.x()), int(Q.y())) pubkey, privkey = genkeys() print("Public key:",(int(pubkey.point.x()),int(pubkey.point.y()))) print("Encrypted flag:",enc(privkey)) # Public key: (99122053878685444817852582103585646482441799670468212049632161370423019963573, 49681263796445807694244738028189208770171168855624587289690892802435841601423) # Encrypted flag: (22455982735997721923198309515515820680837002550923840212531066823876108860098, 49955453626898315794129063911602706078234097783588068635922441060010795905908) nonces = set() for _ in '01': try: msg = bytes.fromhex(input("Message: ")) nonce = bytes.fromhex(input("Nonce: ")) if nonce in nonces: print("Nonce already used") continue nonces.add(nonce) print(sign(msg,nonce,privkey)) except ValueError: print("No hex?") exit() ``` Chall này tôi nhận được 2 điểm từ đường cong: Public key là **dG** và Encrypt flag là **dF** Từ 2 điểm trên ta có $$y_1^2 = x_1^3 + ax_1 + b$$ $$y_2^2 = x_2^3 + ax_2 + b$$ $$(y_1^2 - y_2^2) - (x_1^3 - x_2^3) = a(x_1-x_2)$$ Ta có a, b được tính bằng ``` sage: p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff ....: x1 = 5683931425003308547431366441507256115275884385439908235960128031931809224426 ....: y1 = 103504881232349341101391567415775837049449360982146318680463741565720272773736 ....: x2= 87512180974071789579460785103236717308728056532898014966250203004749159040100 ....: y2 = 5463487042701233117805115066761912607366330124308643383693672326808102345649 ....: a=Mod(((y1^2 - y2^2)-(x1^3 - x2^3))*inverse_mod((x1-x2),p),p) ....: b=Mod(y2^2 - x2^3 - a*x2, p) ....: a, b (115792089210356248762697446949407573530086143415290314195533631308867097853948, 41058363725152142129326129780047268409114441015993725554835256314039467401291) sage: ``` Ta có curve của bài ``` sage: p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff ....: a = 115792089210356248762697446949407573530086143415290314195533631308867097853948 ....: b = 41058363725152142129326129780047268409114441015993725554835256314039467401291 ....: E = EllipticCurve(GF(p), [a, b]) ....: E.order() 115792089210356248762697446949407573529996955224135760342422259061068512044369 sage: ``` Ở đây hàm **bigsur** trông rất dài nhưng thực chất nó là phép xor. Nếu chúng ta chọ **nonce1 = b"\x00"** và **nonce2 = b"\x00\x00"** thì sẽ có thể lấy cùng một **nunce** trong hàm **sign()** mà server ký message Để tìm lại private key **d** tôi sử dụng [️ECDSA Nonce Reuse Attack](https://crypto.stackexchange.com/questions/71764/is-it-safe-to-reuse-a-ecdsa-nonce-for-two-signatures-if-the-public-keys-are-diff) Chúng ta có **r1 = r2 = R**, **(r1, s1)** là chữ ký của $m_1$, **(r2, s2)** là chữ ký của $m_2$ khi đó chúng ta có $$s_1 * k - H(m_1) = s_2 * H(m_2) = R * privatekey$$ $$k = \frac{s_1 - s_2}{H(m_1) - H(m_2)}$$ $$privatekey = \frac{s_1 * k - H(m_1)}{R}$$ Khi có **privatekey** ta dễ dàng tìm lại **F** bằng phép tính $F = private^ {-1} * Q$ ``solved.sage`` ```python from hashlib import md5 from Crypto.Util.number import * from sage.all import * def inverse_mod(a, m): g, x, y = egcd(a, m) if g != 1: raise Exception('Modular inverse does not exist') else: return x % m def egcd(a, b): if a == 0: return (b, 0, 1) else: g, y, x = egcd(b % a, a) return (g, x - (b // a) * y, y) def recover_private_key(H_m1, H_m2, r, s1, s2, q): H_m1_int = bytes_to_long(md5(H_m1).digest()) H_m2_int = bytes_to_long(md5(H_m2).digest()) r_inv = inverse_mod(r, q) d = ((inverse_mod(s1 - s2, q) * (H_m1_int * s2 - H_m2_int * s1) % q) * r_inv) % q return d # vanluongkma@Nitro:~$ nc 13.201.224.182 30773 x1, y2 = (5683931425003308547431366441507256115275884385439908235960128031931809224426, 103504881232349341101391567415775837049449360982146318680463741565720272773736) x2, y2 = (87512180974071789579460785103236717308728056532898014966250203004749159040100, 5463487042701233117805115066761912607366330124308643383693672326808102345649) # Message: 4b435343 # Nonce: 00 # {"msg": "4b435343", "r": "0x634c264ed704268912a6770587a38659be6b14c02276b7b8a357663aa4b807e", "s": "0xee0223546119ce0300f129d9b3df224805b58eb2d6974ff43f6ba9cad1b774cb"} # Message: 4b435343435446 # Nonce: 0000 # {"msg": "4b435343435446", "r": "0x634c264ed704268912a6770587a38659be6b14c02276b7b8a357663aa4b807e", "s": "0xab628f493d21b15f1ae5626f3c08e6533942ffd558d62d9f3470765119ab4b04"} p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff a = 115792089210356248762697446949407573530086143415290314195533631308867097853948 b = 41058363725152142129326129780047268409114441015993725554835256314039467401291 E = EllipticCurve(GF(p), [a, b]) G = E(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5) E.set_order(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 * 0x1) n = int(E.order()) H_m1 = b"KCSC" H_m2 = b"KCSCCTF" r = 0x634c264ed704268912a6770587a38659be6b14c02276b7b8a357663aa4b807e s1 = 0xee0223546119ce0300f129d9b3df224805b58eb2d6974ff43f6ba9cad1b774cb s2 = 0xab628f493d21b15f1ae5626f3c08e6533942ffd558d62d9f3470765119ab4b04 q = int(E.order()) d = recover_private_key(H_m1, H_m2, r, s1, s2, q) print(d) d_inverse=inverse_mod(d,n) Q = E(x2, y2) F = d_inverse*Q print(long_to_bytes(int(F[0]))) ``` ### rr ``chall.py`` ```python from Crypto.Util.number import * from FLAG import flag from random import randint flag = bytes_to_long(flag) n = .. rr = .. ks = [randint(0, rr**(i+1)) for i in range(20)] c1 = pow(sum(k*flag**i for i, k in enumerate(ks)), (1<<7)-1, n) c2 = pow(flag, (1<<16)+1, n) ks = [pow(69, k, rr**(i+2)) for i, k in enumerate(ks)] print(f"{ks = }") print(f"{c1 = }") print(f"{c2 = }") ``` Challenge cho chúng ta 2 bản mã $c_1, c_2$ của **flag** $$ \begin{cases} c_1 = (\sum_{i=1}^{20} k_i flag^i)^{127} \mod n \\ c_2 = flag^{65537} \mod n \\ \end{cases} $$ Những hệ số $k_1, k_2, k_3, ... , k_{20}$ đã biết, chúng ta cần xây dựng đa thức $$f(x) = (\sum_{i=1}^{20} k_i x^i)^{127} - c_1$$ $$g(x) = x^{65537} - c_2$$ Chúng ta có thể tìm GCD của 2 đa thức **f(x), g(x)** để recover lại flag Nhưng mà hệ số $k_i$ cũng đã được mã hóa theo phương trình sau: $$K_i = 69 ^ {k_i} \mod p^{i+1}, k_i < p^{i}, i \in [1...20]$$ Với i = 0 $$K_1 = 69^{k_1} \mod p^2, k_1 < p$$ Nâng cả 2 vế lũy thừa **q** $$K_1^q = (69^q)^{k_1} \mod p^2$$ Với định lý fermat nhỏ( $a^{\phi(p)} = p * k + 1$) ta có : $$\underbrace{(c_1 * p + 1)}_{K_1^q \mod p^2} = {\underbrace{(d_1 * p + 1)}_{69^q \mod p^2}}^{k_1} \mod p^2$$ Khai triển [Binomial theorem](https://en.wikipedia.org/wiki/Binomial_theorem) cho vế phải ta có $$c_1 * p + 1 = \binom{k_1}{0} 1^n (d_1 * p)^0 + \binom{k_1}{1}1^{n-1}(d_1p)^1 \underbrace{ + \cdots }_{0 \mod p^2} \mod p^2$$ Thu được $$c_1p + 1 = 1 + d_1k_1p \mod p^2$$ $$\implies c_1 = d_1 * k_1 \mod p$$ $$\implies k_1 = c_1 * d_1 ^ {-1} \mod p$$ Ta đã giải quyết được discrete logarithm trên $Z_{p^2}$ tiếp theo là $Z_{p^r}$ $$y = g^x \mod p^{r+2}, \ x < p^{(r+1)}$$ Đặt $x = x_r * p^r + ... + x_1 * p^1 + x_0 = p - 1$ ta khai triển như sau $$y^q = (g^q)^x \mod p^{r+2}$$ Tương tự như trên thu được $$=> c_{1} = d_1 * x{1} \mod \ p$$ $$=> x_{1} = c_{1} * d_{1}^{-1} \mod \ p$$ Như vậy ta đã recover được $x_0$, biến đổi phương trình như sau $$y^q = (g^q)^x \mod p^{r+2}$$ $$y^q = (g^q)^{({x_{r} \ p^{r} + … + x_{1} \ p + x_{0}})} \mod p^{r+2}$$ $$(y \ g^{-x_{0}})^q = (g^q)^{p \ ({x_{r} \ p^{r} + … + x_{2} \ p + x_{1}})} \mod p^{r+2}$$ đặt: $y’ = (y \ g^{-x_{0}})$ và $g’ = g^p$ Tương tự như trên ta sẽ thu được $x_1$. Lặp lại đến khi $g^{p^{r+1} \ q} = 1 \mod p^{r+2}$, ta sẽ thu được x hoàn chỉnh. ``solved_1.py`` ```python from data import ks, c1, c2, rr, n from Crypto.Util.number import * def binomial_dlog_sub(y,g,p,q,r=2): C = ZZ(pow(y, q, p^r)) B = ZZ(pow(g, q, p^r)) kx = (C - 1)//p^(r-1) k = (B - 1)//p^(r-1) x = ZZ(kx * pow(k, -1, p) % p) return x def binomial_dlog(y,g,p,q,r): assert r >= 2 y = ZZ(y) g = ZZ(g) xs = [] for i in range(r-1): xi = binomial_dlog_sub(y, g, p, q, i + 2) xs.append(xi) y = ZZ(y * pow(g,-xi,p^r) % p^r) g = ZZ(pow(g,p,p^r)) return ZZ(xs, p) def pgcd(g1, g2): while g2: g1, g2 = g2, g1 % g2 return g1.monic() p = rr q = rr - 1 coeffs = [] g = 69 for rd in range(20): y = ks[rd] r = rd + 2 x = binomial_dlog(y,g,p,q,r) print(f"[+] rounds {rd} check ", ZZ(pow(g, x, p^r)) == y) coeffs.append(x) PR.<x> = PolynomialRing(Zmod(n)) f1 = (sum(k*x**i for i, k in enumerate(coeffs)))^((1<<7)-1) - c1 f2 = x^(65537) - c2 f0 = pgcd(f1,f2) print(long_to_bytes(int(- f0.constant_coefficient() % n))) ``` Cách khác ta sẽ recover lại $k_i$ bằng bài toán trên trường số p-adic ``solved_2.py`` ```python import sys sys.path.append("/mnt/c/Users/dinhv/Documents/Tool/crypto-attacks/") from sage.all import * from data import c1,c2,ks,n,rr from Crypto.Util.number import * from shared.polynomial import fast_polynomial_gcd def dlog_on_Zp(p, exponent_of_p, g, y): Z = Zp(p, prec=exponent_of_p) x_Z = Z(y).log() / Z(g).log() x = x_Z.lift() return x sol = [] for i,k in enumerate(ks): tmp = dlog_on_Zp(rr, i+2, 69, k) sol.append(tmp) print("Found", tmp) P = PolynomialRing(Zmod(n), names='x') (x,) = P.gens() g1 = sum(k*x**i for i, k in enumerate(sol))**((1<<7)-1) - c1 g2 = x**65537 - c2 f = fast_polynomial_gcd(g1,g2) print(long_to_bytes(int(f.small_roots()[0]))) ``` #### Reference [1] [crypto-attack](https://github.com/jvdsn/crypto-attacks/blob/master/shared/polynomial.py) [2] Thanks [nomorecaffeine](https://github.com/nguyen-xuan-quoc) [3] Thanks [tl2cents](https://tanglee.top/2024/02/27/bi0sCTF-2024-Crypto-Writeups/) ### daisy_bel ``chall.py`` ```python from Crypto.Util.number import * from FLAG import flag p = getPrime(1024) q = getPrime(1024) n = p*q c = pow(bytes_to_long(flag), 65537, n) print(f"{n = }") print(f"{c = }") print(f"{p>>545 = }") print(f"{pow(q, -1, p) % 2**955 = }") """ n = 13588728652719624755959883276683763133718032506385075564663911572182122683301137849695983901955409352570565954387309667773401321714456342417045969608223003274884588192404087467681912193490842964059556524020070120310323930195454952260589778875740130941386109889075203869687321923491643408665507068588775784988078288075734265698139186318796736818313573197531378070122258446846208696332202140441601055183195303569747035132295102566133393090514109468599210157777972423137199252708312341156832737997619441957665736148319038440282486060886586224131974679312528053652031230440066166198113855072834035367567388441662394921517 c = 7060838742565811829053558838657804279560845154018091084158194272242803343929257245220709122923033772911542382343773476464462744720309804214665483545776864536554160598105614284148492704321209780195710704395654076907393829026429576058565918764797151566768444714762765178980092544794628672937881382544636805227077720169176946129920142293086900071813356620614543192022828873063643117868270870962617888384354361974190741650616048081060091900625145189833527870538922263654770794491259583457490475874562534779132633901804342550348074225239826562480855270209799871618945586788242205776542517623475113537574232969491066289349 p>>545 = 914008410449727213564879221428424249291351166169082040257173225209300987827116859791069006794049057028194309080727806930559540622366140212043574 pow(q, -1, p) % 2**955 = 233711553660002890828408402929574055694919789676036615130193612611783600781851865414087175789069599573385415793271613481055557735270487304894489126945877209821010875514064660591650207399293638328583774864637538897214896592130226433845320032466980448406433179399820207629371214346685408858 """ ``` Với bài này ta có $$u=q^{-1} mod p$$ $$uq = 1 + kp$$ $$uq^2 = q + kpq$$ $$uq^2 - q = 0 mod n$$ Chúng ta có 545 high bits của p ($p_h$) và 955 low bits của ($q_l^{-1}$) = $q^{-1} mod (p)$ Từ thông tin trên chúng ta có $f(x,y) = (u_h * 2^{955} + u_l) * (q_h * 2^{545} + q_l)^2 - (q_h * 2^{545} + q_l) = 0 \ mod \ (n)$ Chúng ta sẽ sử dụng thuật toán coppersmith để tìm ($q_l, u_h$) Ở đây ta sẽ dùng [**coppersmith multivariate heuristic**](https://github.com/kionactf/coppersmith/blob/main/coppersmith_multivariate_heuristic.py) để giải quyết bài toán này. ``solved.sage`` ```python from coppersmith_multivariate_heuristic import coppersmith_multivariate_heuristic from lll import * from sage.all import * from Crypto.Util.number import * import sys sys.path.append("/coppersmith") n = 13588728652719624755959883276683763133718032506385075564663911572182122683301137849695983901955409352570565954387309667773401321714456342417045969608223003274884588192404087467681912193490842964059556524020070120310323930195454952260589778875740130941386109889075203869687321923491643408665507068588775784988078288075734265698139186318796736818313573197531378070122258446846208696332202140441601055183195303569747035132295102566133393090514109468599210157777972423137199252708312341156832737997619441957665736148319038440282486060886586224131974679312528053652031230440066166198113855072834035367567388441662394921517 c = 7060838742565811829053558838657804279560845154018091084158194272242803343929257245220709122923033772911542382343773476464462744720309804214665483545776864536554160598105614284148492704321209780195710704395654076907393829026429576058565918764797151566768444714762765178980092544794628672937881382544636805227077720169176946129920142293086900071813356620614543192022828873063643117868270870962617888384354361974190741650616048081060091900625145189833527870538922263654770794491259583457490475874562534779132633901804342550348074225239826562480855270209799871618945586788242205776542517623475113537574232969491066289349 msb_p = 914008410449727213564879221428424249291351166169082040257173225209300987827116859791069006794049057028194309080727806930559540622366140212043574 lsb_u = 233711553660002890828408402929574055694919789676036615130193612611783600781851865414087175789069599573385415793271613481055557735270487304894489126945877209821010875514064660591650207399293638328583774864637538897214896592130226433845320032466980448406433179399820207629371214346685408858 msb_q = (n // (msb_p << 545)) x, y = PolynomialRing(Zmod(n), "x, y").gens() f = (y * 2**955 + lsb_u) * (msb_q + x)**2 + (msb_q + x) ans = coppersmith_multivariate_heuristic(f, [2**545, 2**69], beta = 1) lsb_q, msb_u = ans[0] qq = msb_q + x q = int(qq(lsb_q)) p = n // q d = pow(65537, -1, (p - 1) * (q - 1)) m = pow(c, d, n) print(long_to_bytes(m)) ```