## Basic LLL > Simple crypto is the best crypto. > > Author: S1mple > basic-lll.sage ``` def generate(): p = random_prime(2^1024, lbound=2^1023) x=randint(1,2^16) y=randint(1,2^256) a=randint(2^1023,2^1024) q=random_prime(2^1024) n=p*q return x,a,y,n,p x,a,y,n,p = generate() k = x * y + a * p e=65537 print(f"x = {x}") print(f"a = {a}") print(f"n = {n}") print(f"k = {k}") m = b'L3AK{<Redacted>}' flag = int.from_bytes(m, byteorder='big') c= pow(flag, e, n) print(f"c = {c}") ''' x = 54203 a = 139534605978199350449870348663594126359773246906906418074945064315708552206952695156472923968554408862426942537522569163756593332601739006413404986641247624386522169136633429464195370373009454673819688653512479919153332504769835621608305089536245284458011218876474599059184828911301976396971466368457267831713 n = 12909957208634846878337953184362917609451224905637563117148705894888627434882610771803126452504238664471840340722310690445704139825753660053450331966698205860077330083433391290469454571152366284661640391190008258576947840075212180965738595761925516686689797153224716140447515370184846067654512660266993573880775530634588475842083212670090415716860925772115834314563453955681012820960922892736520042799257599331942717963921797157341454739255402633419216921702659541513141028779948257696746810146033667942181244847983610429227387863821351416689099862418820999250005071861968501333899759899513283613946626413863922604073 k = 24474689179117620559916890529357882261493825442019850679598519081287156822984032786458479363048845076078220151760752906879055457682971398809768604333650029141164831566127754715775782823279839766009120238777348170982471623193652714921064243946655726118484337862412275391615166714375745390409664610412156281691721978732319253694004232933156865189917761521085635692596755802274763409871937618659197646864593743015558828475450200247766980008744319676783526158213931581034209356092026748307730083927225249093712227456855972520574747646873074625455900058136458828591335711677741591552501530047335481073272381631524755666119 c = 11185314040721202177044508537272244264288033276739579716599246665772965854249656943282002695659011960313245796587834222078633141747802754149848079632693280265262199729548775879612614113828267471629389698999657686858047585254549801752634049341009476489652456620836030696102393122618822021082792763848220677651608135328630551380537642144416978955966827336280510774254681264136102268730343853559751471313539810499170669215479225898738527316798768622089152851154959800113070358637984124299357803777453137311143202502153552192970732744885328421213081964363890280109214401691255867427694709196120824176729643585687319321473 ''' ``` * We can solve this challenge without using LLL just by simple mathematics. * We have: $$ \begin{aligned} &x \in {(1, 2^{16})},\ y \in {(1, 2^{256})} \Leftrightarrow xy \in (1, 2^{272}) \\ & while\ a \in (2^{1023}, 2^{1024}) \\ \Leftrightarrow \ &\frac{xy}{a} < \frac{2^{272}}{2^{1024}} < 1 \Leftrightarrow \left\lfloor \frac{xy}{a} \right\rfloor = 0 \end{aligned} $$ * From the problem, we have: $$ \begin{aligned} &k = xy \ +ap \\ \Leftrightarrow \ &\frac{k}{a} = \frac{xy}{a} + p \ (k,\ a, \ x, \ y \in \mathbb{N}) \\ \Leftrightarrow \ &\left\lfloor \frac{k}{a} \right\rfloor = \left\lfloor \frac{xy}{a} \right\rfloor + p \\ \Leftrightarrow \ &p = \left\lfloor \frac{k}{a} \right\rfloor \end{aligned} $$ * Then we can decrypt the message Code: ``` from Crypto.Util.number import long_to_bytes k = ... a = ... n = ... c = ... e = ... p = k // a q = n // p phi = (p-1) * (q-1) d = pow(e, -1, phi) print(long_to_bytes(pow(c, d, n))) Flag: L3AK{u_4ctu4lly_pwn3d_LLL_w1th_sh0rt_v3ct0rs_n1c3} ``` ## Lowkey RSA ![image](https://hackmd.io/_uploads/Syz7JlS8ge.png) ``` M = matrix([[x,1,0,0], [a,0,1,0], [-k,0,0,1]]) a = M.LLL() for i in a: if i[-1] == 1: y = i[1] p = i[2] q = n // p phi = (p - 1) * (q - 1) e = 65537 d = pow(e, -1, phi) m = pow(c, d, n) flag = m.to_bytes((m.bit_length() + 7) // 8, byteorder='big') print(f"flag = {flag.decode()}") ``` > This RSA might lowkey be insecure, no cap fr fr. > > Authors: Black, White, Suvoni lowkey-rsa.py ``` from Crypto.Util.number import * def gen_primes(SIZE): p = random_prime(1 << (SIZE - 1), 1 << SIZE) while True: q = random_prime(1 << (SIZE - 1), 1 << SIZE) if p < q: p, q = q, p if q < p < 2*q: break return p, q nbits = 1024 flag = b"L3AK{<REDACTED>}" R = RealField(nbits) p, q = gen_primes(nbits//2) N = p*q phi = (p**4-1)*(q**4-1) N_s = R(N**2) N_ss = R(N**4) t = (2*N_ss-49*N_s + 2)/(4*N+170*N_s) while True: d = randint(1, round(sqrt(t)) - 1) if gcd(phi-d, phi) == 1: break e = inverse_mod(phi-d, phi) c = pow(bytes_to_long(flag), e, N) print(f"e = {e}\nN = {N}\nc = {c}") ''' e = 370641246943654763647982436393968410523035056803076571952063446221981054741105804986870907803130391736840420704227524827167178043545763070520011423497365360567040835216714988776285676818833967899487393611410406708467049153990487431775151667103817558875154145780446157545062795321820537740212495675608976163877567007753523774447008611976905578477758365862741282887079873779055623972564793977884741350325450634869927603664722967323341473363320613467433998603537242156610765948379449307405122629600556105209482040761323271134932553579828576227233549741862990693111061962892676568398083446001891012661453694340879845386900986024512140441823076068075531089610607812090402852586184229193699718454197060072575595570232588935191272416546819793045623275550409871218062357273309812154110783534714662063322116568964675372602159108306251453500390105034890229052958512010283429459687714879084097494098542745605324460172680461006303552579466987732938596341830436505942616479890554056163452471835707573885837976471753073413505028206370632139586750855217201926605743452826397576584492732225029497982216694648573014796836126574081158869231364821712046050068243878660143909750030922147254462228826952501087389154612318844202411291844150163167021 N = 10222014062768125922601962004686361136447658578111413896046596746110249358112354000488449664371774177977274016313103826803116662735101208575040021998413602496525815373151213550295992813258424882626853824039678993334143891154760939712139640336395595628492284893024078520796288685014103193630287988814952604029 c = 4323184196594280140760873888779221921406692838206184372853784052006066772207175604399047711017170078453742445880600303200397632746051500194774569530024097959399922254605516672962219900174336028512116159752401576376530557036245218800162889461620882177398454137759956927838320086276276377067055171421259852996 ''' ``` * We notice that phi is kinda sus ~ $N^4$, I applied the Wiener's Attack Code: ``` from sage.all import * from Crypto.Util.number import * def wiener(c, e, n, N): # Convert e/n into a continued fraction cf = continued_fraction(QQ(e)/QQ(n)) convergents = cf.convergents() for kd in convergents: k = kd.numerator() d = kd.denominator() try: if (e * d + 1) % k == 0: m = int(pow(c, -d, N)) temp = long_to_bytes(m) if b'L3AK' in temp: print(temp) except Exception: continue e = ... c = ... N = ... wiener(c, e, N**4, N) ``` ## Shiro Hero Welp I managed to find the flag after the contest ended. ecc.py ``` #!/usr/bin/env python3 import random from hashlib import sha3_256, sha256 from Crypto.Util.number import bytes_to_long, inverse from Crypto.Cipher import AES from Crypto.Util.Padding import unpad, pad from prng import xorshiro256, MASK64 import hashlib import os class ECDSA: """ECDSA implementation for secp256k1 curve""" # parameters p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F a = 0 b = 7 Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 G = (Gx, Gy) @staticmethod def digest(msg: bytes) -> int: """Hash a message and return as integer""" return bytes_to_long(sha256(msg).digest()) @staticmethod def point_add(P, Q): """Add two points on the elliptic curve""" if P == (None, None): return Q if Q == (None, None): return P (x1, y1), (x2, y2) = P, Q if x1 == x2 and (y1 + y2) % ECDSA.p == 0: return (None, None) if P == Q: l = (3 * x1 * x1) * inverse(2 * y1, ECDSA.p) % ECDSA.p else: l = (y2 - y1) * inverse(x2 - x1, ECDSA.p) % ECDSA.p x3 = (l * l - x1 - x2) % ECDSA.p y3 = (l * (x1 - x3) - y1) % ECDSA.p return (x3, y3) @staticmethod def scalar_mult(k, P): R = (None, None) while k: if k & 1: R = ECDSA.point_add(R, P) P = ECDSA.point_add(P, P) k >>= 1 return R @staticmethod def gen_keypair(): d = random.randint(1, ECDSA.n - 1) Q = ECDSA.scalar_mult(d, ECDSA.G) return d, Q @staticmethod def ecdsa_sign(h: int, d: int, prng: xorshiro256): while True: k = prng() % ECDSA.n if not k: continue x, _ = ECDSA.scalar_mult(k, ECDSA.G) if x is None: continue r = x % ECDSA.n if not r: continue s = (inverse(k, ECDSA.n) * (h + r * d)) % ECDSA.n if s: return r, s @staticmethod def ecdsa_verify(h, Q, sig): r, s = sig if not (1 <= r < ECDSA.n and 1 <= s < ECDSA.n): return False w = inverse(s, ECDSA.n) u1 = (h * w) % ECDSA.n u2 = (r * w) % ECDSA.n x, _ = ECDSA.point_add(ECDSA.scalar_mult(u1, ECDSA.G), ECDSA.scalar_mult(u2, Q)) if x is None: return False return (x % ECDSA.n) == r ``` prng.py ``` #!/usr/bin/python3 from Crypto.Util.number import bytes_to_long, inverse MASK64 = (1 << 64) - 1 def _rotl(x: int, k: int) -> int: return ((x << k) | (x >> (64 - k))) & MASK64 class xorshiro256: def __init__(self, seed): if len(seed) != 4: raise ValueError("seed must have four 64-bit words") self.s = [w & MASK64 for w in seed] @staticmethod def _temper(s1: int) -> int: return (_rotl((s1 * 5) & MASK64, 7) * 9) & MASK64 def next_raw(self) -> int: s0, s1, s2, s3 = self.s t = (s1 << 17) & MASK64 s2 ^= s0 s3 ^= s1 s1 ^= s2 s0 ^= s3 s2 ^= t s3 = _rotl(s3, 45) self.s = [s0, s1, s2, s3] return s1 def randrange(self, start, stop, inclusive=False): if inclusive: return start + self.next_raw() % (stop - start + 1) return start + self.next_raw() % (stop - start) def __call__(self) -> int: return self._temper(self.next_raw()) ``` chal.py ``` from secrets import randbits from prng import xorshiro256 from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from ecc import ECDSA from Crypto.Util.number import bytes_to_long, long_to_bytes import hashlib flag = open("flag.txt", "rb").read() state = [randbits(64) for _ in range(4)] prng = xorshiro256(state) leaks = [prng.next_raw() for _ in range(4)] print(f"PRNG leaks: {[hex(x) for x in leaks]}") Apriv, Apub = ECDSA.gen_keypair() print(f"public_key = {Apub}") msg = b"My favorite number is 0x69. I'm a hero in your mother's bedroom, I'm a hero in your father's eyes. What am I?" H = bytes_to_long(msg) sig = ECDSA.ecdsa_sign(H, Apriv, prng) print(f"Message = {msg}") print(f"Hash = {H}") print(f"r, s = {sig}") key = hashlib.sha256(long_to_bytes(Apriv)).digest() iv = randbits(128).to_bytes(16, "big") cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = iv.hex() + cipher.encrypt(pad(flag, 16)).hex() print(f"ciphertext = {ciphertext}") with open("output.txt", "w") as f: f.write(f"PRNG leaks: {[hex(x) for x in leaks]}\n") f.write(f"public_key = {Apub}\n") f.write(f"Message = {msg}\n") f.write(f"Hash = {H}\n") f.write(f"r, s = {sig}\n") f.write(f"ciphertext = {ciphertext}\n") ``` * The code is kinda long but let's go through one-by-one, but in the first place, there is an important functions we need to know. **prng.next_raw()**: This function will "swap" and do many things with the seed (in this problem is **state**), then return **state[1]** ## The flow of the problem can be divided into two phrases: 1. Create leaks * We started off with **state** - an array containing 4 int 64 bits. Then **state** go through 4 loops of **next_raw** to create new array **leaks** (and our mission is to recover **state** from **leaks**). ![image](https://hackmd.io/_uploads/SkolxJN8lx.png) * The following loop use the previous state of **state** 2. Encrypt * The code generate public and private key, although public key is not gonna do anything. Private key, however, is thrown into signature, with some operation, but the most important two are $$ \begin{aligned} &k = prng() \mod(ECDSA.n) \\ &s = k^{-1}(H + rd) \mod(ECDSA.n) \end{aligned} $$ this will be the clue to retrieve the private key **_temper**: Do something with the input (in this problem is s1) **prng()**: Perform **_temper(next_raw)**, the **next_raw** in here corresponds to loop 5 ![image](https://hackmd.io/_uploads/BkKdIkVLeg.png) ## Solution can be divided also into two phrases: 1. Recover **state** * I used z3 library, which will solve the equation related to bits, then I retrieve the initial **state** 2. Find key * Once get initial **state**, I perform for loop 5 times and get **state[1]** (which i circle with red pen) this means $$ \begin{aligned} &k = \_temper(state[1]) \mod(ECDSA.n) \\ \Leftrightarrow \ &d = \frac{sk - H}{r} \mod(ECDSA.n) \end{aligned} $$ Code: ``` from z3 import BitVec, Solver, RotateLeft, BitVecVal, sat from Crypto.Util.number import long_to_bytes from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import hashlib # for bitvec type def z3_rotl(x, k): return RotateLeft(x, k) def prng_step_z3(s0, s1, s2, s3): t = (s1 << 17) & MASK64 s2 ^= s0 s3 ^= s1 s1 ^= s2 s0 ^= s3 s2 ^= t s3 = z3_rotl(s3, 45) return s0 & MASK64, s1 & MASK64, s2 & MASK64, s3 & MASK64 # for int type def _rotl(x: int, k: int) -> int: return ((x << k) | (x >> (64 - k))) & MASK64 def next_raw(state) -> int: s0, s1, s2, s3 = state t = (s1 << 17) & MASK64 s2 ^= s0 s3 ^= s1 s1 ^= s2 s0 ^= s3 s2 ^= t s3 = _rotl(s3, 45) state = (s0, s1, s2, s3) return state, s1 def _temper(s1: int) -> int: return (_rotl((s1 * 5) & MASK64, 7) * 9) & MASK64 def decrypt(ciphertext: bytes, key: bytes, iv) -> bytes: cipher = AES.new(key, AES.MODE_CBC, iv) return cipher.decrypt(ciphertext) # 1. Find state using Z3 s0, s1, s2, s3 = [BitVec(f's{i}', 64) for i in range(4)] solver = Solver() state = (s0, s1, s2, s3) for i in range(4): state = prng_step_z3(*state) solver.add(state[1] == BitVecVal(int(leaks[i], 16), 64)) assert solver.check() == sat, "No solution?" m = solver.model() initial_state = [m.eval(v).as_long() for v in [s0, s1, s2, s3]] # 2. Find key # 2.1 Find k for i in range(5): initial_state, s1 = next_raw(initial_state) print(f"step {i}: s1 = {hex(s1)}") k = _temper(s1) % n # 2.2 Find d d = ((s * k - H) * pow(r, -1, n)) % n key = hashlib.sha256(long_to_bytes(d)).digest() # 2.3 Decrypt the ciphertext flag = decrypt(ct, key, iv) flag = unpad(flag, 16) print(flag) ```