easyre
一、分析

有用的伪代码就这一段
看得出来关键加密就是for循环里的异或加密
分析这一段加密
1 2 3 4 5 6 7 | input = argv[1];j = 1i64;for ( i = 0i64; i != 43; ++i ){ input->m128i_i8[i] ^= input->m128i_u8[i + 1 + -42 * (j / 42)]; ++j;} |
简化一下就是:
1 2 3 4 | for(int i = 0; i < 43; ++i){ input[i] ^= input[i + 1 - 42 * (i + 1 / 42)]} |
- 前41位,input的前一位xor后一位
- input[41] ^= input[0]
- input[42] ^= input[1]
二、确定cipher
1 2 3 4 5 6 7 8 9 10 11 12 | if (_mm_movemask_epi8(_mm_and_si128(_mm_cmpeq_epi8(_mm_loadu_si128(input), data1), _mm_cmpeq_epi8(_mm_loadu_si128(input + 1), data2))) == 0xFFFF)data1 = [0x0A, 0x0D, 0x06, 0x1C, 0x1D, 0x05, 0x05, 0x5F, 0x0D, 0x03, 0x04, 0x0A, 0x14, 0x49, 0x05, 0x57, 0x0A, 0x0D, 0x06, 0x1C, 0x1D, 0x05, 0x05, 0x5F, 0x0D, 0x03, 0x04, 0x0A, 0x14, 0x49, 0x05, 0x57, 0x00, 0x1B, 0x19, 0x02, 0x01, 0x54, 0x4E, 0x4C, 0x56, 0x00, 0x51, 0x4B, 0x4F, 0x57, 0x05, 0x54, 0x55, 0x03, 0x53, 0x57, 0x01, 0x03, 0x07, 0x04, 0x4A, 0x77, 0x0D]data2 = [0x00, 0x1B, 0x19, 0x02, 0x01, 0x54, 0x4E, 0x4C, 0x56, 0x00, 0x51, 0x4B, 0x4F, 0x57, 0x05, 0x54] |
根据这个if语句中的条件,确定来确定cipher
解释
- _mm_loadu_si128(input):
这是一个 SSE 指令,表示从input地址处加载一个 128 位(16 字节)的未对齐数据到一个 SSE 寄存器中 - _mm_cmpeq_epi8(_mm_loadu_si128(input), data1):
这是一个字节级的比较指令,比较_mm_loadu_si128(input)中的每个字节和data1中的每个字节;结果是每个字节比较的结果:如果某个字节相等,则对应的结果字节为 0xFF(即全1),否则为 0x00(全0) - _mm_and_si128(...):这是一个按位与操作,对前两个比较结果的每个字节进行按位与运算。相当于都为 0xFF(全1,表示相等)时,结果字节才为 0xFF;否则结果字节为 0x00
- _mm_movemask_epi8(...):这是将每个字节的最高位(即第8位)提取出来,形成一个 16 位的整数;若某个字节的最高位是1(即该字节为 0xFF),则对应的位为1;否则为0
- 最后结果要
==0xFFFF,相当于全为1
换言之,取input的32位进行对比,相同则正确,根据data2可以确定校验的后16位在data1中的位置,往前推16位即是校验的前16位
因此,正确的cipher为剩下的data1

三、解题
根据以上分析,可以将整个题简化为一个异或加密,结果与cipher对比
直接写个正面爆破即可完成:
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | cipher = [ 0x0A, 0x0D, 0x06, 0x1C, 0x1D, 0x05, 0x05, 0x5F, 0x0D, 0x03, 0x04, 0x0A, 0x14, 0x49, 0x05, 0x57, 0x00, 0x1B, 0x19, 0x02, 0x01, 0x54, 0x4E, 0x4C, 0x56, 0x00, 0x51, 0x4B, 0x4F, 0x57, 0x05, 0x54, 0x55, 0x03, 0x53, 0x57, 0x01, 0x03, 0x07, 0x04, 0x4A, 0x77, 0x0D]x = ord('f')input = []for i in range(len(cipher)): for num in range(32, 128): if x ^ num == cipher[i]: input.append(num) x = num breakflag = ''for j in range(len(input)): flag += chr(input[j])print('f' + flag)# flag{fcf94739-da66-467c-a77f-b50d12a67437} |
tmaze
根据题目猜测它是T型迷宫
结果上网搜全是动物实验= =,
一、分析
DIE查询可知是64位程序,进入ida分析 →

初始并没有发现有什么引导输入的伪代码,开调!
1.1 传参方式及输入字符
在if(v5 == 43)处下断点,调试可以发现终端并未让输入
这里可以确定程序是命令行传参

在此输入再进行调试即可

在下面的switch-case逻辑中,可以看出要求输入字符xyz
1.2 确定输入长度

可以看出v4是输入的path
1 2 3 4 5 | i = 0;do j = i++;while ( path[j] );if ( i == 43 ) |
分析这一段,do-while循环是在统计输入的字符串长度,检索到0结束
可以确定path的长度为42
1.3 调试switch之前的逻辑
1 | v7 = sub_7FF7D50A1230(&unk_7FF7D50D7000, dword_7FF7D50D7FA0, envp); |
这个语句,unk_7FF7D50D7000中是若干1和0组成的数组,猜测是构成迷宫的参数,dword_7FF7D50D7FA0是固定参数10
sub_7FF7D50A1230里是复杂的迷宫生成函数,对我来说难以分析我就先没管

调试可以知道,data0、data1、data2分别存储了一个地址
- data0:1DDFFDACD70h
- data1:1DDFFDAEA60h
- data2:1DDFFDAEE20h
1.4 调试switch之后的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | if ( argc != 1 ) { m = *path == 0; if ( *path ) { v14 = 1; v15 = data1; k = 0i64; do { chose = path[k]; switch ( chose ) { case 'z': v19 = *(v15 + 16); if ( !v19 || *(v15 + 26) ) goto LABEL_12; break; case 'y': v19 = *(v15 + 8); if ( !v19 || *(v15 + 25) ) goto LABEL_12; break; case 'x': v19 = *v15; if ( !*v15 || *(v15 + 24) ) goto LABEL_12; break; default: goto LABEL_12; } data1 = v19; *(v19 + 27) = 1; k = v14; len = strlen(path); ++v14; v15 = v19; m = len <= k; } while ( !m ); }LABEL_12: if ( m && data1 == v7 ) { v13 = sub_7FF7D50A1770(&qword_7FF7D50D92C0, "yes flag is flag{UUID(md5(your input))}"); sub_7FF7D50A1B70(v13); } |
根据最后的if判断语句和switch逻辑,可以知道data1指向的地址是会发生变化的
v7的地址就是data2存的地址
那么,可以分析出来,data1其实就是当前操作对应地址,最开始的的data1就是start_loc,data2就是最后的地址end_loc
再但拿一个case来分析:
1 2 3 4 5 | case 'z': v19 = *(v15 + 16); if ( !v19 || *(v15 + 26) ) goto LABEL_12; break; |
- 都是对地址操作,v15就是
now_loc,v19存储的是*(now_loc + 16)这个地址的值,而这个值恰好又是一个地址,可以说v19 是next - 要满足不去执行goto的操作,需要让if里的条件为假,满足:next ≠ 0、*(now_loc + 26) = 0
分析完可以得到:

二、解题
根据输入全跟地址相关,以及地址操作全在下半段
就可以考虑不管上半迷宫生成的逻辑
我们可以把1.3分析的内容全部理解为:获得start_loc和end_loc
2.1 解题思路
用idapython,把start_loc到end_loc的内容当作迷宫,根据switch-case的限制逻辑,利用深度搜索算法来输出符合条件的xyz,即path
参考文章:[原创]关于爆破迷宫路径的一系列思考-CTF对抗-看雪-安全社区|安全招聘|kanxue.com
深度优先搜索(DFS) 总结(算法+剪枝+优化总结)-CSDN博客
2.2 EXP
用递归算法实现深度搜索
记录已经访问过的地址(相当于迷宫中已经走过的坐标)为all_visited_loc,来实现减少搜索量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | import idcstart_loc = 0x1DDFFDAEA60end_loc = 0x1DDFFDAEE20def dfs(path, all_visited_loc, now_loc): if now_loc == end_loc: if len(path) == 42: print("Yes! Found the path") for i in range(len(path)): print(path[i], end='') else: print("Not Found the path") return if now_loc in all_visited_loc: return all_visited_loc.append(now_loc) x_location = idc.get_qword(now_loc) y_location = idc.get_qword(now_loc + 8) z_location = idc.get_qword(now_loc + 16) x_judge = idc.get_wide_byte(now_loc + 24) y_judge = idc.get_wide_byte(now_loc + 25) z_judge = idc.get_wide_byte(now_loc + 26) if x_location > 0 and x_judge == 0: path.append("x") dfs(path, all_visited_loc, x_location) path.pop() if y_location > 0 and y_judge == 0: path.append("y") dfs(path, all_visited_loc, y_location) path.pop() if z_location > 0 and z_judge == 0: path.append("z") dfs(path, all_visited_loc, z_location) path.pop() all_visited_loc.pop() returnpath = []all_visited_loc = []dfs(path, all_visited_loc, start_loc) |

2.3 验证

xiran_encrypto
Tips: xiran_encrypto是恶意样本分析,本质是从cha文件里解出一个数据流,clickme文件是chacha20对flag.png文件进行加密。对本人来说太难了,分析不出来QAQ,后续若能理解再来分享。