首页
社区
课程
招聘
[原创] 看雪 2022 KCTF 秋季赛 第十一题 衣锦昼行
2022-12-12 01:59 18163

[原创] 看雪 2022 KCTF 秋季赛 第十一题 衣锦昼行

2022-12-12 01:59
18163

ida打开,main函数是sub_5B4270,没加混淆,但是main函数调用的其他计算函数都加了混淆(大概看了下,与Archaia战队在前几次比赛中用的差不多,难搞)

 

main函数没有混淆,验证逻辑很容易看出来,大概是对name和serial各做一些计算分别得到两个8字节的值,然后比较两个值是否相等。

 

比较好的一点是程序没有加壳与反调试,直接动态调试main函数,发现修改中间值的一个bit也会对结果产生巨大的影响,所以似乎只能分析算法逆推。

 

看了一个小时题目,先去睡觉了(实在不想硬刚代码混淆,再加上昨晚写第十题的wp没怎么睡)

 

晚上起来发现一血只用了一个半小时,果断开始找非预期。

 

main函数调用的其他函数由于被混淆了,导致ida识别的函数签名很乱,main函数的F5伪代码观感很差。

 

还是先修正所有函数的类型,根据汇编里调用函数前的push个数确定参数个数。

 

修正之后的main函数看上去清爽多了,摘抄如下:("..."中间的代码与前后相同(除了数组偏移),在此略去)

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
 
  memset(v70, 0, 0x1C2u);
  memset(v69, 0, sizeof(v69));
  qmemcpy(v116, "abcdefgh", sizeof(v116));
  memset(name, 0, 0x32u);
  *(_DWORD *)Buffer = 0;
  v406 = 0;
  v207 = 0;
  v208 = 0;
  v209 = 0;
  v210 = 0;
  v326 = 0;
  v327 = 0;
  v328 = 0;
  printf(&Format);                              // "请输入用户名:\n"
  gets((int)name);
  v390 = name;
  v293 = &name[1];
  v390 += strlen(v390);
  v289 = ++v390 - &name[1];
  Value = sub_5AE2D3(name, v390 - &name[1]);
  itoa(Value, Buffer, 16);
 
 
  sub_5AD9F9(v116, Buffer, &v207, &v326);
  *(_DWORD *)v70 = v207;
  *(_DWORD *)&v70[4] = v208;
  *(_DWORD *)v69 = v326;
  *(_DWORD *)&v69[4] = v327;
  qmemcpy(v115, "ijklmnop", sizeof(v115));
  v231 = 0;
  v232 = 0;
  v233 = 0;
  v234 = 0;
  v323 = 0;
  v324 = 0;
  v325 = 0;
  ...
  sub_593D03(v126, Buffer, v77, v277);
  qmemcpy(v127, "abcdefgh", sizeof(v127));
  memset(v76, 0, sizeof(v76));
 
 
  v275[0] = 0;
  v275[1] = 0;
  v276 = 0;
  sub_5937B3(v127, Buffer, v76, v275);
  memset(v365, 0, sizeof(v365));
  v366 = 0;
  v3 = sub_5AE2D3(v69, 192);
  sprintf(v365, "%08x", v3);    // v365
 
  v401[0] = 0;
  v401[1] = 0;
  v402 = 0;
  v403 = 0;
  v404 = 0;
  v391[0] = 0;
  v391[1] = 0;
  v392 = 0;
  v393 = 0;
  v394 = 0;
  sub_592CB9(v70, v365, v401);
  v368 = malloc(8u);
  v4 = v368;
  v5 = v403;
  *v368 = v402;
  v4[1] = v5;
  sub_592CB9(&v70[9], v401, v391);
  ...
  v44 = malloc(8u);
  v387 = v44;
  v45 = v403;
  *v44 = v402;
  v44[1] = v45;
  sub_592CB9(&v70[189], v401, v391);            // here, v391 is the correct serial
 
 
  v44 = malloc(8u);
  v387 = v44;
  v45 = v403;
  *v44 = v402;
  v44[1] = v45;
  sub_592CB9(&v70[189], v401, v391);
  memset(serial, 0, sizeof(serial));
  v288 = 0;
  memset(v399, 0, sizeof(v399));
  v400 = 0;
  memset(v395, 0, sizeof(v395));
  v396 = 0;
  v397 = 0;
  v398 = 0;
  printf(&byte_5D0848);                         // "请输入序列号:\n"
  gets((int)serial);
  v389 = serial;
  v291 = &serial[1];
  v389 += strlen(v389);
  v292 = ++v389 - &serial[1];
  if ( v389 - &serial[1] == 16 )
  {
    sub_592C10(&v70[189], v399, serial);
    v47 = v387[1];
    *(_DWORD *)&v399[8] = *v387;
    *(_DWORD *)&v399[12] = v47;
    ...
    sub_592C10(&v70[9], v399, v395);
    v67 = v368[1];
    *(_DWORD *)&v399[8] = *v368;
    *(_DWORD *)&v399[12] = v67;
    sub_592C10(v70, v395, v399);
 
    for ( i = 0; i < 8; ++i )
    {
      if ( v395[i] != v365[i] )
      {
        printf(&fail_1);                        // "登录失败!\n"
        system(aPause_0);
        return 0;
      }
    }
    printf(&success);                           // "登录成功!\n"
    system(aPause_1);
    return 0;
  }
  else
  {
    printf(&fail_2);                            // "登录失败!\n"
    system(Command);
    return 0;
  }
}

修正函数签名后,交叉引用的查找也会变得更准。“登录成功”的条件是v395和v365相等,对v365查找交叉引用可以定位到上面的sprintf,是从name计算出来的一个值;v395则是从serial计算出来的一个值。

 

观察代码,发现v70变量贯穿了大部分代码。
在输入serial之前以v365为初始值正向遍历v70进行了22次迭代计算得到v391;在输入serial之后以serial为初始值反向遍历v70进行了22次迭代计算得到v395。
这两块计算给人感觉很像互逆的。输入公开的name,尝试在0x5B6364下断点查看正向迭代22次后的v391,发现此处的v391正好就是公开的serial。

 

所以最终获得答案的方法很简单,输入KCTF作为name,提取正向迭代22次后的v391,即为对应的serial。

 

验证通过

1
2
3
4
5
6
7
C:\>CrackMe.exe
请输入用户名:
KCTF
请输入序列号:
6FB1CE80914403C4
登录成功!
请按任意键继续. . .

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-12-12 02:02 被mb_mgodlfyn编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 219
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
一起snow8 2023-2-23 15:25
2
0
老师讲的真好
游客
登录 | 注册 方可回帖
返回