五星难度Crackme052

21次阅读

共计 5582 个字符,预计需要花费 14 分钟才能阅读完成。

前言

这个 Crackme 不愧是五星难度,看了别的大佬的破文刚开始都没看懂,当然也可能是我太菜了。到最后还是自己一点点抠出来的,所以我尽量讲的详细一点,这样以后如果有人看别人的文章没看懂的话,看我的说不定就看懂了呢,哈哈哈。

程序观察

程序使用了 UPX 的壳。

使用 PEID 自带的插件就可以成功脱壳,但是必须在 Winxp 环境下,win10 里总是报错。

程序分析

使用 OD 载入程序,搜索字符串

跟进代码,可以看到上方不远处有一个 sscanf 函数调用

这个函数将我们输入的序列号转化为长整型的十六进制,分别保存在地址 12EF70、12EF74、12EF78、12EF7C 处。

所以输入的序列号要分为四部分,每部分使用空格或 “-” 分割。如果输入的序列号不是四部分,就会提示错误。

程序流程

在分析算法之前,先将程序的流程讲明白。

  1. 程序首先取得输入的 name,然后将 name 翻转,拼接到 name 的后面,然后将 ProductID 和 RegisteredOwner 依次拼接到后面。这两项都是从注册表中取得的。如果 RegisteredOwner 不存在,则拼接两个 ProductID。如果这两项都不在,就会再拼接两个翻转的 name,形成一正三反的形式。
  2. 程序将拼接后的用户名计算出 md5。然后取得输入 serial,经过函数计算。最后将计算出值进行比较,相等则通过验证。
  3. 也就是 MD5(name) = F(serial), 则通过比较。我们的目的就是来逆这个函数 F。

算法分析

接下来是程序的关键了。因为下面要对我们输入的序列号进行处理。

外层循环

程序首先建立循环,循环次数为 3 次。
在循环中,程序调用函数 401B90,这个函数是真正的处理函数。

函数 401B90

函数的参数有两个。
第一个参数是序列号部分 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。

  • esi1 = C | eax。又因为最初 eax = esi,所以现在 esi1 的值就是 esi 的值左移 1 位,然后最低位和 C 做或运算得到的值。
  • edi1 的值就是 edi 左移一位后的值。但因为是左移,所以 esi 的最高位 bit 位移到了 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 
  1. 首先,eax 等于 esi 与 0x4 做 and 运算,所以 eax 此时的值除了第 3 位外全为 0,也就是形如 00000000 00000000 00000000 00000?00 的形式。
  2. 将 edx 置 0。
  3. 将 eax 和 edx 继续左移,分别左移了 11 位、18 位。此时 eax 中唯一不为 0 的 bit 位移到了第 32 位,也就是 eax 的头部,此时 eax 形如 ?0000000 00000000 00000000 00000000。edx 还为 0。
  4. 最后一次左移,eax 第 32 位的 bit 左移到了 edx 中。此时 eax 全为 0,edx 形如 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 的呢?

  1. esi 左移一位,最低位与 edi 的最高位做或运算,即可得到 esi1。
  2. esi1 ^ eax 即可得到 esi2,但是 eax 必定为 0,所以 esi1 等于 esi2。

edi 是如何变成 edi2 的呢?

  1. edi 左移一位,即为 edi1,此时 edi1 的最低位是 esi 的最高位。
  2. edi1 ^ edx 即可得到 edi2。因为 edx 一直为 0,在最后因为 eax 的左移最后 edx 才不为 0。所以 edx 的值为
edx = (esi1 & 0x4 >> 2) ^ (esi1 &  0x2000 >>13) ^ (esi1 & 0x80000000 >>31)

那么我们有了 esi2 和 edi2,如何得到 esi 和 edi 呢?

  1. 因为 esi2 = esi1,所以我们直接就能得到 esi1。
  2. 因为 edi1 ^ edx = edi2,所以 edi2 ^ edx = edi1。因为已经有了 esi1,所以 edx 的值可以通过上面的公式计算出来。
  3. 取 esi1 的最低位 DH (这是 edi 的最高位)。
  4. 取 edi2 的最低位 AH (这是 esi 的最高位)。
  5. 将 esi1 右移一位,将 AH 补上最高位,得到 esi。
  6. 将 edi1 右移一位,将 DH 补上最高位,得到 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

相关文件在我的 Github

正文完
 0