Misay's blog

Back

Above#

很好的比赛 感谢出题人师傅们的题目😋

本场比赛的exp归档 ↗ 从这里精彩的命题工作 也让人逐渐思考如何去在Agent时代下 出一道还能让人学到东西的Crypto赛题

  • 跨方向的交叉 Wispher-Line
  • 更加需要观察力 启发性的赛题构造 Wispher-Line & 神秘学
  • 更长的代码量审计 ez_random

的确也感觉 LLM的不断强大 打比赛 做归档会越来越简单 但是解出赛题的反馈也越来越低了…

为了追求效率 Repo里面的源码解析基本都是LLM写的 但是我还是希望在可能被读到的博客上 用点我自己的理解向你来传达我解题时候的思考 工作 乃至写Prompt的思路等等 希望能帮到你 希望你看它感觉有趣


Login#

app.py

似乎是非预期

ADMIN_PASS = os.getenv("ADMIN_PASS", "admin123")
FLAG = os.getenv("FLAG", "XMCTF{xxxxxx}")

# 初始数据,禁止注册 admin
USERS = {"admin": ADMIN_PASS}
py

远程似乎没有设置这个变量 这里直接用admin123登录就可以了

审计了一下预期 关注这里的

def get_session_data(token_hex):
    if not token_hex: return None
    data = bytes.fromhex(token_hex)
    iv, ct = data[:16], data[16:]
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    decrypted = cipher.decrypt(ct)
    print(decrypted)
    return unpad(decrypted, 16).decode(errors='ignore')
py

token是用户可以自行构造输入的 但是靶机直接拿他来进行解密了 那么可自己去构造特殊的token

利用AES unpad末尾字节和长度的关系 可实现中间态的逐字节恢复

  1. 固定一个目标密文块 CiC_i
  2. 可控它前一块 Ci−1C_{i-1},或者首块时可控 IV
  3. 因为Pi=DK(Ci)⊕Ci−1P_i = D_K(C_i) \oplus C_{i-1}
  4. 不断修改 Ci−1C_{i-1} 的最后一个字节,直到 unpad 成功
  5. 一旦成功,说明最后一字节已经变成合法 padding,比如 0x01
  6. 于是可以推出 DK(Ci)D_K(C_i) 该字节的值
  7. 再继续做倒数第二字节,让末尾变成 0x02 0x02
  8. 一直倒推完整个块 拿到 Ii=DK(Ci)I_i = D_K(C_i)
  9. 再构造 Ci−1′=Ii⊕PiC'_{i-1} = I_i \oplus P_i 伪造admin token

预期Oracle打法exp

ECC#

task.py

给出了一个曲线

def add(P1, P2):
    l = (y2 - y1) * pow(x2 - x1, -1, p) % p
    x3 = (l**2 + a * l - b - x1 - x2) % p
    y3 = (l * (x1 - x3) - y1 - a * x3 - c) % p

def double(P):
    denom = (2 * y + a * x + c) % p
    num = (3 * x**2 + 2 * b * x + d - a * y) % p
    l = (num * pow(denom, -1, p)) % p
    x3 = (l**2 + a * l - b - 2 * x) % p
    y3 = (l * (x - x3) - y - a * x3 - c) % p
py

对应方程

y2+cy=x3+bx2+dx+ey^2 + cy = x^3 + bx^2 + dx + e

然后需要解ECDLP

P=m⋅GP=m\cdot G

实际上把曲线的运算代码喂给LLM 让它审计曲线的性质 它会做变形

2y+c→Y2y+c\rightarrow Y Y2=4x3+4bx2+4dx+4e+c2=f(x)Y^2 = 4x^3 + 4bx^2 + 4dx + 4e + c^2=f(x)

发现ff是可以因式分解为

f(x)=4(x−r)3f(x) = 4(x - r)^3

从而可换元得到

Y2=4X3Y^2=4X^3

拷打了一下LLM 给出了这种曲线的处理手法

做换元

u=2XYu = \frac{2X}{Y} u2=4X2Y2=1Xu^2 = \frac{4X^2}{Y^2}=\frac{1}{X}

故而

X=1u2    Y=2u3X=\frac{1}{u^2}\;\;Y=\frac{2}{u^3}

此时曲线上的任何一个点 都可以表达为单参数 uu的简单式子 再去代入点加的方程

椭圆曲线的加法定义为

P1+P2+P3′=OP_1 + P_2 + P_3' = \mathcal{O}

显式设出来直线

Y=kX+bY = kX + b

代入式子

2u3=k(1u2)+b\frac{2}{u^3} = k \left(\frac{1}{u^2}\right) + b

整理

bu3−ku+2=0bu^3 - ku + 2 = 0

由三次方程韦达定理 必定有

u1+u2=−u3u_1+u_2=-u_3

和这边的点加公式是完全一个代数结构的 也就是说

u(P1+P2)=u1+u2=u(P1)+u(P2)u(P_1 + P_2) = u_1 + u_2 = u(P_1) + u(P_2)

我们把PP的坐标代入uu的运算 实现了把DLP映射到一个简洁的加法

这题就这样处理了 主要关系还是还原后的方程 失去了低次项的扰动 太过规整 可直接整理出强代数性质

神秘学#

这题防LLM做的其实是很好的 大部分的Agent都会认为不可解

因为给出多项式

f(x)=x3−ax2+bx−c−knf(x) = x^3 - a x^2 + b x - c - k n

求导后

f′(x)=3x2−2ax+bf'(x) = 3x^2 - 2ax + b

插值 与cc完全无关 就认为不可解了 但是不太准确 类比三次函数拐点/零点重合的情况 可假设导函数的零点和原函数的零点重合 确实能解出flag 图像如下

简要思路就是爆破kk 利用a  ba\;b和xx的数量级差异 恢复a  ba\;b 再假设零点关系 恢复cc解密RSA

相关的原理可以等其他师傅的分析来看看为什么能够这样

sda#

task.py

给出了(Ai,Bi)(A_i,B_i)三组参数 以及约束

Bixi2−φ(Ai)y2=ziB_i x_i^2 - \varphi(A_i) y^2 = z_i

其中

xi2<Aδ,y2<Aδx_i^2 < A^\delta,\quad y^2 < A^\delta

相关参数为

A=max⁡(A1,A2,A3),δ=k(2α−1)2(k+1),k=3A = \max(A_1, A_2, A_3), \quad \delta = \frac{k(2\alpha - 1)}{2(k+1)}, \quad k = 3 ∣zi∣<pi−qi3(pi+qi)y2Ai1/4|z_i| < \frac{p_i - q_i}{3(p_i + q_i)} y^2 A_i^{1/4}

要求恢复y    xiy\;\;x_i计算AES密钥

y2+x12x22x32y^2 + x_1^2 x_2^2 x_3^2

给出的AA是可以很轻松分解的 从而φ(Ai)\varphi(A_i)也可以直接计算 随后可以做换元配平参数次数

令

ki=xi2,v=y2k_i = x_i^2,\quad v = y^2

得到

φ(Ai)v+zi=kiBi\varphi(A_i) v + z_i = k_i B_i

我们有4组未知数v    kiv\;\;k_i 和扰动量ziz_i 这里的线性也很强 自然想到走格 看看变量大小

ziz_i的上界明确给出 稍微估算一下

Ci=p−q3(p+q)A1/4C_i = \frac{p - q}{3(p + q)} A^{1/4}

这个是确定的常数界 注意到分式几乎齐次 那么CiC_i的数量级就基本上是101010^{10} 这里的系数足够小 相对模数BiB_i 那么我们就可以尝试利用线性做格 配平vv的影响

其实Prompt按照这边给 告诉LLM 用格来打zz的小就可以自己调出来这个SVP格了🥲 但我们还是脑一下为什么可以这样打

首先按照前面的思路 我们希望的目标向量是

Vtarget=(z1,z2,z3,v)V_{target} = (z_1, z_2, z_3, v)

但是vv的数量级不知道的 我们补上一个未知 但是确实可以求的参数 来配平数量级

Vtarget=(W1z1,W2z2,W3z3,−v)V_{target} = (W_1 z_1, W_2 z_2, W_3 z_3, -v) 这个目标向量是均匀的

因为zi=kiBi−φ(Ai)vz_i = k_i B_i - \varphi(A_i) v那么代入未知参数 以第一个式子为例子 W1z1=W1(k1B1−φ(A1)v)=k1(B1W1)−v(φ(A1)W1)W_1 z_1 = W_1 (k_1 B_1 - \varphi(A_1) v) = k_1(B_1 W_1) - v(\varphi(A_1) W_1) 再代回目标向量 得到 Vtarget=(k1B1W1−vφ(A1)W1,  k2B2W2−vφ(A2)W2,  k3B3W3−vφ(A3)W3,  −v)V_{target} = (k_1 B_1 W_1 - v \varphi(A_1) W_1,\; k_2 B_2 W_2 - v \varphi(A_2) W_2,\; k_3 B_3 W_3 - v \varphi(A_3) W_3,\; -v) 到这一步 再看格子 L=(B1W10000B2W20000B3W30φ(A1)W1φ(A2)W2φ(A3)W31)L = \begin{pmatrix} B_1 W_1 & 0 & 0 & 0 \\ 0 & B_2 W_2 & 0 & 0 \\ 0 & 0 & B_3 W_3 & 0 \\ \varphi(A_1) W_1 & \varphi(A_2) W_2 & \varphi(A_3) W_3 & 1 \end{pmatrix} 就很自然了 格完取第一条向量的最后一个元素 可以拿到 vv 然后利用已有的代数近似值来估计

ki≈φ(Ai)Bivk_i \approx \frac{\varphi(A_i)}{B_i} v

再回代求ziz_i 验证一下误差关系 然后解密AES就解决了


LLM真的不擅长格吗 很难说 最起码最近用Agent打的比赛调通格子比调通一些对称成功率高很多了 很多时候你告诉他有flatter可以用 要打哪些变量的SVP 确实相关的执行力都超级强 顺带一提AliyunCTF2026的那道Griffin uuid格确实可能很需要一些人工的Trick优化 可以参考ZJUCTF2025 Wuy4n的题

truck#

非常简洁的一道哈希题

chal.py

三层验证

  1. 输入 A, B, C,要求: md5(A) = md5(B) = md5(C)
  2. 输入 D, E, F,要求: md5(md5(A) || D) = md5(md5(B) || E) = md5(md5(C) || F)
  3. 输入 G, H, I,要求: md5(hd || G) = md5(he || H) = md5(hf || I)

询问LLM后发现md5存在一个很经典的同前缀碰撞 简要分析就是

MD5 采用的是一种叫做 Merkle-Damgård (MD) 结构的设计。 这种结构的核心思想是“分块处理”和“状态传递”:

  • 它会将输入的消息分成固定大小的块(Block)
  • 它维护一个内部状态(State),初始为一个固定的初始化向量(IV)
  • 它用一个压缩函数(Compression Function),将“当前块”和“当前状态”混合,生成“下一个状态”
  • 最后一个状态就是最终的哈希值

只要在某个中间步骤,两个不同消息的内部状态变得完全一样了,那么后续无论给它们追加什么相同的数据,它们的最终哈希值都会保持一致

由此可以用 fastcoll ↗ 工具 对md5进行攻击 快速实现对于任何前缀 PP 找到两块不同的 SiS_i 满足

MD5(P∥S0)=MD5(P∥S1)MD5(P \parallel S_0) = MD5(P \parallel S_1) 这样就可以快速的通过第一次校验


关于后续的情景构造 参考 Joux’s Multicollision ↗ 并没有很多工程上的复杂性 我们从中知道 多次fastcoll得到的碰撞的复杂度 和寻找一次fastcoll的复杂度没有数量级的差别 这就允许我们

  • 先用 fastcoll 生成 S0S_0 和 S1S_1,使得 MD5(S0)=MD5(S1)=H1MD5(S_0) = MD5(S_1) = H_1
  • 再把H1H_1作为新的前缀,再跑一次 fastcoll,生成 T0T_0 和 T1T_1,使得 MD5(H1∥T0)=MD5(H1∥T1)MD5(H_1 \parallel T_0) = MD5(H_1 \parallel T_1)

得到以下碰撞

S0∥T0S_0 \parallel T_0 S0∥T1S_0 \parallel T_1 S1∥T0S_1 \parallel T_0 S1∥T1S_1 \parallel T_1


所以说 对于第一层 令 PP为"" 走fascoll 可以得到 A B C三个后缀 都满足 md5(A)==md5(B)==md5(C)==h0

然后对于第二层 由于md5(A)==md5(B)==md5(C)==h0 令前缀为 h0 然后fastcoll 找出D E F 其他同上

第三层也是完全一样的

RSA_LCG#

一道经过修改的LCG题

代数结构梳理#

先生成64bytes的LCG seed 然后构造LCG实例 用RSA的方式给出LCG的两次递推输出si+1≡asi+b(modN)s_{i+1} \equiv a s_i + b \pmod{N}

ci=sie mod Nc_i = s_i^e \bmod N

这里的问题在于NN是不可分解的强合数 故而LCG的线性输出会被模幂进行混淆


然后看向题目给出的hint

leak=(a, (b−a)e mod N)\text{leak} = \left(a,\ (b-a)^e \bmod N\right)

可以换元 令 t=b−at=b-a 然后代入已知的式子

s1≡as0+a+t≡a(s0+1)+t(modN)s_1 \equiv a s_0 + a + t \equiv a(s_0 + 1) + t \pmod{N} s2≡as1+a+t(modN)s_2 \equiv a s_1 + a + t \pmod{N}

相关信息攻击?#

sis_i其实都是关于s0s_0的单自由变量 然后tt是未知变量 和bb一个位置 那么可以把系统视为双未知变量的相关系统 类比RSA的相关信息攻击

整理一下 令

L=te mod NL = t^e \bmod N C1=s1e mod NC_1 = s_1^e \bmod N C2=s2e mod NC_2 = s_2^e \bmod N

如果令未知量

X=s1,Y=tX = s_1,\qquad Y = t

则可以写出一个二元多项式系统:

f1(X)=Xe−C1≡0(modN)f_1(X) = X^e - C_1 \equiv 0 \pmod N f2(Y)=Ye−L≡0(modN)f_2(Y) = Y^e - L \equiv 0 \pmod N f3(X,Y)=(aX+a+Y)e−C2≡0(modN)f_3(X,Y) = (aX+a+Y)^e - C_2 \equiv 0 \pmod N

我们要恢复 X=s1X=s_1 和 Y=tY=t,再逆推出 s0s_0


f3f_3和f1f_1关于XX有公共根s1s_1 但是有额外变量YY的干扰 但是我们发现 f2f_2和f3f_3对于YY也有公共根tt 那么可以尝试关于YY 对f3  f2f_3\;f_2做结式

那么f3f_3就退化为关于XX的单元多项式 这个时候再与f1f_1做结式就可以恢复XX的非平凡根s1s_1了


优化一下#

但是本题的ee为263263 如果强行对f3  f2f_3\;f_2进行结式 相关系数矩阵 以及最后结式的维度会膨胀到 e2=69169e^2 = 69169 再取gcd做 完全无法在4秒内跑通 此时关于这个点 询问LLM后给出了一个很棒的优化思路

我们最终求XX的根实际上是求 gcd⁡(R(X),Xe−C1)\gcd(R(X), X^e - C_1) 由辗转相除的性质

gcd⁡(A,B)=gcd⁡(A mod B,B)\gcd(A, B) = \gcd(A \bmod B, B)

那么 本质上只需求 R′(X)=R(X) mod (Xe−C1)R'(X) = R(X) \bmod (X^e - C_1) 再做gcd也能得到我们想要的结果

也就是说完全没有必要展开到69169维的完整结式 而可以利用相关多项式的对称性进行插值来还原我们希望的余数多项式

LLM给出了具体的优化思路

注意到多项式f3f_3实际上也很特殊

f3(X,Y)=(Y+K)e−C2f_3(X,Y) = (Y + K)^e - C_2

再做结式

R(X)=Res⁡Y((Y+K)e−C2, Ye−L)R(X)=\operatorname{Res}_Y\big((Y+K)^e-C_2,\ Y^e-L\big)

我们的目标结式可以看成关于KK 也就是 XX的单变量函数 接下来LLM的关于扩域等的说明感觉不讲人话 以我的理解就是

现在有两个多项式

f2(Y)=Ye−Lf_2(Y) = Y^e-L f3(X,Y)=(Y+K)e−C2f_3(X,Y) = (Y + K)^e - C_2

注意到联立这两个约束系统 那么 YeY^e和LL本质等价 我们看向 f3f_3 如果所有的YY的次幂都能被常数替换就好了 实际上的确可以 但是以一个更直观的视角

对于这个等式

C2e=(Y+K)eeC_2^{e}=(Y+K)^{e^e}

这下子关于YY 就都是ee以上的次幂了 完全可以用LL的幂次代替 那么消元好 剩下来关于KK的幂次很高 但是都可以换元 G=KeG=K^e 有

a1G1+⋯+aeGe+b1L+b2L2+⋯+beLe+S=C2ea_1G_1+\cdots+a_eG^e+b_1L+b_2L^2+\cdots+b_eL^e+S=C_2^e

太棒了我们得到了一个关于GG的263次多项式 那直接插值就好了 这一步就可以做了


恢复一个X=s1X=s_1 那么再由退化的f3f_3和f1f_1做GCD即可恢复tt

本题给的时间线很死 用cpp来写插值和gcd的部分 勉强卡进了4s界限

Ocean#

chal.py

系统总览#

一道基于停走计时器设计的双LFSR

  • LFSR1是正常的线性反馈 更新移位

  • LFSR2每轮都要根据LFSR1来确认自己的行为

    • 如果LFSR1本轮输出1 那么LFSR2也按照正常行为更新一次
    • 如果LFSR1本轮输出0 那么LFSR2停滞一轮

两个LFSR共用一个seed 靶机返回LFSR2的64位输出 要恢复seed解密secret


有点束手无策的 毕竟两个LFSR的状态迁移要通过output确定关系 但是这个线性没有那么好观测 我们比较宏观的看一下这个系统

所有的行为实际上都只由64位的seed决定 而我们已知反馈多项式 和最终的64位输出 也就是说未知量和已知量是配平的 考虑到我们假设一个seed 就能完整的模拟整个系统的行为 不难让人联想到符号执行


代数结构分析#

LFSR在GF(2)\mathbf{GF(2)}上可以看成关于初始向量seed的线性变换 由于反馈多项式mask是已知的 那么转移矩阵可以直接写出 也就是LFSR的每一步都可以看成 xx的矩阵乘法

但可惜我们观测的是LFSR2 而不是1 我们无法直接确认每一轮的转移情况


但是对于LFSR2的输出 如果我们观察到相邻的两位不一样(比如 zt−1=0z_{t-1} = 0,但 zt=1z_t = 1)说明LFSR2肯定更新了状态

从这里可以推断 LFSR1在这轮必定输出了yt=1y_t=1 这直接对应到矩阵里某一行的乘法结果


那么,然后呢?#

没错 LLM就做到这一步了 这就是它核心的约束思路 它关于这个约束建立了DFS 并且用上了许多剪枝和效率的优化 强行搜索求解了seed 一股非预期的感觉啊🫠


优化细节#

翻转剪枝#

也就是前文提到的 Output的比特翻转可以明确确定LFSR1在此处的输出为1 是最强的一层约束

区间限制#

有点反直觉 但是output里面实际上连续的01块是很多的 类似

11111011111111011010110100000111011100000001001010001110110110
bash

因此观测的时候 对于某个长度确定的chunk 我们如果按位猜测LFSR1的状态 那么猜测复杂度是2L2^L L是chunk长度

但是如果我们猜测的是chunk内 LFSR1产生1的个数 那么猜测复杂度只有L+1L+1 直接数量级的优化了

然后梳理一下能有哪些约束优化

  • 开局必须翻转 作为一个chunk的开始
  • 末尾计算结果必须与下一个chunk的比特值正确对应
  • 由output 可以明确确定一个chunk内LFSR1输出1的个数为 uu 设未知位个数为 vv
    • 此时猜测复杂度退化为 u≤Δ≤u+vu \le \Delta \le u+v
    • 存在Δ=u\Delta = u与Δ=u+v\Delta = u+v两个极值情况

上述的猜测可以构造一个搜索树 然后直接用LFSR2的输出来剪枝

线性结构不变#

对于一个chunk 即使我们确定了Δ\Delta 但是仍然会有(vΔ−u)\binom{v}{\Delta - u} 种排列能够满足相关约束 但是由于LFSR的性质 相关对应比特位的异或性质还是满足的

比如假设确定Δ\Delta后 1001, 01101001,\ 0110都是满足条件的 那么始终都会满足 c1⊕c2=1c_1 \oplus c_2 = 1 与 c1⊕c4=0c_1 \oplus c_4 = 0 从这里又建立了约束

代码中就是每次猜测剪枝约束完Δ\Delta后 把

⟨⨁i=0L−1rows1[s+i+1],x⟩=Δ mod 2\left\langle \bigoplus_{i=0}^{L-1} rows1[s+i+1], x \right\rangle = \Delta \bmod 2

压入约束系统

利用了LFSR对于奇偶性质 以及相关线性关系的混淆其实比较弱

前缀约束#

LFSR的状态是存在严格的前后状态转移约束的 对于已经确定的比特 再给约束系统加入

⟨rows2[m+1],x⟩=⟨rows2[m+2],x⟩=const\langle rows2[m+1], x \rangle = \langle rows2[m+2], x \rangle = \text{const}

防止搜索过深

题目本身的验证解#

DFS加上以上约束 当剪枝到所求个数到能够接受的范围内 就枚举所有可能的seed 拿来解密enc 观察能否得到有格式的 fakeflag{hex}验证解的正确


具体的wp可以去repo看 但是感觉LLM写的话人也看不懂 这道题解决的还是太粗暴了 想看看其他师傅有没有更优雅的解法 相关prompt可以围绕如何建立线性约束 如何缩小候选来多次向LLM提问来尝试逐步深入思路 同时几乎所有的搜索都得用cpp完成 来保证时间够用

ez_random#

这次最喜欢的一道赛题 源码有三个部分

task.py

patcher.py

local_misc.py 太长了 放repo上了

结构梳理#

先梳理一下项目结构 关键是task , patcher主要调用了魔改的random库 local_misc来给task使用

梳理一下task的逻辑

总共有40轮交互机会

  • 交互选1 使用SEED起一个随机流 然后生成36组数据 也就是(ak,bk)=(getrandbits(988), random_prime(get_limit(k))),k=1,…,36(a_k, b_k)=\Bigl(\texttt{getrandbits}(988),\ \texttt{random\_prime}(\texttt{get\_limit}(k))\Bigr),\quad k=1,\dots,36 这里魔改的random_prime会返回(pk,ck)(p_k, c_k),即最终命中的素数和命中前的尝试次数 但是32个块aka_k是经过shuffle的
  • 交互选2 使用SEED起一个一样的随机流 然后进行36次请求 输入xkx_k 进行random_prime(xk⊕(getrandbits(988)⊕get_limit(k)))\texttt{random\_prime}\bigl(x_k \oplus (\texttt{getrandbits}(988) \oplus \texttt{get\_limit}(k))\bigr) 然后用随机流生成256bits的随机数 AES加密flag给你

交互逻辑理清楚了 随机流只与SEED相关 很清楚 要从两组交互中收集到足够多 关于随机流的信息 实现重放 来得到AES的key

魔改#

local_misc中 关键魔改在

调用random_prime的时候 还会返回尝试的次数count 这个的用法先按且不表


漏洞利用#

Part1#

注意到 这个系统的随机性完全由固定的SEED决定 那么每次重连 所有的shuffle getrandbits random_prime用的都是一个随机流

  • 交互1 (ak,bk)=(getrandbits(988), random_prime(get_limit(k))),k=1,…,36(a_k, b_k)=\Bigl(\texttt{getrandbits}(988),\ \texttt{random\_prime}(\texttt{get\_limit}(k))\Bigr),\quad k=1,\dots,36
  • 交互2 random_prime(xk⊕(getrandbits(988)⊕get_limit(k)))\texttt{random\_prime}\bigl(x_k \oplus (\texttt{getrandbits}(988) \oplus \texttt{get\_limit}(k))\bigr)

我们先连一次靶机 选1 取得36组打乱的aia_i 然后直接断开重连 这次选2 我们知道此时生成的988bits的随机数 一定是a0′a_0^{'} 但是它被shuffle了 问题不大 我们只有36组可能 完全可以每次都试一下 用ai⊕get_limit(k)a_i \oplus \texttt{get\_limit}(k) 唯有碰撞成功的时候 会成功调用random_prime(0)\texttt{random\_prime}\bigl(0\bigr) 而

if n < 2:
    raise ValueError("n must be greater than or equal to 2")
py

此时会报错退出 这样我们就知道一开始的a0′a_0^{'}被shuffle到了哪一个 后续可以一样的思路 先发送已经恢复的原988bits的随机数块 对齐状态 再碰撞观察报错 恢复完所有的shuffle 这一步后 我们就成功恢复了交互1中 36组988bits的随机数生成结果


Part2#

接下来就很清晰了 我们知道了一个MT流中 连续很多组输出 数据量而言 988×36=35568  >19968988\times36=35568\;\gt19968 但是每次生成随机数的时候 都进行了random_prime 即使及时返回了count告诉我们尝试的次数 但是还是产生了大量的干扰 这个时候 我们直接针对线性系统建模 发现不满秩 没法直接求解了 但是MT其实还有个考点 如果系统的随机性仅仅由确定的SEED决定 从https://stackered.com/blog/python-random-prediction/#similarities-with-php ↗ 中是可以 利用MT的迁移链来恢复特殊比特的SEED的 这里虽然没给出 但走这个恢复链 能解出128bits的SEED

这个点VNCTF2026的NumberGuesser 也出过 往早UniCTF也出过 不难想到这个链子的构造


Part3#

恢复SEED后重现随机流 然后解密AES即可

*Wispher_Line#

赛中卡死了 这个构造确实没见过

简要说一下 task.apk允许两个端使用unpadding的RSA进行加密通信 但是只给了公钥(e,N)(e,N) NN是2042bits 但是赛中完全没有分解的思路

感谢Triode师傅 ↗赛后的指点 NN的十进制为

n = 1359289594911861706114410263039030781889501874535854365263922081700238941971104298775704733565166223684142297360239921080802503206559783832007855750334958326368538063619171609885459927586035302195455632768752776216956554963499448005445126927786242177611054827389185330231903625073278897391670581843035646913248732183168634640757720768465749375619368398241192399373901594871829578828976563808877743744957547821735170186252185438724528024345696208656639600908162373375395068724897936084947703562847353279296559280237330305142321357271051046159817216535038453709580872952011626071015235951428711736008848437349560705229
bash

转12进制后会得到

1000010000000000000000000000000000000000000002000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000021122233242421223112412143212234212443034210112244444040402042204402242402044404442022224444404040204220440224240204440444202200000000000000000000000000000000000000001122222020201021102201121201022202221011000000000000000000000000000000000000000022444440404020422044022424020444044420221122222020201021102201121201022202221011
bash

呈现了非常不随机的结构

我们按照12进制展开 N=∑i=0kai12iN = \sum_{i=0}^{k} a_i 12^i 这里的aia_i就是已知的 非常有规律的小量 那么 p  qp\;q也存在12进制下的展开

p=∑i=0kbi12ip = \sum_{i=0}^{k} b_i 12^i q=∑i=0kci12iq = \sum_{i=0}^{k} c_i 12^i

设F(x)=∑i=0kaixiF(x) = \sum_{i=0}^{k} a_i x^i

调用现有的多项式分解接口 由于构造的特殊 可以迅速找到一个解

B(x)=(x285+x280+2x240+2x200+x120+2x40+1)  ∣  F(x)B(x)=(x^{285}+x^{280}+2x^{240}+2x^{200}+x^{120}+2x^{40}+1) \;|\; F(x)

随后完成分解 解密RSA

PolarisCTF2026
https://www.zt2misay2.cn/blog/polarisctf2026
Author Misay
Published at 2026年3月30日