帮我完成一下这道ctf挑战,直接输出完整的解题代码并且详细解释

تم الإنشاء في: ٢١ مارس ٢٠٢٦

تمت الإجابة باستخدام GPT-5.4 Thinking بواسطة Chat01

سؤال

伪ASR1.1.py

帮我完成一下这道ctf挑战,直接输出完整的解题代码并且详细解释

Python

python
from math import isqrt n=500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147 s=isqrt(n) s.bit_length(), n.bit_length() (149, 298)

Python

python
import sympy as sp, math, time n=sp.Integer(500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147) t=time.time() #print(sp.factorint(n)) print("starting")

Python

python
import 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)

Python

python
import 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

Python

python
n=500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147 M=2**79 c=n%M c, c.bit_length() (234228817681076387517195, 78)

Python

python
M=2**79 c=n%M s=((n-c)//M)%M c,s,s.bit_length() (234228817681076387517195, 129570351378441188240635, 77)

Python

python
n%4 3

Python

python
from decimal import Decimal,getcontext getcontext().prec=50 n=Decimal(500532925884017190157531654042977388637611201227338971326884172046371105194776392356795147) M=Decimal(2**79) print(n/(M*M))

الفكرة

Refining helper function and output formatting

I'm defining a helper function long_to_bytes to ensure robust conversion of integers to byte arrays, with optional padding. For handling piece lengths, I’ll use [10,10,10,10,None], printing and decoding each piece to show potential issues with missing separators. I'll also include a detailed user guide for reconstructing the data after decryption.

Thought for 11m 39s

الإجابة

核心就两步:

  1. 先分解 nn:因为题目把 pp 生成为 p = 2^79 * r + 1,所以我们已知 pp 的低 79 位。未知量 rr 只有大约 70 bit,满足经典 Coppersmith 已知低位素因子 条件,可以直接把 ppnn 里抠出来。
  2. 再解密每个分块:密文是
cymx279(modn)c \equiv y^m \cdot x^{2^{79}} \pmod n

pp 看时,把它提升到 (p1)/279(p-1)/2^{79} 次方,随机掩码 x279x^{2^{79}} 会被完全消掉,只剩一个 79 位的 2 次幂阶离散对数,可以按位恢复。题目代码里 pyencode()flag_pieces 的结构都把这个攻击面写死了。(伪ASR1.1.py)

下面直接给你完整解题代码(用 Sage 跑,最稳):

python
from 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.")

为什么这题会被这样打

1. 为什么能先分解出 pp

题目里:

p=279r+1,r[269,270)p = 2^{79}r + 1,\quad r \in [2^{69}, 2^{70})

所以 pp低 79 位完全已知,只差一个大约 70 bit 的未知量 rr。这正好落在“已知素因子低位”的 Coppersmith 适用区间里。把

p=279r+1p = 2^{79}r + 1

改写成

r+(279)10(modp)r + (2^{79})^{-1} \equiv 0 \pmod p

就得到一个关于小根 rr 的一次多项式,small_roots() 会直接把它找出来。这个弱点完全来自 get_p() 的构造方式。(伪ASR1.1.py)

2. 为什么随机项 x279x^{2^{79}} 能被抹掉

题目加密是:

cymx279(modn)c \equiv y^m \cdot x^{2^{79}} \pmod n

只看模 pp

因为

p1=279rp-1 = 2^{79}\cdot r

rp=p1279r_p = \frac{p-1}{2^{79}}

那么

(x279)rp=xp11(modp)(x^{2^{79}})^{r_p} = x^{p-1} \equiv 1 \pmod p

于是

crpymrp(modp)c^{r_p} \equiv y^{m r_p} \pmod p

随机掩码被完全消掉

3. 为什么 g=yrpg = y^{r_p} 的阶正好是 2792^{79}

get_y() 保证 y 在模 p 和模 q 下都不是二次剩余,也就是在模 p 下它的离散对数指数是奇数。因此把它提升到 rpr_p 次之后,得到的

g=yrpg = y^{r_p}

正好落在模 pp2792^{79} 阶子群里,而且因为那个指数是奇数,gg 的阶不会缩水,仍然是完整的 2792^{79}。(伪ASR1.1.py)

所以对每个密文分块,都有

h=crp=gm(modp)h = c^{r_p} = g^m \pmod p

这就把原题变成了一个 79 位的 2 幂阶离散对数

4. 为什么这个离散对数可以按位拆

设当前已经恢复了 mm 的低 ii 位,记为 known
那么

hgknown=gmknownh \cdot g^{-known} = g^{m-known}

mknownm-known 一定能被 2i2^i 整除。

再把它提升到 278i2^{78-i} 次幂:

(gmknown)278i=(g278)(mknown)/2i\left(g^{m-known}\right)^{2^{78-i}} = \left(g^{2^{78}}\right)^{(m-known)/2^i}

因为 gg 的阶是 2792^{79},所以

g278=1(modp)g^{2^{78}} = -1 \pmod p

于是这个值只可能是:

  • 1:当前第 ii 位是 0
  • -1 mod p:当前第 ii 位是 1

所以可以从低位到高位,一位一位把 mm 全部抠出来。


还有一个很坑但很关键的点

题目并没有把整条 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 版

مشاركة هذا السؤال والجواب