CryptoCTF 2025 easy-peasy 分类 团队解题 writeup
这次 easy-peasy 分类比赛的时候我一道都没看着,全被队友秒了,这次借写 writeup 的时候我们一起来看一下
要懂得把球传给队友(叛徒除外)
Interpol
题目描述
记消息为 $m$,其长度为 $n$。先生成一系列的点 $(x_i, y_i)$,分为两部分:
第一部分 点 $(x_i, y_i)$ 满足:
其中 $0 \le i < n$
第二部分 点 $(x_i, y_i)$ 满足 $0 \le x_i < 313$,$y_i$ 为一个负的有理数,其中 $i \ge n$
将这两部分点放在一起,构造一个 拉格朗日多项式。已经有这个拉格朗日多项式,求消息 $m$。
我的解答
主要难度在于看懂题目代码在赣神魔。
其实把题目代码中 DATA
的值给打印下来,也能猜到八九不离十。
所以如果我们有那个多项式,并且也知道第一部分的点生成规律是怎么样的话,其实就可以枚举所有的 $i$,代入得到 $x_i$,再代到多项式里面得到 $y_i$,再根据下标关系放到 $m$ 对应的下标里面就行了。
然后在处理 I/O 的时候可能要小猜一下直接用 SageMath 内置的 loads
函数即可从 bytes
中读取多项式(对象)。
还有一个问题:消息 $m$ 的长度 $n$ 不知道怎么办?直接嗯枚举拉倒了:枚举 $f(-1), f(-2), \ldots, f(-n)$,然后枚举到 $f(-(n+1))$ 的时候发现不是整数,就可以得到 $n$ 的值。
代码如下:
1 | with open('output.raw', 'rb') as f: |
得到 flag 为:
1 | CCTF{7h3_!nTeRn4t10naL_Cr!Min41_pOlIc3_0r9An!Zati0n!} |
Mechanic
题目描述
题目基于后量子密码库 quantcrypt,用了 ML-KEM 来生成密钥,以及用 KryptonKEM 来 迭代地 加密 flag 文件。值得注意的是 flag 文件是一个 PNG 文件。
加密一共包含 40 轮,每一轮都有两种可能
- 加密 生成公钥与私钥,并且对上一次加密了的文件利用公钥加密,得到当前加密结果
- 啥都不干 公钥与私钥生成随机的,与正常公私钥等长的随机字节流
每一轮都把 私钥 写到文件中。
有最后的加密结果,求最初的 flag 文件。
我的解答
我一开始看到 40 轮,还以为是利用 flag 的 PNG 头进行中间相遇攻击,知道 flag 的 PNG 头和对应密文,枚举前 20 轮的加密结果,以及后 20 轮的解密结果,然后来对答案。
结果本地跑代码一看,发现每轮的加密结果的文件大小都在增长:譬如我随手画的 flag.png
是 3714 字节,flag_0.enc
是 5892 字节,flag_1.enc
是 9988 字节,flag.png
是 14085 字节……

然后看加密函数的时候发现人家文档里写着:
Uses the KEM
public_key
to encapsulate a 32 byte internally generated shared secret into a KEM ciphertext, which is added as a header to theoutput_file
. The shared secret is transformed with Argon2.Key into a 64 byte symmetric secret key for the Krypton cipher. Then, the plaintext data is read from thedata_file
in chunks and encrypted into ciphertext, writing the encrypted ciphertext chunks into theoutput_file
.
也就是说公钥就在密文里面。
那这个题就更加简单了:我们猜想在解密的时候,库里有某种机制会负责私钥的合法性。
- 如果私钥合法,那么就正常解密
- 如果私钥非法,那么就抛出异常
在测试代码的时候,我发现确实如此:如果私钥非法,会抛出一个错误:
1 | raise errors.CipherVerifyError |
那就好办了:把私钥装进来,从尾到头挨个试就行。
代码如下:
1 | from quantcrypt.kem import MLKEM_1024 |
然后把 flag_-1.enc
重命名为 flag.png
即可

得到 flag 为:
1 | CCTF{k3y_3NcAp5uL4t!0n_M3cH4n1Sms!} |
Vinad
题目描述
$R$ 为 512 个 512 bit 的正整数,$r$ 为一个 512 bit 的正整数
定义 $s(x)$ 函数为 $x$ 的二进制表示中 1 的个数模 2 的余数
定义 $v(x, R)$ 为下面这个二进制数:
生成 $p = v(r, R)$ 为素数,$q$ 为随机 512 bit 素数,$e$ 与 $(p-1)(q-1)$ 互素且 $e = v(r+65537, R)$。
用 RSA 加密消息 $M = m + \sum_{i=0}^{511}R_i$ 得到密文 $c$。已知 $R, n=pq, c = M^e \bmod n$,求消息 $m$
我的解答
值得注意的是,$e$ 是不知道的
然后我们测试了一下,发现$e=p$(之后证明)
观察 $v$ 和 $p$ 函数的定义,发现当我们把$x$ 的二进制形式写出来的时候(高位可以是 0):
然后根据定义就会有
所以假设我们还有一个 $y$
此时就有
也就是说,素数 $p$ 的形式可以进一步被写为
所以此时未知的、影响$p$ 取值的就是$s(x)$的取值,要么是0,要么是1,这个直接枚举一下就行了。
然后就是RSA的经典解密了,代码如下:
1 | #!/usr/bin/env python3 |
代码里面直接利用 $s(0) = 0, s(1) = 1$ 去枚举的。得到 flag 为
1 | CCTF{s0lV1n9_4_Syst3m_0f_L1n3Ar_3qUaTi0n5_0vEr_7H3_F!3lD_F(2)!} |
$e=p$ 的证明
要证明 $e=p$,只需考察 $e=v(r, R)$ 和 $p=v(r+65537, R)$ 的每个二进制位是否相同。
也就是考察 $s(r \oplus R_{i})$ 和 $s((r+65537) \oplus R_{i})$ 是否相同
由上面的线性性质,也就是考察 $s(r) \oplus s(R_{i})$ 和 $s(r+65537) \oplus s(R_{i})$ 是否相同
也就是考察 $s(r)$ 和 $s(r+65537)$ 是否相同
如果不同,则 $s(r)$ 和 $s(r+65537)$ 一奇一偶。反方向进行上面的推理,就有 $s(r \oplus R_{511})$ 和 $s((r+65537) \oplus R_{511})$ 一奇一偶。
由 $p$ 和 $e$ 的定义式,有 $p$ 和 $e$ 一奇一偶。因为 $p$ 为素数,只能是 $p$ 为寄数。那么 $e$ 和 $(p-1)(q-1)$ 必定不互素,因为两者有公约数 2。
所以 $s(r)$ 和 $s(r+65537)$ 相同,即 $e = p$。