看雪2022 KCTF 春季赛 | 第12题设计思路及解析

WriteUp 2年前 (2022) admin
608 0 0
看雪 2022 KCTF春季赛 于5月10日中午12点正式开赛!

第12题《尊严之战》已于今日中午12点截止答题。经统计,3队成功提交flag。

看雪2022 KCTF 春季赛 | 第12题设计思路及解析


他们分别是:

看雪2022 KCTF 春季赛 | 第12题设计思路及解析

接下来和我一起来看看该赛题的设计思路和相关解析吧!



出题团队简介


第12题《尊严之战》出题方 【98k】


看雪2022 KCTF 春季赛 | 第12题设计思路及解析


赛题设计思路


看雪2022 KCTF 春季赛 | 第12题设计思路及解析


kctf2021 战队:98k,队长QQ:1485032842


flag:

kctf{7032240d5e5a1d0e021c04656d2b7c4c}
题目中需要输入“7032240d5e5a1d0e021c04656d2b7c4c”


题目设计说明:

题目使用自定义md5和一个有缺陷的格密码理论的LWE问题设计验证,只有在输入flag之后并且在合适的时间内才能输出源码中一模一样的格子,题目中给定了两组由flag生成的矩阵,看似随机但是能够从中恢复flag。


因为需要渲染latex,让大家更好理解其中的原理,详细的过程在攻击脚本的“kanxue_ctf_2022_98k.pdf”中。


破解思路:

题目的运算清晰并且无保护,使用攻击脚本目录下的exp.ipynb可以获得多组可能的解,然后使用题目中自定义的md5校验即可。


攻击脚本参见攻击脚本目录下的 ipynb。

看雪2022 KCTF 春季赛 | 第12题设计思路及解析
看雪2022 KCTF 春季赛 | 第12题设计思路及解析

看雪2022 KCTF 春季赛 | 第12题设计思路及解析



赛题解析


本赛题解析由看雪论坛专家 Zuni-W 给出:

看雪2022 KCTF 春季赛 | 第12题设计思路及解析 


逆向部分


程序逻辑简单,IDA直接能看出来并把前面计算的正向逻辑写出来(不过x64dbg调试会出问题,不知道这是有意还是无意)。
 
其中mt19937代码来自https://github.com/clibs/mt19937ar
#include<stdio.h>#include<string.h>#include<stdlib.h> #include "mt19937ar.h" int genNumber() {    unsigned long r = genrand_int32();    return r % 127;}  int ANS1[32]={0x37, 0x5A, 0x53, 0x4B, 0x03, 0x3C, 0x25, 0x4F, 0x38, 0x05, 0x16, 0x64, 0x59, 0x17, 0x1F, 0x0F,0x44, 0x0B, 0x48, 0x1C, 0x27, 0x4A, 0x23, 0x63, 0x66, 0x79, 0x2A, 0x21, 0x44, 0x43, 0x65, 0x32};int K1 = 0;int ANS2[32]={0x5B, 0x6B, 0x75, 0x5A, 0x48, 0x6A, 0x6A, 0x23, 0x2C, 0x57, 0x14, 0x6B, 0x6B, 0x35, 0x0E, 0x64,0x65, 0x1A, 0x23, 0x39, 0x73, 0x34, 0x02, 0x3E, 0x19, 0x06, 0x21, 0x46, 0x1C, 0x4B, 0x00, 0x61};int K2 = 0; int main() {    init_genrand(0x0000000091941ABB); // K1 K2    int FLAG[16]={0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};    int s0[1024]={0};    for (int i = 0; i < 1024; i++) { //32        int a = genNumber();        s0[i]=a;        //printf("%.2x n", a);    }    int s1[32]={0};    for (int i = 0; i < 16; i++) {        s1[i]=FLAG[i];    }    for (int i = 16; i < 32; i++) { //16        int a = genNumber();        s1[i]=a;        //printf("%.2x n", a);    }     int s2[32]={0};    for (int i = 0; i < 32; i++) { //32 & 1        int a = genNumber() & 1;        s2[i] = a*29;        //printf("%.2x n", a);    }     //Start Calc    int sA[32]={0};    for(int i=0;i<32;i++){        int s = 0;        for(int j=0;j<32;j++){            int temp = s1[j] * s0[32*i+j];            s+= temp%0x7f;        }        sA[i]=s;    }    for(int i=0;i<32;i++){        sA[i] = (sA[i] + s2[i]) % 0x7f;        printf("%.2x ", sA[i]);    }    puts("");}

考虑爆破随机数seed
#include<stdio.h>#include<string.h>#include<stdlib.h> #include "mt19937ar.h" int genNumber() {    unsigned long r = genrand_int32();    return r % 127;} char A1[8]={0x78, 0x36, 0x4D, 0x3B, 0x3B, 0x61, 0x1F, 0x41}; char A2[8]={0x4D, 0x52, 0x57, 0x24, 0x64, 0x26, 0x2D, 0x6B};int main() {    for (unsigned long long K = 0xffffffff; K >= 0x1; K--) {        init_genrand(K);        char s[8] = { 0 };        for (int i = 0; i < 8; i++) { //1024 cmp            int a = genNumber();            s[i] = a;        }        if (memcmp(s, A1, 8) == 0) {            printf("A1:%xn", K);        }        if (memcmp(s, A2, 8) == 0) {            printf("A2:%xn", K);        }    }}

过了4h无解,程序上也基本没有干扰的地方,猜测这是个密码学问题,把已知数据提出来(2组1024byte+2组32byte)。

密码部分


算法模型


摘了一下算法发现是一个如下的模型:
 
A,B为已知32*32矩阵,ans1,ans2为已知32长向量
 
l=[randint(0,126)]*16+[0]*16
 
r1=[0]*16+[randint(0,126)]*16
 
r2=[0]*16+[randint(0,126)]*16
 
err1=[randint(0,1)]*32
 
err2=[randint(0,1)]*32
 
A*l+A*r1+29*err1=ans1 mod 127
 
B*l+B*r2+29*err2=ans2 mod 127
 
求l


基础理论


是个很明显的LWE问题。能想到用格规约,但是个人并不怎么会,所以硬着头皮分析数值间的关系。
 
我们知道格上有一种对向量的类似GCD的算法,叫做LLL,其效果是可以迭代出一些格中的较短行向量。通过这一特性我们可以构造一些结构,通过增广向量的维数构造关系,将一些在原向量空间中的长向量与增广空间中的短向量进行绑定,通过这种方式迭代长向量就可以记录一些得到短向量的操作留痕,而这种操作留痕往往就是我们需要求解的数量。
 
一般理想状态中,这种绑定最好是两个空间互不干扰的,也就意味着增广部分带来的向量长度相比于原向量的长度,应当忽略不计。这时迭代出的短向量的结果变化忠实记录了迭代过程,没有造成任何影响。但事实上,由于数据有界,而迭代所需的独立条件条数又总是多于实际迭代时原向量的维数,所以必然会造成多解。我们不能放任其发生,便要通过两个空间之间的干扰来进行约束,这就要我们增广出的部分虽然是小量,但并不是可忽略的,应有一定的权重。

格基构造


基础知识与手法了解完,我们开始具体分析应如何构造。
 
首先,我们未知的有两种,一种为每个元素为【0,126】,另一种为0或1,很明显这两组造成的边界影响情况不同,所以其所在的空间圈层也不同,也就意味着,我们需要有两个不同的空间分别记录这两组不同的量。
 
空间划分完,我们先来看最刚性的条件:运算结果相等。
 
这个式子中的约束条件有有限域%127,模元M=127,注意到每一维都执行该操作,且相互独立,所以这一项应形成一个127倍的单位矩阵,由于求模的过程减少了多少个模元我们从未关注,也不必关心,所以其不需要在增广空间中留痕,自然也不需要绑定短向量。
 
此外就是矩阵乘,注意到矩阵乘右侧的向量,每一位都代表了对矩阵的一列的选择,数值代表了共加入了多少次,所以我们可以将矩阵转置,分别增广在空间中一个独立的小单位,这时,当某行被约减时,这个单位对应位置的小单位数量即表达了为达成这个效果,对应的原行被增加了多少次。方便起见,我们在增广空间中放置对应的一个单位矩阵。
 
接下来是err的处理,理论上有两种手法:

1、先不考虑err,通过有限域变换将err带来的误差从29减小为1,这样使用LLL迭代出的原结果误差只要在1就可以满足。这样的话可以减少约减的总体维数(条件数),提高速度(我们知道在格维度高于一定程度后就会不可计算),但注意到空间没有转移,在原空间上记录这个量的话,误差在相对层面上其实并没有改变,所以一旦增广记录时,把原向量放大后,因为原则上增广空间的影响不大,并不会如果我们所想在原向量上约化出1个单位的偏差,还是会优先约减为全0,这时的结果也并不会正确。

2、给每维err单独的维度,在原空间中添加一个29倍的单位矩阵,这时就需要在增广空间中留痕其操作过程,但注意到其数据范围不同,也就意味着我们还需要一个新的增广向量空间,这就带来了新的问题,怎么平衡三个空间之间的数据关系。

对比手法,发现第一组方案有硬伤,第二组方案有理解问题,所以先选择第二组方案。
 
得到草图
看雪2022 KCTF 春季赛 | 第12题设计思路及解析
扣一些细节:注意到AB共用的向量l只有前16位是有值的(后16位随机数被当做了r1和r2),也就是AT与BT的高16行对应转置后对应的行向量约减过程其实是相同的,所以我们可以将AT和BT进一步拆分为上半部分ATU与BTU,和下半部分ATD和BTD,优化维数后得到新格。
看雪2022 KCTF 春季赛 | 第12题设计思路及解析


参数调整


接下来我们需要考虑三个空间之间的约束关系,以及部分未定参数的的选择。
 
前文我们提到了空间之间的约束理论,知道优先级更高的约束条件应赋予更高的权重,以减小空间间的影响。所以原空间相比而言应提供较高的权重。那么问题来了,剩下两个空间之间的关系应怎么处理?
 
我们知道,err的条件其实更严苛,因为他只允许有0或1两种选择,更多的选择都是不合法的;而lr对应的数据是可以充满这个有限域的。也就意味着,err所在的空间应是不受lr所在空间影响的。那么lr所在空间的数据应相比于err所在空间为小量。这里我们取$$L=M^2$$为放大倍数,保证在较大维度中的数据每单位的异动带来的数量变化都远超后边的小量整体在所有范围内的变化。
 
于是得到
看雪2022 KCTF 春季赛 | 第12题设计思路及解析
在第三层中,注意到格中得到的结果绝对值越小越好,所以我们知道,0肯定是比1优先级更低的,这并不利于我们得到的结果符合客观。为了使二者地位均等,我们需要一种把二者映射为相反数使得其绝对值相同,$$f(x)=1-2*x$$这个映射较好,所以我们再稍作改动。
看雪2022 KCTF 春季赛 | 第12题设计思路及解析
这时由于lr对应的维度过小,且经历了有限域映射,结果会自然而然分布为最小剩余系,所以我们不再单独做相同的处理。
 
另一方面,我们要保证最后一行被使用且仅被使用一次,所以就要让其留痕平方不超过结果向量的主要影响的分维度的一个单位变化,在这里我们选取t=sqrt(L),新开辟一列记录结果(这也是L至少为127^2的一个原因)。在结果中找最后一列为t或-t的结果就是合法解待选。
看雪2022 KCTF 春季赛 | 第12题设计思路及解析
迭代后寻找最后一列绝对值为t,第三部分绝对值均为一个L的值即为合法解。这时第二部分的值即为负的lr对应值,特殊的,第二部分前16位即为输入负值%127后的结果。
 
原则上输入求模了127,所以要恢复后再过一遍哈希判断哪个是唯一解。


代码记录

sage: MK1=Matrix(ZZ,64*2+48+1,64*2+48+1)                                                                 sage: for i in range(64):....:     MK1[i,i]=127*127*127*127*127....:     MK1[i+64+48,i+64+48]=127*127*2....:     MK1[i+64+48,i]=29*127*127*127*127....:     ....:                                                                                                    sage: for i in range(16):....:     for j in range(32):....:         MK1[i+64,j]=ZZ(A[j,i])*127*127*127*127....:         MK1[i+64,j+32]=ZZ(B[j,i])*127*127*127*127....:         MK1[i+64+16,j]=ZZ(A[j,i+16])*127*127*127*127....:         MK1[i+64+32,j+32]=ZZ(B[j,i+16])*127*127*127*127....:     MK1[i+64,i+64]=1....:     MK1[i+64+16,i+64+16]=1....:     MK1[i+64+32,i+64+32]=1....:                                                     sage: MK1[-1,-65:-1]=vector([127*127]*64) sage: MK1[-1,64:64+48]=vector([0]*48)                                                                    sage: MK1[-1,:64]=vector(ZZ,ans1+ans2)*127*127*127*127                                                   sage: MK1[-1,-1]=127                                                                                     sage: ANS=MK1.LLL()                                                                                      sage: for i in ANS:....:     if i[-1] in {127,-127} :....:         i[:64],-i[64:64+48]*i[-1]/abs(i[-1]),i[-65:]/(127*i[-1])....:         ....:((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (-15, 50, 36, 13, -33, -37, 29, 14, 2, 28, 4, -26, -18, 43, -3, -51, -56, 40, 23, -61, 9, 46, -26, 36, 25, -63, 25, -62, 47, -62, 31, -62, 35, 25, -33, -40, -58, -34, 53, 27, 3, 41, -35, 7, 48, -8, -12, 56), (-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1/127))((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (55, -37, -40, -31, 5, 23, -52, 12, -54, 17, 31, 60, -13, -17, 4, 35, -55, -5, 14, -36, 61, -57, -18, -36, -39, -7, 15, -46, 9, -51, -13, 20, 8, 48, 31, 30, -33, 53, 31, 20, 20, 44, 16, -37, 4, -58, 58, 36), (1, 3, 1, 5, 5, -5, 5, -3, 1, 1, 1, -3, -1, -1, 1, -3, 1, 1, -1, -3, -3, 3, -3, -3, 1, -3, -3, 1, 5, 3, 1, -3, -5, -3, 1, 1, 1, -1, 1, -1, -1, -1, 3, 3, 3, -3, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 3, 1, 1, -1, 1, 1, 1/127))                                              ……

可以看到,第一组符合题意,由于经历了预处理,这时得到的第二部分即为flag值,处理后得到的flag数值为
 
(112, 50, 36, 13, 94, 90, 29, 14, 2, 28, 4, 101, 109, 43, 124, 76)
 
经过哈希之后得到
 
flag:7032240d5e5a1d0e021c04656d2b7c4c
 
(其实就是直接转化,不用再枚举hash,但提交没有kctf【……】,导致晚出了一小时orz。)


结语


希望本篇的思路可以提供区分于整体提升明可夫斯基界的格构造方案,但以上也是笔者本人的小小尝试记录,如有错误/值得改进之处,还请各位批评指正。




活动专区


看雪 2022 KCTF春季赛圆满结束!


【最佳人气奖】投票开启:https://bbs.pediy.com/thread-273214.htm

【新思路奖】投票开启:https://bbs.pediy.com/thread-273215.htm


投票通道关闭时间:6/15  15:30



如何成为一名出色的CTF选手?
看雪2022 KCTF 春季赛 | 第12题设计思路及解析
看雪2022 KCTF 春季赛 | 第12题设计思路及解析
*点击图片查看详情

入门-基础-进阶-强化,只需四个阶段!摇身一变成为主力、中坚力量

看雪2022 KCTF 春季赛 | 第12题设计思路及解析



看雪2022 KCTF 春季赛 | 第12题设计思路及解析
– End –

看雪2022 KCTF 春季赛 | 第12题设计思路及解析


看雪2022 KCTF 春季赛 | 第12题设计思路及解析

球分享

看雪2022 KCTF 春季赛 | 第12题设计思路及解析

球点赞

看雪2022 KCTF 春季赛 | 第12题设计思路及解析

球在看



看雪2022 KCTF 春季赛 | 第12题设计思路及解析
“阅读原文”查看本赛季战绩

原文始发于微信公众号(看雪学苑):看雪2022 KCTF 春季赛 | 第12题设计思路及解析

版权声明:admin 发表于 2022年6月8日 下午6:14。
转载请注明:看雪2022 KCTF 春季赛 | 第12题设计思路及解析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...