最想给st出题人一拳的一集,赛中没做出来的会打*。
shopping
题目:
1 | flag = b"flag{test}" |
题目基于素数分解,对于两个64bit、且相差30bit以内的素数p、q乘积N,要在有限的步数内把他分解掉,并把结果存在ls数组里,从而获取success值。积累20点success值最终拿到flag。
聚焦一下运算执行步骤:
1 | elif choice == '2': |
预期里p、q高位接近,应该是要对N开根然后加速费马分解之类。但既然用了exec,那么能用的方法就很多了:
既然能输入N,那么自然也可以输入p、q啥的,所以拿满success压根不是问题
可以给coins赋值,之后慢慢操作
再比如可以给success赋值,直接达到flag要求
最简单的话可以直接执行
print(flag)
语句,比如下面这个exp:1
2
3
4
5
6
7
8
9from pwn import *
sh = process(["python3", "task.py"])
sh.recvuntil(b"Welcome to my supermarket\n")
sh.recvuntil(b'give me your choice\n')
sh.sendline(b"2")
sh.sendline(b"+")
sh.sendline(b"a.print(flag).1")
print(sh.recvline().strip().decode())
fak1
题目:
task.py:
1 | from sage.all import * |
ntru.sage:
1 | import random |
题目基于如下环上的ntru:
在连接上靶机之后,靶机会先生成:
- 一组公私钥$pk,sk$,其中$pk$一定是可逆的
- 一个secret_poly,记为$s$,$s$是在模3下balance过的,也就是说它由1、0、-1组成。
之后有10次,每次加密可以选择一个消息$m_i$,靶机会生成一个系数由0、1组成的多项式$r_i$,并计算:
并且在decrypt测试成功之后返回$c_i$给我们。
由于$m_i$是我们自己选的,所以不妨令$c_i = c_i - m_i$,那么每次加密本质上也就是对$s$的加密:
而多组加密用的是同一组公钥,那么两两作差可以消去s,得到:
由于$pk$一定可逆,那么把$pk$求逆移项就有:
那么对于向量$r_i - r_j$来说,其每个位置系数代表:
- 系数为1,说明当前位置$r_i$分量为1,$r_j$分量为0
- 系数为-1,说明当前位置$r_i$分量为0,$r_j$分量为1
- 系数为0则不能做出判断
因此10组均用上作差,理论上来说很大概率可以恢复所有$r_i$,但是实际上只和$r_0$作差已经可以较大概率恢复出$r_0$,然后解出$s$就好。
exp:
1 | from Crypto.Util.number import * |
bl0ck
题目:
aes.py:
1 | #!/usr/bin/env python3 |
server.py:
1 | from aes import AES |
题目基于加了后门的AES。首先通过测试可以发现,不加后门的话,题目是标准的AES实现,而后门的作用是减少AES轮数:
而交互流程是:
输入长16字节的key
之后靶机初始化一个HAeSH类hash,其内部state初始值为全0
可以有最多两轮的交互,每次交互可以输入一个block,并更新hash的内部state值
如果交互两次的话,需要满足以下几个要求才行:
- 输入的两个block需要等长
- AES用于作为加密密钥的midstate不能相同
如果交互结束后,hash的state值依然是16字节全0,就可以获得flag
对于每一次交互来说,输入一个block后,hash的state更新是这样的(state记为s,block记为b,key记为k):
由于s初始值为16字节全0,所以对于两次交互来说应该是:
所以我们有以下两种可能可以获得flag的方法:
只交互一轮,那么就要满足:
交互两轮,那么就要满足:
且:
显然第一种会简单很多,我们可以本地先任意输入$b_1$,然后对0做解密,最后把得到的结果作为key发送并发送$b_1$即可。
预期是用两轮的,是用论文数据来作为9轮AES的碰撞,可以参考dbt的wp
exp(先把题目附件的aes.py加上decrypt block,直接用题目附件给出的网址对应实现即可):
1 | from aes import AES |
signin
题目:
1 | from hashlib import sha256 |
题目基于ECDSA,目标是伪造指定消息qwbs8_send_flag_to_me
的签名值,为此题目给了我们678次签名机会,特殊的地方在于以下几个:
- 私钥$d$的低5字节固定为
qwbs8
- 每次签名除了给出$(r,s)$对,还会额外给出作为nonce的$k$的比特数kbits
赛中思路
给了kbits这一信息,那很显然是要做HNP,同时又因为有$d$的低位,因此考虑不作差消去$d$,而直接把他的低位代入签名式:
既然是要做HNP,那么$k_i$当然要选kbits比较小的,而搜了一下论文发现关于256bit的曲线,2bit以内的泄露做HNP会比较难,于是考虑选择kbits小于等于253的$k_i$,一般来说符合条件的签名会有70-110组。
然而本地造格测了一下发现这样直接HNP,目标向量差的还是有点多,于是考虑做balance优化。balance优化很简单,比如对于当前式子,规约出来的目标向量中的值范围应该是:
那么通过减去1bit的MSB,范围大小可以变成:
这个优化使得目标向量所有值的数量级都下降了1bit,利好规约。此外为了提高成功率还可以做的是:
反复重连靶机,直到满足要求的$k_i$有100组往上
用BKZ进行规约
但这个很慢,时间限制240s,跑不完。所以最后还是选了flatter,大不了多开几个靶机多连几次
比赛中rec就用这个思路,多开了几个靶机反复交互最后出了flag。
赛后思路
然而仔细看,上面其实有一个细节问题,就是对于kbit的$k$,他的范围其实应该是:
所以减去MSB(也就是$2^{kbit-1}$)后范围其实变成了:
这其实并不充分,我们还可以减去一次$2^{kbit-2}$得到:
这样相当于利用上了kbit所透露的下界,如此一来相当于一次签名可以多偷两bit,这样就稳定多了,测试了一下发现说60组kbit小于等于253+BKZ已经可以做到几乎百分百出,并且也很快。
用LLL都行,因为一次偷2bit真的很宽松了就
exp:
1 | from Crypto.Util.number import * |
*f2ke
题目:
1 | from sage.all import * |
和fak1相比:
- ntru.sage文件没有什么变化
- 交互次数从10次提高到了60次
- 每一次交互都会重新生成一组公钥
相当于对于本题来说,我们最多可以获取60组如下形式的密文(仍然做差消去$m_i$):
依然需要解出s来得到flag。
赛中思路
题目情境很符合ntru的广播攻击,广播攻击的核心思路为:
处理密文等式得到:
把等式化为矩阵方程:
如果能够确定$\textbf{r}_i$的内积(也就是$\textbf{r}_i^T\textbf{r}_i$的值),记为$t_i$,那么可以构建一个矩阵方程为:
这其实是个二次等式,所以对应的应该有$n^2$个变量,消去对称的项应该有$\frac{n^2}{2}$个变量
由于在环$R_q$的卷积矩阵的特殊性,所以大多数变量其实系数相同,在模q下又可以合并起来,最终得到的变量个数是$n + \lceil \frac{n}{2} \rceil$个
因此如果能搜集到$n + \lceil \frac{n}{2} \rceil$就可以解线性方程得到$s$
而对于这个题来讲,想要应用广播攻击的思路,有几个问题需要解决:
- $\textbf{r}_i$的内积并不知道
- 等式数量仅有$\frac{n+3}{2}$个,不够$n + \lceil \frac{n}{2} \rceil$,得不到线性方程唯一解
对于第一个问题,由于$\textbf{r}_i$是由01组成的,因此可以对他做$y=2x-1$的线性变换,此时得到的$\textbf{r}’_i$就是一个由1、-1组成的向量,那么其内积显然就是它的长度,也就是n。
而对于第二个问题,对于60次交互,我们能构造60个方程,而实际变量有$117 + \lceil \frac{117}{2} \rceil = 176$个,因此我们能构造的实际上是一个求不出唯一解的不满秩矩阵方程:
而由于我们目标的解x是s的平方变量合并后的结果,其前面176-117个变量是二次项的合并,而后117个变量就是s本身,因此解x并不大,所以期望可以用LLL求一下试试看有没有结果。
但是很遗憾规约结果似乎总是差点,所以这个思路走不通。
非预期解
赛后问了一下解出来的师傅的做法,发现说在环$R_q$下,$x^n+1$其实有如下小系数+稀疏系数的分解:
所以可以在两个子环上对公钥做BKZ,之后crt就可以把私钥求出来,然后解密就得到$s$
其实这个确实应该留个心眼,比如之前折磨过我的一次DASCTF就有一个分解这种不太容易看出来子环的RLWE:
2024-DASCTF-GFCTF-wp-crypto | 糖醋小鸡块的blog (tangcuxiaojikuai.xyz)
hashhash说直接对在$R_q$上BKZ打公钥也可以,不过需要七分钟,超过了限时
甚至听说用g6k可以七八十秒完成,黑科技就是黑科技
预期
赛后rec问了下k1r的预期,思路实在是相当巧妙。
漏洞点依然在于$\textbf{r}_i$是由01组成的,那么回顾刚才的矩阵方程:
$\textbf{r}_i$中的每个分量都是0或1,而0、1是方程$x(x-1)=0$的解,所以对于每个分量,都可以用方程$x(x-1)=0$构造一条等式,因此一次交互实际上来说我们能获得的是n个等式。
而变量有多少个呢?我们每次构造的方程其实也就是关于向量$\textbf{s}$的二次方程,因此去除对称项,变量数量就是$\frac{n^2+n}{2}$个二次项加上$n$个一次项,所以对于$\frac{n+3}{2}$次交互来说刚刚好。
然而一个显著的问题在于时间,题目限时70s,而要完成题目的时间包括:
连靶机拿数据的时间,本地打没啥延迟,线下的话可能得好几秒才行
建矩阵的时间,这一部分我没有怎么优化过,大概要一分钟
这里应该有些办法优化的
求解矩阵方程的时间,这个我就需要70s,所以根本过不了题目
所以也许需要点算力或者改写成cpp实现啥的才行,我是本地把timeout改成300s然后打的。
exp:
1 | from pwn import * |
*b1ock
题目:
des.py:
1 | import random |
server.py:
1 | import os |
第一天结束的时候这个题似乎是0解,所以第一天晚上的时间都花在了白天花了比较多功夫的st那一坨上。第二天回来的时候,这个题解变多了不少,但是又上了两个新题目,以及还剩下一坨st,所以也没有回头看这一道。
再加上确实不太会对称,看到对称就很头大,想逃跑
赛后看了dbt的wp发现这个题很巧妙且有趣,所以简单记一记思路。
题目基于带有后门的48轮DES,其后门在于可以指定一个种子,由种子固定的random会选择和DES正常流程不一样的操作序列,并按这个序列执行加密得到结果。题目允许自己制定种子,并给出一组密文要求还原明文,得到flag。
可以想到这样操作的话,按照普通的des应该会报错,因为输入输出的比特数对于某些操作来说都不一样,所以必须要用某些操作让他们对齐。
而这对齐的操作就有可能出现问题,出现的问题可能会导致DES的加密完全不受密钥影响,因此可以逐步解密还原。而比赛中就停在了这一步,因为分析究竟在什么情况下,轮密钥加这个操作不会影响到加密过程,这个对我来说难度有点太大。
而实际上这一步在wp里用巧妙的方式bypass掉了,题目的test函数就做了个暗示——可以把带后门的DES当作一个黑盒,测试其在哪些种子下对于相同的明文能输出相同的密文,那么这些种子对应的操作序列就是不受轮密钥加影响的。
能想到这一步之后,剩下的步骤就是苦力活,对于找到的种子对应的操作序列写逆操作解密即可,详情可以参考dbt的wp。
$st
这不算个题,至少绝对不算个crypto题,所以不写了。
浪费了一晚上时间(╬゚д゚)▄︻┻┳═一