和rec鏖战到半夜终于做出了ckks,算是锦上添花,恭喜NK拿到冠军!
easy_log
题目
题目描述:
1 | E@sy L0g |
题目:
1 | from Crypto.Util.number import long_to_bytes, bytes_to_long, isPrime |
题目给出了一套曲线点加和倍乘运算法则,在此基础上给出:
- 一个作为模数的合数$n$
- 两个起始点$G_1, G_2$
之后题目大概可以分为两个任务:
- 靶机在$Z_n$下计算$f_1 \cdot G_1$并给出,要求30s内计算出$f_1$并发回给靶机
- 输入一个400bit的素数$p$,靶机在$Z_p$下计算$f_2 \cdot G_2$并给出
思路
可以看出这题就是要解决题目给定曲线上的ECDLP,而一般要根据点加和倍乘运算法则,先找出对应的曲线方程,然后找对应映射,题目才会更清晰一点。
然而实际上这题并不需要这样,首先上factordb可以查到$n$的分解:
1 | n = 0x231d5fa471913e79facfd95e9b874e2d499def420e0914fab5c9f87e71c2418d1194066bd8376aa8f02ef35c1926f73a46477cd4a88beae89ba575bb3e1b04271426c6706356dd8cd9aa742d7ad0343f8939bfd2110d45122929d29dc022da26551e1ed7000 |
由于题目提供了点加和倍乘运算,这使得我们可以在$n$的任意一个素因子$p$下自行计算点乘,而这个操作的意义在于我们可以手动试一下题目点群的阶,测试出点群的阶基本是$p-1$或$p^2-1$。
进一步检查所有$p-1$的因子,会发现光滑的因子很多,这说明可以直接利用题目的点加和倍乘手写一个pohlig hellman+bsgs的ECDLP求解即可。由于第一部分求解的$f_1$是544bit的数量级,而光滑因子乘积就有520bit出头,并且由于有一个$p^3$因子的存在,还可以用p-adic求出$p$下的dlp,所以比特数完全够了。
第二部分就更简单,构造一个$p-1$光滑的不行的$p$发过去,再本地再运行一次pohlig hellman+bsgs就能得到$f_2$。
exp
(最后交给rec手动交互去了,还有个hashcash
的pow要过)
1 | from Crypto.Util.number import long_to_bytes, bytes_to_long, isPrime |
1 | from Crypto.Util.number import * |
1 | def lift_to_pk(p, G, kG): |
1 | k = getrandbits(540) |
1 | ACTF{OL#m9Lpg8D1$<R3&e=10g@1N%F^nQ02pKjgZo0Oo!$Mp} |
其他
虽然题目用不上曲线方程,但比赛中当然还是要靠方程清晰一下思路,这里正好分享一个比较通用的曲线方程插值法。
假设我们拥有足够多的曲线上点(这可以通过题目给的点加和倍乘做到),那么可以假设曲线方程为下列向量的内积:
其中$\mathbf{a}$就是曲线方程每个单项的未知系数组成的向量,因此实际上一个点对就可以提供一个向量方程,搜集足够的单项式向量之后解方程解出$\mathbf{a}$即可。
对于题目来说,可以对$G_2$的倍点应用插值,就可以发现他所在的方程是在$\mathbb{Z}$上成立的$x^2+xy-y^2+1 = 0$,这是一个圆锥曲线,所以不手写ECDLP的话可以去搜下这个圆锥曲线对应点群的映射。
OhMyTetration
题目
题目描述:
1 | Welcome to my Lottery Center! |
题目:
1 | from Crypto.Util.number import bytes_to_long |
简单来说,题目将flag转化为小于等于512bit的大整数$x$,之后随机发送给你一个素数$p$,可以选择一个数字$g$和一个次数$t$发回给靶机,然后靶机计算:
要求从这个值计算出$x$得到flag,并且有一些没有明确说的额外条件:
- $p$并不是完全随机的素数,而是从给定的八个值里随便选的(反复重连测试了一下)
- 发送的$g$需要通过$defineG$函数的check,具体要求不详,但是极慢
思路
首先,多次测试可以得出以下几个额外结论:
- 有几个$p$满足$p-1=2q$,且$q-1$很光滑,基本是32bit的因子
- $g$是素数似乎基本能通过$defineG$函数的check
所以可以类似数信杯的这道DDLLPP,发送$t=2$以及$Z_q$乘法群中的低阶因子作为$g$,然后在已知$f(t, g)$和$g$的情况下bsgs求解$f(t, g) = g^{g^x}$中的子群意义下的$x$并crt起来即可。
exp
$defineG$函数的check实在太慢,接近十分钟才能check一次,有时候直接超时断掉,所以我和rec只能手动开好几个窗口来一个个接下来数据再bsgs,最后手动crt,所以我最后写的相当乱,就贴个rec的exp好了TT:
1 | from Crypto.Util.number import * |
1 | #Lucky won't help you but wisdom can! ACTF{0oooooh_My_T3tr@ti0n!} |
AAALLL
题目
题目描述:
1 | Let’s welcome AAA’s LLL master! |
题目:
1 | from Crypto.Cipher import AES |
题目给出一个$Q = \frac{Z_p[x]}{x^{450}+1}$下的-1,0,1三值多项式$f(x)$,并随机抽取$Z_p$下$x^{450}+1=0$的其中225个根$g_i$,给出所有$(g_i,f(g_i))$,要求还原出三值多项式$f(x)$,从而解AES得到flag。
思路
不妨记$f(x)$的系数向量为$\mathbf{s}$,由拉格朗日插值的矩阵方程,225个点值可列出一个矩阵方程$\mathbf{s}_{1\times 450}A_{450\times 225} = \mathbf{b}_{1\times 225}$,并且可以拆成$\mathbf{s}_1A_1 + \mathbf{s}_2A_2 = \mathbf{b}$,最终可移项化简为$\mathbf{s}_1A_1A_2^{-1} + \mathbf{s}_2 = \mathbf{b}A_2^{-1}$。利用$\mathbf{s}$为短向量的特点,可以构造格:
如果将约束全部取上,则该格的维数为451维,并且由于模数较大、向量极短,可以少取很多组约束并flatter求解。经测试取140组约束即可在几分钟内可以规约出$\mathbf{s}_1$,代入求解$\mathbf{s}_2$即可拼起来得到$f(x)$。
exp
1 | from re import findall |
1 | from Crypto.Cipher import AES |
1 | A = [] |
1 | nums = 140 |
1 | L = flatter(L) |
1 | res = L[0] |
1 | P.<x> = PolynomialRing(GF(p)) |
1 | from Crypto.Cipher import AES |
其他
但是这很显然是个非预期,原因有以下几个:
一般来说,flatter似乎不太适用于这种-1、0、1的很小数量级的规约,只是这题模数大了一些,导致flatter比较适用
要不是题目叫LLL估计真不会往flatter想,这种比较精确的一般都要BKZ
题目给的不是随随便便的点值,而是$x^{450}+1=0$的根
赛后和@rec、@hashhash、@yolbby讨论了一下,其中第二个条件相当关键,factor一下$x^{450}+1$,可以发现它拥有一些结构较特殊的因式:
1 | [x^2 + 1, x^6 + 1, x^10 + 1, x^18 + 1, x^30 + 1, x^50 + 1, x^90 + 1, x^150 + 1, x^450 + 1] |
由于在$Z_p$下,$x^{450}+1$拥有全部450个根$g_i$,这代表着他可以在$F_p$下展开成:
而对于上面那些因式,比如$x^{150}+1$,这代表着$x^{150}+1$也可以写成其中某150个$(x-g_i)$的乘积,而对于这150个特殊的$g_i$来说,由于$g^{150}=-1$,因此其对应的范德蒙德矩阵行向量会出现正负号交替的循环,而此时则可以把对应的行向量三折叠,而系数向量$\mathbf{s}$的每个分量也就对应变成了$s_i - s_{i+150} + s_{i+300}$的形式,这就实现了降维。
而降维的意义在于,$s_i - s_{i+150} + s_{i+300}$这样的分量依然在$Z_p$下显得很小,所以可以规约出这样的所有三项和,这相当于在原来的$\mathbf{s}_{1\times 450}A_{450\times 225} = \mathbf{b}_{1\times 225}$这225个方程基础上,多出了150个新的方程,虽然一部分会是线性相关的,但是依然会有一部分多出来的线性无关的,这就可以让最后的格维数有所降低。
同理,对于$x^{90} + 1$,可以规约出所有五项和,这也可以多出一些方程,在有这些方程的基础上,$\mathbf{s}$的解空间维数就会下降很多,最后规约一个一百三四十维的矩阵即可。
tinyCKKS
题目
题目描述:
1 | EE and ZZ. Isn’t it easy? |
题目:
1 | import random |
这个题目本身文件比较多,所以我也不太能很简洁的描述清楚题目内容,还是要仔细读下他的所有内容才行。这里就直接进做法。
思路
审计代码,发现源码的distribution.sage部分存在一定漏洞:
1 | from random import getrandbits |
这里比较突兀的地方在于高斯采样多项式的系数分布:
- 用了
random
库的getrandbits
- 前一半低次项系数为正,后一半高次项系数为负,并且没有shuffle
- 无论是后面的均匀分布多项式系数采样,还是-1,0,1三值多项式系数采样,都没有再使用
random
库的其他任何函数
那么合理猜测这里的高斯采样是用来进行MT预测的,而主要是生成噪声e的时候会使用高斯采样,因此题目应该是想要想出办法泄露e并解出私钥,并向后继续预测e,从而得到我们需要发回的明文多项式。
但是赛中并没有看出哪里能泄露出e,甚至看不出哪里可以泄露e的任何分量的任何bit(因为不管怎么泄露,够19937bit就有机会),所以没有在这方面继续下去。
而突破点依然在于MT,经过本地测试发现均匀分布的采样里有1024次np.random.choice([-1, 1])
的采样,而经测试,这个采样实际上可以等价于MT意义下的getrandbits(1)
,由此可以构建出一个解题思路:
- 第一次交互,输入
c=1
,之后输入ccc=3
或ccc=4
,在生成ksk
或galois_key
时,会连续生成100个sample_uniform_poly
采样的多项式a
- 从
a
的各项系数是否小于int(q//2)
,可以判断出choice
究竟选了-1还是1,这等价于获得了1bit的MT的信息。100个a
相当于提供了102400=100*1024
bit的信息,远大于19937,所以完全足够 - 凑够19937个bit,从而获取
numpy.random
的初始624个state,从而可以向后预测。这一步可用102400bit中剩余的bit验证是否求解正确 - 第二次交互产生plain时,采用的依然是
numpy
内的函数plain = [round(np.random.uniform(0, r), precision) for _ in range(N)]
,因此可以直接预测该明文对应的编码多项式,发送给靶机即获得flag。
exp
1 | import random |
1 | def strpoly_to_poly(poly): |
1 | print(L.rank()) |
1 | def decision(coeffs, q): |
1 | from pwn import * |
1 | a_bit = [] |
1 | plain = [round(np.random.uniform(0, 10), 4) for _ in range(1024)] |
其他
但很显然这是个非预期,因为numpy之前是真没想过也能打MT,上一次这么震惊还是在@hashhash出的N1CTF的Matrix被sagemath的MT非预期掉XD。