真正的问题并不在 ECDSA 本身。服务生成一个秘密标量后,允许玩家替换椭圆曲线参数,却在过程中悄然复用同一个标量。这样一来,签名密钥就变成了一个位于玩家可影响群上的离散对数问题。
信任边界在哪里失守
易受攻击的流程等价于:
curve = curve_from_player_parameters()
base = curve.random_point()
public = secret_scalar * base对于一条固定且经过谨慎选择的曲线,从 base 和 public 恢复 secret_scalar 理应很困难。然而,玩家在这里可以选择一条群阶含有大型光滑因子的曲线。于是,同一标量会通过公开点对暴露在一个刻意降低了离散对数求解成本的群中。
输入检查还有另一个问题:它检查的是提交的整数,而曲线库之后才会将这些整数对域素数取模。因此,一个与小系数同余的整数,可以先通过长度检查,再在域内产生攻击者想要的小系数。
构造合适的群阶
以下两个曲线族能够简化阶的搜索:
j = 1728曲线族,可由常数项为零的曲线表示;j = 0曲线族,可由一次项系数为零的曲线表示。
复盘过程会为每个候选曲线计算群阶并进行分解。目标不必是一个完全光滑的阶;只要能将若干素数幂因子收集进光滑模数 M,使它足以约束被复用的标量,同时把服务要求的大因子留在剩余余因子中即可。
这里还有一个实际细节:随机基点不一定具有完整曲线阶。攻击必须围绕实际点的阶展开,并且只能保留真正整除该子群阶的因子。
恢复标量的剩余类
对于 Q = dG,Pohlig–Hellman 会针对点阶光滑部分中的每个素数幂,将离散对数拆解成更小的问题:
- 将
G和Q投影到各个素数幂子群; - 在每个子群中求解较小的离散对数;
- 使用中国剩余定理合并这些剩余值;
- 得到
d mod M,而不是一次猜测完整标量。
题目还增加了第二层约束:标量是恰好 21 个小写 ASCII 字符的大端整数表示。若 r 是恢复出的剩余值,则所有候选都满足:
d = r + kM已知长度给出了非常紧的数值区间。检查全部 21 个字节是否都处于小写 a 到 z 之间,就能排除几乎所有候选。最后,只需验证 dG 是否等于给出的公钥点,便能无歧义地确认剩余值。
伪造有效签名
恢复标量后,最后一步就是在选定子群中执行常规 ECDSA 运算。选择一个在点阶模下可逆的临时随机数,由对应点导出 r,再计算:
s = inverse(nonce) * (message_digest + r * d) mod point_order复盘先在本地验证伪造签名,再将其用于题目的验证流程。
防御要点
- 不要跨攻击者可控的群或域参数复用秘密标量。
- 应在约简后验证规范域元素,而不是只检查提交表示的位长度。
- 应同时验证曲线、子群阶与基点。
- 一旦有任何模信息泄漏,固定小写字符串等结构化秘密生成方式就会显著降低熵。
赛事页面:CTFtime 上的 V1T CTF 2026。
