作为练手的 160 个 CrackMe 系列整理分析

CrackMe 来源:【反汇编练习】160个CrackME索引目录1~160建议收藏备用

这个 CrackMe 使用了 RSA 算法,因为程序中也给了相应的提示,在逆的过程中就会有意识的去找这方面的特征,当然 RSA 算法还是需要事先了解下。

算法参考可见:RSA加密算法 OR RSA 算法的加密原理是什么

通过提示字符串可直接定位到按钮的处理函数 004029B0,一开始通过字符串复制设置一些初始变量。接着会检查输入的注册码是否为 14 个纯数字,满足条件后继续。

起初还以为是个大数组,后来想想可能是一些大整数的结构。

变量初始化完毕后会将注册码的 14 个数字简单拆分成 7 + 7 两部分进行处理,接着就到关键函数 00402B1D 这里了。

<前略>
00402A6A  |.  8BC2          mov eax,edx
00402A6C  |.  C64424 17 00  mov byte ptr ss:[esp+0x17],0x0                     ;  "12790891"
00402A71  |.  C64424 0F 00  mov byte ptr ss:[esp+0xF],0x0
00402A76  |.  8B08          mov ecx,dword ptr ds:[eax]
00402A78  |.  894C24 10     mov dword ptr ss:[esp+0x10],ecx
00402A7C  |.  66:8B48 04    mov cx,word ptr ds:[eax+0x4]
00402A80  |.  66:894C24 14  mov word ptr ss:[esp+0x14],cx
00402A85  |.  8B4A 07       mov ecx,dword ptr ds:[edx+0x7]
00402A88  |.  8A40 06       mov al,byte ptr ds:[eax+0x6]
00402A8B  |.  894C24 08     mov dword ptr ss:[esp+0x8],ecx
00402A8F  |.  884424 16     mov byte ptr ss:[esp+0x16],al
00402A93  |.  8D42 07       lea eax,dword ptr ds:[edx+0x7]
00402A96  |.  8D4C24 10     lea ecx,dword ptr ss:[esp+0x10]
00402A9A  |.  66:8B50 04    mov dx,word ptr ds:[eax+0x4]
00402A9E  |.  8A40 06       mov al,byte ptr ds:[eax+0x6]                       ;  注册码从中间拆成两个字符串
00402AA1  |.  51            push ecx
00402AA2  |.  8D8C24 940500>lea ecx,dword ptr ss:[esp+0x594]
00402AA9  |.  66:895424 10  mov word ptr ss:[esp+0x10],dx
00402AAE  |.  884424 12     mov byte ptr ss:[esp+0x12],al
00402AB2  |.  E8 79E6FFFF   call <tsc.strcpy1>
00402AB7  |.  8D5424 18     lea edx,dword ptr ss:[esp+0x18]                    ;  "12890891"
00402ABB  |.  8D8424 E00000>lea eax,dword ptr ss:[esp+0xE0]                    ;  "9901"
00402AC2  |.  52            push edx                                           ; /Arg3 = 0013F254 ASCII "12790891"
00402AC3  |.  8D8C24 040400>lea ecx,dword ptr ss:[esp+0x404]                   ; |
00402ACA  |.  50            push eax                                           ; |Arg2 = 0013F31C ASCII "9901"
00402ACB  |.  51            push ecx                                           ; |Arg1 = 0013F7CC ASCII "1111111"
00402ACC  |.  8D8C24 9C0500>lea ecx,dword ptr ss:[esp+0x59C]                   ; |
00402AD3  |.  C68424 6C0600>mov byte ptr ss:[esp+0x66C],0x4                    ; |
00402ADB  |.  E8 30F8FFFF   call tsc.00402310                                  ; \tsc.00402310
00402AE0  |.  8D5424 08     lea edx,dword ptr ss:[esp+0x8]
00402AE4  |.  8D8C24 380300>lea ecx,dword ptr ss:[esp+0x338]
00402AEB  |.  52            push edx
00402AEC  |.  C68424 640600>mov byte ptr ss:[esp+0x664],0x5
00402AF4  |.  E8 37E6FFFF   call <tsc.strcpy1>
00402AF9  |.  8D4424 18     lea eax,dword ptr ss:[esp+0x18]
00402AFD  |.  8D8C24 E00000>lea ecx,dword ptr ss:[esp+0xE0]
00402B04  |.  50            push eax                                           ; /Arg3 = 0013F31C ASCII "9901"
00402B05  |.  8D9424 CC0400>lea edx,dword ptr ss:[esp+0x4CC]                   ; |
00402B0C  |.  51            push ecx                                           ; |Arg2 = 0013F7CC ASCII "1111111"
00402B0D  |.  52            push edx                                           ; |Arg1 = 0013F254 ASCII "12790891"
00402B0E  |.  8D8C24 440300>lea ecx,dword ptr ss:[esp+0x344]                   ; |
00402B15  |.  C68424 6C0600>mov byte ptr ss:[esp+0x66C],0x6                    ; |
00402B1D  |.  E8 EEF7FFFF   call tsc.00402310                                  ; \tsc.00402310
00402B22  |.  8D8424 700200>lea eax,dword ptr ss:[esp+0x270]
00402B29  |.  8D8C24 000400>lea ecx,dword ptr ss:[esp+0x400]
00402B30  |.  50            push eax
00402B31  |.  C68424 640600>mov byte ptr ss:[esp+0x664],0x7
00402B39  |.  E8 82F2FFFF   call <tsc.strcmp1>
00402B3E  |.  85C0          test eax,eax
00402B40  |.  0F84 BF000000 je tsc.00402C05
00402B46  |.  8D8C24 A80100>lea ecx,dword ptr ss:[esp+0x1A8]
00402B4D  |.  51            push ecx
00402B4E  |.  8D8C24 CC0400>lea ecx,dword ptr ss:[esp+0x4CC]
00402B55  |.  E8 66F2FFFF   call <tsc.strcmp1>
00402B5A  |.  85C0          test eax,eax
00402B5C  |.  0F84 A3000000 je tsc.00402C05                                    ; 跳转则注册成功

这个函数传入了 4 个参数,分别是拆分出的 7 数字注册码,固定值 9901 和固定值 12890891,还有个输出变量。00402310 的内部相当复杂,一看就不是人干事。跳过去看下运行结果,算了个 898713 出来(我填的是 7 个 2)。因为事先已经了解到是 RSA 算法,所以推测是一个快速幂的算法,写个函数验证下,

def f(a, b, n):    
    t=1;
    y=a;
    while (b != 0):
        if b & 1 == 1:
            t = t * y % n;
        y = y * y % n;
        b = b >> 1;
    return t

发现计算结果一致。

然后接着看下面,将前后两个 7 位数字计算完毕后会分别将结果和固定的 8483678 和 5666933 进行比较,两者只要有一个相符即可通过验证。

我们填入的注册码相当于对应 RSA 的消息 n,e = 9901,N = 12890891,然后根据加密公式计算出的结果和程序内置的 c 进行比较,从而完成验证。

RSA 加密:

RSA 解密:

而上面出现的 8483678 和 5666933 就是预定义的密文 c 了。

因为解空间也不大,所以可以直接枚举出正确结果,因为程序中会将注册码拆分,所以结果的位数最多只有 7 位,写个循环很快就可以计算出来,而且正好小于 12890891 的 7 位数结果只有 1 个。将前后两部分的结果分别计算出来就行了。

def f(a, b, n):    
    t=1;
    y=a;
    while (b != 0):
        if b & 1 == 1:
            t = t * y % n;
        y = y * y % n;
        b = b >> 1;
    return t

for i in range(1, 9999999):
    res = f(i, 9901, 12790891)
    if res == 8483678:
        print('first part: ' + str(i))      # first part: 7167622
    elif res == 5666933:
        print('second part: ' + str(i))     # second part: 3196885