25Feb1
huangx607087 2月的切题 1
0.Introduction
近期遇到了几个比较有意思的题目,来看一看!
顺便挂三个令我废透脑筋的题目
1.DASCTF2408-ezSignin
1x01 题目简单分析
先来看一下题目:
1 |
|
乍一看,是一个之前做烂掉的ECDH的密钥交换协议,题目给出了 B 的公钥 $xG$,每一次密钥交换的 sb,B_pri_r
的高位,以及完整的 B_pub_R
的点,并且这边的线性关系式也很简单,$B_{pub,r}$ 给出后,$f(B_{pub,r})$ 也下能当与作为定值给出了。
$$
s_B=B_{pri,r}+f(B_{pub,r})w_B
$$
为了方便展示,我们设 LS,LR,BR
分别记为为 $S,R,P$,其中 $S_{i}=2^{100}s_i+x_i,R_i=2^{100}r_i+y_i$,B 的固定私钥为 $w$。但实际上,LS,LR
中存放的其实是 $s_i$ 和 $r_i$,$x_i$ 和 $y_i$ 是右移被省略的内容。
那么上面的等式就可以写为:
$$
2^{100}s_i+x_i\equiv 2^{100}r_i+y_i-f(P)w \pmod n
$$
整理一下:
$$
x_i-y_i=2^{100}(r_i-s_i)-f(P_i)w-k_in
$$
如果把 $x_i-y_i$ 当作整体,那就是:
$$
z_i=2^{100}(r_i-s_i)-f(P_i)w-k_in
$$
设 $A_i=r_i-s_i,B_i=f(P_i)$,逻辑就出来了:
$$
z_i=2^{100}A_i-wB_i-k_in
$$
所以构造矩阵如下:
由于 $w$ 是 $128$ 位,$z$ 是 $100$ 位,那么需要乘平衡系数为 $\mathrm{diag}(2^{128},1,2^{28},2^{28},2^{28})$,然后LLL求解。代码如下:
1 |
|
这么做貌似确实没有什么问题,我们得到的参数的数量级也对,检查了一下行列式的值,也基本符合要求。但尝试用解出来的 $w$ 解密flag,竟然不对?!
1x02 给 $wG$ 的作用
注意到题目给出了 $wG$ 的值,这个值我们还没有用到。
不管了,测一下它的代码,果然,发现了玄机:最终解出来的 $w$ 和实际的 $w_B$ 有 $±2^{33}$ 数量级的误差,这就导致目标向量其实并不是我们解出来的最短向量。(这个测试的代码可以自己写一个,这个题我是2月1日做的,测试的代码丢了就不发了,自己运行它题目中的代码,输出 $w_B$,再运行上面的解题代码,计算那两个值的差就OK)。
换句话说,$w_B$ 的低 $34$ 位我们不知道,这个值直接枚举太大了,考虑对未知部分进行bsgs,这样才能得到flag。
1 |
|
看来有的时候测一下题目代码还是有必要的:),签个到还真不容易呢。。。
2.[VNCTF2025]sh1kaku_fw
2x01 题目代码
又是一个LWE的题目,来看一下代码!
1 |
|
2x02 题目分析
可以看到题目中给出了一个 $197\times19700$ 的矩阵 $A$,模数 $q$,目标向量 $\vec b$ 和 误差 $e$ 可能的取值。
对于 $A\cdot \vec s+\vec e=b$,如果把矩阵 $A$ 拆成 $19700$ 个行向量,那么每个行向量都有 $\vec a_i\cdot \vec s+e_i=b$,也就是 $b-\vec a_i\cdot \vec s-e_i=0$。
由于 $e_i$ 只有两种取值 $e_1,e_2$,那么必定有 $b-\vec a_i\cdot \vec s-e_1=0$ 或者 $b-\vec a_i\cdot \vec s-e_2=0$。既然两个成立一个,那乘起来就一定有
$$
(b-\vec a_i\cdot \vec s-e_1)(b-\vec a_i\cdot \vec s-e_2)=0
$$
这个方法在25Jan1中解NTRU时也用到过。
既然这样,那么我们要做的就是展开 $\vec a_i\cdot \vec s$,然后这里可以构成一个关于 $s_0,s_1,…s_{196}$ 的多项式。由于是两个相乘,那就会有 $197$ 个一次项 $s_0\sim s_{196}$,$197$ 个平方项 $s_0^2\sim s_{196}^2$ 以及 $197\times98$ 个交叉项 $s_0s_1,…,s_{195}s_{196}$,共 $19700$ 项,将这些变量线性化,看作 $19700$ 个一次的未知数,那我们刚好可以利用题目给出的条件,构造 $19700$ 个线性方程组解这 $19700$ 个未知数。
根据推导,方程左边构造的对应的系数如下:
未知数 | 系数 |
---|---|
$s_i,\space0\le i\lt197$ | $F_i=-2ba_i+(e_1+e_2)a_i-e_1e_2$ |
$s_i^2,\space0\le i\lt197$ | $G_i=a_i^2$ |
$s_is_j,\space0\le i\lt j\lt197$ | $H_{(i,j)}=2a_ia_j$ |
而方程的右边,就是没有 $s$ 的部分,那就是 $-b^2+b(e_1+e_2)-e_1e_2$。
这样,对于 $A$ 中的每个行向量,都可以构造一个这样的线性方程组:
$$
\sum_{i=0}^{196}\left(F_is_i\right)+\sum_{i=0}^{196}\left(G_is_i^2\right)+\sum_{i=0}^{195}\sum_{j=i+1}^{196}\left(H_{(i,j)}s_is_j\right)\equiv -b^2+b(e_1+e_2)-e_1e_2\pmod q
$$
2x03 解题细节
因为分析过程自己已经接触过,所以这一步很顺利,但为了保证正确性,先写个代码测一下:
数据生成代码
1 |
|
测试代码:
1 |
|
在本机测了一组 $n=8$ 和 $n=20$ 都没有问题,看来算法是对的。那就直接上 $n=197$,但这真是炸裂的存在啊!
因为自己windows用的是sage9.3,不妨在虚拟机上跑sage10.4:也就快了一丢丢而已。。。
然后自己问了下出题人dbt,最后一句话引起了我注意:
1 |
|
终于,罪魁祸首找到了,原来sage模 $q$ 有限域太慢了!:
1 |
|
速度果然快起来了,并且快了50倍,好家伙。。。
然而这么做爆内存了(虚拟机内存已经被我调到了 16GB),现在打CTF没高配还不行了是吧,是不是下一台电脑得买 64G 的了?
原来一个int在python中占28字节,那还得再优化一下,用numpy优化成 4 字节,这样内存爆得总算不那么夸张了。。。
1 |
|
不过这个在本地测试的时候,还是跑了 15 分钟,时间还是有点偏长了,原题只有7.5分钟,但解方程组作为正解,这还真不好简化掉。
注意到这里构造矩阵用了 7 分钟,考虑多进程加速:开 4 个进程同步写数据构造矩阵,这样构造矩阵的时间就缩短到了 2 分 20 秒。,耗时大概十二分半,那么我们还有三分钟时间接收和发送数据。由于这个题的交互量不大,因此这个时间也算是够了。
2x04 exp
最终完成的代码如下,4进程耗时 13 分钟,本地把时间调成了 900 秒做的,实在想不出还有什么优化方法了。。。(也看不到出题人的exp):
1 |
|
3.[N1CTF_junior]StrangeCurve
3x01 题目代码
先看一眼题目代码:
1 |
|
3x02 题目分析
这个题目实现的是CSIDH的过程,但对CSIDH做了改动,我们注意action函数中的这一部分:
1 |
|
我们会发现,在第round轮中,有一半概率使得CISDH中取的 $P$ 点的 $y$ 值在 $\mathbb F_{p^2}$ 而不是 $\mathbb F_{p}$ 上,回顾刚学过的 CISDH,这会导致在第round轮同源的方向变为原来的反方向。如果原来的密钥为 $[3,0,3,2,1,4]$,那么理想情况下,密钥的变化应当为:
1 |
|
假设round=2时发生了翻转,那么实际情况就是
1 |
|
也就是说,如果发生翻转,那么第 $2$ 轮中,所有密钥值大于 $2$ 的私钥分量都会逆着走一步,这样和正确的密钥就相差了 $2$ 步。用更加严谨的说法就是:设正确的共享值为 $V$,那么第 $k$ 轮若发生翻转,则最终的共享值为 $[(\prod_{e_i>k}\ell_i)^{-2}]V$ 。
但我们需要注意一个问题,由于在CSIDH中,对于不同的 $\ell$,$P$ 是随机取一个阶为 $\ell$ 的电脑点,因此对于每个 $\ell$,均有 $1/\ell$ 的概率取到无穷远点 $\mathcal O$,这就会导致这一轮提前结束,但不会影响到最终结果(在 $\ell$ 较小的时候该情况出现较多)。
1 |
|
这个过程也可以用下面的示意图表示,虽然最终结果一样,但轮数多了 1 轮。不过这种错误可以用多次取样规避。
1 |
|
但就算出现上面这种情况,由于私钥的最大值为 $6$,而题目允许输入的最大值为 $15$。经过测试发现,round的值几乎不会超过 $11$,因此可以先发送 $15$,获取正确的共享秘密值 (也就是蒙哥马利参数 $a_{15}$),然后多次测试 $5,4,3,2,1,0$,取次最大概率(因为最大概率是 $a_{15}$,该概率占到一半,测了一下,取样 $600$ 次基本就可以得到理想状态下的 $a_5,…,a_0$ 值)。
然后看了一下 出题人的博客,他给出的做法是MITM,也就是中间相遇攻击,但没有给出进一步的细节。然后我就以为是从 $a_{15},a_{5},a_{4},a_{3}$ 走一个过程,然后再从 $a_{0},a_1,a_2,a_3$ 走一个过程,找相同的 $a_3$。考虑到私钥是 $0\sim6$ 的取值,且私钥长度为 $26$,因此可以赌每个值出现次数不超过 $5$,每一轮需要枚举 $83000$ 多次,耗时三小时,即使开四进程也要 40 分钟一轮,$1000$ 次的成功概率约为 $180$ 次。
3x03 正确做法
然后我问了一下出题人,结果发现是从 $a_{15}$ 出发,每次分别只考虑一个值。在理想情况下,设 $e=[6,0,6,2,5,4,6,3]$,那么我们有:
$$
a_{5}=[-2,0,-2,0,0,0,-2,0]a_{15}
$$
我们可以对半找一个中间值 $a_{5,15}$,满足:
$$
a_{5,15}=[-2,0,-2,0,0,0,0,0]
$$
这样,从 $a_{15}$ 去逆推 $a_{5,15}$,只需要考虑后半部分,从 $a_5$ 去正推 $a_{5,15}$,只需要去推前半部分,两边枚举的复杂度就会降低到原来的一半。
可以直接利用他的action,把round设置成9999去正推。而逆推根据CSIDH的加三等价性,也就是 [3+i for i in privkey]
,那么可以把不需要逆推的部分设置成3,需要逆推的部分设置成1。对应代码如下:
1 |
|
但这样第二个步骤会走得很慢。因为priv中有很多 $3$,意味着程序至少要走三轮。
所以可以把action这么改,这样就反过来走了:
1 |
|
3x04 exp
3.4.1 初步exp
根据3x03的分析,初步给出exp。
1 |
|
运行结果,耗时约 2h。
程序每一轮输出结果为:$(2,1056),(166,5160),(174,5164),(702,5548),(1022,5565),(2046,6079)$,那么整合这些结果和flag的密文,就可以解密了:
1 |
|
但我们可以看到上面的进度条:这一部分那我们建立字典还是消耗了非常长的时间。
3.4.2 exp++
回顾刚才的思路不难注意到:$a_{15}$ 到 $a_{5}$ 的枚举中,我们所有私钥值 $e_i>5$ 的部分会被标记出来;而 $a_{15}$ 和 $a_{4}$ 的枚举中,所有私钥值 $e_i>4$ 的部分会被标记出来。后者标记的内容是完全包含前者的。因此可以进一步设置两个状态标记,消除掉不符合的情况,也就是 $a_4$ 的标记没有包含 $a_5$ 的情况,我们在这种情况下直接 contiue
掉,不做 action
以加速过程。
这一部分代码如下
1 |
|
那么完整的改善后的EXP就是:
1 |
|
运行结果,还是缩短了一点时间的:
9.总结
不得不说有的题是真的难:(,需要的trick一时半会还真想不到,得去多积累。。。
2,3两个题官方只给了思路,没有给exp,在这里也算自己写了一下exp,或许记忆更深刻吧
后面准备再看看鸡块佬的那个 New Year Ring 4中的trick,那也是个RLWE的多项式构造,虽然他博客里面讲得很清楚了,但不自己写代码实现以下,或者测一下,可能还是不会太理解。