作为练手的 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