RSA 給你 c, e, N 還有 p xor q 等於說我們知道 p, q 在二進位下每一位相加的結果,我們又有 N 那就是對二進制乘法做 DFS 稍微解釋一下: 譬如說我們二分搜深度是 8 的時候,我們慢慢可以釐清出來前幾位 (譬如說4位) 一定要是多少才能滿足前幾位是 N 的前幾位,其實在十進位裡面是一樣的東西。 exp.py
# Given values nbits = 512 n = 113811568965055236591575124486758679392744553312134148909105203346767338399571149835776281246434662598568061596388663253038256689345299177200416663539845688277447346395189677568405388952270634599590543939397457325519084988358577805564978282375882831765408646889940777372958745826393653515323881370943911243589 e = 65537 c = 28637971616659975415203771281328378878549288421921080859079174552593926682380791394169267513651195690175911968893108214839850128311436983081661719981958725955998997347063633351893769712863719014753154993940174947685060864532241899917269380408066913133029163844049218414849768354727966161277243216291473824377 hint = 157624334507300300837306007943988438905196981213124202656160912356046979618961372023595598201180149465610337965346427263713514476892241848899142885213492
# hint = p ^ qr where qr is q bit-reversed # p = sum(p_i 2^i) # q = sum(q_i 2^i) # qr = sum(q_i 2^(511-i)) # hint_i = p_i ^ q_{511-i}
defget_bit(val, i): return (val >> i) & 1
# DFS state: current bit k (from 0 up to 255) # p_bits: list of bits of p, index 0 to 511 # q_bits: list of bits of q, index 0 to 511
# Check hint consistency for bits 0 and 511 # hint_0 = p_0 ^ q_511 = 1 ^ 1 = 0 # hint_511 = p_511 ^ q_0 = 1 ^ 1 = 0 if get_bit(hint, 0) != 0or get_bit(hint, 511) != 0: print("Warning: Hint bits 0 or 511 are not 0 as expected for odd primes.")
defsolve(k): if k == 256: # All bits should be determined now p = int(''.join(map(str, p_bits[::-1])), 2) q = int(''.join(map(str, q_bits[::-1])), 2) if p * q == n: return p, q returnNone
# Determine possible (p_k, q_k) such that (p * q) % 2^(k+1) == n % 2^(k+1) # Let p_low_k = p % 2^k, q_low_k = q % 2^k # (p_low_k + p_k 2^k) * (q_low_k + q_k 2^k) = n % 2^(k+1) # p_low_k * q_low_k + (p_k * q_low_k + q_k * p_low_k) 2^k = n % 2^(k+1) # Since q_low_k % 2 = 1 and p_low_k % 2 = 1: # (p_low_k * q_low_k // 2^k + p_k + q_k) % 2 = (n // 2^k) % 2 p_low_prev = int(''.join(map(str, p_bits[k-1::-1] if k > 0else ['0'])), 2) q_low_prev = int(''.join(map(str, q_bits[k-1::-1] if k > 0else ['0'])), 2) target_bit = get_bit(n, k) current_val = (p_low_prev * q_low_prev) >> k needed = (target_bit - current_val) % 2 # p_k + q_k = needed (mod 2) for pk in [0, 1]: qk = needed ^ pk # Now we have p_k and q_k # Use hint to get p_{511-k} and q_{511-k} # hint_k = p_k ^ q_{511-k} => q_{511-k} = hint_k ^ p_k # hint_{511-k} = p_{511-k} ^ q_k => p_{511-k} = hint_{511-k} ^ q_k q_high_k = get_bit(hint, k) ^ pk p_high_k = get_bit(hint, 511-k) ^ qk # Consistency checks if k == 511 - k: if pk != p_high_k or qk != q_high_k: continue # Save old values to restore old_pk = p_bits[k] old_qk = q_bits[k] old_phk = p_bits[511-k] old_qhk = q_bits[511-k] # Check if already set (e.g. if k reached the middle) if (p_bits[k] isnotNoneand p_bits[k] != pk) or \ (q_bits[k] isnotNoneand q_bits[k] != qk) or \ (p_bits[511-k] isnotNoneand p_bits[511-k] != p_high_k) or \ (q_bits[511-k] isnotNoneand q_bits[511-k] != q_high_k): continue p_bits[k], q_bits[k] = pk, qk p_bits[511-k], q_bits[511-k] = p_high_k, q_high_k # Pruning with MSB bounds # We know some bits at the ends. # p = p_high_sum + p_mid + p_low_sum p_low_sum = int(''.join(map(str, [p_bits[i] if p_bits[i] isnotNoneelse0for i inrange(k+1)][::-1])), 2) q_low_sum = int(''.join(map(str, [q_bits[i] if q_bits[i] isnotNoneelse0for i inrange(k+1)][::-1])), 2) p_high_sum = int(''.join(map(str, [p_bits[i] if p_bits[i] isnotNoneelse0for i inrange(511-k, 512)][::-1])), 2) << (511-k) q_high_sum = int(''.join(map(str, [q_bits[i] if q_bits[i] isnotNoneelse0for i inrange(511-k, 512)][::-1])), 2) << (511-k) p_min = p_high_sum + p_low_sum q_min = q_high_sum + q_low_sum # Max unknown part is bits k+1 to 511-k-1 if511-k-1 >= k+1: max_mid = (1 << (511-k)) - (1 << (k+1)) else: max_mid = 0 p_max = p_min + max_mid q_max = q_min + max_mid if p_min * q_min <= n <= p_max * q_max: res = solve(k + 1) if res: return res # Restore p_bits[k], q_bits[k] = old_pk, old_qk p_bits[511-k], q_bits[511-k] = old_phk, old_qhk returnNone
print("Starting DFS...") result = solve(1) # Start from bit 1 as bit 0 is fixed if result: p, q = result print(f"Found p: {p}") print(f"Found q: {q}") phi = (p - 1) * (q - 1) d = pow(e, -1, phi) m = pow(c, d, n) print(f"Flag: {long_to_bytes(m)}") else: print("No solution found.")
Hard_RSA
改取了一個 $\phi$ 值是 $(p^6-1)\times(q^6-1)$,生成 d 後再拿 d 對這個 $\phi$ 做 inverse 取 e
chal.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
from Crypto.Util.number import getPrime, bytes_to_long from secrets import flag
nbits = 512
p = getPrime(nbits) q = getPrime(nbits) N = p * q
phi = (p ** 6 - 1) * (q ** 6 - 1) d = getPrime(int(nbits * 2)) e = pow(d, -1, phi) m = bytes_to_long(flag) c = pow(m, e, N)
print(f"{N=}") print(f"{e=}") print(f"{c=}")
注意到這個 $\phi$ 值就是 $\phi(N^6)$,而在 Wiener’s Attack 裡面我們關心的是 $d<N^\delta$ 我們知道這是 $\phi(N^6)$ 所以就對 $N^6$ 和已知的 e 一樣做 Wiener’s Attack 連分數可以收斂回去拿到 d
N = 74919314162966623026780409394635730851359423962042152804673359696062303592948792225237959325724332015193893411896458285500680923291830113157053732353835717437056282529643649606999636042375356170625223068407005600597432512115745426297620503763041544738664221739366981075762409535100379250338620618401995088237 e = 115382440363851496163981840486384107492561192043935907058980266086827528886481753709205601977721854806609873255930935032352098823336758578514742052017664624320880734668505025950239731519576865823805968986213809315084654931730883582863309373723686304734918644860412520769285969343584540789781409990492019944165824625815300405767426485317259941101998781323411466463777306753584000597164370440130463322472428512359842079912370327303953535847288486792265729271519703439492772330110292227648936855652822622441290491046329984519666372475460813076592933292830035116887451745183745100906127431677048635228709073140092528296564430074027966676751247853438866388663691773416941464827914578065911403270794793189044046310361812370155771970529353757475322332721624146410615784452731493876192295337760243245754571266092484216808152656042093317366135329225792879900416688516879481855055555090043162672499662746617097324126665279154039087354142631899072583591875973448566342261418757933958097491430624847778317939143939884707606327061346526895306935081814341167557148974540052519764420837504151687245003054945691565644413351468736320044794906326974209391387438080503466412668871800294636642414652459851564340761041186735760949703483158497968185223108721790483665796212381899768853381692611758739543856321631250649771030357257110672427660801480674245151183281118006855095936338551269908982677103650278164957043853181158383261830382128373562401278765223263898827117486913915002789859131458355569155109058470780395630260922250802643984468511286231457608106175597829905176417125226959444682393922189933288709798973807586352487516329712976257944503755516552005154708122299515447476835290766731627959030431596313710392337334874777621376729257439721939577794752381362801045781355354034613981044768946879601448500750228731652608416508751483139575431367276816127751324213761 c = 46597101834449995414927716136287390792937263485498695607622613274985303919148099277379370499625616739447192289360957892792459785981292432370520768029645163669042553465834050214430965629198749117682681268121937191602353019996971164117733452207322953311422907574742284390173017434719506924876701654539254320355
for frac in convergents: k = frac.numerator() d = frac.denominator() if k == 0: continue m = pow(c, int(d), N) flag = long_to_bytes(int(m)) ifb'nctf{'in flag orb'flag{'in flag orb'NCTF{'in flag: print(f"Found flag: {flag.decode()}") break
defsolve(): io = remote('114.66.24.221', 38498) io.recvuntil(b'Here is my seed: ') seed_str = io.recvline().strip().decode() print(f"[*] Received seed: {seed_str}") s = int(seed_str) words = [] temp = s while temp: words.append(temp & 0xffffffff) temp >>= 32 ifnot words: words = [0] n = len(words) new_words = words + [(w - n) % (2**32) for w in words] s2 = 0 for i, w inenumerate(new_words): s2 += w << (32 * i) io.sendline(str(s2).encode()) response = io.recvall(timeout=5).decode() print(response) if __name__ == "__main__": solve()
yqs
主辦方說知道大家都用 ai 解題就用 ai 出題了 :/ 給你一個最多能用一百次的 lwe oracle + 一個 Short Weierstrass Form ECC Adder lwe 每次 A 都不一樣,s 則是固定 secret vector 方法是你輸入的訊息做 $<A, s> + msg + e$ 其中 e 的範圍是 0~3 而 msg 你每次輸入什麼自己會知道,A 是向量,對…根本不標準 lwe。 Notation: $<A, s>$ 是內積
from sage.allimport * from pwn import * import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import os
# Context p = 2**255 - 19 a = 486662 N = 77 ebits = 2
defpack_point(P): x, y = P return (int(x) << 256) | int(y)
defunpack_point(n): x = n >> 256 y = n & ((1 << 256) - 1) return (x, y)
defvec_to_int(v, q): result = 0 for i inrange(len(v)): result += int(v[i]) * (q ** i) return result
defint_to_vec(n, q): coeffs = [] for i inrange(N): coeffs.append(n % q) n //= q return coeffs
defsolve_lwe(samples, q): m = 90 samples = samples[:m] A = [] B_prime = [] for a_int, b, m_encoded in samples: a_vec = int_to_vec(a_int, q) A.append(a_vec) B_prime.append((b - m_encoded) % q) T = 1 mat = matrix(ZZ, m + N + 1, m + 1) for i inrange(m): mat[i, i] = q for i inrange(N): for j inrange(m): mat[m + i, j] = A[j][i] for j inrange(m): mat[m + N, j] = B_prime[j] mat[m + N, m] = T res = mat.LLL() for row in res: ifabs(row[-1]) == T: sign = 1if row[-1] == T else -1 e = [(sign * row[i]) % q for i inrange(m)] e_small = [] for val in e: if val <= 5: e_small.append(val) elif val >= q - 5: e_small.append(val - q) else: break iflen(e_small) == m andall(0 <= val <= 3for val in e_small): AM = matrix(GF(q), A) BM = vector(GF(q), [(B_prime[i] - e_small[i]) % q for i inrange(m)]) s = AM.solve_right(BM) return s returnNone