帮我非常详细的讲解这道ctf题目中的每一个知识点,具体是怎样证明怎样利用,把每一个知识点与解决的代码...
생성일: 2026년 3월 4일
답변에 사용된 모델 GPT-5.2 Thinking by Chat01
생성일: 2026년 3월 4일
답변에 사용된 모델 GPT-5.2 Thinking by Chat01
下面我按“题目服务端逻辑 → 漏洞点(知识点)→ 证明/推导 → 如何利用 → 对应解题代码”的顺序,把这题从零到一拆开讲清楚,并在最后给一条学习路径+训练方法。我会直接对照你上传的 server.py 和 offical.py 来讲。 (server.py) (offical.py)
p,q 各 512 bit,n=pqphi=(p-1)(q-1)e,直到 gcd(e,phi)=1d = e^{-1} mod phi(Python 里用 pow(e,-1,phi)) (server.py)plain 和一个位 x,它不是用标准 e,而是用 e ^ (1<<x)(把 e 的第 x 位翻转)去做 pow(plain, e^(1<<x), n) (server.py)cipher,它做 pow(cipher, d, n) 返回明文(字节形式再 base64) (server.py)secret = flag^e mod n,并把 safe=False(之后 encrypt/decrypt 输出会被 disguise 处理) (server.py)关键点:
- 你能在 safe=True 阶段拿到“干净”的 encrypt 输出(没 disguise)。
- 一旦调用 Get flag,之后所有输出都会 disguise,但 Decrypt 仍然存在。这通常意味着“密文→明文”某些信息还能泄漏。
c = m^e mod nm = c^d mod n,其中 ed ≡ 1 (mod phi(n)),phi(n)=(p-1)(q-1) (server.py)如果
那么等价于
(“同余 ⇔ 差是 n 的倍数”)
这句是本题 GCD 求 n 的根本原因。
服务端给你:指数 e 的第 x 位翻转:e' = e ^ (1<<x)。(server.py)
phi=(p-1)(q-1) 一定是偶数(因为 p,q 都是奇素数,p-1,q-1 都是偶数)gcd(e,phi)=1gcd(e,phi) 至少包含 2,不可能为 1当 bit0=1 时:
因为奇数的二进制最低位是 1,异或 1 就变成 0,相当于减 1。
这就是 offical.py 注释里那段“phi 偶数→e 奇数→翻转最低位相当于 e-1”的来源。(offical.py)
在 safe=True 阶段我们能得到真实的模 n 结果。offical.py 先做:(offical.py)
pythonc1 = encrypt(2, 0) # 2^(e-1) mod n c1_ = encrypt(4, 0) # 4^(e-1) mod n c2 = encrypt(3, 0) # 3^(e-1) mod n c2_ = encrypt(9, 0) # 9^(e-1) mod n
c1^2 - c1_ 一定是 n 的倍数因为:
c1 = 2^(e-1) mod nc1_ = 4^(e-1) mod n = (2^2)^(e-1) = 2^(2(e-1)) mod n而:
所以:
同理:
因此:
一组差值可能是 k*n(带倍数),两组取 gcd 通常能把倍数消掉,逼近 n。
于是 offical.py 做:(offical.py)
pythonn = GCD(c1**2-c1_, c2**2-c2_)
脚本随后把 n 里 2~999 的小因子除掉:(offical.py)
pythonfor i in range(2, 1000): if n%i==0: while n%i==0: n=n//i
原因:你得到的 gcd 可能是 n * t,其中 t 往往是一些小因子(比如差值里额外带出来的公共小因子),把它们剥掉更稳。严格来说不保证只含小因子,但 CTF 中常这么处理。
这一块是本题最巧妙的利用:你并不知道 e,但你能查询 2^(e xor 2^i)。
我们有:
encrypt(2,0) 给 2^(e-1) mod n(因为 bit0 翻转)代码对应:(offical.py)
pythonc = c1 * 2 % n # c = 2^e mod n
你查询:
pythonc_ = encrypt(2, i) # 2^(e xor 2^i) mod n
分两种情况:
情况 A:e 的第 i 位是 1
翻转会把该位从 1→0,相当于减去 2^i:
所以
两边乘上 :
也就是会“对上”你之前算出的 c=2^e mod n。
情况 B:e 的第 i 位是 0
翻转会变成 0→1,相当于加上 2^i:
则:
(除非出现极小概率的巧合:,可忽略)
(offical.py)
pythonfor i in range(1, 50): c_ = encrypt(2, i) if c_ * pow(2, 1<<i, n) % n == c: e_[i] = 1 else: e_[i] = 0
pow(2, 1<<i, n) 计算 c,判定位为 1,否则为 0。e_[0]=1 是因为我们证明了 e 必为奇数。(offical.py)最后拼成整数 e:(offical.py)
pythone_ = e_[::-1] e = int(''.join(map(str, e_)), 2)
到这里你已经完全恢复了公钥 (n,e)。
Get flag 之后 safe=False,encrypt/decrypt 输出会走:(server.py)
pythonif not safe: plain = self.disguise(plain)
disguise 做了两轮 XOR:(server.py)
pythonmask = os.urandom(1) for i in range(len(res)): res[i] ^= mask0 for i in range(len(msg)-1, -1, -1): res[i] ^= mask # 第一轮这里的 mask 还是 mask0 mask = os.urandom(1)
看最后一个字节 res[-1]:
res[-1] ^= mask0res[-1] ^= mask,此时 mask == mask0所以最后字节变成:
完全抵消,最后一个字节保持原值。
而 offical.py 在 decrypt 输出后做 bytes_to_long,这个整数的 mod 256 正好取的是最后一个字节(大端 bytes 的最后一位是最低有效字节)。因此即使被 disguise,p % 256 仍然是真实明文的最低字节。(offical.py) (server.py)
对 RSA:
则:
所以你能在密文层面乘上 ,解密后明文会乘上 。
令 。如果 flag 明文是 ,题目给你:
你把密文迭代更新:
则解密得到:
代码对应(每轮都做一次乘法):(offical.py)
pythonc = c * pow(256, e, n) % n p = decrypt(c) # 这里p的最后一字节是真实的 (m*256^i mod n) 的最后一字节
设:
其中:
对两边取 mod (即看最低字节):
因为 gcd(n,256)=1(n 是奇数),所以 n 在 mod 256 下可逆:
也就是:
这正是脚本里的:(offical.py)
python(-inverse(n,256) * p % 256)
还要一个递推关系:
从
乘 B:
再除以 n,商为:
注意 ,它正好是 t_i 的最低一个 base-256“数字”,因为 一定是 256 的倍数。
而我们上一节算出的 就是这个新“数字”。因此可以用 base-256 累积:
代码对应:(offical.py)
pythonk = k*256 + (-inverse(n,256)*p % 256)
这里的 k 就是在迭代维护 t_i。
因为 flag 被 pad 到 127 字节(即小于 ),脚本迭代 127 次得到 。(server.py) (offical.py)
由:
脚本用:
pythonlong_to_bytes(k*n//256**127)
相当于取 当作 m 的恢复值。(offical.py)
实战里更稳的做法:通常会再用 padding 结构(PKCS#7)验证/微调(比如尝试 +0..255),但这题很多时候直接就能出可读 flag(脚本也用
decode(errors='ignore')容错)。(offical.py) (server.py)
b64decode → bytes_to_long (offical.py) (server.py)encrypt/decrypt/getFlag 三个函数做的就是“菜单交互 + 编解码”。(offical.py)e_[0]=1 + 用 encrypt(2,0) 作为 (offical.py) (server.py)n = GCD(c1**2-c1_, c2**2-c2_) (offical.py)c = c1*2%n 得到 (offical.py)if c_ * 2^{2^i} == c 判定位 i 是否为 1 (offical.py)disguise 的“最后字节 XOR 两次同一 mask”抵消 (server.py)p % 256 就是只取最后字节 (offical.py)c=c*pow(256,e,n)%n 把明文乘 256 (offical.py)(-inverse(n,256)*p%256) 恢复 t_i mod 256 (offical.py)k=k*256+... base-256 递推构造 t_i (offical.py)k*n//256**127 近似求回 m (offical.py)我建议按“数学 → RSA → 典型攻击 → CTF工程实现”四层走,每层都要配练习。
要做到看到公式就能变形:
pow(a,-1,m) 的意义)pow(x,e,n) 快)练习方法:
要掌握:
phi(n)、ed≡1 (mod phi) 的由来gcd(e,phi)=1练习方法:
Dec(Enc(m))==mEnc(m)*Enc(s) == Enc(m*s)(注意是同一个 e 同一个 n)建议顺序:
练习方法:
必须熟练:
remote、sendline/recvline、交互稳定性bytes_to_long/long_to_bytes、端序与“最低字节”的关系long_to_bytes 会丢前导 0)练习方法:
server.py 本地跑起来(固定随机种子/打印 e,n),先验证每一步推导都正确。(server.py)如果你愿意,我也可以把 offical.py 重新写成一份**带“推导式注释 + 每一步断言检查 + 更稳的末尾校验(利用 PKCS#7 padding 验证/微调)”**的版本,让你对每个变量(c、p、k、t_i)到底代表什么更直观。