(五星难度)Crackme052

前言

这个 Crackme 不愧是五星难度,看了别的大佬的破文刚开始都没看懂,固然也多是我太菜了。到最后仍是本身一点点抠出来的,因此我尽可能讲的详细一点,这样之后若是有人看别人的文章没看懂的话,看个人说不定就看懂了呢,哈哈哈。git

程序观察

程序使用了 UPX 的壳。
20191107180328.pnggithub

使用 PEID 自带的插件就能够成功脱壳,可是必须在 Winxp环境下,win10 里老是报错。算法

20191107192237.png

程序分析

使用 OD 载入程序,搜索字符串
20191107192653.png函数

跟进代码,能够看到上方不远处有一个 sscanf 函数调用
20191107194023.png加密

这个函数将咱们输入的序列号转化为长整型的十六进制,分别保存在地址 12EF70、12EF7四、12EF7八、12EF7C 处。
20191107193956.pngspa

因此输入的序列号要分为四部分,每部分使用空格或 "-" 分割。若是输入的序列号不是四部分,就会提示错误。
20191107194142.png插件

程序流程

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

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

算法分析

接下来是程序的关键了。由于下面要对咱们输入的序列号进行处理。blog

外层循环

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

函数401B90

函数的参数有两个。
第一个参数是序列号部分1的地址,也就是 12EF70;
第二个参数是 0xBADCODE / (0x50 + i) 的商,i 是 esi 的值。
20191107200036.png

接下来进入到函数内部。
首先是赋初值部分
20191107200800.png

接下里是循环部分

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>

最后是结尾
20191107203717.png
能够看到,最后将 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

20191108090939.png

在 Win10 中,生成的 MD5 为 0x0000C9B8, 0x37D4BC64, 0x1D98AF82, 0xDBF464AA,对应的序列号为 2FCEE274 EBC4892B 5EB25588 232790EB

20191108092138.png

相关文件在个人 Github

相关文章
相关标签/搜索