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
            break
 
flag = ''
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_locdata2就是最后的地址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_locend_loc

2.1 解题思路

idapython,把start_locend_loc的内容当作迷宫,根据switch-case的限制逻辑,利用深度搜索算法来输出符合条件的xyz,即path

参考文章:[原创]关于爆破迷宫路径的一系列思考-CTF对抗-看雪-安全社区|安全招聘|kanxue.com

CTF中迷宫题型的解题思路_ctf迷宫-CSDN博客

深度优先搜索(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 idc
 
start_loc = 0x1DDFFDAEA60
end_loc = 0x1DDFFDAEE20
 
def 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()
    return
 
path = []
all_visited_loc = []
dfs(path, all_visited_loc, start_loc)

图片描述

2.3 验证

图片描述

xiran_encrypto

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

上传的附件 programs.zip