Misay's blog

Back

冲浪发现的比赛 比赛地址 https://imaginaryctf.org/ ↗

赛题质量不错 写一下blog来复盘

pwq#

task.py

RSA相关 给出

h≡(p+q)emod  wh\equiv (p+q)^e \mod w

ww已知 可以直接解出

p+qmod  wp+q\mod w

又已知n=pqn=pq 可以分别解出 p0≡pmod  wp_0\equiv p\mod w 与q0≡qmod  wq_0\equiv q\mod w 此时可做

p=uw+p0p=uw+p_0 q=vw+q0q=vw+q_0

未知量 u  v∼2112u\;v\sim2^{112} 构造

(uw+p0)(vw+q0)≡0mod  n(uw+p_0)(vw+q_0)\equiv 0\mod n 2112<n182^{112}\lt n^{\frac{1}{8}}

打二元Copper即可

exp.sage

RSA2026#

server.py

from Crypto.Util.number import *
FLAG = b"ictf{REDACTED123456}"
assert len(FLAG) == 20

while True:
  print("Let's build an RSA-2026 public key together! I provide the exponent, you provide the modulu.")
  e = getRandomNBitInteger(2026)
  N = int(input("N = "))
  assert e.bit_length() == N.bit_length() == 2026, "We failed to collaborate on a RSA-2026 key :("
  
  m = bytes_to_long(FLAG)
  c = pow(m, N, e) # I might have mixed up the exponent and modulus. oh well
  print(f"{c = }")
py

修正一下参数顺序

靶机生成2026bits模数NN,我们可以自己构造2026bits公钥指数ee 得到密文

本题关键切入有两个

flag 只有160bits

模数NN的生成调用的是getRandomNBitInteger 也就是说是有可能生成一个合数的

考虑取比较多组数据 (ei,Ni)(e_i,N_i) 得到cic_i 对于一系列较小的 我们提前假设好素数 aia_i 当NiN_i取足够多时 能够得到 ai  ∣  Nia_i \;|\; N_i 这个概率大约是 1ai\frac{1}{a_i} 如果我们传入的eie_i对应ϕ(ai)=ai−1\phi(a_i)=a_i-1可逆 我们就可以解出 mi≡mmod  aim_i\equiv m\mod a_i 积累足够多数据 做CRT即可恢复mm

本题取了20000组数据 构造一致的2026bits的大素数eie_i

data-collect.py

24线程大约跑15分钟

recover.py

Equinox#

ECC.py

Equinox.py

基于ECC的Oracle靶机

这里给出了一个标准的ECC的Wiersstrass曲线 编码一个曲线

E:y2≡x3+ax+bmod  p\mathbf{E}:y^2\equiv x^3+ax+b\mod p

曲线不奇异 看似很正常 阶数和pp比较接近 但是不能本质上简化DLP的求解

随后 定义基点GG 也没什么特殊的

生成128bits的keykey 215bits的随机数rr

定义Equinox类 其包含state变量 215bits的aa

靶机先给你

hint1≡y(a⋅r⋅G)+keymod  phint_1\equiv y(a\cdot r\cdot G)+key\mod p

state aa可以被如下操作修改

输入msgmsg 计算

mask = bytes_to_long(SHA256.new(msg).digest()) & ((1 << 215) - 1)  
py

也就是msgmsg进行SHA256哈希后取低215位

再

a=a⊕maska=a\oplus mask

我们可以无限次添加msgmsg调节state 但是只有一次获取hint机会 也就是

hint2≡y(a1⋅r⋅G)+keymod  phint_2\equiv y(a_1\cdot r\cdot G)+key\mod p

其中

a1=a⊕mask1⊕⋯⊕maskia_1=a\oplus mask_1\oplus\cdots\oplus mask_i

注意到我们有3个未知量 r  a  keyr\;a\;key 但是只有两条方程hint1  hint2hint_1\;hint_2

一开始考虑利用keykey比较小 找线性关系做格来规约 但是在ECC中显然非线性还是强

先考虑如何把异或变成线性式子 因为

a⊕11⋯11=2215−1−aa\oplus 11\cdots11=2^{215}-1-a

这里测试出一个比较关键的性质

2215−1≡−15mod  #E2^{215}-1\equiv -15\mod \mathbf{\#E}

也就是说 如果我们构造

mask1⊕⋯⊕maski=111⋯111mask_1\oplus\cdots\oplus mask_i=111\cdots111

那么

hint2≡y(a1⋅r⋅G)+key≡y((2215−1−a)⋅r⋅G)+keymod  phint_2\equiv y(a_1\cdot r\cdot G)+key\equiv y((2^{215}-1-a)\cdot r\cdot G)+key\mod p hint2≡y((−15−a)⋅r⋅G)+keyhint_2\equiv y((-15-a)\cdot r\cdot G)+key

那么如何构造我们预期的一系列maskmask呢

注意到我们的目标向量mm可以看成GF(2)\mathbf{GF(2)}上一组列向量v215×1v_{215\times1}

我们考虑采集ii组数据 m215×1m_{215\times1} 拼接得到矩阵M215×iM_{215\times i} 问题此时描述为寻找选中向量ci×1c_{i\times1} 使得

M215×i×ci×1=m215×1M_{215\times i}\times c_{i\times1}=m_{215\times1}

我们可以不断增加ii 重新取样 让这个系统满秩 来解出cic_i

因为我们参与掩码的aa实际上也是完全唯一的 我们如果随机控制mm的低4位 比如说全取00000000 此时的

m⊕a≡(2215−1−a+offset)≡(offset−15−a)mod  #Em\oplus a\equiv (2^{215}-1-a+\text{offset})\equiv(\text{offset}-15-a)\mod \mathbf{\#E}

offset是0~15之间随机取值 也就是说我们平均链接16次靶机 就会出现一次 m⊕a≡−am\oplus a\equiv -a

此时

hint2≡y(−a⋅r⋅G)+key≡−y(a⋅r⋅G)+keymod  phint_2\equiv y(-a\cdot r\cdot G)+key\equiv-y(a\cdot r\cdot G)+key\mod p

直接

key≡hint1+hint22mod  pkey\equiv \frac{hint_1+hint_2}{2}\mod p

exp.py

Elegant


Illiterate#

chal.py

靶机允许构造一条标准Wiersstrass曲线

E:y2≡x3+ax+bmod  p\mathbf{E}:y^2\equiv x^3+ax+b\mod p

可以自己给靶机发送”曲线的阶”#E\mathbf{\#E} 与生成元 GG 靶机定义了一系列检测 尝试让用户发送的曲线素数不具备奇异性等常见漏洞 随后构造

f1 = int.from_bytes(flag[:18],'big')
f2 = int.from_bytes(flag[18:],'big')
P1 = curve.mul(f1,generator)
P2 = curve.mul(f2,generator)
py

所以目标是构造通过测试的伪素数pp 对应生成曲线 E\mathbf{E} 解ECDLP

检测方式#

isPrime函数

def isPrime(p):
    e = p // 2
    if all(pow(a, e, p) == (jacobiSymbol(a, p) % p) for a in witnesses):
        return True
py

对特定的几个witness中的数aa 进行Euler素性判断等式

a(p−1)2≡(ap)(modp)a^{\frac{(p-1)}{2}} \equiv \left(\frac{a}{p}\right) \pmod{p}

梳理服务端的检测主要如下

校验 含义
isPrime(p) p 需通过伪素数测试
p≥2140p ≥ 2^{140} 下界约 1.39×10⁴²
isPrime(E) E 需通过伪素数测试
#E≥2140\#E ≥ 2^{140} 阶的下界
$ #E - p
(E−p−1)2≤4p(E-p-1)² ≤ 4p Hasse 界
pk≢1mod  E    ;  k∈[1,999]p^k ≢ 1 \mod E\;\;;\; k∈[1,999] 防 MOV 攻击
E⋅G=O\mathbf{E}·\mathbf{G} = \mathbf{O} 验证阶正确

这里主要的检测手法都是判断曲线的阶#E\mathbf{\#E} 不能被轻松的推到易于求解的DLP

绕过构造#

核心问题还是解ECDLP 构造素数pp通过isPrime只是第一步 需要让构造的曲线EE的阶数易于做DLP 故而思路链条大概为

构造#E=∏qi→对应构造出素数p→构造复乘曲线满足#E恰为曲线的阶→做ECDLP\text{构造}\#E=\prod q_i\rightarrow \text{对应构造出素数}p\rightarrow \text{构造复乘曲线满足}\#E\text{恰为曲线的阶}\rightarrow\text{做ECDLP}

伪素数构造#

要通过这里16组witness的检测 同时还要便于做DLP 考虑类似

E=(6L+1)(12L+1)(18L+1),L≡0(mod12)E = (6L+1)(12L+1)(18L+1), \quad L \equiv 0 \pmod{12}

其中 p1=6L+1, p2=12L+1, p3=18L+1p_1 = 6L+1,\ p_2 = 12L+1,\ p_3 = 18L+1 均为素数

此时有对每个素因子 pip_i,有 (pi−1)∣(E−1)(p_i - 1) \mid (E - 1)

所以对任意 gcd⁡(a,E)=1\gcd(a, E) = 1 aE−1≡1(modE)a^{E-1} \equiv 1 \pmod{E}

落到靶机的检测 需要满足

a(E−1)2≡(aE)(modE)a^{\frac{(E-1)}{2}} \equiv \left(\frac{a}{E}\right) \pmod{E}

推导一下也就是

∏i=13(api)=1对所有 16 个 witness a\prod_{i=1}^{3} \left(\frac{a}{p_i}\right) = 1 \quad \text{对所有 16 个 witness } a

搜索方式是令

p1=L+1,p2=2L+1,p3=3L+1,E=p1p2p3p_1=L+1,\quad p_2=2L+1,\quad p_3=3L+1,\quad E=p_1p_2p_3

从(21406)13(\frac{2^{140}}{6})^{\frac{1}{3}}开始搜索 LL自增12 并且对齐 每轮做一些检验

搜索约 272 秒后找到第 4 个候选:

E = 1393987673345391394931205800131659111954649 (141 bits)
p1 = 61475569549627,  p2 = 122951139099253,  p3 = 184426708648879
plaintext

三个因子均约 47 bits 141bits略小于18*8=144 爆破即可

曲线/模数构造#

还是利用老朋友复乘CM曲线阶的可计算性 注意要让曲线的类数为1 这样曲线不变量是已知且确定的 直接构造曲线就行了 这里找到的曲线是

y2≡x3+3mod  py^2\equiv x^3+3\mod p

交互/解ECDLP#

得到阶数 模数的构造后 可以直接发送过去 得到两个点的值 因为阶能拆成三个47bits的小因子 可以在O(q)O(\sqrt{q})的复杂度下做BSGS解DLP 实测大概14分钟能够跑完

后续求出来第一段的flag bit还不够 稍微爆破一下即可

solve.sage

imaginaryCTF-Ⅰ
https://www.zt2misay2.cn/blog/imaginaryctf-%E2%85%B0
Author Misay
Published at 2026年2月15日