CryptoCTF 2024 hard 分类 团队解题 writeup 之二
这次 hard 分类的题目难道是碳基生物能整出来的活儿???
O7R
题目描述
题目基于 RSA 和七!段!数!码!管!
题目给出了 $p, q, n, n^2$ 的七段数码管的 损坏形式,以及密文 $c$ 的七段数码管形式,其中一个数的七段数码管的损坏形式被定义为:这个七段数码管的每一位数都会以 50%的概率,某一段数码管本来要亮的,结果没亮了,如下就是一个数位 7
损坏了一个数码管的例子:
1 | xxxx xxxx |
题目没说 $e$ 是多少(可能要靠猜,譬如 $e=65537$),求明文 $m$。
我的解答
当时比赛的时候,我没看这道题,未央、三顺七、苏氨酸当时还就被这个男的女的折磨,多捞哦!
这尼玛是碳基生物整得出的活儿?
首先得把他那个七段数码管切成数,至少转换成七段数码管的某个表示,譬如长度为 7 的 01 串。
我们先约定一下这七段数码管的编号:
1 | 0000 |
然后就是把 PDF 转换成图片,这里随便找一个 网站 转换一下
然后就是抠图的过程,这里可以搞个蒙版去对一下抠的对不对,看看行不行,如果不行的话得继续调,譬如这样就得继续看看
这是因为,数字与数字间的距离 不能 直接用两个相邻的数来得到,应该用最左和最有的距离减去,再除以中间间隔的数的个数,这样得到的距离才不会受到累计误差的影响。
1 | import numpy as np |
这样就得到了一个阶段性的结果。
然后我们将十个数字的硬编码写出来,并且用模 $10^k$,不断增加 $k$ 的手法来求解,恢复出 $p$ 和 $q$。注意:
- $p, q, n, n^2$ 的七段数码管表示是 有损坏的
- $c$ 的七段数码管表示是 无损坏的
有损坏可以描述为,这些数每一段长度为 7 的 01 串表示和正常的数位的长度为 7 的 01 串表示相比:
- 不会 出现
(1, 0)
的情况; - 可能 出现
(0, 1)
的情况;且如果出现了这种情况,至多出现一次
得到 $c$,以及求解 $p$ 和 $q$ 的代码如下:
1 | import pickle |
然后就猜 $e=65537$ 并套用 RSA 解密:
1 | from Crypto.Util.number import * |
得到 flag 为:
1 | CCTF{5eVEn_S39Men7_RSA_w!Th_k3Y_rEc0v3Ry!!!} |
其实感觉……还好?YeYe5 拿了一血,哟西
Solmaz
题目描述
题目基于 ECC。
先生成 4 个 32bit 素数 $p_1, p_2, p_3, p_4$,$p=4(p_1p_2p_3p_4)^2+1$。
然后就是生成椭圆曲线 $E_{p}: y^2 = x^3 + c x$,并且该椭圆曲线的阶为 $p-1$。
生成椭圆曲线上两个点 $P, Q$ 满足 $Q = m P$,已知这两个点 $P, Q$,求 $m$
我的解答
和 Chochol 一样,我们 不知道 素数 $p$。
所以我们还是需要将两个点的坐标代回曲线式联立,并消去 $c$,得到一个数,然后 $p$ 就是这个数的素因子。
1 | P = |
然后因为已知 $p-1$ 的素因子小于 $2^{32}$,所以直接使用 Pollard’s p-1 并挂机即可。
一开始想着把 Pollard’s p-1 的 python 实现直接喂给 ChatGPT,让它生成用 GMP 写的 C++代码,但是好像出了点问题……
总之做法应该是这样的,多线程(多进程)应该占不到什么便宜,毕竟 Pollard’s p-1 要一路乘方。
先给出套娃的 python 实现,总之他在比赛的时候挂出来了:
1 | import _thread |
这个代码是用一个线程去做乘方,另一个线程去做,但是这样的话,跟 python 的 GIL 配合得不是很好(等 python 3.13 吧)。在石卡库的机器上只用 1h 就能出结果来,但是我的机器就要挂得比较久,神奇。
总之一通挂之后,得到素数 $p$ 的值为
1 | 30126567747372029007183424263223733382328264316268541293679065617875255137317 |
然后因为 $p-1=4(p_1p_2p_3p_4)^2$,可以直接套 Pohlig-Hellman 解:
1 | from Crypto.Util.number import * |
得到 flag 为:
1 | CCTF{3cC_d1ViSibil!7Y} |
赛后反思
其实之前居然又 TMD 有丶忘记 Pollard’s p-1 是怎么玩的去了。
其实原理也就是利用
已知 $p_1, p_2, p_3, p_4$ 均为 32bit 素数,所以指数那里把所有的 32bit 素数乘起来,也会有:
其中
然后:
- 我一开始在实现的时候是把 $2 \sim 2^{32}$ 的所有整数乘起来
- 后来发现只需要把 $2^{31} \sim 2^{32}$ 的所有整数乘起来
- 后来发现只需要把 $2^{31} \sim 2^{32}$ 的所有奇数乘起来
- 后来发现用
next_prime
会更快 - 当然因为 C++实现
next_prime
也需要一些工夫(至少实现求 $2^16$ 以内素数的线性筛,这个对大学生来说信手拈来,但是对工地狗来说汗流浃背),所以直接打表 100 以内的素数进行判断,就已经能加快很多了 - 最终耗时 51min 就能跑完算法
C++代码如下:
1 |
|
阶为 $p-1$ 其实也可以套 MOV attack:
1 | def get_embedding_degree(q, n, max_k): |
Tesvir
题目描述
题目先将明文 $m$ 表示成 bit 表示形式(01 串),然后重排成 24 个 bit 一行的矩阵,这 24 个 bit 会在以右边追加 0 的形式被扩充为 223 个 bit,右边的右边还会加上(m 中 0 的个数)那么多个 1。
举个栗子:
1 | 01000001010011110100110 |
这个明文矩阵记为 $\mathbf{M}$。
参数生成 记 $\mathbb{F} = GF(223^{24}), q = 223^{24} - 1$。
- 生成一个系数在 $GF(223)$ 中的不可约、首一多项式 $f$,并把 $f$ 的系数转到 $\mathbb{F}$ 中
- 生成 $F$ 中的生成元 $g$
- $t$ 为 $f$ 的一个根
- 生成 $\{1, 2, \ldots, 223\}$ 的一个排列 $r$
公钥生成
- 生成整数 $d$ 满足 $1 < d < q$
- 生成 $\{1, 2, \ldots, 223\}$ 的一个排列 $p$,其中 $p_{i}$ 为这个排列 $p$ 的第 $i$ 个元素
- 计算 $b_{i}$ 为 $t + r_{p_{i}} - 1$ 对 $g$ 的离散对数
- 计算 $c_i = (b_i + d) \bmod q$
- 公钥为 $c_1, c_2, \ldots, c_{223}$
加密算法
记 $\vec{c} = \begin{bmatrix}c_1 & c_2 & \ldots & c_{233}\end{bmatrix}^\top$,则有
已知密文 $C$,公钥 $\vec{c}$ 以及排列 $r$,求明文 $\mathbf{M}$
我的解答
其实看到前面一大摞,都感觉比较抽象,
直接看到最后的加密算法就好,已知公钥和密文,求明文。
他写成了矩阵的形式,我们直接把每行的运算拆开来看,就是一个 01 背包。
所以直接套格就行了。
注意到因为有一个补 1 的过程,所以我们可以枚举右边 1 的个数,只对前 24 个 bit 求解。
然后还有一点就是补 1 的过程之后,有 24 个 1,所以我们还能得到前面未知数求和的一个约束式。
枚举 $t=1, 2, \ldots, 24$,记 $C’ = C - \sum_{k=1}^{t}c_{224-t} + k q$(枚举,注意到 $0 \le k < 24$,可以把这个 $k$ 放进去,但是没必要),最后格也就变成这样:
求解代码:
1 | from Crypto.Util.number import * |
其实把 $q$ 放进格还是有必要的,实测可以快很多:
1 | from Crypto.Util.number import * |
解得
1 | CCTF{9j_h0W_dO_yUo_5oLvE_tH!s_m1X3D_5eMi_H4rD_T4Sk!!!? |
然后石卡库就尬在这里不会做了,因为结尾不是 }
,当时我就破罐子破摔,在结尾再加一个 }
,结果一交就过了,WTF???
所以 flag 就是
1 | CCTF{9j_h0W_dO_yUo_5oLvE_tH!s_m1X3D_5eMi_H4rD_T4Sk!!!?} |
赛后复盘
检查一下:
1 | f"{ord('}'):08b}" |
然后再检查一下这两个位置对应的 pubkey
是不是同余
1 | with open('output_updated.txt') as f: |
结果输出的不是 0。这放 flag 的在搞飞机呢?
老子他妈的不懂什么是格!!老子他妈的只知道暴力!!!
事实上,这题暴力也可以解。
因为 $n=24$,也就是明文矩阵 $\mathbf{M}$ 的一行只有 24bit 的信息,也就是 3byte 的信息,所以可以枚举 3byte 的可打印字符,然后构建一个对应前 24bit 部分和到 3byte 消息的映射。
最后直接尝试将密文减去末尾(补 1 的那一部分的)部分和,并拿着这个值到映射里面反查出消息的内容即可。
代码如下:
1 | from Crypto.Util.number import * |
其实这个暴力的速度还蛮快,而且比较无脑,爽。