solly 发表于 2019-7-15 02:25

160 个 CrackMe 之 159 - Torn@do.2 脱壳修复、注册分析及注册机

本帖最后由 solly 于 2019-7-15 23:58 编辑

160 个 CrackMe 之 159 - Torn@do.2 是一个加了壳的 CrackMe ,文件信息如下:

显示未知的壳,用 "scan /t" 重新扫描一下:

这次显示为 "EncryptPE" 了。而且其文件末尾为字符串“eEnCrypter! :) 2nd&mi”,没见过,只有手动脱壳了。
节信息如下:

看起来与我原来脱过的 043 Ding Boy 的壳很象,也是很多节,并依次对下一节解码,也就是节名为 ".Stone" 的节,都是解码用的。
在 Windows 10 下OD载入 CrackMe,如下图所示:

往下拖可以看到 0x0041F096 有一个 jmp eax,我们直接选择在这一条指令上按 F4 , 然后再 F8 就会跳转到下一节的解码代码,与这一节基本一样,也是到 jmp eax 处 F4, 再F8跳转到下一节的解码代码,这个操作有多次,一直来到下面代码处,如下图,才来到 OEP:

上图的 0x00403560 才是 OEP,到这里我们就可以脱壳了。
再往下拖一下,可以找到 WinMain() 函数的调用,如下图:

如上图,call 00401DE0 就是调用 WinMain() 函数。
我们先脱壳,脱壳后,我们运行一下,报一个错误,运行不了,错误提示如下所示:

是一个 API 函数的问题,这个 API 函数是 DefWindowProcA(),我们需要手动修复这个问题,我们重新来到 OEP。用“管理员身份运行” ImportREC,如下图所示:

可以看到,最后一行的 Thunk 的 Valid 为 NO,点击旁边的 ”Show Invalid" 按钮,如下图显示:

其中那条 NtdllDefWindowProc_A 就是要手动修复的,鼠标左键双击这一行,弹出手动导入表编辑对话框,如下图所示:

我们先在 "Module" 下拉列表中,将“ntdll.dll” 改成 "User32.dll",如下图所示:

然后在 Function 列表中找到“DefWindowProcA”这一行并选择,如上图所示,选好后按 “OK” 即可,这时显示如下图:

所有的函数都有效了,这时就是可以 "Fix Dump" 了,如下图所示,选择要修复的 Dump 文件即可。

这样操作后,就可以正常在 Windows 10 运行了,如果还不行,还可以用 LordPE 来 dump,不用 OD 的 dump 功能,如下图所示:

再按前面的方法修复,就应该可以在 Windows 10 下运行了。


脱壳后,OD 重新载入 F9 直接运行,来到其界面:

随便输入几个信息,点“Validate”,没有动静,按3次后,“Validate” 按钮不可用了,提示变成了:"TRY AGAIN!",如下图所示:

通过前面所述,我们知道WinMain() 函数入口在 0x00401DE0,我们浏览一下这个 WinMain()函数,可以看到这是一个标准的 Windows 窗口API应用的代码,最后面是消息循环处理。
从最前的4行代码,我们找到了 WndProc 的入口为 0x00401FD0,如下所示:
00401DE0    83EC 4C                sub   esp, 4C
00401DE3    C74424 00 30000000   mov   dword ptr , 30
00401DEB    C74424 04 03000000   mov   dword ptr , 3
00401DF3    C74424 08 D01F4000   mov   dword ptr , 00401FD0

我们跟随立即数,来到 0x00401FD0,如下所示,是 WndProc() 的主处理代码:
00401FD0    8B4C24 08            mov   ecx, dword ptr                          ; nMessage
00401FD4    56                     push    esi
00401FD5    83F9 01                cmp   ecx, 1                                       ; WM_CREATE
00401FD8    74 2F                  je      short 00402009
00401FDA    83F9 02                cmp   ecx, 2                                       ; WM_DESTROY
00401FDD    0F84 84000000          je      00402067
00401FE3    81F9 11010000          cmp   ecx, 111                                       ; WM_COMMAND
00401FE9    0F84 86000000          je      00402075
00401FEF    8B4424 14            mov   eax, dword ptr                         ; lParam
00401FF3    8B5424 10            mov   edx, dword ptr                         ; wParam
00401FF7    8B7424 08            mov   esi, dword ptr                          ; hWnd
00401FFB    50                     push    eax                                          ; 默认消息处理
00401FFC    52                     push    edx
00401FFD    51                     push    ecx
00401FFE    56                     push    esi
00401FFF    FF15 D4E24000          call    dword ptr [<&USER32.DefWindowProcA>]         ; ntdll.NtdllDefWindowProc_A
00402005    5E                     pop   esi
00402006    C2 1000                retn    10

最后那个 call User32.DefWindowProcA 就是我们前面修复的那个 API 调用。
我们再次跟随 WM_CREATE 消息处理的“je 00402009”,来到 0x00402009,如下所示:
00402009    8B4424 14            mov   eax, dword ptr
0040200D    68 F4010000            push    1F4
00402012    68 88C14000            push    0040C188                                       ; ASCII "F:\Downloads\crack\159_Torn@do.2"
00402017    6A 00                  push    0
00402019    8B48 04                mov   ecx, dword ptr
0040201C    890D F8BE4000          mov   dword ptr , ecx
00402022    FF15 64E24000          call    dword ptr [<&KERNEL32.GetModuleHandleA>]       ; KERNEL32.GetModuleHandleA
00402028    50                     push    eax
00402029    FF15 68E24000          call    dword ptr [<&KERNEL32.GetModuleFileNameA>]   ; KERNEL32.GetModuleFileNameA
0040202F    85C0                   test    eax, eax
00402031    74 10                  je      short 00402043
00402033    B9 5C000000            mov   ecx, 5C
00402038    3888 88C14000          cmp   byte ptr , cl
0040203E    74 03                  je      short 00402043
00402040    48                     dec   eax
00402041^ 75 F5                  jnz   short 00402038
00402043    8B7424 08            mov   esi, dword ptr
00402047    6A 00                  push    0                                              ; lParam == 0x00000000
00402049    C680 88C14000 00       mov   byte ptr , 0
00402050    68 77777777            push    77777777                                       ;wParam == 0x77777777
00402055    68 11010000            push    111                                          ;nMessage == WM_COMMAND
0040205A    56                     push    esi
0040205B    FF15 E4E24000          call    dword ptr [<&USER32.PostMessageA>]             ; USER32.PostMessageA
00402061    33C0                   xor   eax, eax
00402063    5E                     pop   esi
00402064    C2 1000                retn    10

可以看到,在处理 WM_CREATE 消息时,在最后,给窗口 Post 了一条自定义的 WM_COMMAND 消息。我们再次回到前面的 WndProc()框架函数,来到 WM_COMMAND 消息处理跳转 "je 00402075",跟随这个跳转,来到 WM_COMMAND 消息处理代码处:
00402075    817C24 10 77777777   cmp   dword ptr , 77777777
0040207D    75 21                  jnz   short 004020A0
0040207F    8B7424 08            mov   esi, dword ptr
00402083    6A 00                  push    0
00402085    68 B0204000            push    004020B0
0040208A    A1 F8BE4000            mov   eax, dword ptr
0040208F    56                     push    esi
00402090    6A 65                  push    65
00402092    50                     push    eax
00402093    FF15 E0E24000          call    dword ptr [<&USER32.DialogBoxParamA>]          ; USER32.DialogBoxParamA
00402099    56                     push    esi
0040209A    FF15 F0E24000          call    dword ptr [<&USER32.DestroyWindow>]            ; USER32.DestroyWindow
004020A0    33C0                   xor   eax, eax
004020A2    5E                     pop   esi
004020A3    C2 1000                retn    10

这个消息处理非常简单,就是只要wParam为自定义的 0x77777777 就显示 CrackMe 的主对话框,因此这里就是显示主界面的地方,我们也找到了 DlgProc() 的入口为 0x004020B0。
再次“跟随立即数”,来到 0x004020B0,对话框消息处理框架代码如下:
004020B0    8B4424 08            mov   eax, dword ptr
004020B4    53                     push    ebx
004020B5    56                     push    esi
004020B6    83F8 02                cmp   eax, 2                                       ; WM_DESTROY
004020B9    57                     push    edi
004020BA    0F84 C6000000          je      00402186
004020C0    3D 10010000            cmp   eax, 110                                       ; WM_INITDIALOG
004020C5    74 13                  je      short 004020DA
004020C7    3D 11010000            cmp   eax, 111                                       ; WM_COMMAND
004020CC    0F84 9D000000          je      0040216F
004020D2    33C0                   xor   eax, eax
004020D4    5F                     pop   edi
004020D5    5E                     pop   esi
004020D6    5B                     pop   ebx
004020D7    C2 1000                retn    10


我们跟随 WM_INITDIALOG 消息处理跳转 "je 004020DA" 后,可以看到这样一段代码:
004020F6    6A 00                  push    0                                              ; 0 - Disabled, 1 - Enabled
004020F8    8B35 A0E24000          mov   esi, dword ptr [<&USER32.GetDlgItem>]          ; USER32.GetDlgItem
004020FE    68 EC030000            push    3EC                                          ; "Request" ID
00402103    53                     push    ebx                                          ; hDlg
00402104    FFD6                   call    esi                                          ; call GetDlgItem()
00402106    50                     push    eax                                          ; Control hwnd
00402107    FF15 98E24000          call    dword ptr [<&USER32.EnableWindow>]             ; USER32.EnableWindow


这段代码就是在 CrackMe 启动时将 "Request" 按钮的状态改为 "Disabled" 状态的。

我们再回到 DlgProc(),跟随处理 WM_COMMAND 跳转 "je 0040216F",来到其处理框架代码:
0040216F    8B4424 18            mov   eax, dword ptr
00402173    25 FFFF0000            and   eax, 0FFFF
00402178    3D EB030000            cmp   eax, 3EB                                       ; Validate 按钮
0040217D    7F 1B                  jg      short 0040219A
0040217F    74 54                  je      short 004021D5                                 ; 跳转去进行注册验证
00402181    83F8 02                cmp   eax, 2                                       ; WM_CLOSE
00402184    74 37                  je      short 004021BD
00402186    A1 08BE4000            mov   eax, dword ptr
0040218B    50                     push    eax
0040218C    FF15 F4E14000          call    dword ptr [<&GDI32.DeleteObject>]            ; GDI32.DeleteObject
00402192    33C0                   xor   eax, eax
00402194    5F                     pop   edi
00402195    5E                     pop   esi
00402196    5B                     pop   ebx
00402197    C2 1000                retn    10


可以看到,跟随 “ je 004021D5” 后就可以到达注册验证的代码了:
004021D5   > \8B5C24 10                mov   ebx, dword ptr                      ;Case 3EB of switch 00402178
004021D9   .53                     push    ebx
004021DA   .E8 31F1FFFF            call    00401310                                    ;假的序列号验证,实际取用户名
004021DF   .83C4 04                  add   esp, 4
004021E2   .8BF8                     mov   edi, eax                                    ;eax ===> "solly"
004021E4   .B9 FFFFFFFF            mov   ecx, -1
004021E9   .2BC0                     sub   eax, eax
004021EB   .F2:AE                  repne   scas byte ptr es:
004021ED   .F7D1                     not   ecx
004021EF   .2BF9                     sub   edi, ecx
004021F1   .8BC1                     mov   eax, ecx
004021F3   .C1E9 02                  shr   ecx, 2
004021F6   .8BF7                     mov   esi, edi
004021F8   .BF 30BF4000            mov   edi, 0040BF30                                 ;ASCII "solly"
004021FD   .F3:A5                  rep   movs dword ptr es:, dword ptr
004021FF   .8BC8                     mov   ecx, eax
00402201   .53                     push    ebx
00402202   .83E1 03                  and   ecx, 3
00402205   .F3:A4                  rep   movs byte ptr es:, byte ptr
00402207   .E8 74F1FFFF            call    00401380                                    ;假的序列号验证,实际取公司名
0040220C   .83C4 04                  add   esp, 4
0040220F   .8BF8                     mov   edi, eax                                    ;eax ===> "company"
00402211   .B9 FFFFFFFF            mov   ecx, -1
00402216   .2BC0                     sub   eax, eax
00402218   .F2:AE                  repne   scas byte ptr es:
0040221A   .F7D1                     not   ecx
0040221C   .2BF9                     sub   edi, ecx
0040221E   .8BD1                     mov   edx, ecx
00402220   .C1E9 02                  shr   ecx, 2
00402223   .8BF7                     mov   esi, edi
00402225   .BF E0BD4000            mov   edi, 0040BDE0                                 ;ASCII "company"
0040222A   .F3:A5                  rep   movs dword ptr es:, dword ptr
0040222C   .8BCA                     mov   ecx, edx
0040222E   .53                     push    ebx
0040222F   .83E1 03                  and   ecx, 3
00402232   .F3:A4                  rep   movs byte ptr es:, byte ptr
00402234   .E8 B7F1FFFF            call    004013F0                                    ;假的序列号验证,实际取序列名
00402239   .83C4 04                  add   esp, 4
0040223C   .8BF8                     mov   edi, eax                                    ;eax ===> "78787878"
0040223E   .B9 FFFFFFFF            mov   ecx, -1
00402243   .2BC0                     sub   eax, eax
00402245   .F2:AE                  repne   scas byte ptr es:
00402247   .F7D1                     not   ecx
00402249   .2BF9                     sub   edi, ecx
0040224B   .8BD1                     mov   edx, ecx
0040224D   .C1E9 02                  shr   ecx, 2
00402250   .8BF7                     mov   esi, edi
00402252   .BF 28BE4000            mov   edi, 0040BE28                                 ;ASCII "78787878"
00402257   .F3:A5                  rep   movs dword ptr es:, dword ptr
00402259   .8BCA                     mov   ecx, edx
0040225B   .83E1 03                  and   ecx, 3
0040225E   .F3:A4                  rep   movs byte ptr es:, byte ptr
00402260   .53                     push    ebx                                           ;EBX == 0x00B50DE8
00402261   .68 28BE4000            push    0040BE28                                    ;ASCII "78787878"
00402266   .68 E0BD4000            push    0040BDE0                                    ;ASCII "company"
0040226B   .68 30BF4000            push    0040BF30                                    ;ASCII "solly"
00402270   .E8 7BFAFFFF            call    00401CF0                                    ;真正的注册码验证位置
00402275   .83C4 10                  add   esp, 10
00402278   .66:833D 20BE4000 28      cmp   word ptr , 28                         ; 是否成功!
00402280   .75 1E                  jnz   short 004022A0                              ; == 0x28 表示成功!
00402282   .6A 00                  push    0                                             ; /lParam = NULL
00402284   .A1 F8BE4000            mov   eax, dword ptr                      ; |
00402289   .68 A0254000            push    004025A0                                    ; |DlgProc = Torn@do_.004025A0
0040228E   .53                     push    ebx                                           ; |hOwner
0040228F   .6A 68                  push    68                                          ; |pTemplate = 68
00402291   .50                     push    eax                                           ; |hInst => NULL
00402292   .FF15 E0E24000            call    dword ptr [<&user32.DialogBoxParamA>          ; \DialogBoxParamA
00402298   .33C0                     xor   eax, eax
0040229A   .5F                     pop   edi
0040229B   .5E                     pop   esi
0040229C   .5B                     pop   ebx
0040229D   .C2 1000                  retn    10
004022A0   >66:A1 74A04000         mov   ax, word ptr
004022A6   .66:40                  inc   ax
004022A8   .66:A3 74A04000         mov   word ptr , ax
004022AE   .66:3D 0300               cmp   ax, 3                                       ; 尝试3次
004022B2   .75 32                  jnz   short 004022E6
004022B4   .6A 00                  push    0                                             ; /Enable = FALSE
004022B6   .8B35 A0E24000            mov   esi, dword ptr [<&user32.GetDlgItem>          ; |USER32.GetDlgItem
004022BC   .68 EB030000            push    3EB                                           ; |/ControlID = 3EB (1003.)
004022C1   .53                     push    ebx                                           ; ||hWnd
004022C2   .FFD6                     call    esi                                           ; |\GetDlgItem
004022C4   .50                     push    eax                                           ; |hWnd
004022C5   .8B3D 98E24000            mov   edi, dword ptr [<&user32.EnableWindow>      ; |USER32.EnableWindow
004022CB   .FFD7                     call    edi                                           ; \EnableWindow
004022CD   .68 68A04000            push    0040A068                                    ; /Text = "TRY AGAIN!"
004022D2   .68 EE030000            push    3EE                                           ; |ControlID = 3EE (1006.)
004022D7   .53                     push    ebx                                           ; |hWnd
004022D8   .FF15 F8E24000            call    dword ptr [<&user32.SetDlgItemTextA>          ; \SetDlgItemTextA
004022DE   .66:A1 74A04000         mov   ax, word ptr
004022E4   .EB 0C                  jmp   short 004022F2
004022E6   >8B3D 98E24000            mov   edi, dword ptr [<&user32.EnableWindow>      ;USER32.EnableWindow
004022EC   .8B35 A0E24000            mov   esi, dword ptr [<&user32.GetDlgItem>          ;USER32.GetDlgItem
004022F2   >66:A3 74A04000         mov   word ptr , ax
004022F8   .66:3D 0300               cmp   ax, 3                                       ; 尝试3次
004022FC   .7E 21                  jle   short 0040231F
004022FE   .6A 00                  push    0
00402300   .68 EB030000            push    3EB
00402305   .53                     push    ebx
00402306   .FFD6                     call    esi
00402308   .50                     push    eax
00402309   .FFD7                     call    edi
0040230B   .6A 40                  push    40                                          ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
0040230D   .68 98A74000            push    0040A798                                    ; |Title = "Message from TORN@DO"
00402312   .68 10A74000            push    0040A710                                    ; |Text = "You're using a window editing tool like THE CUSTOMISER!",LF,"You have had 3 chances for entering the right code!",LF,"Do you think that's fair?"
00402317   .6A 00                  push    0                                             ; |hOwner = NULL
00402319   .FF15 DCE24000            call    dword ptr [<&user32.MessageBoxA>]             ; \MessageBoxA
0040231F   >33C0                     xor   eax, eax
00402321   .5F                     pop   edi
00402322   .5E                     pop   esi
00402323   .5B                     pop   ebx
00402324   .C2 1000                  retn    10

从上面的我的注释中,可以看到,前面三次都是没有用的验证,迷惑用的,实际用途只是取界面上输入的三个字符串。直正的验证是如下代码处:
00402260   .53                     push    ebx                                           ;EBX == 0x00B50DE8
00402261   .68 28BE4000            push    0040BE28                                    ;ASCII "78787878"
00402266   .68 E0BD4000            push    0040BDE0                                    ;ASCII "company"
0040226B   .68 30BF4000            push    0040BF30                                    ;ASCII "solly"
00402270   .E8 7BFAFFFF            call    00401CF0                                    ;真正的注册码验证位置
00402275   .83C4 10                  add   esp, 10
00402278   .66:833D 20BE4000 28      cmp   word ptr , 28                         ; 是否成功!
00402280   .75 1E                  jnz   short 004022A0                              ; == 0x28 表示成功!
上面的 call 00401CF0 才是真正的验证代码,其代码下:
00401CF0/$53                     push    ebx
00401CF1|.56                     push    esi
00401CF2|.8B5C24 14                mov   ebx, dword ptr                 ;序列号
00401CF6|.57                     push    edi
00401CF7|.55                     push    ebp
00401CF8|.53                     push    ebx
00401CF9|.E8 F2FCFFFF            call    004019F0                               ;strcmp(),判断是否空串
00401CFE|.83C4 04                  add   esp, 4
00401D01|.53                     push    ebx                                    ;ebx ===> SN
00401D02|.E8 19FAFFFF            call    00401720                               ;sn~sn,固定字符 "IDC40"
00401D07|.83C4 04                  add   esp, 4
00401D0A|.83F8 01                  cmp   eax, 1
00401D0D|.74 0E                  je      short 00401D1D
00401D0F|.66:C705 20BE4000 FFFF    mov   word ptr , 0FFFF
00401D18|.5D                     pop   ebp
00401D19|.5F                     pop   edi
00401D1A|.5E                     pop   esi
00401D1B|.5B                     pop   ebx
00401D1C|.C3                     retn
00401D1D|>8B6C24 20                mov   ebp, dword ptr
00401D21|.8B7C24 18                mov   edi, dword ptr
00401D25|.8B7424 14                mov   esi, dword ptr
00401D29|.55                     push    ebp
00401D2A|.53                     push    ebx
00401D2B|.57                     push    edi
00401D2C|.56                     push    esi
00401D2D|.E8 EEFCFFFF            call    00401A20                               ;无用校验
00401D32|.83C4 10                  add   esp, 10
00401D35|.53                     push    ebx
00401D36|.E8 55FAFFFF            call    00401790                               ;sn~sn,固定字符 "ETBL"
00401D3B|.83C4 04                  add   esp, 4
00401D3E|.83F8 01                  cmp   eax, 1
00401D41|.74 0E                  je      short 00401D51
00401D43|.66:C705 20BE4000 FFFF    mov   word ptr , 0FFFF
00401D4C|.5D                     pop   ebp
00401D4D|.5F                     pop   edi
00401D4E|.5E                     pop   esi
00401D4F|.5B                     pop   ebx
00401D50|.C3                     retn
00401D51|>53                     push    ebx
00401D52|.E8 89FAFFFF            call    004017E0                               ;序列号最后4字符,当前时间校验
00401D57|.83C4 04                  add   esp, 4
00401D5A|.83F8 01                  cmp   eax, 1
00401D5D|.74 0E                  je      short 00401D6D
00401D5F|.66:C705 20BE4000 FFFF    mov   word ptr , 0FFFF
00401D68|.5D                     pop   ebp
00401D69|.5F                     pop   edi
00401D6A|.5E                     pop   esi
00401D6B|.5B                     pop   ebx
00401D6C|.C3                     retn
00401D6D|>55                     push    ebp
00401D6E|.53                     push    ebx
00401D6F|.57                     push    edi
00401D70|.56                     push    esi
00401D71|.E8 FAFDFFFF            call    00401B70                               ;无用校验
00401D76|.83C4 10                  add   esp, 10
00401D79|.A1 80C34000            mov   eax, dword ptr                 ;FirstInstallDateTime转换成字符串后的长度
00401D7E|.50                     push    eax
00401D7F|.53                     push    ebx
00401D80|.E8 EBFAFFFF            call    00401870                               ;注册表FirstInstallDateTime验证,但由于的BUG,变成固定数字(1702505)的验证
00401D85|.83C4 08                  add   esp, 8
00401D88|.83F8 01                  cmp   eax, 1
00401D8B|.74 0E                  je      short 00401D9B
00401D8D|.66:C705 20BE4000 FFFF    mov   word ptr , 0FFFF
00401D96|.5D                     pop   ebp
00401D97|.5F                     pop   edi
00401D98|.5E                     pop   esi
00401D99|.5B                     pop   ebx
00401D9A|.C3                     retn
00401D9B|>55                     push    ebp
00401D9C|.53                     push    ebx
00401D9D|.57                     push    edi
00401D9E|.56                     push    esi
00401D9F|.E8 8CFEFFFF            call    00401C30                               ;无用校验
00401DA4|.83C4 10                  add   esp, 10
00401DA7|.53                     push    ebx                                    ;ebx ===> sn
00401DA8|.E8 63FBFFFF            call    00401910                               ;注册信息验证,包括注册表的 RegisteredOwner、输入的用户名和公司名验证
00401DAD|.83C4 04                  add   esp, 4
00401DB0|.83F8 01                  cmp   eax, 1
00401DB3|.74 0E                  je      short 00401DC3
00401DB5|.66:C705 20BE4000 FFFF    mov   word ptr , 0FFFF
00401DBE|.5D                     pop   ebp
00401DBF|.5F                     pop   edi
00401DC0|.5E                     pop   esi
00401DC1|.5B                     pop   ebx
00401DC2|.C3                     retn
00401DC3|>55                     push    ebp
00401DC4|.E8 97F6FFFF            call    00401460                               ;设置成功状态
00401DC9|.83C4 04                  add   esp, 4
00401DCC|.5D                     pop   ebp
00401DCD|.5F                     pop   edi
00401DCE|.5E                     pop   esi
00401DCF|.5B                     pop   ebx
00401DD0\.C3                     retn

这里説明一下,从上面代码中可以看到,有一个时间验证(call 004017E0),代码如下:
004017E0/$83EC 04                  sub   esp, 4
004017E3|.33C9                     xor   ecx, ecx
004017E5|.8D4424 01                lea   eax, dword ptr
004017E9|.56                     push    esi
004017EA|.8B5424 0C                mov   edx, dword ptr                             ;edx ===> SN
004017EE|.57                     push    edi
004017EF|.8BFA                     mov   edi, edx                                          ;edi ===> SN
004017F1|.884C24 08                mov   byte ptr , cl
004017F5|.66:8908                  mov   word ptr , cx
004017F8|.8848 02                  mov   byte ptr , cl
004017FB|.B9 FFFFFFFF            mov   ecx, -1
00401800|.2BC0                     sub   eax, eax
00401802|.F2:AE                  repne   scas byte ptr es:
00401804|.F7D1                     not   ecx                                             ;ecx = len(sn)+1
00401806|.8BFA                     mov   edi, edx
00401808|.2BC0                     sub   eax, eax
0040180A|.8A4C11 FB                mov   cl, byte ptr                         ;sn
0040180E|.884C24 08                mov   byte ptr , cl
00401812|.B9 FFFFFFFF            mov   ecx, -1
00401817|.F2:AE                  repne   scas byte ptr es:
00401819|.F7D1                     not   ecx                                             ;ecx = len(sn)+1
0040181B|.8BFA                     mov   edi, edx
0040181D|.8A4411 FC                mov   al, byte ptr                         ;sn
00401821|.B9 FFFFFFFF            mov   ecx, -1
00401826|.884424 09                mov   byte ptr , al
0040182A|.2BC0                     sub   eax, eax
0040182C|.F2:AE                  repne   scas byte ptr es:
0040182E|.F7D1                     not   ecx                                             ;ecx = len(sn)+1
00401830|.8BFA                     mov   edi, edx
00401832|.2BC0                     sub   eax, eax
00401834|.8A4C11 FD                mov   cl, byte ptr                         ;sn
00401838|.884C24 0A                mov   byte ptr , cl
0040183C|.B9 FFFFFFFF            mov   ecx, -1
00401841|.F2:AE                  repne   scas byte ptr es:
00401843|.F7D1                     not   ecx                                             ;ecx = len(sn)+1
00401845|.8D4424 08                lea   eax, dword ptr
00401849|.8A5411 FE                mov   dl, byte ptr                         ;sn
0040184D|.885424 0B                mov   byte ptr , dl
00401851|.50                     push    eax
00401852|.E8 291C0000            call    00403480                                          ;atoi(sn)
00401857|.83C4 04                  add   esp, 4
0040185A|.8BF0                     mov   esi, eax                                          ;esi = 0
0040185C|.E8 3FFEFFFF            call    004016A0                                          ;GetLocalTime
00401861|.2BC6                     sub   eax, esi                                          ;eax = 0x0000081C = 2076
00401863|.5F                     pop   edi
00401864|.5E                     pop   esi
00401865|.83F8 01                  cmp   eax, 1
00401868|.1BC0                     sbb   eax, eax
0040186A|.83C4 04                  add   esp, 4
0040186D|.F7D8                     neg   eax
0040186F\.C3                     retn
其中又有一个调用(call 004016A0),就是根据当前时间生成一段注册验证,对注册码最后4个数字进行验证:
004016A0/$83EC 10                  sub   esp, 10
004016A3|.8D4424 00                lea   eax, dword ptr
004016A7|.56                     push    esi
004016A8|.50                     push    eax                                             ; /pLocaltime
004016A9|.FF15 6CE24000            call    dword ptr [<&kernel32.GetLocalTime>]            ; \GetLocalTime
004016AF|.33D2                     xor   edx, edx
004016B1|.33C0                     xor   eax, eax
004016B3|.66:8B5424 06             mov   dx, word ptr                               ;月份 7
004016B8|.33C9                     xor   ecx, ecx
004016BA|.66:8B4424 0A             mov   ax, word ptr                               ;日 6
004016BF|.0FAFD0                   imul    edx, eax                                          ;EDX = 0x2A
004016C2|.66:8B4C24 0E             mov   cx, word ptr                               ;分钟,cx = 0x09
004016C7|.8B4424 0C                mov   eax, dword ptr                             ;eax = 0x00090003
004016CB|.25 FFFF0000            and   eax, 0FFFF                                        ;小时 eax = 3
004016D0|.0FAFC1                   imul    eax, ecx                                          ;eax == 0x1B
004016D3|.0FBE0D 60BF4000          movsx   ecx, byte ptr                             ;RegisteredOwner,注册表项字符串的第1个字符的 ASCII 值。
004016DA|.03D0                     add   edx, eax                                          ;edx = 0x2A + 0x1B
004016DC|.8B4424 04                mov   eax, dword ptr                             ;ax = 2019
004016E0|.2BD1                     sub   edx, ecx                                          ;ecx == RegisteredOwner
004016E2|.25 FFFF0000            and   eax, 0FFFF
004016E7|.8D3402                   lea   esi, dword ptr                         ;esi = edx + eax + 0x45 + 0x7E3 = 69 + 2019 = 2088
004016EA|.56                     push    esi
004016EB|.E8 A0FBFFFF            call    00401290                                          ;检查 SoftICE (Win9x) 调试
004016F0|.83C4 04                  add   esp, 4
004016F3|.85C0                     test    eax, eax
004016F5|.74 06                  je      short 004016FD
004016F7|.81C6 43010000            add   esi, 143
004016FD|>56                     push    esi
004016FE|.E8 CDFBFFFF            call    004012D0                                          ;检查 SoftICE (NT) 调试
00401703|.83C4 04                  add   esp, 4
00401706|.85C0                     test    eax, eax
00401708|.74 06                  je      short 00401710
0040170A|.81C6 71020000            add   esi, 271
00401710|>8BC6                     mov   eax, esi
00401712|.5E                     pop   esi
00401713|.83C4 10                  add   esp, 10
00401716\.C3                     retn

也就是説,其验证时间精准到了分钟,生成的序列号有效时间最多60秒,如果注册机生成序列号与你输入序列号不是在同一分钟数字时(不是指1分钟内)序列号就是无效的,也就是説当输入序列号时,只要秒钟跨过了第59秒,就得重新生成序列号。
注册码前面两段是固定的,由 call 00401720 和 call 00401790 实现的,比较简单,这里不对其进行分析。
call 00401870 是对注册表中的一个值进行验证,这样,序列号与具体机器有关,不是通用的了,必须要有注册机才能搞定注册。这个调用是处理注册表中FirstInstallDateTime 这个值的,不过这个值只有在 Windows 9x 系统下才有,Win NT 系列,如 Windows 10 下就没有这个注册表项。另外 CrackMe 还要用到一个注册表项: RegisteredOwner,这个也只有在 Windows 9x 下才有。这个调用的主要代码如下(其前面有一段代码中没有用的,迷惑用的,不贴上来了):
00401870/$83EC 64                  sub   esp, 64
00401873|.53                     push    ebx
00401874|.56                     push    esi
00401875|.57                     push    edi
00401876|.33DB                     xor   ebx, ebx                                          ;int i = 0;
00401878|.8D7C24 0D                lea   edi, dword ptr
0040187C|.55                     push    ebp
0040187D|.33C0                     xor   eax, eax
0040187F|.B9 18000000            mov   ecx, 18
00401884|.885C24 10                mov   byte ptr , bl                           ;sn_time = '\0'
00401888|.F3:AB                  rep   stos dword ptr es:                           ;len 0x63
0040188A|.66:AB                  stos    word ptr es:
0040188C|.AA                     stos    byte ptr es:
0040188D|.33F6                     xor   esi, esi                                          ;int j = 0;
0040188F|.8B6C24 7C                mov   ebp, dword ptr                            ;FirstInstallDateTime 转换成字符串后的长度 len(FirstInstallDateTime) = 7
00401893|.3BE8                     cmp   ebp, eax
00401895|.74 24                  je      short 004018BB
00401897|.8B5424 78                mov   edx, dword ptr                            ;edx ===> SN
0040189B|>8BFA                     /mov   edi, edx
0040189D|.B9 FFFFFFFF            |mov   ecx, -1
004018A2|.2BC0                     |sub   eax, eax
004018A4|.F2:AE                  |repne   scas byte ptr es:
004018A6|.F7D1                     |not   ecx
004018A8|.49                     |dec   ecx                                              ;len(sn)
004018A9|.46                     |inc   esi                                              ;j ++
004018AA|.2BCD                     |sub   ecx, ebp                                       ;len(sn) - len(company)
004018AC|.03CB                     |add   ecx, ebx                                       ;c = len(sn) - len(company) + i
004018AE|.43                     |inc   ebx                                              ;i++
004018AF|.3BF5                     |cmp   esi, ebp                                       ;j<len(company)
004018B1|.8A4411 FB                |mov   al, byte ptr                          ;sn
004018B5|.884434 0F                |mov   byte ptr , al
004018B9|.^ 72 E0                  \jb      short 0040189B
004018BB|>8D4424 10                lea   eax, dword ptr                            ;sn_time == sn段
004018BF|.50                     push    eax
004018C0|.E8 BB1B0000            call    00403480                                          ;atoi(FirstInstallTime) = 0x0019FA69 = 1702505
004018C5|.83C4 04                  add   esp, 4
004018C8|.8BF0                     mov   esi, eax
004018CA|.A1 FCBE4000            mov   eax, dword ptr                            ; == 0x0019FA74 == 1702516, FirstInstallTime
004018CF|.894424 10                mov   dword ptr , eax                           ;eax == 1702516
004018D3|.C74424 14 00000000       mov   dword ptr , 0
004018DB|.DF6C24 10                fild    qword ptr
004018DF|.D9C0                     fld   st
004018E1|.E8 731C0000            call    00403559                                          ;tanh(1702516) = 1.0000
004018E6|.DC0D 20904000            fmul    qword ptr                               ;1.0 * 11.00
004018EC|.DEE9                     fsubp   st(1), st                                       ;1702516-11=1702505
004018EE|.E8 590D0000            call    0040264C
004018F3|.2BC6                     sub   eax, esi                                          ;eax = 0x0019FA69 = 1702505
004018F5|.5D                     pop   ebp
004018F6|.5F                     pop   edi
004018F7|.83F8 01                  cmp   eax, 1
004018FA|.1BC0                     sbb   eax, eax
004018FC|.5E                     pop   esi
004018FD|.F7D8                     neg   eax
004018FF|.5B                     pop   ebx
00401900|.83C4 64                  add   esp, 64
00401903\.C3                     retn
就是将 FirstInstallDateTime 进行简单计算后,与输入的序列号相关字段进行比较,相等即通过。
还有一个调用(call 00401910)就是对我们在界面上输入的用户名和公司名进行校验,当中还包括注册表项 RegisteredOwner 的验证,如下:
00401910/$83EC 64                  sub   esp, 64
00401913|.33C0                     xor   eax, eax
00401915|.B9 18000000            mov   ecx, 18                                           ;ecx == 0x18 == 24
0040191A|.56                     push    esi
0040191B|.C64424 04 00             mov   byte ptr , 0
00401920|.57                     push    edi
00401921|.8D7C24 09                lea   edi, dword ptr
00401925|.F3:AB                  rep   stos dword ptr es:
00401927|.66:AB                  stos    word ptr es:
00401929|.BE 0C000000            mov   esi, 0C                                           ;int i = 12
0040192E|.B9 FFFFFFFF            mov   ecx, -1
00401933|.AA                     stos    byte ptr es:
00401934|.8B5424 70                mov   edx, dword ptr                            ;edx ===> SN
00401938|.2BC0                     sub   eax, eax
0040193A|.8BFA                     mov   edi, edx                                          ;edi ===> SN
0040193C|.F2:AE                  repne   scas byte ptr es:
0040193E|.F7D1                     not   ecx
00401940|.49                     dec   ecx                                             ;ecx = len(sn) = 0x21 = 33
00401941|.2B0D 80C34000            sub   ecx, dword ptr                            ;FirstInstallDateTime 转换成字符串后的长度
00401947|.83E9 05                  sub   ecx, 5                                          ;5 (当前时间的长度 "-nnnn")
0040194A|.3BCE                     cmp   ecx, esi                                          ;len(sn) - 12 <= 12
0040194C|.76 24                  jbe   short 00401972
0040194E|>8A4432 FF                /mov   al, byte ptr                          ;sn
00401952|.46                     |inc   esi                                              ;i++
00401953|.8BFA                     |mov   edi, edx                                       ;edi ====> sn
00401955|.B9 FFFFFFFF            |mov   ecx, -1
0040195A|.884434 FB                |mov   byte ptr , al                         ;tmp = sn
0040195E|.2BC0                     |sub   eax, eax
00401960|.F2:AE                  |repne   scas byte ptr es:
00401962|.F7D1                     |not   ecx
00401964|.49                     |dec   ecx
00401965|.2B0D 80C34000            |sub   ecx, dword ptr                         ;FirstInstallDateTime 转换成字符串后的长度
0040196B|.83E9 05                  |sub   ecx, 5                                           ;5 (当前时间的长度 "-nnnn")
0040196E|.3BCE                     |cmp   ecx, esi
00401970|.^ 77 DC                  \ja      short 0040194E
00401972|>8D4424 08                lea   eax, dword ptr                             ;eax ===> "398378258"
00401976|.50                     push    eax
00401977|.E8 041B0000            call    00403480                                          ;atoi()
0040197C|.83C4 04                  add   esp, 4
0040197F|.8BF8                     mov   edi, eax                                          ;eax == 0x17BEC512 == 398378258
00401981|.E8 0AFCFFFF            call    00401590                                          ;RegisteredOwner索引check
00401986|.8BF0                     mov   esi, eax                                          ;eax == 0x14 == 20
00401988|.E8 B3FBFFFF            call    00401540                                          ;RegisteredOwner内容check
0040198D|.33F0                     xor   esi, eax                                          ;eax == 0x01BA == 442, esi = 0x01AE == 430
0040198F|.68 E0BD4000            push    0040BDE0                                          ;ASCII "ite company"
00401994|.68 30BF4000            push    0040BF30                                          ;ASCII "solly88"
00401999|.E8 42FCFFFF            call    004015E0                                          ;注册名和公司名checkcode
0040199E|.83C4 08                  add   esp, 8
004019A1|.03C6                     add   eax, esi                                          ;long a = checkcode + regOwner_check
004019A3|.8D0CC0                   lea   ecx, dword ptr                         ;ecx == a * 9
004019A6|.8D14C8                   lea   edx, dword ptr                         ;edx == a * 73
004019A9|.8D0C52                   lea   ecx, dword ptr                         ;ecx == a * 219 = 778983
004019AC|.C1E1 02                  shl   ecx, 2                                          ;ecx == a * 876
004019AF|.2BC8                     sub   ecx, eax                                          ;ecx == a * 875
004019B1|.C1E1 06                  shl   ecx, 6                                          ;ecx == a * 875*64 = a * 56000
004019B4|.2BC8                     sub   ecx, eax                                          ;ecx == a * 55999
004019B6|.03C9                     add   ecx, ecx                                          ;ecx == a * 111998
004019B8|.894C24 08                mov   dword ptr , ecx                            ;ecx == 0x17BEBFB6 = 398376886
004019BC|.C74424 0C 00000000       mov   dword ptr , 0
004019C4|.DF6C24 08                fild    qword ptr
004019C8|.D9C0                     fld   st
004019CA|.D9FE                     fsin                                                      ;sin(398376886) = -0.6866350580165284495
004019CC|.DC0D 28904000            fmul    qword ptr                               ; = 1999.0000, sin()*1999 = -1372.5834809750404020
004019D2|.DEE9                     fsubp   st(1), st                                       ;st == 398378258.58348095420 = 398376886 - (-1372.5834809750404020)
004019D4|.E8 730C0000            call    0040264C                                          ;eax == 0x17BEC512 == 398378258
004019D9|.2BC7                     sub   eax, edi                                          ;edi = atoi()
004019DB|.5F                     pop   edi
004019DC|.5E                     pop   esi
004019DD|.83F8 01                  cmp   eax, 1
004019E0|.1BC0                     sbb   eax, eax
004019E2|.83C4 64                  add   esp, 64
004019E5|.F7D8                     neg   eax
004019E7\.C3                     retn
其中,对用户名和公司名处理的调用(call 004015E0)如下所示:
004015E0/$83EC 08                  sub   esp, 8
004015E3|.33D2                     xor   edx, edx                                          ;long sum = 0;
004015E5|.B9 FFFFFFFF            mov   ecx, -1
004015EA|.53                     push    ebx
004015EB|.66:BB 0100               mov   bx, 1                                             ;int i = 1
004015EF|.56                     push    esi
004015F0|.8B7424 14                mov   esi, dword ptr                            ;esi ===> "solly"
004015F4|.57                     push    edi
004015F5|.8BFE                     mov   edi, esi                                          ;edi ===> "solly"
004015F7|.2BC0                     sub   eax, eax
004015F9|.F2:AE                  repne   scas byte ptr es:
004015FB|.F7D1                     not   ecx
004015FD|.49                     dec   ecx                                             ;ecx == 用户名长度
004015FE|.83F9 01                  cmp   ecx, 1
00401601|.76 2F                  jbe   short 00401632
00401603|>0FBFC3                   /movsx   eax, bx                                          ;i
00401606|.0FBE4430 FF            |movsx   eax, byte ptr                         ;eax == name
0040160B|.8BC8                     |mov   ecx, eax                                       ;ecx == name
0040160D|.C1E0 02                  |shl   eax, 2                                           ;eax == name * 4
00401610|.66:43                  |inc   bx                                             ;i++
00401612|.8BFE                     |mov   edi, esi                                       ;edi ===> "solly"
00401614|.8D04C0                   |lea   eax, dword ptr                      ;eax == name * 36,   (4*8+4)
00401617|.8D0CC1                   |lea   ecx, dword ptr                      ;ecx == name * 289,(36*8+1)
0040161A|.8D04C9                   |lea   eax, dword ptr                      ;eax == name * 2601, (289*9)
0040161D|.B9 FFFFFFFF            |mov   ecx, -1
00401622|.03D0                     |add   edx, eax                                       ;sum += eax;
00401624|.2BC0                     |sub   eax, eax
00401626|.F2:AE                  |repne   scas byte ptr es:
00401628|.0FBFC3                   |movsx   eax, bx                                          ;i
0040162B|.F7D1                     |not   ecx
0040162D|.49                     |dec   ecx                                              ;len(solly)
0040162E|.3BC8                     |cmp   ecx, eax                                       ;i<len(name)
00401630|.^ 77 D1                  \ja      short 00401603                                 ;sum = 0x00118ACA
00401632|>66:BB 0100               mov   bx, 1                                             ;int i = 1
00401636|.8B7424 1C                mov   esi, dword ptr                            ;esi ===> "company"
0040163A|.8BFE                     mov   edi, esi                                          ;edi ===> "company"
0040163C|.B9 FFFFFFFF            mov   ecx, -1
00401641|.2BC0                     sub   eax, eax
00401643|.F2:AE                  repne   scas byte ptr es:
00401645|.F7D1                     not   ecx
00401647|.49                     dec   ecx                                             ;len(company)
00401648|.83F9 01                  cmp   ecx, 1
0040164B|.76 21                  jbe   short 0040166E
0040164D|>0FBFC3                   /movsx   eax, bx                                          ;i
00401650|.66:43                  |inc   bx                                             ;i++
00401652|.8BFE                     |mov   edi, esi                                       ;edi ===> "company"
00401654|.0FBE4C30 FF            |movsx   ecx, byte ptr                         ;company
00401659|.03D1                     |add   edx, ecx                                       ;sum += company
0040165B|.B9 FFFFFFFF            |mov   ecx, -1
00401660|.2BC0                     |sub   eax, eax
00401662|.F2:AE                  |repne   scas byte ptr es:
00401664|.0FBFC3                   |movsx   eax, bx                                          ;i
00401667|.F7D1                     |not   ecx
00401669|.49                     |dec   ecx                                              ;len(company)
0040166A|.3BC8                     |cmp   ecx, eax                                       ;i<len(company)
0040166C|.^ 77 DF                  \ja      short 0040164D                                 ;sum == 0x00118D48
0040166E|>8D0492                   lea   eax, dword ptr                         ;eax = sum * 5
00401671|.8D0C42                   lea   ecx, dword ptr                         ;ecx = sum * 11
00401674|.894C24 0C                mov   dword ptr , ecx                            ;ecx == 0x00C11218 == 12653080
00401678|.C74424 10 00000000       mov   dword ptr , 0
00401680|.DF6C24 0C                fild    qword ptr
00401684|.D9FA                     fsqrt                                                   ;sqrt(12653080) == 3557.1168100021682220
00401686|.E8 C10F0000            call    0040264C                                          ;eax == 0x0DE5 == 3557
0040168B|.5F                     pop   edi
0040168C|.5E                     pop   esi
0040168D|.5B                     pop   ebx
0040168E|.83C4 08                  add   esp, 8
00401691\.C3                     retn
另外对 RegisteredOwner 的验证有两次,比较简单,见注册机,不在此分析。另外,前面时间验证中,也用到了 RegisteredOwner 字符串的第1个字符(RegisteredOwner)的 ASCII 码值。

因为其有对注册表项进行验证,所以其需要读取注册表中的数据,不过,其在读取 FirstInstallDateTime 有问题,并没有取得这个值,而是取得保存这个值的地址,将这个地址参与了序列号的计算,这段取注册表信息的代码如下:
00401E59|.68 F8A64000            push    0040A6F8                                          ;ASCII "FirstInstallDateTime"
00401E5E|.68 CCA64000            push    0040A6CC                                          ;ASCII "SOFTWARE\Microsoft\Windows\CurrentVersion"
00401E63|.68 02000080            push    80000002
00401E68|.E8 43F6FFFF            call    004014B0                                          ;读注册表,返回结果是一个地址,存于 EAX 并返回。
00401E6D|.83C4 0C                  add   esp, 0C
00401E70|.8B2D B4E24000            mov   ebp, dword ptr [<&user32.wsprintfA>]            ;USER32.wsprintfA
00401E76|.50                     push    eax                                             ; /<%lu>,这里有Bug,传入的不是读取的时间,而是指向时间的地址指针,固定为 0x0019FA74
00401E77|.68 C8A64000            push    0040A6C8                                          ; |Format = "%lu",此格式是将返回地址当作一个无符号长整数进行操作
00401E7C|.68 90BF4000            push    0040BF90                                          ; |s = Torn@do_.0040BF90
00401E81|.FFD5                     call    ebp                                             ; \wsprintfA
00401E83|.83C4 0C                  add   esp, 0C
00401E86|.BF 90BF4000            mov   edi, 0040BF90                                     ;ASCII "1702516"
00401E8B|.68 90BF4000            push    0040BF90                                          ;ASCII "1702516"
00401E90|.E8 EB150000            call    00403480                                          ;atoi(pointer)
00401E95|.83C4 04                  add   esp, 4
00401E98|.B9 FFFFFFFF            mov   ecx, -1
00401E9D|.A3 FCBE4000            mov   dword ptr , eax                           ;保存 0x0019FA74,实际是一个地址指针,不是注册表的 FirstInstallDateTime
00401EA2|.2BC0                     sub   eax, eax
00401EA4|.F2:AE                  repne   scas byte ptr es:
00401EA6|.F7D1                     not   ecx
00401EA8|.49                     dec   ecx                                             ;前面timestamp字符串的长度
00401EA9|.68 B8A64000            push    0040A6B8                                          ;ASCII "RegisteredOwner"
00401EAE|.68 CCA64000            push    0040A6CC                                          ;ASCII "SOFTWARE\Microsoft\Windows\CurrentVersion"
00401EB3|.890D 80C34000            mov   dword ptr , ecx                           ;保存时间字符串的长度
00401EB9|.68 02000080            push    80000002
00401EBE|.E8 EDF5FFFF            call    004014B0                                          ;读注册表
问题出在调用 wsprintf() 这个函数的参数,因为 Call 004014B0 这个函数读取注册表信息后,返回的是一个地址,而 wsprintf()中,引用的是这个地址,所以转换成字符串也是一个地址值,不是真正的 FirstInstallDateTime的值,如果要修改这个 Bug,可以进行以下修改,如下图所示:

将 0x00401E6D处的 add esp, 0C 改成 mov eax, ,然后在下面 0x00401E83 处的 add esp, 0C 改成 add esp, 18,这样堆栈也平衡了,Bug也消除了。

基本的注册验证过程分析到这里,用注册机生成注册码,并抓紧时间输入(粘贴),校验正确时,显示如下:

一定要注意时间,最好在当前分钟的前 0~30秒内生成序列号,后30秒来输入注册码并验证,不然可能序列号就过期了。

另外,在 Windows 10 之类的新版 Windows 下,缺少注册表项,需要导入以下信息,不然也通不过 CrackMe 的注册验证,64位系统如下:
Windows Registry Editor Version 5.00


"FirstInstallDateTime"=hex:c1,b1,98,4c
"RegisteredOrganization"="ite"
"RegisteredOwner"="solly"
"ProductId"="51163-030-0389753-07447"
32位系统如下:
Windows Registry Editor Version 5.00


"FirstInstallDateTime"=hex:c1,b1,98,4c
"RegisteredOrganization"="ite"
"RegisteredOwner"="solly"
"ProductId"="51163-030-0389753-07447"
当然,键值可以改成自己的,键名不能改动。

分析完毕,下面是注册机源码,使用 Dev-C++调试通过:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <windows.h>

union CBData{
      DWORD n;
      byte buffer;
};

CBData readKey(HKEY hKey, char * path, char * key);
void getRegInfo();
void getPart1();
void getPart2();
long getRegTimeStamp(int index);
long getCheckRegOwner();
long getCheck(char * name, char * company, int index);
void getCurrDateTime(int index);

bool testSoftICE95();
bool testSoftICENT();

char sn;
CBData data;
long nFirstInstallDateTime = 0;
long nLenOfFirstInstallDateTime = 0;
char sFirstInstallDateTime;
char RegisteredOwner;
char RegisteredOrganization;
char ProductId;

int main(int argc, char** argv) {
      memset(sn, 0, 48);
      memset(sFirstInstallDateTime, 0, 128);
      memset(RegisteredOwner, 0, 128);
      memset(RegisteredOrganization, 0, 128);
      memset(ProductId, 0, 128);
      
      getRegInfo();
      
      getPart1();
      sn = '-';

      getPart2();
      sn = '-';
      
      char name[]    = "solly88";
      char company[] = "ite company";
      int n = getCheck(name, company, 11);

    sn = '-';

      int m = getRegTimeStamp(10+n+2);
      
      sn = '-';
      
      /// 最后4字节为当前时间检查
      getCurrDateTime(10+n+3+m);
      
      printf("\n SN = %s\n", sn);
      
      return 0;
}

void getPart1() {
    sn = 0x0D ^ 0x49;
    sn = 0x07 ^ 0x44;
    sn = 0x0D ^ 0x39;
    sn = 0x1D ^ 0x54;
    sn = 0x09 ^ 0x39;
}

void getPart2() {
      sn = 0x06 ^ 0x52;
      sn = 0x07 ^ 0x45;
      sn = 0x1F ^ 0x53;
      sn = 0x11 ^ 0x54;
}

/**

***/
/// FirstInstallTime
long getRegTimeStamp(int index) {
      /// 0040BF90:1702516
    //long a = 1702505;
    ////
    //long FirstInstallDateTime = 1702516; /// 实际上是一个指向保存FirstInstallDateTime的内存地址 Win10
    //long FirstInstallDateTime = 6617404; /// 实际上是一个指向保存FirstInstallDateTime的内存地址 Win98
    //long FirstInstallDateTime = 6682940; /// 实际上是一个指向保存FirstInstallDateTime的内存地址 Win98_unpacked
    long FirstInstallDateTime = nFirstInstallDateTime; /// 正常时是一个 Timestamp
   
    long a = FirstInstallDateTime - tanh(FirstInstallDateTime) * 11.0;/// 当在win10下取的是地址时,这里是固定值 1702505
            
    //char str_a;
    sprintf(sFirstInstallDateTime, "%d", a);
    long n = strlen(sFirstInstallDateTime);
    for(int i=0; i<n; i++ ) {
            sn = sFirstInstallDateTime;
      }
      
      return n;
}

long getCheckRegOwner() {// Proc_00401590 和 Proc_00401540
      long sum1 = 0;
      long sum2 = 0;
      long n = strlen(RegisteredOwner);
      for(int i=1; i<n; i++) {
                sum1 += i*2;
                sum2 += RegisteredOwner;
      }
      //printf("check reg owner: %d - %d\n", sum1, sum2);
      
      return sum1 ^ sum2;
}

long getCheck(char * name, char * company, int index) {/// Proc_004015E0

      long checkbase = getCheckRegOwner();
      
      long sum = 0;
      int n = strlen(name);
      for(int i=0; i<n-1; i++) {
                sum += name * 2601;
      }
      int m = strlen(company);
      for(int i=0; i<m-1; i++) {
                sum += company;
      }
      
      double x = sum * 11;
      int code1 = (long)sqrt(x);
      ////
      //printf("check code1: %d, %d\n", sum, code1);
      long a = code1 + checkbase;
      long c = a * 111998;
      double y = c - sin(c) * 1999.0;
      long code2 = (long)y;
      
      //printf("check code2: %d\n", code2);
      char code_str;
      sprintf(code_str, "%d", code2);
      int len = strlen(code_str);
      for(int i=0; i<len; i++) {
                sn = code_str;
      }
      
      return len;
}

bool testSoftICE95() {
      HANDLE h = CreateFile("\\\\.\\SICE",
                              GENERIC_READ|GENERIC_WRITE,
                                                FILE_SHARE_READ|FILE_SHARE_WRITE,
                                                NULL, OPEN_EXISTING,
                                                FILE_ATTRIBUTE_NORMAL, NULL);
      if(h != INVALID_HANDLE_VALUE) {
                CloseHandle(h);
                return true;
      }
      
      return false;
}

bool testSoftICENT() {
      HANDLE h = CreateFile("\\\\.\\NTICE",
                              GENERIC_READ|GENERIC_WRITE,
                                                FILE_SHARE_READ|FILE_SHARE_WRITE,
                                                NULL, OPEN_EXISTING,
                                                FILE_ATTRIBUTE_NORMAL, NULL);
      if(h != INVALID_HANDLE_VALUE) {
                CloseHandle(h);
                return true;
      }
      
      return false;
}

/// Current date and time
void getCurrDateTime(int index) {
      long a = RegisteredOwner;
      time_t timer;
      struct tm * st;
      time(&timer);
      st = localtime(&timer);
      long t = (st->tm_year + 1900) + ((st->tm_mon + 1) * st->tm_mday - a) + st->tm_hour * st->tm_min;
      
      ///// SoftICE 检查,修正时间检查值(基本无用)
      bool isExistsSICE95 = testSoftICE95();
      bool isExistsSICENT = testSoftICENT();
      
      if (isExistsSICE95) {
                t += 0x143;
      }
      if (isExistsSICENT) {
                t += 0x271;
      }

      char str_t;
      itoa(t, str_t, 10);
      sn = str_t;
      sn = str_t;
      sn = str_t;
      sn = str_t;
      ///
      //printf("time: %d-%d-%d %d:%d\n", st->tm_year+1900, st->tm_mon+1, st->tm_mday, st->tm_hour, st->tm_min)      ;
}

CBData readKey(HKEY hKey, char * path, char * key) {
      HKEY h = (HKEY)-1;
      DWORD cbData;
      DWORD regType = -1;

      data.n = 1;
      
      long s = RegOpenKeyEx(hKey, path, 0, KEY_READ, &h);
      if( s != ERROR_SUCCESS) {
                data.n = -1;
                return data;
      }
      
      s = RegQueryValueEx(h, key, NULL, NULL, NULL, &cbData);
      if(s == ERROR_SUCCESS) {
                RegQueryValueEx(h, key, NULL, & regType, data.buffer, &cbData);
      }
      
      RegCloseKey(h);
      
      return data;
}

void getRegInfo() {
      char subKey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion";
      char key1[] = "FirstInstallDateTime";
      char key2[] = "RegisteredOwner";
      char key3[] = "RegisteredOrganization";
      char key4[] = "ProductId";
      CBData cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key1);
      nFirstInstallDateTime = cb.n;
      struct tm * tm_reg = localtime((time_t *)&nFirstInstallDateTime);
      sprintf(sFirstInstallDateTime, "%lu", nFirstInstallDateTime);
      nLenOfFirstInstallDateTime = strlen(sFirstInstallDateTime);
      printf(" DateTime: %d-%d-%d %d:%d:%d\n", tm_reg->tm_year + 1900, tm_reg->tm_mon + 1,
                              tm_reg->tm_mday, tm_reg->tm_hour, tm_reg->tm_min, tm_reg->tm_sec);
      printf("Timestamp: %d,len=%d\n", nFirstInstallDateTime, nLenOfFirstInstallDateTime);
      ////
      cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key2);
      strcpy(RegisteredOwner, (char *)cb.buffer);
      ////
      cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key3);
      strcpy(RegisteredOrganization, (char *)cb.buffer);
      ////
      cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key4);
      strcpy(ProductId, (char *)cb.buffer);
      
      printf("             ProductId: %s\n", ProductId);
      printf("       RegisteredOwner: %s\n", RegisteredOwner);
      printf("RegisteredOrganization: %s\n", RegisteredOrganization);
}

solly 发表于 2019-7-15 02:27

160 个 CrackMe 之 078 - fireworx.10 注册算法中的变量初始化 BUG 分析

本帖最后由 solly 于 2019-8-5 22:51 编辑

CrackMe 078 是一个 Delphi 编译的 CrackMe,也是加壳的,OD 加载后,入口代码是以下4行代码:
00429318 >90                     nop
00429319    90                     nop
0042931A    90                     nop
0042931B    75 00                  jnz   short 0042931D
0042931D- E9 DE5C0400            jmp   0046F000
最后的跳转指令是跳转到壳代码入口,如下所示,解壳代码也简单:
0046F000    60                     pushad
0046F001    E8 00000000            call    0046F006
0046F006    5D                     pop   ebp
0046F007    81ED EAA84300            sub   ebp, 0043A8EA
0046F00D    B8 E4A84300            mov   eax, 0043A8E4
0046F012    03C5                     add   eax, ebp
0046F014    2B85 78AD4300            sub   eax, dword ptr
0046F01A    8985 84AD4300            mov   dword ptr , eax
0046F020    80BD 6EAD4300 00         cmp   byte ptr , 0
0046F027    75 15                  jnz   short 0046F03E
0046F029    FE85 6EAD4300            inc   byte ptr
0046F02F    E8 1D000000            call    0046F051
0046F034    E8 73020000            call    0046F2AC
0046F039    E8 0A030000            call    0046F348
0046F03E    8B85 70AD4300            mov   eax, dword ptr
0046F044    0385 84AD4300            add   eax, dword ptr
0046F04A    894424 1C                mov   dword ptr , eax
0046F04E    61                     popad
0046F04F    FFE0                     jmp   eax

在 0x0046F04F jmp eax 处,按 F4 就脱壳了,再 F8 就到了 OEP,可以进行脱壳,脱壳后也需要象 159 一样,进行 API 导入表手动修正,就是修正 DefWindowProc() 的引用。
正常启动后,界面如下:


如果成功注册,会显示以下界面:

当什么都不输入时,点击“OK”图片,会引发其第一个BUG,如下图所示:

我们随便输入一些数据,进行验证:

但是这个 CrackMe 的注册验证中,用到了一个未有效初始化的值,ESI 中的值,这个值应该是一个 Delphi 函数的 Sender 参数值,不过直接将其当作求累加和的基数,得不到正确的注册码。这可能是其第二个 Bug。
其事件处理代码如下:
00423F88   .53            push    ebx
00423F89   .56            push    esi
00423F8A   .57            push    edi
00423F8B   .83C4 E8         add   esp, -18
00423F8E   .8BFA            mov   edi, edx
00423F90   .8BF0            mov   esi, eax                        ; ESI = EAX = Sender,这里初始化的 ESI。
00423F92   .8BD7            mov   edx, edi
00423F94   .8BC6            mov   eax, esi
00423F96   .8B08            mov   ecx, dword ptr
00423F98   .FF51 F0         call    dword ptr
00423F9B   .F646 40 02      test    byte ptr , 2
00423F9F   .74 09         je      short 00423FAA
00423FA1   .33D2            xor   edx, edx
00423FA3   .8BC6            mov   eax, esi
00423FA5   .E8 8AEAFFFF   call    00422A34
00423FAA   >F646 44 02      test    byte ptr , 2
00423FAE   .74 3B         je      short 00423FEB
00423FB0   .66:8366 44 FD   and   word ptr , 0FFFD
00423FB5   .8BD4            mov   edx, esp
00423FB7   .8B47 08         mov   eax, dword ptr
00423FBA   .E8 7D25FEFF   call    0040653C
00423FBF   .FF7424 04       push    dword ptr
00423FC3   .FF7424 04       push    dword ptr
00423FC7   .8D5424 10       lea   edx, dword ptr
00423FCB   .8BC6            mov   eax, esi
00423FCD   .8B08            mov   ecx, dword ptr
00423FCF   .FF51 44         call    dword ptr
00423FD2   .8D4424 10       lea   eax, dword ptr     ; |
00423FD6   .50            push    eax                        ; |pRect
00423FD7   .E8 9423FEFF   call    <jmp.&user32.PtInRect>   ; \PtInRect
00423FDC   .85C0            test    eax, eax
00423FDE   .74 0B         je      short 00423FEB
00423FE0   .8BC6            mov   eax, esi
00423FE2   .66:BB ECFF      mov   bx, 0FFEC
00423FE6   .E8 25EEFDFF   call    00402E10                   ; 这里会间接去 call Image2Click()
00423FEB   >8BD7            mov   edx, edi
00423FED   .33C9            xor   ecx, ecx
00423FEF   .8BC6            mov   eax, esi
00423FF1   .E8 56FFFFFF   call    00423F4C
00423FF6   .83C4 18         add   esp, 18
00423FF9   .5F            pop   edi
00423FFA   .5E            pop   esi
00423FFB   .5B            pop   ebx
00423FFC   .C3            retn

上面是Image控件的事件处理框架代码,通过 call 00402E10 调用Click处理代码,进行注册验证,验证代码如下:
00453E94/.55            push    ebp
00453E95|.8BEC            mov   ebp, esp
00453E97|.6A 00         push    0
00453E99|.6A 00         push    0
00453E9B|.6A 00         push    0
00453E9D|.53            push    ebx
00453E9E|.56            push    esi
00453E9F|.57            push    edi
00453EA0|.8BF8            mov   edi, eax                  ;Sender 参数
00453EA2|.33C0            xor   eax, eax
00453EA4|.55            push    ebp                         ;指向本函数返回地址
00453EA5|.68 B63F4500   push    00453FB6                  ;SEH
00453EAA|.64:FF30         push    dword ptr fs:          ;Save old SEH
00453EAD|.64:8920         mov   dword ptr fs:, esp   ;Install new SEH
00453EB0|.8D55 FC         lea   edx, dword ptr       ;接收指向用户名缓冲区的指针
00453EB3|.8B87 C8020000   mov   eax, dword ptr     ;Owner 参数,控件对象实例
00453EB9|.E8 5AE9FCFF   call    00422818                  ;TControl.GetText(), 取得用户名,eax 为长度
00453EBE|.33DB            xor   ebx, ebx                  ;int i = 0
00453EC0|>8B45 FC         /mov   eax, dword ptr    ;eax ===> 用户名: "solly",最多19位,多出部分无效。
00453EC3|.0FB64418 FF   |movzx   eax, byte ptr ;name,从delphi字符串长度最高位字节起(0x00),即 name, movzx 无符号扩展
00453EC8|.03F0            |add   esi, eax                   ;esi 初始化值等于 0x02308CFC(Win10 64bit),sum += Ord(name)
00453ECA|.8D55 F8         |lea   edx, dword ptr    ;保存返回地址的指针
00453ECD|.8BC6            |mov   eax, esi                   ;只有esi变成负数才有可能转换成11字符的字符串
00453ECF|.E8 9437FBFF   |call    00407668                   ;IntToStr()
00453ED4|.8D55 F4         |lea   edx, dword ptr
00453ED7|.8B87 D0020000   |mov   eax, dword ptr
00453EDD|.E8 36E9FCFF   |call    00422818                   ;取得注册码假码"7878787878"
00453EE2|.8B45 F4         |mov   eax, dword ptr    ;eax ===> 输入的注册码
00453EE5|.8A00            |mov   al, byte ptr          ;sn
00453EE7|.8B55 F8         |mov   edx, dword ptr    ;edx ===> 计算的注册码
00453EEA|.3A02            |cmp   al, byte ptr          ;假码第一个字符与name求和字符串第一个字符比较 sn == IntToStr(sum)
00453EEC|.0F85 9F000000   |jnz   00453F91
00453EF2|.8B45 F4         |mov   eax, dword ptr    ;eax ===> 输入的注册码
00453EF5|.8A40 01         |mov   al, byte ptr        ;sn
00453EF8|.8B55 F8         |mov   edx, dword ptr    ;edx ===> 计算的注册码
00453EFB|.3A42 01         |cmp   al, byte ptr
00453EFE|.0F85 8D000000   |jnz   00453F91
00453F04|.8B45 F4         |mov   eax, dword ptr    ;eax ===> 输入的注册码
00453F07|.8A40 02         |mov   al, byte ptr        ;sn
00453F0A|.8B55 F8         |mov   edx, dword ptr    ;edx ===> 计算的注册码
00453F0D|.3A42 02         |cmp   al, byte ptr
00453F10|.75 7F         |jnz   short 00453F91
00453F12|.8B45 F4         |mov   eax, dword ptr
00453F15|.8A40 03         |mov   al, byte ptr        ;sn
00453F18|.8B55 F8         |mov   edx, dword ptr
00453F1B|.3A42 03         |cmp   al, byte ptr
00453F1E|.75 71         |jnz   short 00453F91
00453F20|.8B45 F4         |mov   eax, dword ptr
00453F23|.8A40 04         |mov   al, byte ptr        ;sn
00453F26|.8B55 F8         |mov   edx, dword ptr
00453F29|.3A42 04         |cmp   al, byte ptr
00453F2C|.75 63         |jnz   short 00453F91
00453F2E|.8B45 F4         |mov   eax, dword ptr
00453F31|.8A40 05         |mov   al, byte ptr        ;sn
00453F34|.8B55 F8         |mov   edx, dword ptr
00453F37|.3A42 05         |cmp   al, byte ptr
00453F3A|.75 55         |jnz   short 00453F91
00453F3C|.8B45 F4         |mov   eax, dword ptr
00453F3F|.8A40 06         |mov   al, byte ptr        ;sn
00453F42|.8B55 F8         |mov   edx, dword ptr
00453F45|.3A42 06         |cmp   al, byte ptr
00453F48|.75 47         |jnz   short 00453F91
00453F4A|.8B45 F4         |mov   eax, dword ptr
00453F4D|.8A40 07         |mov   al, byte ptr        ;sn
00453F50|.8B55 F8         |mov   edx, dword ptr
00453F53|.3A42 07         |cmp   al, byte ptr
00453F56|.75 39         |jnz   short 00453F91
00453F58|.8B45 F4         |mov   eax, dword ptr
00453F5B|.8A40 08         |mov   al, byte ptr        ;sn
00453F5E|.8B55 F8         |mov   edx, dword ptr
00453F61|.3A42 08         |cmp   al, byte ptr
00453F64|.75 2B         |jnz   short 00453F91
00453F66|.8B45 F4         |mov   eax, dword ptr
00453F69|.8A40 09         |mov   al, byte ptr        ;sn
00453F6C|.8B55 F8         |mov   edx, dword ptr
00453F6F|.3A42 09         |cmp   al, byte ptr
00453F72|.75 1D         |jnz   short 00453F91
00453F74|.8B45 F4         |mov   eax, dword ptr
00453F77|.8A40 0A         |mov   al, byte ptr        ;sn
00453F7A|.8B55 F8         |mov   edx, dword ptr
00453F7D|.3A42 0A         |cmp   al, byte ptr        ;总共11次字符比较,只有当 ESI 的值为负数(-1000000000以下)可以转换成11位字符串
00453F80|.75 0F         |jnz   short 00453F91
00453F82|.A1 EC654500   |mov   eax, dword ptr
00453F87|.8B00            |mov   eax, dword ptr
00453F89|.8B10            |mov   edx, dword ptr
00453F8B|.FF92 CC000000   |call    dword ptr          ;显示成功的消息
00453F91|>43            |inc   ebx
00453F92|.83FB 13         |cmp   ebx, 13
00453F95|.^ 0F85 25FFFFFF   \jnz   00453EC0
00453F9B|.33C0            xor   eax, eax
00453F9D|.5A            pop   edx
00453F9E|.59            pop   ecx
00453F9F|.59            pop   ecx
00453FA0|.64:8910         mov   dword ptr fs:, edx
00453FA3|.68 BD3F4500   push    00453FBD
00453FA8|>8D45 F4         lea   eax, dword ptr
00453FAB|.BA 03000000   mov   edx, 3
00453FB0|.E8 57F8FAFF   call    0040380C
00453FB5\.C3            retn
00453FB6   .^ E9 EDF2FAFF   jmp   004032A8
00453FBB   .^ EB EB         jmp   short 00453FA8
00453FBD   .5F            pop   edi
00453FBE   .5E            pop   esi
00453FBF   .5B            pop   ebx
00453FC0   .8BE5            mov   esp, ebp
00453FC2   .5D            pop   ebp
00453FC3   .C3            retn

序列号就是对用户名的所有字符的 ASCII 码值求累加和,并与esi中的值相加,再转换成一个10进制的字符串。
而Bug问题出在 0x00453EC8add esi, eax 这一行,这里 esi 没有在事件函数内初始化,而是直接引用的函数外传入的值(应该是上一层函数 Sender 对象的地址值),并且在不同的操作系统环境,有没有脱壳等不同情况下,其值不固定,所以这应该算是一个BUG,不然,无论怎么处理也得不到11位注册码来进行校验。根据上面的比较代码,注册码有11位,只有当 esi 为负数时(至少小于-1000000000)才可能通过 IntToStr() 函数转换成11位注册码。但是,地址值 0x80000000 ~ 0xFFFFFFFF 都是操作系统的地址空间,应用程序中不会产生 0x80000000 以上的变量地址值,所以,就算通过内存注册机方式,这个 crackme 基本上也没有可能通过其验证算法。

只有修改 esi 的初始值在 0x80000000 以上、0xC4653600以下时才可能生成11位的序列号,至于怎么改,就不讨论了,这里只指出这可能是一个 bug。

wapj152321 发表于 2019-7-15 08:08

谢谢楼主分享

hyhhj 发表于 2019-7-16 20:11

我怎么什么多看不懂啊,有没有基础点的教程啊,,我刚开始学{:1_907:}

qazwsx_c 发表于 2019-7-16 20:23


谢谢楼主分享

灵影 发表于 2019-7-17 10:32

学学思路,任重道远啊。自己一上手就蒙了

ABKing 发表于 2019-8-8 14:41

谢谢楼主分享

HackerWen 发表于 2019-8-15 10:08

楼主是把160个CrackMe都制服了啊

zch333333 发表于 2019-11-19 07:10

谢谢楼主分享

ghl32116 发表于 2019-11-20 00:38

多谢楼主教程 积累经验是一切的一切{:1_893:}
页: [1] 2
查看完整版本: 160 个 CrackMe 之 159 - Torn@do.2 脱壳修复、注册分析及注册机