这个 Crackme 不愧是五星难度,看了别的大佬的破文刚开始都没看懂,固然也多是我太菜了。到最后仍是本身一点点抠出来的,因此我尽可能讲的详细一点,这样之后若是有人看别人的文章没看懂的话,看个人说不定就看懂了呢,哈哈哈。git
程序使用了 UPX 的壳。github
使用 PEID 自带的插件就能够成功脱壳,可是必须在 Winxp环境下,win10 里老是报错。算法
使用 OD 载入程序,搜索字符串函数
跟进代码,能够看到上方不远处有一个 sscanf 函数调用加密
这个函数将咱们输入的序列号转化为长整型的十六进制,分别保存在地址 12EF70、12EF7四、12EF7八、12EF7C 处。spa
因此输入的序列号要分为四部分,每部分使用空格或 "-" 分割。若是输入的序列号不是四部分,就会提示错误。插件
在分析算法以前,先将程序的流程讲明白。code
接下来是程序的关键了。由于下面要对咱们输入的序列号进行处理。blog
程序首先创建循环,循环次数为 3次。
在循环中,程序调用函数401B90,这个函数是真正的处理函数。md5
函数的参数有两个。
第一个参数是序列号部分1的地址,也就是 12EF70;
第二个参数是 0xBADCODE / (0x50 + i) 的商,i 是 esi 的值。
接下来进入到函数内部。
首先是赋初值部分
接下里是循环部分
00401BA5 > 8BEF mov ebp,edi ; loc_401BA5 00401BA7 B9 01000000 mov ecx,0x1 00401BAC C1ED 1F shr ebp,0x1F 00401BAF 896C24 18 mov dword ptr ss:[esp+0x18],ebp 00401BB3 8BC6 mov eax,esi 00401BB5 8BD7 mov edx,edi 00401BB7 33ED xor ebp,ebp 00401BB9 E8 B21B0000 call <egis_11.__allshl> 00401BBE 8B4C24 18 mov ecx,dword ptr ss:[esp+0x18] 00401BC2 0BEA or ebp,edx 00401BC4 0BC8 or ecx,eax 00401BC6 33D2 xor edx,edx 00401BC8 8BF1 mov esi,ecx 00401BCA B9 0B000000 mov ecx,0xB 00401BCF 8BC6 mov eax,esi 00401BD1 8BFD mov edi,ebp 00401BD3 83E0 04 and eax,0x4 00401BD6 E8 951B0000 call <egis_11.__allshl> 00401BDB 8BCE mov ecx,esi 00401BDD 33ED xor ebp,ebp 00401BDF 81E1 00200000 and ecx,0x2000 00401BE5 33D5 xor edx,ebp 00401BE7 33C1 xor eax,ecx 00401BE9 B9 12000000 mov ecx,0x12 00401BEE E8 7D1B0000 call <egis_11.__allshl> 00401BF3 8BCE mov ecx,esi 00401BF5 33D5 xor edx,ebp 00401BF7 81E1 00000080 and ecx,0x80000000 00401BFD 33C1 xor eax,ecx 00401BFF B9 01000000 mov ecx,0x1 00401C04 E8 671B0000 call <egis_11.__allshl> 00401C09 33F0 xor esi,eax 00401C0B 33FA xor edi,edx 00401C0D 4B dec ebx 00401C0E ^ 75 95 jnz short <egis_11.loc_401BA5>
最后是结尾
能够看到,最后将 esi 的值赋给了 serial[0],edi 的值赋给了 esi[1]。
因此在循环处理的时候,咱们只要紧盯着 esi 和 edi 便可。
关于 allshl 函数,也要说几句。 allshl 函数是用来在 32 位CPU上进行 64位数的左移运算。共有 3个参数,eax为64位数的低32位,edx 为64位数的高32位,ecx为这个64位数要左移的位数。
由于循环部分有点长,因此咱们将其分为三部分
00401BA5 > 8BEF mov ebp,edi ; ebp = edi = serial[2] 00401BA7 B9 01000000 mov ecx,0x1 00401BAC C1ED 1F shr ebp,0x1F ; serial[2] >> 31 00401BAF 896C24 18 mov dword ptr ss:[esp+0x18],ebp 00401BB3 8BC6 mov eax,esi ; eax = serial[0] 00401BB5 8BD7 mov edx,edi ; edx = serial[1] 00401BB7 33ED xor ebp,ebp 00401BB9 E8 B21B0000 call <egis_11.__allshl> 00401BBE 8B4C24 18 mov ecx,dword ptr ss:[esp+0x18] 00401BC2 0BEA or ebp,edx 00401BC4 0BC8 or ecx,eax 00401BC6 33D2 xor edx,edx 00401BC8 8BF1 mov esi,ecx 00401BCA B9 0B000000 mov ecx,0xB 00401BCF 8BC6 mov eax,esi 00401BD1 8BFD mov edi,ebp
咱们能够将其简化为下面的部分
C = edi >>31 ebp = 0 SHL(edx, eax, 1) ------------------------------------ ecx = C ecx = ecx | eax = C | eax ebp = ebp | edx = 0 | edx = edx esi1 = ecx = C | eax = C | (esi<<1) edi1 = ebp = edx = edi
C 即为 edi 右移31位后的 bit 值,也就是 edi 的最高位 bit。
在这部分,esi 和 edi 的值发生了变化。我将其称为 esi1 和 edi1。
00401BC6 33D2 xor edx,edx 00401BC8 8BF1 mov esi,ecx 00401BCA B9 0B000000 mov ecx,0xB 00401BCF 8BC6 mov eax,esi 00401BD1 8BFD mov edi,ebp 00401BD3 83E0 04 and eax,0x4 00401BD6 E8 951B0000 call <egis_11.__allshl> 00401BDB 8BCE mov ecx,esi 00401BDD 33ED xor ebp,ebp 00401BDF 81E1 00200000 and ecx,0x2000 00401BE5 33D5 xor edx,ebp 00401BE7 33C1 xor eax,ecx 00401BE9 B9 12000000 mov ecx,0x12 00401BEE E8 7D1B0000 call <egis_11.__allshl> 00401BF3 8BCE mov ecx,esi 00401BF5 33D5 xor edx,ebp 00401BF7 81E1 00000080 and ecx,0x80000000 00401BFD 33C1 xor eax,ecx 00401BFF B9 01000000 mov ecx,0x1 00401C04 E8 671B0000 call <egis_11.__allshl>
在第二部分,esi 和 edi 的值并无发生变化,因此主要是观察 eax 和 edx 的变化。
将其简化为下面部分
eax = esi1 & 0x4 = (C|eax) & 0x4 eax 除第3位其他全为0 edx = 0 SHL(edx, eax, 0xb) eax 除第 14位其他全为0 ---------------------------------------- ebp = 0 ecx = esi1 & 0x2000 eax 除第14位外其他全为0 eax = eax ^ ecx eax 除第14位外其他位全为0 edx = edx ^ ebp = edx ^ 0 SHL(edx, eax, 0x12) eax 除第32位外其他全为0 ----------------------------------------- ecx = esi1 & 0x80000000 eax = eax ^ ecx edx = edx ^ ebp = edx ^ 0 SHL(edx, eax, 0x1) eax 全为0
00000000 00000000 00000000 00000?00
的形式。?0000000 00000000 00000000 00000000
。edx 还为 0。00000000 00000000 00000000 0000000?
。因此不管 eax 和 edx 的值为多少,到最后 eax 一定为 0,edx 只有最后一位 bit 不为0,是由 eax 左移得来的。
00401C09 33F0 xor esi,eax 00401C0B 33FA xor edi,edx
转化为
esi2 = esi1 ^ eax = esi ^ 0 = esi1 edi2 = edi1 ^ edx
由于 eax 一定为 0,因此 esi2 的值等于 esi1 的值。
edi2 的值等于 edi1 和 edx 作异或运算后的值。
那么在一个循环中,esi 是如何变成 esi2 的呢?
edi 是如何变成 edi2 的呢?
edx = (esi1 & 0x4 >> 2) ^ (esi1 & 0x2000 >>13) ^ (esi1 & 0x80000000 >>31)
那么咱们有了 esi2 和 edi2,如何获得 esi 和 edi 呢?
我并无写出完整的注册机,只写了将加密运算后的用户名逆算法生成序列号的部分。也就是说 md5(name) = F(serial) 中,我只写了后半部分,没有写 md5(name) 部分,因此使用的时候须要本身到程序中找加密后的 name,而后改成 Key[4] 的值便可。
#include <stdio.h> #include <string.h> #include <Windows.h> int Keygen() { unsigned long key[4] = { 0x0000D886, 0x36542100, 0x6C7EC6F2, 0xD4D3E0AE }; for (int i = 2; i >= 0; i--) { unsigned long num = 0xBADC0DE / (i + 0x50); unsigned long esi = *(key + i); unsigned long edi = *(key + i + 1); int C = 0; for (unsigned long j = num; j > 0; j--) { C = ((esi & 0x4) >> 2) ^ ((esi & 0x2000) >> 13) ^ ((esi & 0x80000000) >> 31); edi = edi ^ C; int DH = esi & 1; //DH 为原来 edi 的最高位 int AH = edi & 1; //AH 为原来 esi 的最高位 esi = (esi >> 1) | (AH << 31); edi = (edi >> 1) | (DH << 31); } *(key + i) = esi; *(key + i + 1) = edi; } printf("%X %X %X %X", key[0], key[1], key[2], key[3]); return 0; } int main(int argc, char* argv[]) { Keygen(); return 0; }
好比个人用户名为 1234
。
在 WinXP 中,生成的 MD5 为0x0000D886, 0x36542100, 0x6C7EC6F2, 0xD4D3E0AE
,对应的序列号为 83627B75 47DC1507 CE6FBBCD DAA36F3E
在 Win10 中,生成的 MD5 为 0x0000C9B8, 0x37D4BC64, 0x1D98AF82, 0xDBF464AA
,对应的序列号为 2FCEE274 EBC4892B 5EB25588 232790EB