160 个 CrackMe 之 056 Elraizer.2 解码代码破解、注册分析及注册机实现

160 个 CrackMe 之 056 Elraizer.2 是 Elraizer 第2个 CrackMe,第1个已破解,见我这个贴子:
https://www.52pojie.cn/thread-953152-1-1.html
这两个 CrackMe 都比较复杂,而且都会通过注册码来计算的结果来确定内存地址或汇编代码。如果注册码输入不正确,CrackMe是无法正常运行的。
这个 CrackMe 的反调试比较强,而且代码和相关字符串资源都是加密的,虽然没有加壳,但与  120 - Cronos CrackMe 一样,需要在代码执行前动态解密,字符串在使用前也需要先解码。
更BT的是,其利用 WinMain() 的最后一个参数 nShowCmd 作为解密密钥,而 nShowCmd 参数,通过调试器打开时,在 Windows 9x 下,传入的值是不一样的,会导致解密实际上失败,但你还不一定看得出来,因为解密过程没有错,只是密钥有问题。
先还是看看文件信息吧,也没什么特别的:

首先説一下  WinMain(),WinMain()函数是Microsoft的一个传统函数命名,它是提供给用户的Windows应用程序的入口点。它的函数声明如下:
[C] 纯文本查看 复制代码

int WINAPI WinMain(          
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
);

关键在于 nCmdShow 参数,CrackMe 使用这个参数的值来解密大多数代码(还会与其它值进行运算后才当密钥使用),nShowCmd 有很多取值,常用也就两种:
[C] 纯文本查看 复制代码

SW_SHOWDEFAULT = 10
SW_SHOWNORMAL  = 1
SW_HIDE        = 0
SW_SHOW        = 5

在 Win9x 下,双击打开时,传入是 SW_SHOWNORMAL,当用 OD 之类的调试器载入时,传递给 CrackMe 的是 SW_SHOWDEFAULT ,而在 WinNT 系列内核的系统,两种撕开 方式都是传入的都是 SW_SHOWDEFAULT ,而 CrackMe 需要传入的是 SW_SHOWNORMAL ,所以,在最新系统中都不能正常运行 CrackMe。为了解决这个问题,我们只有先在其启动函数 start() 中先处理这个问题,然后再调用 WinMain()函数。
如下图所示,在其 start() 部分,有一段这样的代码,下好用来处理 nShowCmd 的问题:

具体代码如下:[Asm] 纯文本查看 复制代码

00401B76     8975 D0                  mov     dword ptr [ebp-30], esi                                      ; 初始化 StartupInfo.dwFlags = 0
00401B79     8D45 A4                  lea     eax, dword ptr [ebp-5C]
00401B7C     50                       push    eax
00401B7D     FF15 78914000            call    dword ptr [<&KERNEL32.GetStartupInfoA>]                      ; KERNEL32.GetStartupInfoA
00401B83     E8 EF030000              call    00401F77                                                     ; _wincmdln
00401B88     8945 9C                  mov     dword ptr [ebp-64], eax
00401B8B     F645 D0 01               test    byte ptr [ebp-30], 1                                         ; StartupInfo.dwFlags = 0x81 = STARTF_FORCEOFFFEEDBACK(0x80) | STARTF_USESHOWWINDOW(0x01),如果这里是0x01,则表示是通过其它进程启动的,并传递了 nCmdShow 参数,因此后面会将nShowCmd改成错误值,导致WinMain()内部代码解码出错,程序无法继续执行。
00401B8F     74 06                    je      short 00401B97
00401B91     0FB745 D4                movzx   eax, word ptr [ebp-2C]                                       ; 检查 nShowCmd参数,StartupInfo.wShowWindow = 0x0A = SW_SHOWDEFAULT,正常启动应为 0x01 (SW_SHOWNORMAL);
00401B95     EB 03                    jmp     short 00401B9A                                               ; 这里改成 2 个 nop,就可以进行调试了。
00401B97     6A 0A                    push    0A                                                           ; 如果检测到了是间接启动的,改成0x0A,正确值应是, 0x01,这里可改成 push 01,就可用进行调试了。
00401B99     58                       pop     eax
00401B9A     50                       push    eax                                                          ; nShowCmd,正确值应为 0x01
00401B9B     FF75 9C                  push    dword ptr [ebp-64]                                           ; lpCommandLine
00401B9E     56                       push    esi                                                          ; hPrevInstance
00401B9F     56                       push    esi
00401BA0     FF15 74914000            call    dword ptr [<&KERNEL32.GetModuleHandleA>]                     ; KERNEL32.GetModuleHandleA
00401BA6     50                       push    eax                                                          ; hInstance
00401BA7     E8 D4FAFFFF              call    00401680                                                     ; WinMain() 程序主过程入口00401BAC     8945 A0                  mov     dword ptr [ebp-60], eax                                      ; eax = 0

其中有处理参数 nShowCmd 的代码,我们改动一下就可以了,如下图:

具体改动代码好下:[Asm] 纯文本查看 复制代码

00401B91  |.  0FB745 D4         movzx   eax, word ptr [ebp-2C]
00401B95      90                nop
00401B96      90                nop
00401B97      6A 01             push    1                                         ;  强行修改为 SW_SHOWNORMAL,即 0x01
00401B99  |.  58                pop     eax

这样,不管什么方式启动 CrackMe,都会传递给 WinMain() 一个正确的 nShowCmd。另外,也激活了 OD 的 patch 功能,记录代码的变化,包括 CrackMe 自解密的代码变化,便于后面我们保存这些变化。
完成这一步后,我们就可以进行正常调试和跟踪了。
首先 F7 进入上面的 WinMain()函数,如下位置,需按 F7 进行:
[Asm] 纯文本查看 复制代码

00401BA6  |.  50                push    eax
00401BA7  |.  E8 D4FAFFFF       call    00401680                                  ;  WinMain(),F7 进行该函数

进入WinMain()后如下图所示:

标为蓝色底的两行代码就是处理 nShowCmd 参数,就是从参数变量中取出来,存入到全局变量中,便于后面各处的解码过程使用这个数据,代码如下:
[Asm] 纯文本查看 复制代码

004016AD   .  8B45 14           mov     eax, dword ptr [ebp+14]                   ;  取出 nShowCmd 参数
004016B0   .  A3 20784000       mov     dword ptr [407820], eax                   ;  保存 nShowCmd,留待后面继续使用

执行到这一行,我们可以看到 EAX = 0x00000001,即 SW_SHOWNORMAL,如下图所示:

接下来就是第一次代码解操作,如下图蓝色底色的代码:

我们先看看解码函数,如下图:

具体代码如下,同时我们也可以看到,至少有6个地方会调用这个函数。
[Asm] 纯文本查看 复制代码

;////////////////////////////// 加/解密函数 /////////////////////////////////////////

004017E0  /$  8B4C24 04       mov     ecx, dword ptr [esp+4]    ;  密文地址
004017E4  |.  8B4424 0C       mov     eax, dword ptr [esp+C]    ;  长度
004017E8  |.  56              push    esi
004017E9  |.  33F6            xor     esi, esi                  ;  初始化较验和
004017EB  |.  8D1401          lea     edx, dword ptr [ecx+eax]  ;  n
004017EE  |.  3BCA            cmp     ecx, edx
004017F0  |.  73 1E           jnb     short 00401810
004017F2  |.  8A4424 0C       mov     al, byte ptr [esp+C]      ;  取密钥
004017F6  |.  53              push    ebx
004017F7  |.  57              push    edi
004017F8  |>  8A19            /mov     bl, byte ptr [ecx]       ;  读取密文
004017FA  |.  32D8            |xor     bl, al                   ; 加/解密
004017FC  |.  0FBEFB          |movsx   edi, bl
004017FF  |.  8819            |mov     byte ptr [ecx], bl       ;  保存明文
00401801  |.  03F7            |add     esi, edi                 ;  计算较验和
00401803  |.  41              |inc     ecx                      ;  i++
00401804  |.  3BCA            |cmp     ecx, edx
00401806  |.^ 72 F0           \jb      short 004017F8
00401808  |.  5F              pop     edi
00401809  |.  8BC6            mov     eax, esi                  ;  返回校验和
0040180B  |.  5B              pop     ebx
0040180C  |.  5E              pop     esi
0040180D  |.  C2 0C00         retn    0C

就是简单的 XOR 运算加/解密,并计算校验和。
我们直接执行解码操作,如下图,解码完成后,数据区变红的部分就是解码后的代码:

接下来的一个 call 00401620 就是调用刚才解码的代码,如下图所示:

按 F7 进入函数,如下图:

可以看到红色的代码部分就是前面解码出来的代码,还可以看出,OD已经将“花指令”标示出来了。花指令如下:
[Asm] 纯文本查看 复制代码

00401626  |. /EB 05             jmp     short 0040162D
00401628  |  |AD                db      AD
00401629  |  |23                db      23                                        ;  CHAR '#'
0040162A  |  |CC                int3
0040162B  |  |55                db      55                                        ;  CHAR 'U'
0040162C  |  |FF                db      FF

我们可以去除这些指令,也可不去除,因为经 OD 处理分析后,也不影响我们阅读汇编代码,去不去就无所谓了。
上图中标蓝色底的代码是对字符串进行解码的函数调用(call 00401000),该函数内容如下图:

可见其是通过一个参数来控制加/解密的,当参数值为0表示解密,为1表示加密。CrackMe 会在使用字符串前先进行解密,使用完后马上重新进行加密,所以我们可以先把加密的代码去除,如下图:

修改后具体代码如下:
[Asm] 纯文本查看 复制代码

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
00401000  /$  56             push    esi
00401001  |.  8B7424 08      mov     esi, dword ptr [esp+8]
00401005  |.  56             push    esi                     ; /String
00401006  |.  FF15 10784000  call    dword ptr [407810]      ; \lstrlenA
0040100C  |.  03C6           add     eax, esi
0040100E  |.  3BF0           cmp     esi, eax
00401010  |.  73 22          jnb     short 00401034
00401012  |.  53             push    ebx
00401013  |.  8A5C24 10      mov     bl, byte ptr [esp+10]   ;  参数:0-解密,1-加密
00401017  |>  8A06           /mov     al, byte ptr [esi]     ;  取字符
00401019  |.  84DB           |test    bl, bl
0040101B  |.  75 04          |jnz     short 00401021
0040101D  |.  04 40          |add     al, 40                 ;  解密
0040101F  |.  EB 02          |jmp     short 00401023
00401021      90             nop                             ;  加密(add  al, 0C0)
00401022      90             nop                             ;  先 NOP 掉,不允许重新加密字符串
00401023  |>  8806           |mov     byte ptr [esi], al     ;  存字符
00401025  |.  46             |inc     esi
00401026  |.  56             |push    esi                    ; /String
00401027  |.  FF15 10784000  |call    dword ptr [407810]     ; \lstrlenA
0040102D  |.  03C6           |add     eax, esi
0040102F  |.  3BF0           |cmp     esi, eax
00401031  |.^ 72 E4          \jb      short 00401017
00401033  |.  5B             pop     ebx
00401034  |>  5E             pop     esi
00401035  \.  C2 0800        retn    8

这样在跟踪过程中可以看到明文的字符串。
我们返回前面的 CrackMe 代码,可以看到,其通过调用 EnumWindows() API函数枚举窗口,如下所示:

枚举窗口时,会通过回调函数(0x004015C0)检查一个“Eip"的窗口,如果存在这个窗口,就会将下一步解密需要的密钥参数修改成 3,具体见回调函数,修改参数的是这一行代码:
[Asm] 纯文本查看 复制代码

00401604  |.  C705 4C794000 030>mov     dword ptr [40794C], 3                     ;  设置错误密钥

本来在 [0x0040794C] 处保存的是 0。
从这个函数接下来的部分就是解密另一部分代码了,如上面图所示,解码从0x004014F0开始的代码,并且密钥通过计算得到:
[Asm] 纯文本查看 复制代码

0040165C  |.  6A 13             push    13
0040165E  |.  A1 20784000       mov     eax, dword ptr [407820]                   ;  nShowCmd
00401663  |.  0305 4C794000     add     eax, dword ptr [40794C]                   ;  如果回调检查出Eip窗口,这里值为3
00401669  |.  50                push    eax
0040166A  |.  68 F0144000       push    004014F0                                  ;  待解码代码位置
0040166F  |.  E8 6C010000       call    004017E0                                  ;  解码, eax 返回 checkSum

可见,用到了[0x0040794C]的值,如果该值为3,则后面解码就会出错,CrackMe 也就无法继续了。

我们返回到主函数(WinMain),下面接下几个调用,与前面类似,都是解码,运行,再解码,再运行,都是反调试用的,如检查 frogsICE 等,就不详説了,反正对 OD 也没有影响。这一部分代码在 WinMain() 中如下所示:
[Asm] 纯文本查看 复制代码

004016AD   .  8B45 14                 mov     eax, dword ptr [ebp+14]       ;  取得解密密钥(nShowCmd)
004016B0   .  A3 20784000             mov     dword ptr [407820], eax       ;  eax == nShowCmd == 1,保存,在后面解码时要用到
004016B5   .  6A 14                   push    14                            ;  长度
004016B7   .  50                      push    eax                           ;  密钥
004016B8   .  68 20164000             push    00401620                      ;  解密位置(下面的函数)
004016BD   .  E8 1E010000             call    004017E0                      ;  解密下一个过程
004016C2   .  8945 E4                 mov     dword ptr [ebp-1C], eax
004016C5   .  3D AC020000             cmp     eax, 2AC                      ;  检查较验和
004016CA   .  75 54                   jnz     short 00401720
004016CC   .  E8 4FFFFFFF             call    00401620                      ;  执行解密后的函数,并解密下一个过程
004016D1   .  3D 2E010000             cmp     eax, 12E                      ;  检查较验和
004016D6   .  75 1D                   jnz     short 004016F5
004016D8   .  E8 13FEFFFF             call    004014F0                      ;  执行解密后的函数,并解密下一个过程
004016DD   .  3D 7F040000             cmp     eax, 47F                      ;  检查较验和
004016E2   .  75 11                   jnz     short 004016F5
004016E4   .  E8 B7FDFFFF             call    004014A0                      ;  执行解密后的函数,并解密下一个过程
004016E9   .  3D 12000000             cmp     eax, 12                       ;  检查较验和
004016EE   .  75 05                   jnz     short 004016F5
004016F0   .  E8 1BFEFFFF             call    00401510                      ;  int3检查,如果正常则修正DlgProc

我们主要説説最后一个调用(call 00401510),其也包含一部解码后的代码,如下所示:
[Asm] 纯文本查看 复制代码

00401510   $  55                      push    ebp
00401511   .  8BEC                    mov     ebp, esp
00401513   .  51                      push    ecx
00401514   .  53                      push    ebx
00401515   .  56                      push    esi
00401516   .  57                      push    edi
00401517     /EB 02                   jmp     short 0040151B                ;  花指令
00401519     |12FF                    adc     bh, bh                        ;  花指令
0040151B     \66:BE 4746              mov     si, 4647                      ; "FG", int 3 检查SoftICE用的参数,调用SoftICE执行命令
0040151F      66:BF 4D4A              mov     di, 4A4D                      ; "JM", 作用同上,ax=功能号,如0x0911是执行命令。dx==>命令字符串
00401523   .  90                      nop
00401524   .  EB 05                   jmp     short 0040152B                ;  花指令
00401526   .  AD                      db      AD
00401527      23                      db      23                            ;  CHAR '#'
00401528      CC                      int3
00401529      55                      db      55                            ;  CHAR 'U'
0040152A      FE                      db      FE
0040152B   >  C705 C4704000 2D5A0000  mov     dword ptr [4070C4], 5A2D      ;  "-Z",这个值由下面 int3 异常处理程序修正,静态初始化值为 0x000025AD,这里改成 mov dword ptr [4070C4], 0x16
00401535   .  EB 05                   jmp     short 0040153C                ;  这里要改成 jmp     short 0040153D,跳过 int3 指令
00401537      AD                      db      AD
00401538      23                      db      23                            ;  CHAR '#'
00401539      CC                      int3
0040153A      55                      db      55                            ;  CHAR 'U'
0040153B      FF                      db      FF
0040153C   >  CC                      int3                                  ;  引发 int3 中断,去执行中断处理程序
0040153D   .  EB 05                   jmp     short 00401544                ;  花指令
0040153F      AD                      db      AD
00401540      23                      db      23                            ;  CHAR '#'
00401541      CC                      int3
00401542      55                      db      55                            ;  CHAR 'U'
00401543      FE                      db      FE
00401544   >  68 14784000             push    00407814
00401549   .  E8 D2020000             call    00401820
0040154E   .  83C4 04                 add     esp, 4
00401551   .  EB 05                   jmp     short 00401558                ;  花指令
00401553      AD                      db      AD
00401554      23                      db      23                            ;  CHAR '#'
00401555      CC                      int3
00401556      55                      db      55                            ;  CHAR 'U'
00401557      FF                      db      FF
00401558   >  A1 44794000             mov     eax, dword ptr [407944]
0040155D   .  33C0                    xor     eax, eax
0040155F      90                      nop
00401560      90                      nop
00401561      90                      nop
00401562      90                      nop
00401563   .  A3 44794000             mov     dword ptr [407944], eax
00401568   .  EB 05                   jmp     short 0040156F                ;  花指令
0040156A      AD                      db      AD
0040156B      23                      db      23                            ;  CHAR '#'
0040156C      CC                      int3
0040156D      55                      db      55                            ;  CHAR 'U'
0040156E      FF                      db      FF
0040156F   >  6A 32                   push    32
00401571   .  8B0D 44794000           mov     ecx, dword ptr [407944]
00401577   .  030D C4704000           add     ecx, dword ptr [4070C4]
0040157D   .  51                      push    ecx
0040157E   .  68 10134000             push    00401310
00401583   .  E8 58020000             call    004017E0
00401588   .  8945 FC                 mov     dword ptr [ebp-4], eax
0040158B   .  6A 14                   push    14
0040158D   .  6A 0C                   push    0C                            ;  密钥
0040158F   .  BA 10154000             mov     edx, 00401510                 ;  入口地址
00401594   .  83C2 14                 add     edx, 14                       ;  edx == 0x00401524
00401597   .  52                      push    edx
00401598   .  E8 43020000             call    004017E0                      ;  解码
0040159D   .  817D FC 8CFDFFFF        cmp     dword ptr [ebp-4], -274
004015A4   .  74 02                   je      short 004015A8
004015A6   .  EB 0A                   jmp     short 004015B2
004015A8   >  C705 84704000 10134000  mov     dword ptr [407084], 00401310
004015B2   >  5F                      pop     edi
004015B3   .  5E                      pop     esi
004015B4   .  5B                      pop     ebx
004015B5   .  8BE5                    mov     esp, ebp
004015B7   .  5D                      pop     ebp
004015B8   .  C3                      retn

上面代码也含有花指令没有去除。
其中关键代码下图所示:

具体代码如下:
[Asm] 纯文本查看 复制代码

0040152B    C705 C4704000 2D5A0000   mov     dword ptr [4070C4], 5A2D           ; "-Z", 0x5A2D ===> 0x0016,全局变量静态初值为 0x0025AD
00401535    EB 06                    jmp     short 0040153C                     ; 修改这里,跳过 0x0040153C 处的 int 3,改成 jmp 0x0040153D 即可。
00401537    90                       nop
00401538    90                       nop
00401539    CC                       int3
0040153A    90                       nop
0040153B    90                       nop                                        ;  下面引发 int3 中断,去执行中断处理程序
0040153C    CC                       int3                                       ; eax=0x0012, edx=00401544, 利用 int 3 将 0x5A2D 改成 0x16
0040153D    EB 05                    jmp     short 00401544

这里给全局量[0x004070C4]赋值为 0x00005A2D,这个是传递给 SoftICE 的,如果 SICE 在运行,当下面 int 3 执行时,全进入 SICE,但不会有CrackMe需要的正常操作,如果没有加载 SoftICE ,则会通过 CrackMe 本身异常处理 Handler 来处理这个 int3 异常,这个异常处理过程如下:
[Asm] 纯文本查看 复制代码

; int 3 异常处理,已去花指令(nop填充):
00401740    55                       push    ebp
00401741    8BEC                     mov     ebp, esp
00401743    83EC 0C                  sub     esp, 0C
00401746    53                       push    ebx
00401747    56                       push    esi
00401748    57                       push    edi
00401749    8B45 08                  mov     eax, dword ptr [ebp+8]
0040174C    8B08                     mov     ecx, dword ptr [eax]
0040174E    8B11                     mov     edx, dword ptr [ecx]
00401750    8955 FC                  mov     dword ptr [ebp-4], edx
00401753    68 44794000              push    00407944                             ; time_t * t
00401758    E8 C3000000              call    00401820                             ; _time(t)
0040175D    83C4 04                  add     esp, 4
00401760    EB 05                    jmp     short 00401767
00401762    90                       nop
00401763    90                       nop
00401764    CC                       int3
00401765    90                       nop
00401766    90                       nop
00401767    A1 C4704000              mov     eax, dword ptr [4070C4]               ; 取全局参数
0040176C    8945 F8                  mov     dword ptr [ebp-8], eax
0040176F    817D F8 2D5A0000         cmp     dword ptr [ebp-8], 5A2D               ; 检查参数是否"-Z"
00401776    75 20                    jnz     short 00401798
00401778    C745 F4 FFFFFFFF         mov     dword ptr [ebp-C], -1
0040177F    EB 05                    jmp     short 00401786
00401781    90                       nop
00401782    90                       nop
00401783    CC                       int3
00401784    90                       nop
00401785    90                       nop
00401786    C745 F8 16000000         mov     dword ptr [ebp-8], 16                 ; 将5A2D改成0x16
0040178D    8B4D F8                  mov     ecx, dword ptr [ebp-8]                ; ecx == 0x16
00401790    890D C4704000            mov     dword ptr [4070C4], ecx               ; 保存 ecx 到全局变量, 即保存 0x16
00401796    EB 07                    jmp     short 0040179F
00401798    C745 F4 01000000         mov     dword ptr [ebp-C], 1
0040179F    68 14784000              push    00407814                              ; time_t *
004017A4    E8 77000000              call    00401820                              ; _time()
004017A9    83C4 04                  add     esp, 4
004017AC    8B15 44794000            mov     edx, dword ptr [407944]               ; 前面保存的时间 (0x00401758 处的调用)
004017B2    2B15 14784000            sub     edx, dword ptr [407814]               ; 计算指令执行时间差 (0x004017A4 处的调用)
004017B8    8915 44794000            mov     dword ptr [407944], edx               ; 保存计算后的时间差,须为0
004017BE    EB 0F                    jmp     short 004017CF
004017C0    EB 01                    jmp     short 004017C3                        ; 花指令(执行不到)
004017C2    90                       nop                                           ; 已nop花指令
004017C3    C745 F8 16000000         mov     dword ptr [ebp-8], 16                 ; 将5A2D改成0x16(执行不到)
004017CA  - E9 B7FF3F00              jmp     00801786                              ; 错误地址,和上一条指令一起,应该也是花指令(执行不到)
004017CF    8B45 F4                  mov     eax, dword ptr [ebp-C]
004017D2    5F                       pop     edi
004017D3    5E                       pop     esi
004017D4    5B                       pop     ebx
004017D5    8BE5                     mov     esp, ebp
004017D7    5D                       pop     ebp
004017D8    C2 0400                  retn    4

其中以下一段代码:
[Asm] 纯文本查看 复制代码

00401767    A1 C4704000              mov     eax, dword ptr [4070C4]               ; 取全局参数
0040176C    8945 F8                  mov     dword ptr [ebp-8], eax
0040176F    817D F8 2D5A0000         cmp     dword ptr [ebp-8], 5A2D               ; 检查参数是否"-Z"
00401776    75 20                    jnz     short 00401798
00401778    C745 F4 FFFFFFFF         mov     dword ptr [ebp-C], -1
0040177F    EB 05                    jmp     short 00401786
00401781    90                       nop
00401782    90                       nop
00401783    CC                       int3
00401784    90                       nop
00401785    90                       nop
00401786    C745 F8 16000000         mov     dword ptr [ebp-8], 16                 ; 将5A2D改成0x16
0040178D    8B4D F8                  mov     ecx, dword ptr [ebp-8]                ; ecx == 0x16
00401790    890D C4704000            mov     dword ptr [4070C4], ecx               ; 保存 ecx 到全局变量, 即保存 0x16
00401796    EB 07                    jmp     short 0040179F
00401798    C745 F4 01000000         mov     dword ptr [ebp-C], 1

会将 前面已赋值 0x5A2D 的全局变量[0x004070C4]的值改成 0x00000016,而这个 0x16 就是最后一次代码解码需要的密钥值之一。
返回到函数 00401510 中,可以看到如下解码过程用到了这个值:
[Asm] 纯文本查看 复制代码

00401571    8B0D 44794000            mov     ecx, dword ptr [407944]            ; 保存的时间差(参见 0x004017B8的指令)
00401577    030D C4704000            add     ecx, dword ptr [4070C4]            ; int3 异常处理过程修正此地址值为 0x16
0040157D    51                       push    ecx                                ; ecx == 0x16
0040157E    68 10134000              push    00401310                           ; 解码地址
00401583    E8 58020000              call    004017E0                           ; 解码代码

如果在 OD 中简单跳过 int3 指令,要本就没法解码后面的代码了,这也是最大坑了,包括加载了 SICE 也会掉了此坑。

另外还有一个坑,就是主对框过程有两个,一个真的,一个假的,真的就是上面代码解码出来的函数,入口为 0x00401310。
而如果上面解码不对,也就是 checkSum也不对,就不会修正 DialogBoxParamA 的 DlgProc 参数,修正DlgProc参数代码如下:

[Asm] 纯文本查看 复制代码

0040159D    817D FC 8CFDFFFF         cmp     dword ptr [ebp-4], -274            ; 检查 0x00401583 处的调用结果(校验和)
004015A4    74 02                    je      short 004015A8
004015A6    EB 0A                    jmp     short 004015B2                     ; 解码校验和不正确,则跳过修正 DlgProc 地址。
004015A8    C705 84704000 10134000   mov     dword ptr [407084], 00401310       ; 修正主对话框的 DlgProc 地址

给个图形象一点:

上面[00407084]中保存 DlgProc 参数。
前面解码完成后,CrackMe 会将自己这个函数再次加密,如图所示位置的调用:

其实就是解码函数,再次XOR操作,就是加密了,具体代码如下:
[Asm] 纯文本查看 复制代码

0040158B    6A 14                    push    14                                 ; 长度
0040158D    6A 0C                    push    0C                                 ; 密钥
0040158F    BA 10154000              mov     edx, 00401510
00401594    83C2 14                  add     edx, 14                            ; edx = 0x00401524
00401597    52                       push    edx                                ; 解码地址
00401598    E8 43020000              call    004017E0                           ; 加密代码,实际则是破坏本函数内0x00401524开始的20个字节的代码,checkSum 存于 eax

因为代码解码函数的功能已全部完成,并且为了阻止上面的调用重新加密代码,我们可以修改加密函数了,不让其再有效,而且,我们等下用 OD 保存明码 patch 后,以后运行也不需解密了,先将函数修改,如下图所示:

将xor指令nop掉,就去除其加/解密功能,只剩下计算 checkSum 的功能,所以也不影响CrackMe对 checkSum 的检查,修改后的代码如下:
[Asm] 纯文本查看 复制代码

004017FA      90                nop                                               ;  去掉这里,即可取消解码操作
004017FB      90 nop

还有一个问题,在 WinNT 系列内核中,这个函数中的 int3 也不会执行异常处理过程,而会导至致操作系统直接关闭 CrackMe ,因此,我们需要处理掉这个 int3 才能在 win10 下运行。
同时,还要平衡 checkSum 值,因此对”花指令“代码进行调整,用了两个 Xor 指令来调整的,调整的代码部分如下:
[Asm] 纯文本查看 复制代码

0040152B      C705 C4704000 16000000   mov     dword ptr [4070C4], 16     ; 直接保存密钥常量 0x16,不需要进行异常处理。 
00401535      EB 06                    jmp     short 0040153D             ; 跳过 0040153C 处的 int3 指令
00401537      35 00636290              xor     eax, 90626300              ; 校验和平衡处理
0040153C      CC                       int3
0040153D      EB 05                    jmp     short 00401544
0040153F      35 00000090              xor     eax, 90000000              ; 校验和平衡处理
00401544      68 14784000              push    00407814
00401549      E8 D2020000              call    00401820
0040154E      83C4 04                  add     esp, 4
00401551      EB 05                    jmp     short 00401558

完成这些操作后,第一部分脱密工作已经完成,CrackMe 可以在 Win9x内核/NT系列内核的系统上直接运行了。我们可以通过 OD 的 patch 保存功能,将所有解码的修改保存了,如下图所示:

这样操作以后,我们可以重新加载 CrackMe,进行下一步进行处理。

我们再次回到 WinMain(),剩下的代码就是显示主对话框了:

就是执行上面蓝底部分的代码。

直接  F9 显示主对话界面:

可以看到,需要输入一个注册码,再点”IsThisOk“按钮来检查输入的注册码是否正确。

我们前面已经説了,主对话框的 DlgProc 入口为 0x00401310。如下图所示:

按钮事件处理代码如下:

具体代码如下:
[Asm] 纯文本查看 复制代码

00401310     55                       push    ebp
00401311     8BEC                     mov     ebp, esp
00401313     83EC 14                  sub     esp, 14
00401316     53                       push    ebx
00401317     56                       push    esi
00401318     57                       push    edi
00401319     8B45 0C                  mov     eax, dword ptr [ebp+C]
0040131C     8945 F8                  mov     dword ptr [ebp-8], eax
0040131F     8B4D F8                  mov     ecx, dword ptr [ebp-8]
00401322     894D F4                  mov     dword ptr [ebp-C], ecx
00401325     836D F4 10               sub     dword ptr [ebp-C], 10         ; WM_CLOSE
00401329     837D F4 00               cmp     dword ptr [ebp-C], 0
0040132D     0F84 8E000000            je      004013C1                      ; goto handle WM_CLOSE(0x10)
00401333     816D F4 00010000         sub     dword ptr [ebp-C], 100        ; WM_INITDIALOG(0x110)
0040133A     837D F4 00               cmp     dword ptr [ebp-C], 0
0040133E     0F84 8E000000            je      004013D2                      ; goto handle WM_INITDIALOG
00401344     836D F4 01               sub     dword ptr [ebp-C], 1          ; WM_COMMAND(0x111)
00401348     837D F4 00               cmp     dword ptr [ebp-C], 0
0040134C     74 05                    je      short 00401353                ; goto handle WM_COMMAND
0040134E     E9 8A000000              jmp     004013DD
00401353     8B55 10                  mov     edx, dword ptr [ebp+10]       ; 处理 WM_COMMAND 消息
00401356     8955 F0                  mov     dword ptr [ebp-10], edx
00401359     8B45 F0                  mov     eax, dword ptr [ebp-10]
0040135C     8945 EC                  mov     dword ptr [ebp-14], eax
0040135F     836D EC 01               sub     dword ptr [ebp-14], 1         ; ControlID = 0x01
00401363     837D EC 00               cmp     dword ptr [ebp-14], 0
00401367     74 02                    je      short 0040136B
00401369     EB 54                    jmp     short 004013BF
0040136B     68 04010000              push    104                           ; 处理ControlID=1的控件事件
00401370     68 3C784000              push    0040783C                      ; 保存假码的 buffer 地址
00401375     6A 64                    push    64
00401377     8B0D 24784000            mov     ecx, dword ptr [407824]       ; [0x00407824] = 0x00A50FE0
0040137D     51                       push    ecx
0040137E     FF15 30784000            call    dword ptr [407830]            ; USER32.GetDlgItemTextA
00401384     68 3C784000              push    0040783C                      ; 注册码,测试通过的SN: 6#"!!!!!!!!!!!!! 或 BZZ
00401389     E8 62FEFFFF              call    004011F0                      ; 处理注册码,计算后面解用的密钥(call 00401180 解码代码的密钥)
0040138E     8945 FC                  mov     dword ptr [ebp-4], eax        ; xorKey, eax 为sn计算后的结果, 正确结果为 0x0000F84B,
00401391     6A 06                    push    6                             ; 解码长度
00401393     8B55 FC                  mov     edx, dword ptr [ebp-4]
00401396     52                       push    edx                           ; edx == 0xFFFFF84B 才是正确结果
00401397     B8 C0124000              mov     eax, 004012C0                 ; 需要解码的函数入口地址
0040139C     83C0 26                  add     eax, 26                       ; 加上偏移量0x26,eax = 004012E6,即解码开始位置
0040139F     50                       push    eax
004013A0     E8 DBFDFFFF              call    00401180                      ; 使用计算后注册码运行值(xorKey)解码6字节代码
004013A5     8945 FC                  mov     dword ptr [ebp-4], eax        ; 解码后的校验和
004013A8     817D FC 5B8E0000         cmp     dword ptr [ebp-4], 8E5B       ; checkSum = 0x8E5B, 才是正确值,可以通过校验和反算出 xorKey = 0xF84B,所以SN的计算处理结果为0xF84B,则表示输入SN是正确的
004013AF     75 0C                    jnz     short 004013BD
004013B1     EB 05                    jmp     short 004013B8
004013B3     90                       nop
004013B4     90                       nop
004013B5     CC                       int3
004013B6     90                       nop
004013B7     90                       nop
004013B8     E8 03FFFFFF              call    004012C0                      ; 解码字符串并显示完成CrackMe的破解消息框
004013BD     EB 26                    jmp     short 004013E5                ; 去下面代码关闭CrackMe对话框,退出CrackMe
004013BF     EB 20                    jmp     short 004013E1
004013C1     6A 01                    push    1
004013C3     8B0D 24784000            mov     ecx, dword ptr [407824]       ; hWnd
004013C9     51                       push    ecx
004013CA     FF15 48794000            call    dword ptr [407948]            ; USER32.EndDialog
004013D0     EB 0F                    jmp     short 004013E1
004013D2     8B55 08                  mov     edx, dword ptr [ebp+8]
004013D5     8915 24784000            mov     dword ptr [407824], edx
004013DB     EB 04                    jmp     short 004013E1
004013DD     33C0                     xor     eax, eax
004013DF     EB 06                    jmp     short 004013E7
004013E1     33C0                     xor     eax, eax
004013E3     EB 02                    jmp     short 004013E7
004013E5   ^ EB DA                    jmp     short 004013C1
004013E7     5F                       pop     edi
004013E8     5E                       pop     esi
004013E9     5B                       pop     ebx
004013EA     8BE5                     mov     esp, ebp
004013EC     5D                       pop     ebp
004013ED     C2 1000                  retn    10

其注册码处理函数是 call 004011F0,具体代码如下:
[Asm] 纯文本查看 复制代码

004011F0    55                       push    ebp
004011F1    8BEC                     mov     ebp, esp
004011F3    83EC 0C                  sub     esp, 0C
004011F6    53                       push    ebx
004011F7    56                       push    esi
004011F8    57                       push    edi
004011F9    C745 F8 00000000         mov     dword ptr [ebp-8], 0        ; int snResult = 0
00401200    8B45 08                  mov     eax, dword ptr [ebp+8]      ; eax ==> sn[i], eax ===> 假码 787878787878787878
00401203    0FBE08                   movsx   ecx, byte ptr [eax]         ; ecx = sn[i]
00401206    894D F4                  mov     dword ptr [ebp-C], ecx      ; [ebp-c] == sn[i]
00401209    8B55 F4                  mov     edx, dword ptr [ebp-C]
0040120C    8B45 08                  mov     eax, dword ptr [ebp+8]      ; eax ===> 假码 787878787878787878
0040120F    83C0 01                  add     eax, 1                      ; i ++
00401212    8945 08                  mov     dword ptr [ebp+8], eax
00401215    85D2                     test    edx, edx                    ; 字符串是否结束
00401217    0F84 8A000000            je      004012A7
0040121D    8B4D F4                  mov     ecx, dword ptr [ebp-C]      ; sn[i]
00401220    83E1 7F                  and     ecx, 7F                     ; ecx = sn[i] & 0x7F
00401223    894D F4                  mov     dword ptr [ebp-C], ecx      ; [ebp-0C] = sn[i] = sn[i] & 0x7F
00401226    EB 05                    jmp     short 0040122D
00401228    90                       nop
00401229    90                       nop
0040122A    CC                       int3
0040122B    90                       nop
0040122C    90                       nop
0040122D    8B55 F8                  mov     edx, dword ptr [ebp-8]      ; edx = snResult
00401230    3355 F4                  xor     edx, dword ptr [ebp-C]      ; snResult ^= sn[i]
00401233    83E2 0F                  and     edx, 0F
00401236    8955 FC                  mov     dword ptr [ebp-4], edx      ; snResult_tmp = (snResult ^ (sn[i])) & 0x0F
00401239    EB 05                    jmp     short 00401240
0040123B    90                       nop
0040123C    90                       nop
0040123D    CC                       int3
0040123E    90                       nop
0040123F    90                       nop
00401240    8B45 F8                  mov     eax, dword ptr [ebp-8]     ; snResult
00401243    C1F8 04                  sar     eax, 4
00401246    8B4D FC                  mov     ecx, dword ptr [ebp-4]
00401249    69C9 81100000            imul    ecx, ecx, 1081
0040124F    33C1                     xor     eax, ecx
00401251    8945 F8                  mov     dword ptr [ebp-8], eax     ; snResult = (snResult>>4)^(snResult_tmp*0x1081)
00401254    EB 05                    jmp     short 0040125B
00401256    90                       nop
00401257    90                       nop
00401258    CC                       int3
00401259    90                       nop
0040125A    90                       nop
0040125B    8B55 F4                  mov     edx, dword ptr [ebp-C]     ; sn[i]
0040125E    C1EA 04                  shr     edx, 4
00401261    8B45 F8                  mov     eax, dword ptr [ebp-8]     ; snResult
00401264    33C2                     xor     eax, edx
00401266    83E0 0F                  and     eax, 0F
00401269    8945 FC                  mov     dword ptr [ebp-4], eax     ; snResult_tmp = ((sn[i]>>4)^snResult) & 0x0F
0040126C    EB 05                    jmp     short 00401273
0040126E    90                       nop
0040126F    90                       nop
00401270    CC                       int3
00401271    90                       nop
00401272    90                       nop
00401273    8B4D F8                  mov     ecx, dword ptr [ebp-8]     ; snResult
00401276    C1F9 04                  sar     ecx, 4
00401279    8B55 FC                  mov     edx, dword ptr [ebp-4]     ; snResult_tmp
0040127C    69D2 81100000            imul    edx, edx, 1081
00401282    33CA                     xor     ecx, edx
00401284    894D F8                  mov     dword ptr [ebp-8], ecx     ; snResult = (snResult>>4)^(snResult_tmp * 0x1081)
00401287    EB 05                    jmp     short 0040128E
00401289    90                       nop
0040128A    90                       nop
0040128B    CC                       int3
0040128C    90                       nop
0040128D    90                       nop
0040128E    8B45 F8                  mov     eax, dword ptr [ebp-8]     ; snResult
00401291    C1F8 04                  sar     eax, 4
00401294    8B4D FC                  mov     ecx, dword ptr [ebp-4]     ; snResult_tmp
00401297    69C9 81100000            imul    ecx, ecx, 1081
0040129D    33C1                     xor     eax, ecx
0040129F    8945 F8                  mov     dword ptr [ebp-8], eax     ; snResult = (snResult>>4)^(snResult_tmp * 0x1081)
004012A2  ^ E9 59FFFFFF              jmp     00401200
004012A7    8B45 F8                  mov     eax, dword ptr [ebp-8]     ; retVal = snResult
004012AA    5F                       pop     edi
004012AB    5E                       pop     esi
004012AC    5B                       pop     ebx
004012AD    8BE5                     mov     esp, ebp
004012AF    5D                       pop     ebp
004012B0    C2 0400                  retn    4

这个函数会将输入的注册码通过运算,计算出一个 WORD 类型的密钥,然后再使用这个值去解码另外一段代码,如果注册码不对,就会解码失败,校验和通不过,也就不会提示你成功!而是直接退出 CrackMe。
我们反算注册码分成两步,首先要计算出这个”密钥“,然后再根据”密钥“反推注册码。

因为有解密后的校验和,我们只要用一个循环,从 0~65535 全部试一遍,就肯定可以得到至少一个密钥了(也只有一个)。这个密钥是用来解密下图中6个字节代码的:

具体代码如下:
[Asm] 纯文本查看 复制代码

004012C0   $  6A 00             push    0
004012C2   .  68 65704000       push    00407065                                  ;  待解码字符串
004012C7   .  E8 34FDFFFF       call    00401000
004012CC   .  6A 00             push    0
004012CE   .  68 5D704000       push    0040705D                                  ;  待解码字符串
004012D3   .  E8 28FDFFFF       call    00401000
004012D8   .  6A 00             push    0
004012DA   .  68 5D704000       push    0040705D
004012DF   .  68 65704000       push    00407065
004012E4   .  6A 00             push    0
004012E6   .  B4 ED             mov     ah, 0ED                                   ;  待解码数据,共6字节
004012E8   .  57800BF8          dd      F80B8057                                  ;  待解码数据
004012EC   .  6A 01             push    1
004012EE   .  68 65704000       push    00407065
004012F3   .  E8 08FDFFFF       call    00401000
004012F8   .  6A 01             push    1
004012FA   .  68 5D704000       push    0040705D
004012FF   .  E8 FCFCFFFF       call    00401000
00401304   .  C3                retn

根据这6字节数据和校验和,反推密钥,得到密钥后,再就是反推注册了,注册机代码放在后面。我们可以算到一个简单而正确的三字符注册码:BZZ。如下图,我们输入注册码:

再次点击 "IsThisOk",来测试注册码,如下图:

得到密钥:

解码,得到校验和:

解码后的代码如下图所示:

就是一个 MessageBoxA 的调用,用来显示破解成功的提示。具体代码如下:
[Asm] 纯文本查看 复制代码

004012C0     6A 00                    push    0
004012C2     68 65704000              push    00407065                  ; ASCII "YES, YOU'RE RIGHT :)"
004012C7     E8 34FDFFFF              call    00401000                  ; 解码下面 MessageBox 字符串
004012CC     6A 00                    push    0
004012CE     68 5D704000              push    0040705D                  ; ASCII "Finish"
004012D3     E8 28FDFFFF              call    00401000                  ; 解码下面 MessageBox 字符串
004012D8     6A 00                    push    0
004012DA     68 5D704000              push    0040705D                  ; ASCII "Finish"
004012DF     68 65704000              push    00407065                  ; ASCII "YES, YOU'RE RIGHT :)"
004012E4     6A 00                    push    0
004012E6     FF15 1C784000            call    dword ptr [40781C]        ; USER32.MessageBoxA
004012EC     6A 01                    push    1
004012EE     68 65704000              push    00407065                  ; ASCII "YES, YOU'RE RIGHT :)"
004012F3     E8 08FDFFFF              call    00401000                  ; 加密字符串
004012F8     6A 01                    push    1
004012FA     68 5D704000              push    0040705D                  ; ASCII "Finish"
004012FF     E8 FCFCFFFF              call    00401000                  ; 加密字符串
00401304     C3                       retn

最后结果显示如下:

表示破解成功了!!!

注册机代码如下,使用 Dev-C++ 调试通过:

[C] 纯文本查看 复制代码

#include <iostream>
#include <string.h>
int count = 0;
char sn[256];

int checkSN(char * code);
int getPrevCheckValue(int LastCheckValue, int index);

int main(int argc, char** argv) {
	unsigned short codeBase[] = {0xEDB4, 0x8057, 0xF80B};
	unsigned short checkSum = 0x8E5B;
	
	//// 通过checkSum反推解密密钥 
	unsigned short xorKey=0;
	do {
		unsigned short snResult2_tmp = 0;
		for(int j=0; j<3; j++) {
			snResult2_tmp += (xorKey ^ codeBase[j]);
		}
		if(snResult2_tmp == checkSum) {
			printf("xorKey = 0x%04X\n", xorKey);
			break; 
		}
		xorKey++; /// checkValue
	} while(xorKey<0xFFFF); //// 搜索范围 0~65535
	

	//int LastCheckValue = 0x0000F84B;
	int LastCheckValue = xorKey & 0x0000FFFF;
	int index = 0;
	
	//// 计算注册码 
	int checkStatus = getPrevCheckValue(LastCheckValue, index);
	if(checkStatus == -1) {
		printf("NO key, count = %d\n", count);
	}
	
	if(checkStatus == 0) {
		printf("Found.\n");
		sn[count] = '\0';
		strrev((char *)sn);      // 反转SN 
		printf("SN = %s\n", sn);

		//char sn_test[] = "787878787878787878";
		//printf("sn Test = 0x%08X\n", checkSN(sn_test));
		printf("test sn, get xorKey = 0x%08X\n", checkSN(sn));
		////
	}
	
	return 0;
}

int checkSN(char * code) {
	int snResult, snResult_tmp; 
	unsigned int s;
	
	int j = 0;
	snResult = 0;
	while((s=code[j++]) != 0) {
		s &= 0x7F;
		snResult_tmp = (snResult ^ (s)) & 0x0F;          //// snResult_tmp 的范围是0x00~0x0F,因此后面 snResult_tmp*0x1081 < 0xFFFF 
		snResult = (snResult>>4)^(snResult_tmp*0x1081);  //// 结果的最高位4bit(0xF000)是后面乘积的最高位4bit,而snResult初始值和计算值均小于0xFFFF,因此计算结果 snResult < 0xFFFF 
		////
		snResult_tmp = ((s>>4)^snResult) & 0x0F;          //// snResult_tmp 的范围是0x00~0x0F,因此后面 snResult_tmp*0x1081 < 0xFFFF 
		snResult = (snResult>>4)^(snResult_tmp * 0x1081); //// 结果的最高位4bit(0xF000)是后面乘积的最高位4bit,因此 snResult < 0xFFFF  
		snResult = (snResult>>4)^(snResult_tmp * 0x1081); //// 结果的最高位4bit(0xF000)是后面乘积的最高位4bit,因此 snResult < 0xFFFF 
	}
	
	return snResult;// & 0x0FFFF;
}

//// short checkSum = 0x8E5B;
//// int LastCheckValue = xorKey = 0x0000F84B;
int getPrevCheckValue(int LastCheckValue, int index) {
	if(LastCheckValue == 0) {
		//// found sn
		count = index;
		sn[index] = '\0';
		
		return 0;
	}
	
	if(index > 3) {  // 最长4位 SN
	//if(index > 7) {  // 最长8位 SN
	//if(index > 15) { // 最长16位 SN
		//// sn is too long
		return -1;
	}
	
	//for(int i=0x21; i<0x7F; i++) {    // 键盘可输入的非空格字符的Ascii码值(0x21~0x7E) 
	//for(int i=0x7E; i>=0x21; i--) {   // 键盘可输入的非空格字符的Ascii码值(0x21~0x7E) ,逆序
	//for(int i=0x30; i<=0x39; i++) {   // 仅测试数字注册码 (找不到合适的纯数字SN)
	//for(int i=0x39; i>=0x30; i--) {   // 仅测试数字注册码 (找不到合适的纯数字SN),逆序
	//for(int i=0x41; i<=0x5A; i++) {   // 仅测试大写字母注册码  (11秒内找到4位SN: "XNCA")
	for(int i=0x5A; i>=0x41; i--) {   // 仅测试大写字母注册码,逆序 (5秒内找到3位SN: "BZZ")
	//for(int i=0x61; i<=0x7A; i++) {   // 仅测试小写字母注册码 (7秒内找到4位SN: "tnba")
	//for(int i=0x7A; i>=0x61; i--) {   // 仅测试小写字母注册码 ,逆序
		unsigned int aKey = i & 0x7F;
		for(int j=0x0000; j<0x10000; j++) { //上一轮计算值 prevResult(0x0000~0xFFFF), 65536次尝试 
			int prevResult = j;
			////---------------------------------------------------------
			int snResult_tmp = (aKey ^ prevResult) & 0x0F; /// 0x00~0x0F
			int snResult = (prevResult>>4)^(snResult_tmp * 0x1081);
			
			snResult_tmp = ((aKey>>4) ^ snResult) & 0x0F;  /// 0x00~0x0F
			snResult = (snResult>>4)^(snResult_tmp*0x1081);
			int currResult = (snResult>>4)^(snResult_tmp*0x1081);
			//printf("Key: 0x%02X, prevResult = %d: snResult = 0x%08X\n", i, j, currResult);
			if(currResult == LastCheckValue) {
				sn[index] = aKey;
				//printf("Key: 0x%02X, prevResult = 0x%08X: snResult = 0x%08X\n", i, j, currResult);
				//return prevResult;
				int b = getPrevCheckValue(prevResult, index+1);
				if(b==0) {
					return 0;
				} 
			}
		}
	}
	
	return -1;
}

THE END
喜欢就支持以下吧
点赞0
分享
评论 抢沙发
  • 管埋员的头像-小北的自留地

    昵称

  • 取消
    昵称