学长出的RE题

学长说,做出我这到题,你就可以到网上找CrackMe做了。那就做做看吧。

引言

分析

学长给了个提示:

找到key!听说这个程序还能当计算器用??喵喵喵?

先随便输输,发现输错不会有任何提示,会让你不断重输,没什么发现,直接OD载入先。

追踪

老方法,先查找字符串,找到一些:

上下翻翻发现这些:

向下f8,可以发现,就是这个jnz让我们不断输入key:

观察后发现eax存的是我们输入的key的长度,说明key必须为20位长度。根据下面两个jnz以及printf,我猜测CALL 004010A0和CALL 00401000为两个关键CALL。见下

  • CALL 004010A0的分析
    这里用到IDA,f5分析得:
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
// a1=eax=12FE14,a2=ebx=key_address,a3=edi=length
signed int __usercall sub_4010A0@<eax>(int a1@<eax>, int a2@<ebx>, unsigned int a3@<edi>)
{
unsigned int v3; // esi@1
char v4; // cl@2
unsigned int v5; // ecx@5

v3 = 0; // v3为位数计数器
if ( a3 )
{
do // do for each char
{
v4 = *(_BYTE *)(v3 + a2); // v4 = each char
if ( v4 == 61 ) // key第一位不能为'='
break;
if ( v4 < 43 ) // 第一位不能小于43'+'
return 1;
if ( v4 > 122 ) // 第一位不能大于122'Z'
return 1;
v5 = byte_4020CD[v4]; // 在4020CD上的相应的字符
if ( v5 == -1 ) // eof不用管
return 1;
switch ( v3 & 3 ) // 结果依次为0,1,2,3 x5组
{
case 0u: // 【a1=eax=12FE14 , a2=ebx=key_address , a3=edi=length】
*(_BYTE *)a1 = 4 * v5; // 乘4
break;
case 1u:
*(_BYTE *)a1++ += (v5 >> 4) & 3; // +=(v5 >> 4) & 3
if ( v3 < a3 - 3 || *(_BYTE *)(a2 + a3 - 2) != 61 ) //不是最后三位 或 倒数第三位 不等于 '='
*(_BYTE *)a1 = 16 * v5; // 16 * v5
break;
case 2u:
*(_BYTE *)a1++ += (v5 >> 2) & 0xF; // +=(v5 >> 2) & 15
if ( v3 < a3 - 2 || *(_BYTE *)(a2 + a3 - 1) != 61 ) //不是最后两位 或 倒数第二位 不等于 '='
*(_BYTE *)a1 = (_BYTE)v5 << 6; // v5 << 6
break;
case 3u:
*(_BYTE *)a1++ += v5; +=v5
break;
default:
break;
}
++v3;
}
while ( v3 < a3 );
}
return 0;
}

ps.我分析完以后才发现此处为base64解密,水平不够,哪怕发现了是3/4的转换也没想到base64,分析代码的时候并没有发现,耗了很多时间

以下为4020CD之后的一些二进制数据:

1
2
3
4
5
6
7
8
9
10
11
004020CD  00 00 00 00 00 00 00 FD   63 62 58 00 00 00 00 02
004020DD 00 00 00 67 00 00 00 B8 21 00 00 B8 11 00 00 50
004020ED 31 40 00 A8 31 40 00 00 00 00 00 3E FF FF FF 3F
004020FD 34 35 36 37 38 39 3A 3B 3C 3D FF FF FF FF FF FF
0040210D FF 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E
0040211D 0F 10 11 12 13 14 15 16 17 18 19 FF FF FF FF FF
0040212D FF 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28
0040213D 29 2A 2B 2C 2D 2E 2F 30 31 32 33 6B 65 79 00 73
0040214D 75 63 63 65 73 73 66 75 6C 00 00 67 69 76 65 20
0040215D 6D 65 20 25 73 3A 00 25 73 00 00 66 61 69 6C 00
0040216D 00 00 00 48 00 00 00 00 00 00 00 00 00 00 00 00

看完没什么思路。转向401000的分析。

  • CALL 401000的分析

IDA分析得:

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
__int64 __cdecl sub_401000(const char *a1)
{
unsigned int v1; // eax@1
signed int v2; // edi@1
unsigned int v3; // ebx@1
int v4; // esi@4
const char v5; // al@5
int v6; // eax@7
__int64 v8; // [sp+Ch] [bp-8h]@1

v1 = strlen(a1); // v1=变化后的字符串的长度
v2 = 0;
v3 = v1; // v3=v1
v8 = 0i64; // v8最终结果,初始化
if ( !v1 || (signed int)v1 > 13 ) // 在13位之前有一位为0,字符串长度小于等于13(没有0则等于15)
goto LABEL_13;
if ( (signed int)v1 > 0 )
{
v4 = 8 * v1 + 4206760; //v4为地址常量 (8 * v1 + 0x4030A8)
do
{
v5 = a1[v2]; //v5为变化字符串的每一位,变化后的字符串中不是数字就是大写字母
if ( v5 < 48 || v5 > 57 )
{
if ( v5 < 65 || v5 > 90 )
goto LABEL_13;
v6 = v5 - 55; // v5是大写字母,v6=v5-55 v6属于[10,35]
}
else
{
v6 = v5 - 48; // v5是数字,v6=v5-48 v6属于[0,9] ,综上,v6属于[0,35]
}
v8 += v6 * *(_QWORD *)v4; // v8 += v6 * (v4上的值)
++v2;
v4 -= 8; // v4 -= 8
}
while ( v2 < (signed int)v3 ); // 对变化字符串每一位操作
if ( v8 < 0 ) // v8 == 7089074166928978739 'base2333' '0x 62617365 32333333'
LABEL_13:
exit(1);
}
return v8;
}

以下为0x4030A8后的一些二进制数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x4030A8    0x59000000 5A000000
0x01000000 00000000
0x24000000 00000000
0x10050000 00000000
0x40B60000 00000000
0X00A11900 00000000
0X00A49A03 00000000
0x0010BF81 00000000
0x0040DE3E 12000000
0x000041D7 90020000
0x00002445 5E5C0000
0x000010B9 41FD0C00
0x00004006 3E9DD301
0x000000E1 B81CC241

由Little-Endian知,v4实际读取到的数据为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0000005A00000059
0000000000000001 36^0
0000000000000024 36^1
0000000000000510 36^2
000000000000B640 36^3
000000000019A100 36^4
00000000039AA400 36^5
0000000081BF1000 36^6
000000123EDE4000 36^7
00000290D7410000 36^8
00005C5E45240000 36^9
000CFD41B9100000 36^10
01D39D3E06400000 36^11
41C21CB8E1000000 36^12

发现其实是一个36进制到16进制的转换,转换的结果为6261736532333333h,反向计算的经过004010A0加密以后的字符串应该为1HUXV29D2KFUB

  • 再转向CALL 004010A0
    既然知道了加密后的字符串,通过004020CD处的数据慢慢解密(真的很慢,如果发现是base64就是几秒钟的事情了)可以得到最终的key:MUhVWFYyOUQyS0ZVQg==

我艰辛的过程大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
01 |a*4|   |   |   |   |   |   |   |   |   |   |   |   |   |   | 77  M
02 |b |b*F| | | | | | | | | | | | | | 85 U
03 | |c |c | | | | | | | | | | | | | 104 h
04 | | |d | | | | | | | | | | | | | 86 V
05 | | | |e*4| | | | | | | | | | | | 87 W
06 | | | |f |f*F| | | | | | | | | | | 70 F
07 | | | | |g |g | | | | | | | | | | 89 Y
08 | | | | | |h | | | | | | | | | | 121 y
09 | | | | | | |i*4| | | | | | | | | 79 O
10 | | | | | | |j |j*F| | | | | | | | 85 U
11 | | | | | | | |k |k | | | | | | | 81 Q
12 | | | | | | | | |l | | | | | | | 121 y
13 | | | | | | | | | |m*4| | | | | | 83 S
14 | | | | | | | | | |n |n*F| | | | | 48 0
15 | | | | | | | | | | |o |o | | | | 90 Z
16 | | | | | | | | | | | |p | | | | 86 V
17 | | | | | | | | | | | | |q*4| | | 81 Q
18 | | | | | | | | | | | | |2 |0 | | 103 g
19 | | | | | | | | | | | | | |0 |s | =
20 | | | | | | | | | | | | | | |t | =
| 49| 72| 85| 88| 86| 50| 57| 68| 50| 75| 70| 85| 66| 00| 00|
1 H U X V 2 9 D 2 K F U B
  • 后记

虽说key是解出来了,但毕竟是入门的题目,解了这么就还是太慢,反思自己,连常见加密的算法都不熟练。