160 个 CrackMe 之 160 Torn@do.3 注册分析和注册机生成注册文件

本帖最后由 solly 于 2019-7-21 22:25 编辑

160  个 CrackMe 之 160 Torn@do.3 是一个文件验证的 CrackMe。
先看看文件信息,如下图:



显示有未知的壳保护,再按 "Scan /t"看看:



这下显示 ASPack v1.08.01 的壳,节的情况如下:



最后一节应该就是壳代码。


不管这些,先用 OD 载入 CrackMe,EP 代码如下图所示:



需要跳转到 0x00426000,应该就跳转去最后一节的代码。跳转过去后,如下图所示:



看起来可以用 "ESP"定律脱壳。不过应该也可以在 0x0042608E 处直接 F4 脱壳,脱壳后,来到 OEP (0x00405500):



我这里代码显示不正确,使用“分析”==>"分析代码"功能就可恢复显示,如上图菜单。恢复后如下图所示:



直接用 OD 脱壳,默认设置即可,直接保存脱壳文件。


我们运行一下脱壳后的 CrackMe,不能正常启动,弹出一个错误:



看来还要修复 IAT 才可以。以“管理员身份运行” ImportREC 1.6,并选择CrackMe 进程,取得所有Imports,如下图所示:



可以看到,有5个NO,我们选中前4个显示 NO 的 Thunk,并删除:



删除后,如下图,只有一个 NO 了:



我们点击右边的 Show Invalid 按钮,找到无效的导入项,如下图:



找到开始运行出错的那一个导入项,双击这一项“NtdllDefWindowProc_A”,弹出编辑import对话框:



先将 Module 下拉列表中改成 user32.dll,如下图所示,在下拉列表中选择即可:



选择好后,下面的 Function 列表就是显示的 user32.dll 的导出函数了:



我们在这个 Function 列表中,找到 DefWindowProcA 并选中,如下图所示:



再点“OK”,就会将导入函数改成我们刚才所选的函数:



然后就可以 "Fix Dump" 了,选择刚才 Dump 出来的 CrackMe 程序:



点“打开”就可以了,显示修复成功!如下图所示:



再次运行修复后的 CrackMe,可以正常运行了,如下图所示,显示了其主界面:





关闭 CrackMe,重新用 OD 载入,直接来到了 OEP:


下面进行注册验证的分析。


先做静态分析,找到验证位置。从OEP开始向下找,我们可以找到 WinMain() (00405638 call 00403270)入口:



跟随进入 WinMain() (入口在 0x00403270)函数,在 WinMain() 函数中有一段代码中读取注册表的,如下图所示:



读取 ("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion")下的 ProductId 的值,这个值在 Windows 9x 系列下是有的,但在新版本的 Windows 下已没有了,需要导入如下注册表信息,以下是 Win 64 位系统下需要导入的:
[HTML] 纯文本查看 复制代码

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"


Win 32 位系统下,导入注册表信息为:[HTML] 纯文本查看 复制代码

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"

其键值部分可以改成自己想要的。长度大于1,最好小于32字符。

处理好注册表后,继续向后分析,在处理完注册表信息后,CrackMe 创建了一个标准 Windows API 窗口程序,因此,我们来找其 Windows 窗口消息回调函数 WndProc,再次回到 WinMain() 入口(0x00403270),在其后面就可看到 WndProc 函数(0x00403400),如下图第4行:

跟随 WndProc,来到其入口:



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

00403400   .  8B4C24 08          mov     ecx, dword ptr [esp+8]
00403404   .  56                 push    esi
00403405   .  83F9 01            cmp     ecx, 1                                    ;  WM_CREATE; Switch (cases 1..111)
00403408   .  74 2F              je      short 00403439
0040340A   .  83F9 02            cmp     ecx, 2                                    ;  WM_DESTROY
0040340D   .  0F84 84000000      je      00403497
00403413   .  81F9 11010000      cmp     ecx, 111                                  ;  WM_COMMAND
00403419   .  0F84 86000000      je      004034A5
0040341F   .  8B4424 14          mov     eax, dword ptr [esp+14]                   ;  Default case of switch 00403405
00403423   .  8B5424 10          mov     edx, dword ptr [esp+10]
00403427   .  8B7424 08          mov     esi, dword ptr [esp+8]
0040342B   .  50                 push    eax                                       ; /lParam
0040342C   .  52                 push    edx                                       ; |wParam
0040342D   .  51                 push    ecx                                       ; |Message
0040342E   .  56                 push    esi                                       ; |hWnd
0040342F   .  FF15 D4124100      call    dword ptr [<&user32.DefWindowProcA>]      ; \DefWindowProcA
00403435   .  5E                 pop     esi
00403436   .  C2 1000            retn    10

先看看初始化的 WM_CREATE 消息处理,跟随 je 0x00403439,如下:



可以看到,最后一段代码向自己 Post 了一条自定义的 WM_COMMAND 消息,其wParam == 0x77777777。如下所示:
[Asm] 纯文本查看 复制代码

00403473   > \8B7424 08          mov     esi, dword ptr [esp+8]
00403477   .  6A 00              push    0                                             ; /lParam = 0
00403479   .  C680 C8EC4000 00   mov     byte ptr [eax+40ECC8], 0                      ; |
00403480   .  68 77777777        push    77777777                                      ; |wParam = 77777777
00403485   .  68 11010000        push    111                                           ; |Message = WM_COMMAND
0040348A   .  56                 push    esi                                           ; |hWnd
0040348B   .  FF15 D8124100      call    dword ptr [<&user32.PostMessageA>]            ; \PostMessageA
00403491   .  33C0               xor     eax, eax

既然是 WM_COMMAND 消息,我们回到 WndProc,跟随 WM_COMMAND 消息处理的 je 0x004034A5,如下:



代码很简单,只要判断出 wParam == 0x77777777,就创建一个对话框,并显示,这个就主界面对话框,并且,当对话框关闭后,就调用 DestroyWindow() 退出 CrackMe。如下所示:
[Asm] 纯文本查看 复制代码

004034A5   > \817C24 10 77777777 cmp     dword ptr [esp+10], 77777777                  ;  Case 111 (WM_COMMAND) of switch 00403405
004034AD   .  75 21              jnz     short 004034D0
004034AF   .  8B7424 08          mov     esi, dword ptr [esp+8]
004034B3   .  6A 00              push    0                                             ; /lParam = NULL
004034B5   .  68 E0344000        push    004034E0                                      ; |DlgProc = [url=mailto:Torn@do_.004034E0]Torn@do_.004034E0[/url]
004034BA   .  A1 B0EA4000        mov     eax, dword ptr [40EAB0]                       ; |
004034BF   .  56                 push    esi                                           ; |hOwner
004034C0   .  6A 65              push    65                                            ; |pTemplate = 65
004034C2   .  50                 push    eax                                           ; |hInst => NULL
004034C3   .  FF15 94124100      call    dword ptr [<&user32.DialogBoxParamA>]         ; \DialogBoxParamA
004034C9   .  56                 push    esi                                           ; /hWnd
004034CA   .  FF15 E0124100      call    dword ptr [<&user32.DestroyWindow>]           ; \DestroyWindow
004034D0   >  33C0               xor     eax, eax
004034D2   .  5E                 pop     esi
004034D3   .  C2 1000            retn    10

从上面的图中可以到,DlgProc 为 0x004034E0,我们跟随立即数,来到 DlgProc,如下图所示:



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

004034E0   .  8B4424 08          mov     eax, dword ptr [esp+8]
004034E4   .  56                 push    esi
004034E5   .  83F8 02            cmp     eax, 2                                        ;  WM_DESTROY; Switch (cases 2..111)
004034E8   .  74 70              je      short 0040355A
004034EA   .  3D 10010000        cmp     eax, 110                                      ;  WM_INITDIALOG
004034EF   .  74 0D              je      short 004034FE
004034F1   .  3D 11010000        cmp     eax, 111                                      ;  WM_COMMAND
004034F6   .  74 46              je      short 0040353E
004034F8   .  33C0               xor     eax, eax                                      ;  Default case of switch 004034E5
004034FA   .  5E                 pop     esi
004034FB   .  C2 1000            retn    10

先看看 WM_INITDIALOG 消息处理过程,跟随 je 004034FE,如下:



可以看到,最后有一段如下代码:
[Asm] 纯文本查看 复制代码

0040351A   .  6A 00              push    0                                         ; /Enable = FALSE, 高置成禁用状态
0040351C   .  68 EC030000        push    3EC                                       ; |/ControlID = 3EC (1004.)
00403521   .  56                 push    esi                                       ; ||hWnd
00403522   .  FF15 A0124100      call    dword ptr [<&user32.GetDlgItem>]          ; |\GetDlgItem
00403528   .  50                 push    eax                                       ; |hWnd
00403529   .  FF15 9C124100      call    dword ptr [<&user32.EnableWindow>]        ; \EnableWindow
0040352F   .  56                 push    esi
00403530   .  E8 5BE6FFFF        call    00401B90                                  ;  注册验证
00403535   .  83C4 04            add     esp, 4
00403538   .  33C0               xor     eax, eax

调用 EnableWindow() 将 “Request” 按钮设置成禁用状态了。

然后,调用 call 0x00401B90 函数,这个函数就是注册文件校验函数,后面具体分析,先跟随进去看看:



我们在函数开始点下一个断点。等下动态分析时,便会断下来。

我们再回到 DlgProc,找到 WM_COMMAND 消息处理代码(0x0040353E)如下:
[Asm] 纯文本查看 复制代码

0040353E   > \8B4424 10          mov     eax, dword ptr [esp+10]                   ;  Case 111 (WM_COMMAND) of switch 004034E5
00403542   .  25 FFFF0000        and     eax, 0FFFF
00403547   .  83F8 02            cmp     eax, 2                                    ;  Switch (cases 2..3ED)
0040354A   .  74 20              je      short 0040356C
0040354C   .  3D EC030000        cmp     eax, 3EC
00403551   .  74 2F              je      short 00403582
00403553   .  3D ED030000        cmp     eax, 3ED
00403558   .  74 72              je      short 004035CC
0040355A   >  A1 48EA4000        mov     eax, dword ptr [40EA48]                   ;  Default case of switch 00403547
0040355F   .  50                 push    eax                                       ; /hObject => NULL
00403560   .  FF15 EC114100      call    dword ptr [<&gdi32.DeleteObject>]         ; \DeleteObject
00403566   .  33C0               xor     eax, eax
00403568   .  5E                 pop     esi
00403569   .  C2 1000            retn    10
0040356C   >  8B7424 08          mov     esi, dword ptr [esp+8]                    ;  Case 2 of switch 00403547
00403570   .  6A 00              push    0                                         ; /Result = 0
00403572   .  56                 push    esi                                       ; |hWnd
00403573   .  FF15 C4124100      call    dword ptr [<&user32.EndDialog>]           ; \EndDialog
00403579   .  B8 01000000        mov     eax, 1
0040357E   .  5E                 pop     esi
0040357F   .  C2 1000            retn    10
00403582   >  66:833D 84EA4000 6>cmp     word ptr [40EA84], 63                     ;  Case 3EC of switch 00403547
0040358A   .  6A 00              push    0                                         ; /Enable = FALSE
0040358C   .  74 1D              je      short 004035AB                            ; |
0040358E   .  8B7424 0C          mov     esi, dword ptr [esp+C]                    ; |
00403592   .  68 EC030000        push    3EC                                       ; |/ControlID = 3EC (1004.)
00403597   .  56                 push    esi                                       ; ||hWnd
00403598   .  FF15 A0124100      call    dword ptr [<&user32.GetDlgItem>]          ; |\GetDlgItem
0040359E   .  50                 push    eax                                       ; |hWnd
0040359F   .  FF15 9C124100      call    dword ptr [<&user32.EnableWindow>]        ; \EnableWindow
004035A5   .  33C0               xor     eax, eax
004035A7   .  5E                 pop     esi
004035A8   .  C2 1000            retn    10
004035AB   >  8B7424 0C          mov     esi, dword ptr [esp+C]                    ; |
004035AF   .  68 E0364000        push    004036E0                                  ; |DlgProc = [url=mailto:Torn@do_.004036E0]Torn@do_.004036E0[/url]
004035B4   .  56                 push    esi                                       ; |hOwner
004035B5   .  A1 B0EA4000        mov     eax, dword ptr [40EAB0]                   ; |
004035BA   .  6A 68              push    68                                        ; |pTemplate = 68
004035BC   .  50                 push    eax                                       ; |hInst => NULL
004035BD   .  FF15 94124100      call    dword ptr [<&user32.DialogBoxParamA>]     ; \DialogBoxParamA
004035C3   .  B8 01000000        mov     eax, 1
004035C8   .  5E                 pop     esi
004035C9   .  C2 1000            retn    10
004035CC   >  8B7424 08          mov     esi, dword ptr [esp+8]                    ;  Case 3ED of switch 00403547
004035D0   .  6A 00              push    0                                         ; /lParam = NULL
004035D2   .  68 F0354000        push    004035F0                                  ; |DlgProc = [url=mailto:Torn@do_.004035F0]Torn@do_.004035F0[/url]
004035D7   .  A1 B0EA4000        mov     eax, dword ptr [40EAB0]                   ; |
004035DC   .  56                 push    esi                                       ; |hOwner
004035DD   .  6A 67              push    67                                        ; |pTemplate = 67
004035DF   .  50                 push    eax                                       ; |hInst => NULL
004035E0   .  FF15 94124100      call    dword ptr [<&user32.DialogBoxParamA>]     ; \DialogBoxParamA
004035E6   .  B8 01000000        mov     eax, 1
004035EB   .  5E                 pop     esi
004035EC   .  C2 1000            retn    10

上面代码有一关键判断,如下所示:
[Asm] 纯文本查看 复制代码

00403582   > \66:833D 84EA4000 6>cmp     word ptr [40EA84], 63                     ;  "Reqest" Handler,[0x0040EA84] == 0x63 表示注册成功; Case 3EC of switch 00403547
0040358A   .  6A 00              push    0                                         ; /Enable = FALSE
0040358C   .  74 1D              je      short 004035AB                            ; |如果 [0x0040EA84] == 0x63 则去显示成功对话框,不相等则将 Request 设为禁用状态

可见注册成功的标志就是 [0x0040EA84] == 0x63。

下面的进一步动态分析 CrackMe 的行为,首先看看注册表读写部分。
[Asm] 纯文本查看 复制代码

; 读取注册表 ProductId 键值
;
004032E8    68 84D74000            push    0040D784                             ; ASCII "ProductId"
004032ED    68 58D74000            push    0040D758                             ; ASCII "SOFTWARE\Microsoft\Windows\CurrentVersion"
004032F2    68 02000080            push    80000002
004032F7    E8 A4E6FFFF            call    004019A0                             ; 读取键值,eax 为返回地址
004032FC    83C4 0C                add     esp, 0C
004032FF    BF 60D14000            mov     edi, 0040D160                        ; 保存 ProductId 的缓冲区
00403304    50                     push    eax                                  ; str
00403305    68 28D74000            push    0040D728                             ; ASCII "%s"
0040330A    68 60D14000            push    0040D160                             ; buffer
0040330F    FF15 B8124100          call    dword ptr [4112B8]                   ; USER32.wsprintfA,将 ProductId 存在 [0x0040D160]
00403315    66:BA 0100             mov     dx, 1
00403319    83C4 0C                add     esp, 0C
0040331C    B9 FFFFFFFF            mov     ecx, -1
00403321    2BC0                   sub     eax, eax
00403323    F2:AE                  repne   scas byte ptr es:[edi]
00403325    F7D1                   not     ecx
00403327    49                     dec     ecx                                   ; 取得 ProductId 的长度
00403328    83F9 01                cmp     ecx, 1
0040332B    72 2C                  jb      short 00403359                        ; 小于1字节表示出错
0040332D    0FBFC2                 movsx   eax, dx
00403330    66:42                  inc     dx
00403332    BF 60D14000            mov     edi, 0040D160                         ; ProductID 字符串
00403337    8A88 5FD14000          mov     cl, byte ptr [eax+40D15F]
0040333D    80C1 05                add     cl, 5                                 ; 加密 ProductID 字符串,就是每个字符的 ASCII 码值加上5
00403340    8888 C7D14000          mov     byte ptr [eax+40D1C7], cl             ; 在 [0x0040D1C6] 存入加密后的 ProductID
00403346    B9 FFFFFFFF            mov     ecx, -1
0040334B    2BC0                   sub     eax, eax
0040334D    F2:AE                  repne   scas byte ptr es:[edi]
0040334F    0FBFC2                 movsx   eax, dx
00403352    F7D1                   not     ecx
00403354    49                     dec     ecx                                   ;  加密的数据长度
00403355    3BC8                   cmp     ecx, eax
00403357  ^ 73 D4                  jnb     short 0040332D

从注册表读取 ProductId 后,会进行加密,加密后  ProductId 变成 "6789:26789:;<=267892:;<="。
原始的 ProductId 为:"12345-12345678-1234-5678"。

前面我们在文件校验函数下了断点,CrackMe 初始化时就会调用,OD便会断下来,代码动态分析如下:
[Asm] 纯文本查看 复制代码

00401B90    6A 00                    push    0
00401B92    E8 99FEFFFF              call    00401A30                    ; 无用的验证
00401B97    83C4 04                  add     esp, 4
00401B9A    6A 00                    push    0
00401B9C    E8 9FF7FFFF              call    00401340                    ; 无用的验证
00401BA1    83C4 04                  add     esp, 4
00401BA4    6A 00                    push    0
00401BA6    E8 85F8FFFF              call    00401430                    ; 无用的验证
00401BAB    83C4 04                  add     esp, 4
00401BAE    6A 00                    push    0
00401BB0    E8 ABF9FFFF              call    00401560                    ; 无用的验证
00401BB5    83C4 04                  add     esp, 4
00401BB8    6A 00                    push    0
00401BBA    E8 21FBFFFF              call    004016E0                    ; 无用的验证
00401BBF    83C4 04                  add     esp, 4
00401BC2    6A 00                    push    0
00401BC4    E8 87FBFFFF              call    00401750                    ; 无用的验证
00401BC9    83C4 04                  add     esp, 4
00401BCC    6A 00                    push    0
00401BCE    E8 DDFBFFFF              call    004017B0                    ; 无用的验证
00401BD3    83C4 04                  add     esp, 4
00401BD6    6A 00                    push    0
00401BD8    E8 63FCFFFF              call    00401840                    ; 无用的验证
00401BDD    83C4 04                  add     esp, 4
00401BE0    6A 00                    push    0
00401BE2    E8 09FDFFFF              call    004018F0                    ; 无用的验证
00401BE7    83C4 04                  add     esp, 4
00401BEA    6A 00                    push    0
00401BEC    E8 3F010000              call    00401D30                      ; 判断 CrackMe 目录中是否存在文件 "REGISTRATION.DAT"
00401BF1    83C4 04                  add     esp, 4
00401BF4    85C0                     test    eax, eax                      ; eax == 0 表示文件不存在
00401BF6    0F84 1C010000            je      00401D18
00401BFC    6A 00                    push    0
00401BFE    E8 BD010000              call    00401DC0                      ; 判断 CrackMe 目录中文件 "REGISTRATION.DAT" 的长度是否为 1024 字节
00401C03    83C4 04                  add     esp, 4
00401C06    85C0                     test    eax, eax                      ; eax == 0 表示文件长度不正确
00401C08    0F84 0A010000            je      00401D18
00401C0E    6A 00                    push    0
00401C10    E8 1BFEFFFF              call    00401A30                      ; 判断注册文件内容 file_content[153] 开始的null结尾的字符串长度大于3字节, 小于 32 字节
00401C15    83C4 04                  add     esp, 4
00401C18    85C0                     test    eax, eax
00401C1A    0F84 F8000000            je      00401D18
00401C20    6A 00                    push    0
00401C22    E8 69020000              call    00401E90                      ; 判断 注册文件 前10字节必须为 06 0A 15 07 13 10 0A 72 0C 00
00401C27    83C4 04                  add     esp, 4
00401C2A    85C0                     test    eax, eax
00401C2C    0F84 E6000000            je      00401D18
00401C32    6A 00                    push    0
00401C34    E8 47030000              call    00401F80                      ; 注册文件第11~17字节判断,必须等于串:07 20 34 3E 09 07 0A
00401C39    83C4 04                  add     esp, 4
00401C3C    85C0                     test    eax, eax
00401C3E    0F85 D4000000            jnz     00401D18
00401C44    6A 00                    push    0
00401C46    E8 E5040000              call    00402130                      ; 注册文件第18~21字节判断,必须等于串:08 00 00 05
00401C4B    83C4 04                  add     esp, 4
00401C4E    85C0                     test    eax, eax
00401C50    0F84 C2000000            je      00401D18
00401C56    6A 00                    push    0
00401C58    E8 03060000              call    00402260                      ; 注册文件第22~38字节判断:08 04 00 12 0A 12 03 12 02 2C 43 08 02 2A 0A 44 54
00401C5D    83C4 04                  add     esp, 4
00401C60    85C0                     test    eax, eax
00401C62    0F85 B0000000            jnz     00401D18
00401C68    6A 00                    push    0
00401C6A    E8 41090000              call    004025B0                      ; 注册文件第81~84字节处理,转换成时间(年份):(file_content[82]*100 + file_content[83])),大于或等于 1999, 并且(file_content[82]*100 + file_content[83])> 2019(系统当前年份)
00401C6F    83C4 04                  add     esp, 4
00401C72    85C0                     test    eax, eax
00401C74    0F84 9E000000            je      00401D18
00401C7A    6A 00                    push    0
00401C7C    E8 0F0B0000              call    00402790                      ; 检查 file_content[84] <= 0x17, file_content[85] <= 0x3B,时 : 分
00401C81    83C4 04                  add     esp, 4
00401C84    85C0                     test    eax, eax
00401C86    0F84 8C000000            je      00401D18
00401C8C    6A 00                    push    0
00401C8E    E8 0D0C0000              call    004028A0                      ; file_content[208]~file_content[296] 为字符串:"SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!"
00401C93    83C4 04                  add     esp, 4
00401C96    85C0                     test    eax, eax
00401C98    74 7E                    je      short 00401D18
00401C9A    6A 00                    push    0
00401C9C    E8 FF0C0000              call    004029A0                      ; file_content[336] 开始为 ProductID 的加密字符串(根据注册表中的字符串数据变量)
00401CA1    83C4 04                  add     esp, 4
00401CA4    85C0                     test    eax, eax
00401CA6    75 70                    jnz     short 00401D18
00401CA8    6A 00                    push    0
00401CAA    E8 110E0000              call    00402AC0                      ; 注册文件前100字节校验和检查,并且 ((checkSum xor 1999)/10) 小于 128,等于 file_content[944] 的值
00401CAF    83C4 04                  add     esp, 4
00401CB2    85C0                     test    eax, eax
00401CB4    74 62                    je      short 00401D18
00401CB6    6A 00                    push    0
00401CB8    E8 230F0000              call    00402BE0                      ; 同上一函数,并加上验证 file_content[945] 的值
00401CBD    83C4 04                  add     esp, 4
00401CC0    85C0                     test    eax, eax
00401CC2    74 54                    je      short 00401D18
00401CC4    6A 00                    push    0
00401CC6    E8 45100000              call    00402D10                      ; 功能同上一函数,校验和与 file_content[946]比较
00401CCB    83C4 04                  add     esp, 4
00401CCE    85C0                     test    eax, eax
00401CD0    74 46                    je      short 00401D18
00401CD2    6A 00                    push    0
00401CD4    E8 87110000              call    00402E60                      ; 对前面3个校验和进行校验,校验结果等于 file_content[947] 的值
00401CD9    83C4 04                  add     esp, 4
00401CDC    85C0                     test    eax, eax
00401CDE    74 38                    je      short 00401D18
00401CE0    6A 00                    push    0
00401CE2    E8 89120000              call    00402F70                      ; 注册文件前384字节校验和检查,校验结果等于 file_content[948] 的值
00401CE7    83C4 04                  add     esp, 4
00401CEA    85C0                     test    eax, eax
00401CEC    75 2A                    jnz     short 00401D18
00401CEE    6A 00                    push    0
00401CF0    E8 8B130000              call    00403080                      ; 对前面5次校验结果的和进行校验,file_content[949] == sqrt(sum(checkSum1+checkSum2+checkSum3+checkSum4+checkSum5))
00401CF5    83C4 04                  add     esp, 4
00401CF8    85C0                     test    eax, eax
00401CFA    74 1C                    je      short 00401D18
00401CFC    6A 00                    push    0
00401CFE    E8 8D140000              call    00403190                      ; 检查注册文件的最后一个字符 file_content[1023] 为 'R', 即 0x52
00401D03    83C4 04                  add     esp, 4
00401D06    85C0                     test    eax, eax
00401D08    74 0E                    je      short 00401D18
00401D0A    8B4424 04                mov     eax, dword ptr [esp+4]        ; hDlg
00401D0E    50                       push    eax
00401D0F    E8 CCF5FFFF              call    004012E0                      ; 设置注册成功的信息
00401D14    83C4 04                  add     esp, 4
00401D17    C3                       retn
00401D18    8B4424 04                mov     eax, dword ptr [esp+4]
00401D1C    50                       push    eax
00401D1D    E8 6EF5FFFF              call    00401290                      ; 设置注册失败的信息
00401D22    83C4 04                  add     esp, 4
00401D25    C3                       retn

总共有10多次验证,我们把注册文件当作一个字符数组来説明,file_content[] 表示这个校验文件的内容,下标为文件内偏移量。
[HTML] 纯文本查看 复制代码

(1)以下等式全部要成立(call 00401E90)
file_content[0] == 0x06
file_content[1] == 0x0A
file_content[2] == 0x15
file_content[3] == 0x07
file_content[4] == 0x13
file_content[5] == 0x10
file_content[6] == 0x0A
file_content[7] == 0x72
file_content[8] == 0x0C
file_content[9] == 0x00

(2)以下全部相等(call 00401F80)
file_content[10] == 0x07
file_content[11] == 0x20
file_content[12] == 0x34
file_content[13] == 0x3E
file_content[14] == 0x09
file_content[15] == 0x07
file_content[16] == 0x0A

(3) (call 00402130)
file_content[17] != 0x08
file_content[18] != 0x00
file_content[19] != 0x00
file_content[20] != 0x05

(4) (call 00402260)
file_content[23] == 0x4D
file_content[24] == 0x53
file_content[25] == 0x08
file_content[26] == 0x04
file_content[27] == 0x00
file_content[28] == 0x12
file_content[29] == 0x0A
file_content[30] == 0x12
file_content[31] == 0x03
file_content[32] == 0x12
file_content[33] == 0x02
file_content[34] == 0x2C
file_content[35] == 0x43
file_content[36] == 0x08
file_content[37] == 0x02
file_content[38] == 0x2A
file_content[39] == 0x0A
file_content[40] == 0x44
file_content[41] == 0x54

(5) (call 004025B0)
注册文件第81~84字节处理,转换成时间(年份),大于或等于 1999
     file_content[80] 无判断
    (file_content[82]*100 + file_content[83])> 2019(当前年份)
     file_content[81]>=0x04, 4, 月份

(6) (call 00402790)
检查 file_content[84] <= 0x17, file_content[85] <= 0x3B,0x17 == 23,0x3B == 59,这个校验就是 小于 “23时59分” 的意思

(7) (call 004028A0)
file_content[208]~file_content[296] 为字符串:"SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!"

CrackMe 这段文字保存在以下地址区域:
0040D100  53 55 50 50 4F 52 54 20 54 48 45 20 53 4F 46 54  SUPPORT THE SOFT
0040D110  57 41 52 45 20 41 55 54 48 4F 52 53 20 42 59 20  WARE AUTHORS BY
0040D120  42 55 59 49 4E 47 20 54 48 45 20 50 52 4F 47 52  BUYING THE PROGR
0040D130  41 4D 53 20 49 46 20 59 4F 55 20 55 53 45 20 54  AMS IF YOU USE T
0040D140  48 45 4D 20 41 46 54 45 52 20 43 52 41 43 4B 49  HEM AFTER CRACKI
0040D150  4E 47 20 54 48 45 4D 21                          NG THEM!

(8) (call 004029A0)
file_content[336] 开始为变形后的注册表值 ProductID (ASCII "6789:26789:;<=267892:;<=")

(9) (call 00402AC0)
注册文件前100字节校验和检查,并且 ((checkSum xor 1999)/10) 小于 128,等于 file_content[944] 的值

(10) (call 00402BE0)
同上一函数,并加上验证 file_content[945] 的值

第10次验证后面还有几个校验,与第10次有点类似,与10差不多,见上面代码部分。

最后一个验证(15)是检查文件最后一个字节是否为字母 ‘R'。

上面代码中每个验证函数都会执行文件名解码,并依次调用 sprintf(call 004053E0),fopen(call 004053C0),fread( call 00405240),fclose(call 004051D0)等函数,取得文件内容。然后才是校验代码。有些在校验代码中还会有 call 调用,都不用管,基本上都是无用调用

如果全校验成功,则会调用 call 004012E0,设置注册成功状态,如下代码:
[Asm] 纯文本查看 复制代码

004012E0    56                       push    esi
004012E1    6A 01                    push    1                              ; 将 "Request" 按钮设置为可用状态
004012E3    8B7424 0C                mov     esi, dword ptr [esp+C]         ; hDlg
004012E7    68 EC030000              push    3EC                            ; "Request" ControlID
004012EC    56                       push    esi
004012ED    FF15 A0124100            call    dword ptr [4112A0]             ; USER32.GetDlgItem
004012F3    50                       push    eax
004012F4    FF15 9C124100            call    dword ptr [41129C]             ; USER32.EnableWindow
004012FA    66:C705 84EA4000 6300    mov     word ptr [40EA84], 63          ; 设置注册成功标志
00401303    A1 B4D04000              mov     eax, dword ptr [40D0B4]        ; 校验次数 0x0F
00401308    8B15 B0EA4000            mov     edx, dword ptr [40EAB0]        ; hInstance
0040130E    66:05 C800               add     ax, 0C8
00401312    0FB7C8                   movzx   ecx, ax
00401315    51                       push    ecx                            ; ResourceID: 0x00D7
00401316    52                       push    edx                            ; hInstance
00401317    FF15 E8124100            call    dword ptr [4112E8]             ; USER32.LoadIconA
0040131D    50                       push    eax
0040131E    6A F2                    push    -0E                            ; GCL_HICON, Replaces a handle to the icon associated with the class.
00401320    56                       push    esi
00401321    FF15 EC124100            call    dword ptr [4112EC]             ; USER32.SetClassLongA
00401327    6A 00                    push    0
00401329    8B0D B0EA4000            mov     ecx, dword ptr [40EAB0]        ; hInstance
0040132F    68 E0364000              push    004036E0                       ; DlgProc
00401334    56                       push    esi
00401335    6A 68                    push    68
00401337    51                       push    ecx
00401338    FF15 94124100            call    dword ptr [411294]             ; call USER32.DialogBoxParamA(),显示成功注册对话框
0040133E    5E                       pop     esi
0040133F    C3                       retn

其中 [0x0040D0B4] 为校验次数,如果全部成功,则 [0x0040D0B4] == 0x0F,如果强行暴破跳过前面的校验,这个值就不正确,因为这个值是在前面10多个校验函数中修改的。
并且在 0x004012FA 处设置 [0x0040EA84] 的值为 0x63,表示注册校验成功!!!
分析就到这里,下面是注册机源码,使用 Dev-C++ 调试通过:
[Asm] 纯文本查看 复制代码

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <windows.h>

int checkValue1 = 0;  /// 0x0040D1A8
int checkValue2 = 0;  /// 0x0040D1AC
int checkValue3 = 0;  /// 0x0040D1B0
int checkValue4 = 0;  /// 0x0040D1B4
int checkValue5 = 0;  /// 0x0040D1B8

int checkCode1 = 0;  /// 0x0040EA94
int checkCode2 = 0;  /// 0X0040EABC

char ProductID[256];  /// 操作系统ID,Windows9x系列才有,Windows7以后无此注册表键值 

char regFile[1024];

typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;

BOOL IsWow64();
int makeRegistryFile();
int getProductID(char * defaultID);
int makeRegInfo();
int saveRegInfo();

int main(int argc, char** argv) {
        
        memset(ProductID, 0, 256);
        memset(regFile, 0, 1024);

/**
Windows 32位系统 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"

Windows 64位系统 
[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"
**/
        char productID[] = "12345-12345678-1234-5678"; /// 注册表键值 
        getProductID(productID);
        
        makeRegInfo();
        saveRegInfo();
        
        return 0;
}

int getProductID(char * defaultID) {
        /// "12345-12345678-1234-5678" ===> "6789:26789:;<=267892:;<="
        
        char key[]  = "ProductId";
        char subKey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion";
        HKEY h  = HKEY_LOCAL_MACHINE;  /// 0x80000002
        HKEY hKey = (HKEY)0xFFFFFFFF;
        DWORD keyType, cbData = 0, n = 0;
        DWORD s = RegOpenKeyExA(h, subKey, 0, KEY_READ, &hKey);
        if(s == ERROR_SUCCESS) {
                s = RegQueryValueExA(hKey, key, NULL, NULL, NULL, &cbData); /// 返回缓冲区大小
                if(s == ERROR_SUCCESS) {
                        if(cbData>255) {
                                cbData = 255; //// 防止溢出 
                        }
                        n = cbData;
                        s = RegQueryValueExA(hKey, key, NULL, &keyType, (LPBYTE)ProductID, &cbData);
                }        
        }
        RegCloseKey(hKey);
        if(n<=0) {
                strcpy(ProductID, defaultID);
                printf("ERROR: ProductID is NOT EXISTS in Registry.\n");
                makeRegistryFile();  //// 生成注册表文件 
        }
        
        return 0;
}

int makeRegistryFile() {
        char registry_x86[] = "Windows Registry Editor Version 5.00\n\n[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion]\n\"ProductID\"=\"12345-12345678-1234-5678\"\n";
        char registry_x64[] = "Windows Registry Editor Version 5.00\n\n[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion]\n\"ProductID\"=\"12345-12345678-1234-5678\"\n";
        char * reg = NULL;
        if(IsWow64()) {
                reg = registry_x64; //// 64位系统 
        } else {
                reg = registry_x86; //// 32位系统 
        }
        FILE * f = fopen("ProductID.reg", "w");
        int n = fwrite(reg, strlen(reg), 1, f);
        fflush(f);
        fclose(f);
        
        if(n==1) {
                printf("Make registry file successed, please import registry file: ProductID.reg\n");
        }
        
        return 0;
}

BOOL IsWow64() {
        BOOL bIsWow64 = FALSE;
        //IsWow64Process is not available on all supported versions of Windows.
        //Use GetModuleHandle to get a handle to the DLL that contains the function
        //and GetProcAddress to get a pointer to the function if available.
        fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
        GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
        if(NULL != fnIsWow64Process)
        {
                if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
                {
                        //handle error
                        printf("get os bit error\n"); /// 出错了 
                }
        }
        return bIsWow64;
}

int makeRegInfo() {
/// call    00401A30      ; 判断注册文件内容 file_content[153] 开始的null结尾的字符串长度大于3字节, 小于 31 字节
/// 字符串内容不限,只要长度大于3字节, 小于 31 字节即可。 
    char * p1 = &reg;File[153];
    strcpy(p1, "[Cracked by solly, 2019-07-11.]"); /// 注意:长度不能大于31个字符,否则会栈缓冲溢出,覆盖函数返回地址。 

/// call    00401E90      ; 判断 注册文件 前10字节必须为 06 0A 15 07 13 10 0A 72 0C 00
    regFile[0] = 0x06;
    regFile[1] = 0x0A;
    regFile[2] = 0x15;
    regFile[3] = 0x07;
    regFile[4] = 0x13;
    regFile[5] = 0x10;
    regFile[6] = 0x0A;
    regFile[7] = 0x72;
    regFile[8] = 0x0C;
    regFile[9] = 0x00;

/// call    00401F80      ; 注册文件第11~17字节判断,必须等于串:07 20 34 3E 09 07 0A
    regFile[10] = 0x07;
    regFile[11] = 0x20;
    regFile[12] = 0x34;
    regFile[13] = 0x3E;
    regFile[14] = 0x09;
    regFile[15] = 0x07;
    regFile[16] = 0x0A;

/// call    00402130      ; 注册文件第18~21字节判断,必须等于串:08 00 00 05
    regFile[17] = 0x08;
    regFile[18] = 0x00;
    regFile[19] = 0x00;
    regFile[20] = 0x05;

/// call    00402260      ; 注册文件第22~38字节判断:08 04 00 12 0A 12 03 12 02 2C 43 08 02 2A 0A 44 54
    regFile[21] = 0x08;
    regFile[22] = 0x04;
    regFile[23] = 0x00;
    regFile[24] = 0x12;
    regFile[25] = 0x0A;
    regFile[26] = 0x12;
    regFile[27] = 0x03;
    regFile[28] = 0x12;
    regFile[29] = 0x02;
    regFile[30] = 0x2C;
    regFile[31] = 0x43;
    regFile[32] = 0x08;
    regFile[33] = 0x02;
    regFile[34] = 0x2A;
    regFile[35] = 0x0A;
    regFile[36] = 0x44;
    regFile[37] = 0x54;

/// call    004025B0      ; 注册文件第81~84字节处理,转换成时间的年份(file_content[82]*100 + file_content[83])),大于或等于 1999, 
/// 实际上 (file_content[82]*100 + file_content[83]) > 2019(系统当前年份) 才可以。 
/// 以下填充的是 9999-12-31
    regFile[80] = 0x1F;  // 31   /// 日 
    regFile[81] = 0x0C;  // 12   /// 月 
    regFile[82] = 0x63;  // 99   /// 年 
    regFile[83] = 0x63;  // 99   /// 年 

/// call    00402790      ; 检查 file_content[84] <= 0x17, file_content[85] <= 0x3B
    regFile[84] = 0x17;  // 23   /// 时 
    regFile[85] = 0x3B;  // 59   /// 分 
    //regFile[86] = 0x3B;  // 59   /// 秒,可不填充,没有用到 
    regFile[86] = 0x1E;  // 30   /// 秒,不能填充 0x3B,因为刚好会导致后面的检查码为 0x1A,引起 fread()函数出错,读取文件不完整 

/// call    004028A0      ; file_content[208]~file_content[296] 为固定字符串:
/// "SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!"
    char * p2 = &reg;File[208];
    strcpy(p2, "SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!");

/// call    004029A0      ; file_content[336] 开始为 ProductID 的加密字符串
    int n = strlen(ProductID); /// 注册表中的产品ID(明文) 
    for(int i=0; i<n; i++) {
            regFile[336 + i] = ProductID[i] + 5;  /// 保存密文,加密就是 ASCII 码 + 0x05 
        }
        
/// call    00402AC0      ; 注册文件前100字节校验和检查,
/// 并且 ((checkSum xor 1999)/10) 小于 128,等于 file_content[944] 的值
        int sum1 = 0;
        for(int i=0; i<100; i++) {    //// 0x64
                sum1 += (int)regFile[i];
        }
        int check1 = checkCode1 * checkCode1;
        int check2 = (checkCode2 >= 10) ? checkCode2 : 10;
        sum1 = ((sum1 ^ 1999) | check1) / check2;
        
        int j = 0x30;
        while(sum1 > 127) {
                sum1 -= 127;
                regFile[j++] = -127; //// 校正校验值 < 128 
        }
        

        checkValue1 = sum1;
        regFile[944] = sum1;        
        
/// call    00402BE0      ; 同上一函数,并加上验证 file_content[945] 的值
    int sum2 = sum1 * sum1 / 0x54;
        checkValue2 = sum2;
        regFile[945] = sum2;        
    
/// call    00402D10      ; 功能同上一函数,校验和与file_content[946]比较
    int sum3 = 0;
    for(int i=0; i<384; i++) {   /// 0x180
            sum3 += (int)regFile[i];
        }
//        int k = 0x130;
//        while(sum3 > 127) {
//                sum3 -= 127;
//                regFile[k++] = -127; //// 校正校验值 < 128 
//        }
        sum3 = (sum3 ^ (sum2 * sum1)) / 534; /// 0x216;
    
        checkValue3 = sum3;
        regFile[946] = sum3;        
    
///  call    00402E60      ; 对前面3个校验和进行校验,校验结果等于 file_content[947] 的值
    int sum4 = (checkValue1 + checkValue2 + checkValue3);
        sum4 *= (checkValue3 / checkValue2);
        sum4 *= (checkValue1 / checkValue2);
        sum4 /= 10;
        
        checkValue4 = sum4;
        regFile[947] = sum4;

/// call    00402F70       ; 注册文件前384字节校验和检查,校验结果等于 file_content[948] 的值
    int sum5 = 0;
    for (int i=0; i<384; i++)  {   /// 0x180
            sum5 += (int)regFile[i];
        }
        sum5 = (sum5 ^ 0xFF) / 500;  /// 0x01F4

        checkValue5 = sum5;
        regFile[948] = sum5;

/// call    00403080      ; 对前5次校验结果的和进行校验, 
/// file_content[949] == sqrt(sum(checkSum1+checkSum2+checkSum3+checkSum4+checkSum5))
    int sum6 = checkValue1 + checkValue2 + checkValue3 + checkValue4 + checkValue5;
    sum6 = (int)sqrt(sum6);
    
    regFile[949] = sum6;

/// call    00403190       ; 检查注册文件的最后一个字符 file_content[1023] 为 'R', 即 0x52
    regFile[1023] = 'R';   /// 0x52
}

int saveRegInfo() {
        FILE * f = fopen("REGISTRATION.DAT", "wb");
        size_t n = fwrite(regFile, 1024, 1, f);
        fflush(f);
        fclose(f);
        if(n == 1) {
                printf("save registration data file successed! \ncopy \"REGISTRATION.DAT\" to directory of CrackMe.\n");
        } else {
                printf("save registration data file failured!\n");
        }
}

运行注册机,生成注册文件“REGISTRATION.DAT”,并放在与 CrackMe 相同的目录中,启动 CrackMe,首先就会弹出注册成功的对话框,如下所示:



点“OK”后,进入主界面,如下图所示:



其"Request"按钮的状态不再是“禁用”状态了。

完毕!!!

附注:在Windows XP 下脱壳时,会报一个错误,如下:




脱壳代码中有一个 call 做了校验,脱壳时跳过这个 call 调用即可避免报错并能完成脱壳。


附件是前面校验函数调用的子函数分析,太长了,不贴出来了,当作附件下载。

Crk_Check.txt

2019-7-21 21:17 上传

点击文件名下载附件

下载积分: 吾爱币 -1 CB

114.65 KB, 下载次数: 1, 下载积分: 吾爱币 -1 CB

文件校验子函数

THE END
喜欢就支持以下吧
点赞0
分享
评论 抢沙发
  • 管埋员

    昵称

  • 取消
    昵称