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