运气还不错,做出了所有crypto方向的题目,在这里记录一下题解。
SignAhead
题目描述:
1 | 试试伪造token和md5签名! |
题目:
1 | from hashlib import md5 |
看得出来是很经典的md5哈希长度扩展攻击,直接随便找了个脚本来用就能出。详细原理及代码可以参考:
MD5哈希碰撞之哈希长度拓展攻击 - 知乎 (zhihu.com)
因为赛前的确只是听说,没仔细了解过原理,所以先直接当次脚本小子。
exp:
1 | import math |
basiclog
题目:
1 | import signal |
题目要求在30min内解一个DLP,并发送给靶机从而拿到flag。具体来说要解的是下面式子的x:
可以检验出g是一个q阶元素,所以最外层的指数上有模一个q来加快运算。
由于x只有48bit,所以可以想到是用bsgs。那还是用bsgs的思路先把x拆一波:
其中有:
然后把a和b的两部分分开:
也就最终得到:
所以爆破a建立左侧字典,然后爆破b来查字典即可。不过考虑到30min可能比较紧张,所以采取了一些优化:
- 用gmpy2的powmod来计算模幂
- g^at可以看作多个g^t累乘,所以不用多次计算大的模幂
这样优化一下后,发现30min的时间并不是特别紧张,运气好就能跑出来,我也就一次成功了。
exp:
1 | import random |
后面和zima师傅交流了一下,可以知道一个事实:既然字典不会变,所以完全可以打完字典再去交互拿数据,时间就绰绰有余了。
basiccry
题目:
1 | import random |
连接上靶机后,题目生成一个ZZ上的256x256的随机01矩阵rr,并用flag的二进制串生成长为256的GF(2)上的向量mm。之后可以自行输入一个ZZ上的长为256的向量ii,然后靶机会返回:
- cc=mm+rr
- dd=rr*ii
相当于要在已知ii、cc、dd的情况下求出mm来。
而刚才特意念了一遍每个向量究竟是ZZ上的,还是模2上的。其目的就在于要发现这样一个事实:虽然rr仅由01组成,但他是ZZ上的,而由于ii也在ZZ上,所以dd也在ZZ上。所以可以直接令ii为一个超递增序列,然后用dd的第一个值就能解出rr的第一行,就可以得到mm了。
exp:
1 | from Pwn4Sage.pwn import * |
crypto_sign_in_5
题目描述:
1 | This challenge will be the last one in the crypto_sign_in series. It's just a coin game.We believe that you can solve it, so good luck! |
题目:
1 | from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler |
out足足有30多M所以没办法放在这里,有兴趣的师傅可以找我要。
具体来说,这是一个Decision-LWE问题,把flag去掉头尾后转为二进制串,并根据当前bit具体是1还是0,来选择生成一个LWE样本或是一个随机样本。要做的事情就是判断出每个样本究竟是LWE样本还是随机样本从而还原flag的每个bit。
用以下语句可以打印出来调的这个LWE的私钥s:
1 | print(lwe._LWE__s) |
可以发现私钥也是在模q下随便取的,所以不存在私钥是短向量的漏洞。因此着眼点就只有想办法利用e都是64bit数量级的这一事实。
只给一组样本的话,的确应该是没有办法判定这个困难问题的。所以想到的是多取几组数据来一起规约,如果能规约出符合要求的短向量的话,说明取的这几组都是LWE样本,也就都对应flag中的1。但是实测了一下,至少要四组正确数据才够,这就只有1/16的机会成功(当然,由于每个字符均可见,所以最高bit肯定是0,可以不取这些数据,来增大取到1对应的数据的概率)
然后普通LWE的造格思路还是挺明确的,造的格子如下:
其中,a的下标(x,y,z)的含义是第x组样本中的矩阵A的y行z列;b下标(x,y)的含义是第x组样本的第y个值。这个格有的线性关系是:
然后由于s并不是一个较小的值,所以需要对1和e做配平处理才行。造格的测试脚本是:
1 | from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler |
跑起来可以发现,由于维度不小,所以十几分钟才能跑完一组(看maple博客发现把矩阵上下换个顺序就只需要六分多钟,不清楚是为什么,还是LLL理解得不够),而由于我们本身就是随机取的四组样本,本来就只有1/16的概率都是LWE样本,所以还需要跑多组拼一下手气才能出结果,所以这个时间没法接受。还有一个严峻的问题是out文件实在太大,直接粘到jupyter里会卡死,然后又没有找到让Arch里的sage10.2读文件的办法,所以只能用本机的9.3来读文件,也就只能用9.3来做LLL,这就又会慢很多。
然后在maple的博客里找到了一种叫做primal attack的LWE造格的降维技巧:
TSG CTF 2023 WriteUps | 廢文集中區 (maple3142.net)
大概看了一下思路,发现这样造格之所以可以降维,是因为把格中需要规约出s那一部分去掉了,就变成仅仅规约出e然后再计算得到s,这样还有一个好处是直接避免了s本身数量级不小带来的规约困难。生成四组LWE的数据并且拼到一起,发现由于这个降维硬生生降了77维,所以即使是用9.3的LLL,也能在一分钟多一点就规约出私钥。
所以现在的任务就很简单,随机取out里的四组样本并规约,虽然一定能得到一个向量,但是只有四组样本都是LWE样本的时候,才会得到正确的私钥s的短向量。也就是说,如果某两次随机取的四组样本,其得到的向量相同,那么就可以断定两个事实:
- 得到的这个向量是正确私钥s
- 这两次取到的样本都是LWE样本,在flag里也就都是1
有了s后,就可以对每个样本计算:
然后检查向量e中的数值是否位于2^64之内就可以判断该样本是不是LWE样本了,也就可以判断该bit是0还是1从而得到flag。
exp:
1 | from Crypto.Util.number import * |