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

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

这个五星的还真有点麻烦,主要是前面还原 C 代码花了不少时间2333,如果直接抽代码出来应该能省下不少功夫。。。

整个流程是这样:

  1. 输入用户名,进行 u(用户名) 变换后计算 md5(我都逆完了才反应过来,其实看到那组初始 key 就该注意到的,而且没把一开始用 PEiD 看到的 md5 算法当回事。。OTZ),得到一个结果 key;
  2. 以输入的注册码为初始值,经过 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] 了,然后算出结果当然不对啦。

result

最后附上注册机源码:

#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;
}