作为练手的 160 个 CrackMe 系列整理分析
CrackMe 来源:【反汇编练习】160个CrackME索引目录1~160建议收藏备用
这个五星的还真有点麻烦,主要是前面还原 C 代码花了不少时间2333,如果直接抽代码出来应该能省下不少功夫。。。
整个流程是这样:
- 输入用户名,进行 u(用户名) 变换后计算 md5(我都逆完了才反应过来,其实看到那组初始 key 就该注意到的,而且没把一开始用 PEiD 看到的 md5 算法当回事。。OTZ),得到一个结果 key;
- 以输入的注册码为初始值,经过 code = f(input) 的变换后,将 key 与 code 进行比较,相等就通过校验。
其中 u(用户名) = 用户名+用户名反转+产品ID+所有者ID。后面两个分别是注册表中 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion 里的 ProductId 和 RegisteredOwner。如果 RegisteredOwner 不存在将会用 ProductId 替代,即接上两个 ProductId。
既然是 md5 ,那么这样只能在 f 上下手了,首先还原回来的 f 大概长这样:
unsigned int* ParseCode(unsigned int *input, int time) { DWORD esi, edi, eax, ecx, ebx, edx, ebp; esi = input[0]; edi = input[1]; while (time--) { v18 = ebp = edi >> 31 edx:eax = Lshl(edi:esi, 1) ebp = edx edi = ebp ecx = v18 | eax edx = 0 eax = esi = ecx eax &= 4 edx:eax = Lshl(edx:eax, 0xb) ecx = esi ecx &= 0x2000 eax &= ecx edx:eax = Lshl(edx:eax, 0x12) ecx = esi edx ^= ebp ecx &= 0x80000000 eax ^= ecx edx:eax = Lshl(edx:eax, 0x1) esi ^= eax edi ^= edx } input[0] = esi; input[1] = edi; return input; }
这里只是简单地还原了一下,其中 edx:eax 表示 64 为整数,edx 为高 32 位。Lshl 是 64 位无符号左移的一个包装。稍作整理,做点等效变换,然后分析一下有没有可以简化的计算步骤。
首先需要发现 v18 和 esi、edi,以及一开始左移一位之间的关系,其实就是一个循环左移。精简后得到下面的代码。
unsigned int* ParseCode(unsigned int *input, int time) { DWORD esi, edi, eax, ecx, ebx, edx, ebp; esi = input[0]; edi = input[1]; while (time--) { Rol64((unsigned __int64)edi << 32 | esi, 1, &edi, &esi); // eax 形如 00000000 00000000 00000000 00000?00 eax = (esi) & 4; edx = 0; Lshl(edx, eax, 0xb, &edx, &eax); // eax 形如 00000000 00000000 00?00000 00000000 ecx = esi & 0x2000; eax ^= ecx; Lshl(edx, eax, 0x12, &edx, &eax); // eax 形如 ?0000000 00000000 00000000 00000000 ecx = esi & 0x80000000; eax ^= ecx; Lshl(edx, eax, 0x1, &edx, &eax); // eax === 0 // 此时 edx 等于 !!(esi & 4) ^ !!(esi & 0x2000) ^ !!(esi & 0x80000000) edi ^= edx; } input[0] = esi; input[1] = edi; return input; }
既然开头的位移是循环位移,那么首先可以放点心了。然后观察这三组看似无规律的位与和异或,好吧我写在注释里了,因为高位的 edx 为 0,低位的 eax 经过一通左移也变为 0 了,然后仅有的一个有效位被移入 edx 最低位,最后简化的结果就是下面这样:
unsigned int* ParseCode(unsigned int *input, int time) { DWORD esi, edi; esi = input[0]; edi = input[1]; while (time--) { Rol64((unsigned __int64)edi << 32 | esi, 1, &edi, &esi); edi ^= (!!(esi & 0x4) ^ !!(esi & 0x2000) ^ !!(esi & 0x80000000)); } input[0] = esi; input[1] = edi; return input; }
f 的逆就不用多说了吧。
至于 md5 算法的还原。。。难度不大(都知道 md5 了),里面有一段读取了 exe 的内存,所以要把它们拖过来。中间的翻译过程细心点不要看错了就好。。。有些地方我一开始也搞错,比如第一个循环里的第三个和第四个,计算循环左移用的地址一直是固定的 constAddr1[0] 和 constAddr1[1],我一开始弄成 constAddr1[ii] 了,然后算出结果当然不对啦。
最后附上注册机源码:
#include <cassert> #include <array> #include <cstring> #include <windows.h> std::array<std::pair<int, int>, 16> constAddr1 = { std::pair<int,int>(0x41b0b0, 0xD76AA478), std::pair<int,int>(0x41b0b4, 0xE8C7B756), std::pair<int,int>(0x41b0b8, 0x242070DB), std::pair<int,int>(0x41b0bc, 0xC1BDCEEE), std::pair<int,int>(0x41b0c0, 0xF57C0FAF), std::pair<int,int>(0x41b0c4, 0x4787C62A), std::pair<int,int>(0x41b0c8, 0xA8304613), std::pair<int,int>(0x41b0cc, 0xFD469501), std::pair<int,int>(0x41b0d0, 0x698098D8), std::pair<int,int>(0x41b0d4, 0x8B44F7AF), std::pair<int,int>(0x41b0d8, 0xFFFF5BB1), std::pair<int,int>(0x41b0dc, 0x895CD7BE), std::pair<int,int>(0x41b0e0, 0x6B901122), std::pair<int,int>(0x41b0e4, 0xFD987193), std::pair<int,int>(0x41b0e8, 0xA679438E), std::pair<int,int>(0x41b0ec, 0x49B40821), }; std::array<std::pair<int, int>, 16> constAddr2 = { std::pair<int,int>(0x41b0f0, 0xF61E2562), std::pair<int,int>(0x41b0f4, 0xC040B340), std::pair<int,int>(0x41b0f8, 0x265E5A51), std::pair<int,int>(0x41b0fc, 0xE9B6C7AA), std::pair<int,int>(0x41b100, 0xD62F105D), std::pair<int,int>(0x41b104, 0x02441453), std::pair<int,int>(0x41b108, 0xD8A1E681), std::pair<int,int>(0x41b10c, 0xE7D3FBC8), std::pair<int,int>(0x41b110, 0x21E1CDE6), std::pair<int,int>(0x41b114, 0xC33707D6), std::pair<int,int>(0x41b118, 0xF4D50D87), std::pair<int,int>(0x41b11c, 0x455A14ED), std::pair<int,int>(0x41b120, 0xA9E3E905), std::pair<int,int>(0x41b124, 0xFCEFA3F8), std::pair<int,int>(0x41b128, 0x676F02D9), std::pair<int,int>(0x41b12c, 0x8D2A4C8A), }; std::array<std::pair<int, int>, 16> constAddr3 = { std::pair<int,int>(0x41b130, 0xFFFA3942), std::pair<int,int>(0x41b134, 0x8771F681), std::pair<int,int>(0x41b138, 0x6D9D6122), std::pair<int,int>(0x41b13c, 0xFDE5380C), std::pair<int,int>(0x41b140, 0xA4BEEA44), std::pair<int,int>(0x41b144, 0x4BDECFA9), std::pair<int,int>(0x41b148, 0xF6BB4B60), std::pair<int,int>(0x41b14c, 0xBEBFBC70), std::pair<int,int>(0x41b150, 0x289B7EC6), std::pair<int,int>(0x41b154, 0xEAA127FA), std::pair<int,int>(0x41b158, 0xD4EF3085), std::pair<int,int>(0x41b15c, 0x04881D05), std::pair<int,int>(0x41b160, 0xD9D4D039), std::pair<int,int>(0x41b164, 0xE6DB99E5), std::pair<int,int>(0x41b168, 0x1FA27CF8), std::pair<int,int>(0x41b16c, 0xC4AC5665), }; std::array<std::pair<int, int>, 16> constAddr4 = { std::pair<int,int>(0x41b170, 0xF4292244), std::pair<int,int>(0x41b174, 0x432AFF97), std::pair<int,int>(0x41b178, 0xAB9423A7), std::pair<int,int>(0x41b17c, 0xFC93A039), std::pair<int,int>(0x41b180, 0x655B59C3), std::pair<int,int>(0x41b184, 0x8F0CCC92), std::pair<int,int>(0x41b188, 0xFFEFF47D), std::pair<int,int>(0x41b18c, 0x85845DD1), std::pair<int,int>(0x41b190, 0x6FA87E4F), std::pair<int,int>(0x41b194, 0xFE2CE6E0), std::pair<int,int>(0x41b198, 0xA3014314), std::pair<int,int>(0x41b19c, 0x4E0811A1), std::pair<int,int>(0x41b1a0, 0xF7537E82), std::pair<int,int>(0x41b1a4, 0xBD3AF235), std::pair<int,int>(0x41b1a8, 0x2AD7D2BB), std::pair<int,int>(0x41b1ac, 0xEB86D391), }; DWORD AddressValue(DWORD addr) { for (auto& p : constAddr1) if (p.first == addr) return p.second; for (auto& p : constAddr2) if (p.first == addr) return p.second; for (auto& p : constAddr3) if (p.first == addr) return p.second; for (auto& p : constAddr4) if (p.first == addr) return p.second; assert("address not found." && 0); return 0; } int Rol(int a1, char a2) { __asm { mov eax, a1 mov cl, a2 rol eax, cl mov a1, eax } return a1; } // 0x401700 unsigned int* ComputeKey(char* str, unsigned int *key) { int esi = key[0]; int edi = key[1]; int ebp = key[2]; int ebx = key[3]; PDWORD v11 = PDWORD(str + 8); int ii = 0; for (int i = 0; i < 4; ++i) { esi = edi + Rol(constAddr1[ii].second + (edi & ebp | ebx & ~edi) + esi + v11[-2], 7); ebx = esi + Rol(constAddr1[ii+1].second + (esi & edi | ebp & ~esi) + ebx + v11[-1], 12); ebp = ebx + Rol(AddressValue((DWORD)((char *)v11 + constAddr1[0].first - str)) + (esi & ebx | edi & ~ebx) + ebp + v11[0], 17); edi = ebp + Rol(AddressValue((DWORD)((char *)v11 + constAddr1[1].first - str)) + (ebp & ebx | esi & ~ebp) + edi + v11[1], 22); ii += 4; v11 += 4; // 一次加 16 字节 } ii = 0; BYTE original_6 = 6; for (int i = 0; i < 4; ++i) { int v1 = Rol(constAddr2[ii].second + (edi & ebx | ebp & ~ebx) + esi + *PDWORD(str + 4 * ((original_6 - 5) & 0xF)), 5); esi = edi + v1; int v2 = Rol(constAddr2[ii+1].second + ((edi + v1) & ebp | edi & ~ebp) + ebx + *PDWORD(str + 4 * (original_6 & 0xF)), 9); ebx = esi + v2; int v3 = Rol(constAddr2[ii+2].second + (edi & (esi + v2) | esi & ~edi) + ebp + *PDWORD(str + 4 * ((original_6 + 5) & 0xF)), 14); ebp = ebx + v3; edi = ebp + Rol(constAddr2[ii+3].second + (esi & (ebx + v3) | ebx & ~esi) + edi + *PDWORD(str + 4 * ((original_6 - 6) & 0xF)), 20); original_6 += 4; ii += 4; } ii = 0; int v33 = ebp ^ ebx; BYTE original_neg_5 = -5; int original_neg_8 = -8; for (int i = 0; i < 4; ++i) { int v1 = Rol(constAddr3[ii].second + (edi ^ v33) + esi + *PDWORD(str + 4 * ((original_neg_5 - 6) & 0xF)), 4); esi = edi + v1; int v2 = Rol(constAddr3[ii+1].second + ((edi + v1) ^ edi ^ ebp) + ebx + *PDWORD(str + 4 * (original_neg_8 & 0xF)), 11); ebx = esi + v2; int v3 = Rol(constAddr3[ii+2].second + (esi ^ edi ^ (esi + v2)) + ebp + *PDWORD(str + 4 * (original_neg_5 & 0xF)), 16); ebp = ebx + v3; int v4 = ebp ^ ebx; edi = ebp + Rol(constAddr3[ii+3].second + (esi ^ v4) + edi + *PDWORD(str + 4 * ((original_neg_5 + 3) & 0xF)), 23); v33 = v4; original_neg_8 -= 4; original_neg_5 -= 4; ii += 4; } ii = 0; BYTE original_0 = 0; BYTE original_neg_2 = -2; for (int i = 0; i < 4; ++i) { int v1 = Rol(constAddr4[ii].second + (ebp ^ (edi | ~ebx)) + esi + *PDWORD(str + 4 * (original_0 & 0xF)), 6); esi = edi + v1; int v2 = Rol(constAddr4[ii+1].second + (edi ^ ((edi + v1) | ~ebp)) + ebx + *PDWORD(str + 4 * ((original_neg_2 - 7) & 0xF)), 10); ebx = esi + v2; int v3 = Rol(constAddr4[ii+2].second + (esi ^ ((esi + v2) | ~edi)) + ebp + *PDWORD(str + 4 * (original_neg_2 & 0xF)), 15); ebp = ebx + v3; edi = ebp + Rol(constAddr4[ii+3].second + (ebx ^ ((ebx + v3) | ~esi)) + edi + *PDWORD(str + 4 * ((original_neg_2 + 7) & 0xF)), 21); original_0 -= 4; original_neg_2 -= 4; ii += 4; } key[0] += esi; key[1] += edi; key[2] += ebp; key[3] += ebx; return key; } unsigned __int64 Lshl(DWORD high, DWORD low, int shift, PDWORD high1, PDWORD low1) { unsigned __int64 v1 = (unsigned __int64)high << 32 | low; v1 <<= shift; *high1 = (DWORD)(v1 >> 32); *low1 = (DWORD)v1; return v1; } unsigned __int64 Rol64(unsigned __int64 a, char shift, PDWORD high1, PDWORD low1) { unsigned __int64 v1 = (a << shift) | (a >> (64 - shift)); *high1 = (DWORD)(v1 >> 32); *low1 = (DWORD)v1; return v1; } unsigned __int64 Ror64(unsigned __int64 a, char shift, PDWORD high1, PDWORD low1) { unsigned __int64 v1 = (a >> shift) | (a << (64 - shift)); *high1 = (DWORD)(v1 >> 32); *low1 = (DWORD)v1; return v1; } unsigned int* ParseCodeReverse(unsigned int *a1, int a2) { DWORD esi, edi; esi = a1[0]; edi = a1[1]; while (a2--) { edi ^= (!!(esi & 0x4) ^ !!(esi & 0x2000) ^ !!(esi & 0x80000000)); Ror64((unsigned __int64)edi << 32 | esi, 1, &edi, &esi); } a1[0] = esi; a1[1] = edi; return 0; } unsigned int* ParseCode(unsigned int *a1, int a2) { DWORD esi, edi; esi = a1[0]; edi = a1[1]; while (a2--) { Rol64((unsigned __int64)edi << 32 | esi, 1, &edi, &esi); edi ^= (!!(esi & 0x4) ^ !!(esi & 0x2000) ^ !!(esi & 0x80000000)); } a1[0] = esi; a1[1] = edi; return 0; } int main(int argc, char**argv) { // argv[1] = Name // argv[2] = ProductId // argv[3] = RegisteredOwner if (argc != 4) abort(); char str[512]; strcpy_s(str, sizeof(str), argv[1]); strcat_s(str, sizeof(str), _strrev(argv[1])); strcat_s(str, sizeof(str), argv[2]); strcat_s(str, sizeof(str), argv[3]); memset(str + strlen(str), 0, sizeof(str) - strlen(str)); int pos = 0x40 - (strlen(str) + 1) & 0x3f; if (pos <= 7) pos += 0x40; pos += strlen(str) + 1; str[strlen(str)] = '\x80'; *PDWORD(str + pos - 8) = strlen(str) * 8; unsigned int key[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 }; for (int i = 0; i < pos; i += 0x40) ComputeKey(str + i, key); key[0] &= 0xffff; for (int i = 2; i >= 0; --i) // i 也要倒过来 ParseCodeReverse(&key[i], 0xbadc0de / (0x50 + i)); printf("%#x %#x %#x %#x\n", key[0], key[1], key[2], key[3]); return 0; }