帮我完成一下这道ctf挑战,直接输出完整的解题代码并且详细解释
创建于:2026年3月21日
使用 GPT-5.4 Thinking 回答 Chat01
创建于:2026年3月21日
使用 GPT-5.4 Thinking 回答 Chat01
帮我完成一下这道ctf挑战,直接输出完整的解题代码并且详细解释
pythonfrom math import isqrt n=500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147 s=isqrt(n) s.bit_length(), n.bit_length() (149, 298)
pythonimport sympy as sp, math, time n=sp.Integer(500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147) t=time.time() #print(sp.factorint(n)) print("starting")
pythonimport sympy as sp from math import log2 N=500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147 M=2**79 x=sp.symbols('x') # monic polynomial f = x + a mod N a = pow(M, -1, N) % N f = sp.Poly(x + a, x, domain=sp.ZZ) print("N bits", N.bit_length(), "X bits", 70)
pythonimport sympy as sp, math, itertools, fractions, sys, os, statistics from sympy import Matrix def coppersmith_univariate_linear(N, a, X, m=4, t=None): x=sp.symbols('x') if t is None: t=m f = sp.Poly(x + a, x, domain=sp.ZZ) # monic d=1 n = d*m + t # build gg polys in z (unscaled) z=sp.symbols('z') # maybe directly with z and x*X substitution gg=[] for i in range(m): for j in range(d): poly = (z*X)**j * (N**(m-i)) * sp.expand(f.as_expr().subs(x, z*X)**i) gg.append(sp.Poly(poly, z, domain=sp.ZZ)) for i in range(t): poly = (z*X)**i * sp.expand(f
pythonn=500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147 M=2**79 c=n%M c, c.bit_length() (234228817681076387517195, 78)
pythonM=2**79 c=n%M s=((n-c)//M)%M c,s,s.bit_length() (234228817681076387517195, 129570351378441188240635, 77)
pythonn%4 3
pythonfrom decimal import Decimal,getcontext getcontext().prec=50 n=Decimal(500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147) M=Decimal(2**79) print(n/(M*M))
核心就两步:
p = 2^79 * r + 1,所以我们已知 的低 79 位。未知量 只有大约 70 bit,满足经典 Coppersmith 已知低位素因子 条件,可以直接把 从 里抠出来。模 看时,把它提升到 次方,随机掩码 会被完全消掉,只剩一个 79 位的 2 次幂阶离散对数,可以按位恢复。题目代码里 p、y、encode() 和 flag_pieces 的结构都把这个攻击面写死了。(伪ASR1.1.py)
下面直接给你完整解题代码(用 Sage 跑,最稳):
pythonfrom sage.all import * # ========================= # public data # ========================= k = 79 n = Integer(500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147) y = Integer(213088474978954913521695933149257926315459990908578573756933176330915972508162260163992936) cs = [ Integer(57494912618263048538571755953837772127117773898872797680570116373460237301011181142984690), Integer(344186007342959044249362172584754916978318670779607618696087105142714882053499189453591750), Integer(11170932486684627637967687021711067484959106608189352734064089980678923008744240797135422), Integer(73837068555811384284867151570572743386582880055013744261872093001909203963879165023864836), Integer(64356403000986744386743473269071732498867064770469172347340097989063717305436807805878673), ] # ========================= # helpers # ========================= def i2b(x, size=None): x = int(x) if x == 0: b = b"\x00" else: b = x.to_bytes((x.bit_length() + 7) // 8, "big") if size is not None: b = b.rjust(size, b"\x00") return b def dlog_power_of_two(h, g, p, k): """ Solve h = g^m mod p, where ord(g)=2^k and 0 <= m < 2^k. Recover m bit by bit from low bit to high bit. """ g = Integer(g) h = Integer(h) p = Integer(p) # g^(2^i) precompute gpows = [power_mod(g, 1 << i, p) for i in range(k)] known = 0 g_known = Integer(1) # g^known mod p for i in range(k): # t = g^(m-known) t = (h * inverse_mod(g_known, p)) % p # Raise to 2^(k-1-i): # If current unknown bit is 0 => result = 1 # If current unknown bit is 1 => result = -1 mod p u = power_mod(t, 1 << (k - 1 - i), p) if u == 1: bit = 0 elif u == p - 1: bit = 1 known |= (1 << i) g_known = (g_known * gpows[i]) % p else: raise ValueError(f"unexpected value while extracting bit {i}: {u}") return known # ========================= # step 1: recover p with Coppersmith # p = 2^79 * r + 1, with r < 2^70 # Let inv = (2^79)^(-1) mod n. # Then root r satisfies: r + inv == 0 (mod p) # ========================= PR = PolynomialRing(Zmod(n), 'x') x = PR.gen() invM = inverse_mod(2**k, n) f = x + invM # r is around 70 bits roots = f.small_roots(X=2**70, beta=0.49) if not roots: raise ValueError("small_roots failed; try beta=0.48/0.5 or tweak X slightly") r = Integer(roots[0]) p = Integer((2**k) * r + 1) assert n % p == 0 q = n // p print("[+] p =", p) print("[+] q =", q) # ========================= # step 2: kill the randomizer modulo p # c = y^m * x^(2^k) mod n # Let rp = (p-1)/2^k. # Then modulo p: # c^rp = (y^rp)^m * (x^(2^k))^rp = (y^rp)^m # because 2^k * rp = p-1 # ========================= rp = (p - 1) // (2**k) g = power_mod(y, rp, p) # g should have exact order 2^79 assert power_mod(g, 2**k, p) == 1 assert power_mod(g, 2**(k - 1), p) == p - 1 ms = [] for c in cs: h = power_mod(c, rp, p) # h = g^m mod p m = dlog_power_of_two(h, g, p, k) ms.append(m) print("[+] recovered integers:") for i, m in enumerate(ms): print(f" m[{i}] = {m}") # The original code slices as: # [0:10], [11:21], [22:32], [33:43], [44:] # so first 4 pieces are 10 bytes each; last one is variable. pieces = [] for i, m in enumerate(ms): if i < 4: pieces.append(i2b(m, 10)) else: pieces.append(i2b(m)) print("[+] recovered pieces (repr):") for i, b in enumerate(pieces): print(f" piece[{i}] = {b!r}") print("[+] recovered pieces (utf-8 best effort):") for i, b in enumerate(pieces): print(f" piece[{i}] = {b.decode('utf-8', errors='replace')}") print() print("[!] Note:") print(" The source code encrypts only 5 slices:") print(" flag[0:10], flag[11:21], flag[22:32], flag[33:43], flag[44:]") print(" so bytes at offsets 10, 21, 32, 43 were NEVER encrypted.") print(" You can recover the 5 plaintext chunks exactly,") print(" then fill the 4 missing separator characters by flag format/context.")
题目里:
所以 的低 79 位完全已知,只差一个大约 70 bit 的未知量 。这正好落在“已知素因子低位”的 Coppersmith 适用区间里。把
改写成
就得到一个关于小根 的一次多项式,small_roots() 会直接把它找出来。这个弱点完全来自 get_p() 的构造方式。(伪ASR1.1.py)
题目加密是:
只看模 :
因为
令
那么
于是
随机掩码被完全消掉。
get_y() 保证 y 在模 p 和模 q 下都不是二次剩余,也就是在模 p 下它的离散对数指数是奇数。因此把它提升到 次之后,得到的
正好落在模 的 阶子群里,而且因为那个指数是奇数, 的阶不会缩水,仍然是完整的 。(伪ASR1.1.py)
所以对每个密文分块,都有
这就把原题变成了一个 79 位的 2 幂阶离散对数。
设当前已经恢复了 的低 位,记为 known。
那么
而 一定能被 整除。
再把它提升到 次幂:
因为 的阶是 ,所以
于是这个值只可能是:
1:当前第 位是 0-1 mod p:当前第 位是 1所以可以从低位到高位,一位一位把 全部抠出来。
题目并没有把整条 flag 都加密进去。源码里实际加密的是:
flag[0:10]flag[11:21]flag[22:32]flag[33:43]flag[44:]也就是说 下标 10、21、32、43 这 4 个字节从来没有进过加密函数,所以它们不可能从密文里恢复;你能恢复的是 5 段明文本身,然后按 flag 格式把那 4 个分隔符补回去。这个信息也是源码直接给出来的。(伪ASR1.1.py)
你要的话,我下一条可以继续帮你把这份 Sage 脚本改成纯 Python + fpylll 版。