2022 SUSCTF nebula战队 crypto部分题解
large_case
题目源码:
1 | from Crypto.Util.number import * |
没给e,但是给了e的一个条件,由的三个素因子相乘得到。这样e肯定是整除n的阶了,所以考虑用AMM算法在有限域开根,并且e不会很大,否则穷举量很大。首先找找的素因子,取前一百万个素数(再大即使e对了穷举量也不能接受):
1 | # sage |
然后验证结果c是否满足对应的e次剩余,方法就是直接开n次根,能够直接开出来就是e的可能素因子。sage求解时间太长,就单独试试AMM。
1 | e1_list=[] |
最后验证出最小的一个e是:
1 | e=757*66553*5156273 |
刚好对应题目的small,little,large三个size,其中我们必须舍弃large size,因为穷举量真的太大了。所以我们必须要找到另外一个可代替的条件,注意到填充条件:
1 | assert len(message)>L and len(message)<2*L |
强行把message填充到3*1024比特,且后面1024比特是已知全零的,这就给了我们一个额外的条件,所以我们完全不用求解关于第三个素数上的有限域开根。由此脚本如下:
1 | from Crypto.Util.number import * |
跑完全部结果在我电脑这需要1个多小时,开服务器并行跑,几分钟就出了。
Ez_Pager_Tiper
data里面文件的名字是日期的base64编码,可以对应文件内容的第一行日期。这样对于每个文件我们知道固定格式开头的18个字节,也就是144比特。这很重要。然后重点关注magic_box里面的操作:
1 | class lfsr(): |
confusion
是个很神奇的操作,主要看看这个位运算:(-magic & magic),相当于:取magic的最低位的一个1对应的数值。比如,它就会得到,也就是4,依此类推。然后我们再看confusion
内的操作就很清楚了,while循环内output每次异或的值就是magic从低到高的每一位的数值,也就是出去之后,output的变化等价于:,再关注cnt的值,这个运算:now >> (now.bit_length() - 1)
其实是恒为1的数,那么cnt也就是不断在0,1之间跳动。如果magic的二进制表示中有奇数个1,那么cnt就是1,反之为0。那么confusion的全部过程也就清楚了:
也就是:
- magic二进制表示内有奇数个1:,从而cipher的密钥流也就是lfsr2的输出流,对应problem1的情况。
- magic二进制表示内有偶数个1:,从而cipher的密钥流也就是lfsr1和lfsr2的输出流异或的结果,对应problem2的情况。
之后思路就是针对lfsr的攻击了。
注意到lfsr只有12比特长,这个穷举空间至多:,纯暴力穷举即可。几十秒结束,得到seed2和mask2。
1 | from tqdm import tqdm |
放一段解密出来的明文:
1 | Date: 1984-04-01 |
可以去网上找到相关的文章:Love of life的第一章。把这篇文章存一下。之后第二个加密的文件里的部分文字也应该是这里面的。我们把这篇文章一段一段分开,所以密文2的原文大概有90+种可能的开头。
我们得到了mask2,先穷举可能的明文,然后同理直接爆破seed3(代码里写的seed2,无所谓了),得到lfsr2的输出流,进而得到lfsr1可能的输出(的穷举量,也需要执行这么多次BM算法,所以BM算法输入长度不能太大),对这个输出用BM算法解出来的多项式次数等于64就是正确解。
已知日期那行的长度是144比特,一般随机比特流BM算法输出的多项式次数为比特流长度的一般左右,这里也就是62,因此需要更长明文来作BM算法的输入保证结果的唯一性和正确性。拿上面文章的每一段开头放进去。(注意标点符号之类的)。直接下载原文,按照每一行进行穷举,大概90+行。输出长度只要取到取28字节就可以了(越长BM算法越慢),这时用BM算法每穷举2^12种输出流大概只要10s,并且长度也足够。上面的爆破得到mask1和seed3,从而就可以得到seed1了,从而解密得到flag。
脚本:
1 | # sage |
然后用得到的参数解密即可,,挺不错的一道关于LFSR的题。