solly 发表于 2019-7-21 19:31

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

本帖最后由 solly 于 2019-7-22 09:24 编辑

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 位系统下需要导入的:
Windows Registry Editor Version 5.00


"ProductID"="12345-12345678-1234-5678"
在 Win 32 位系统下,导入注册表信息为:Windows Registry Editor Version 5.00


"ProductID"="12345-12345678-1234-5678"
其键值部分可以改成自己想要的。长度大于1,最好小于32字符。

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


跟随 WndProc,来到其入口:

具体框架代码如下所示:
00403400   .8B4C24 08          mov   ecx, dword ptr
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                    ;Default case of switch 00403405
00403423   .8B5424 10          mov   edx, dword ptr
00403427   .8B7424 08          mov   esi, dword ptr
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。如下所示:
00403473   > \8B7424 08          mov   esi, dword ptr
00403477   .6A 00            push    0                                             ; /lParam = 0
00403479   .C680 C8EC4000 00   mov   byte ptr , 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。如下所示:
004034A5   > \817C24 10 77777777 cmp   dword ptr , 77777777                  ;Case 111 (WM_COMMAND) of switch 00403405
004034AD   .75 21            jnz   short 004034D0
004034AF   .8B7424 08          mov   esi, dword ptr
004034B3   .6A 00            push    0                                             ; /lParam = NULL
004034B5   .68 E0344000      push    004034E0                                    ; |DlgProc = Torn@do_.004034E0
004034BA   .A1 B0EA4000      mov   eax, dword ptr                      ; |
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,如下图所示:

具体代码如下:
004034E0   .8B4424 08          mov   eax, dword ptr
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,如下:

可以看到,最后有一段如下代码:
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)如下:
0040353E   > \8B4424 10          mov   eax, dword ptr                    ;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                    ;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                   ;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 , 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                   ; |
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                   ; |
004035AF   .68 E0364000      push    004036E0                                  ; |DlgProc = Torn@do_.004036E0
004035B4   .56               push    esi                                       ; |hOwner
004035B5   .A1 B0EA4000      mov   eax, dword ptr                    ; |
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                   ;Case 3ED of switch 00403547
004035D0   .6A 00            push    0                                       ; /lParam = NULL
004035D2   .68 F0354000      push    004035F0                                  ; |DlgProc = Torn@do_.004035F0
004035D7   .A1 B0EA4000      mov   eax, dword ptr                    ; |
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


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

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

下面的进一步动态分析 CrackMe 的行为,首先看看注册表读写部分。
; 读取注册表 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                    ; USER32.wsprintfA,将 ProductId 存在
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:
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
0040333D    80C1 05                add   cl, 5                                 ; 加密 ProductID 字符串,就是每个字符的 ASCII 码值加上5
00403340    8888 C7D14000          mov   byte ptr , cl             ; 在 存入加密后的 ProductID
00403346    B9 FFFFFFFF            mov   ecx, -1
0040334B    2BC0                   sub   eax, eax
0040334D    F2:AE                  repne   scas byte ptr es:
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便会断下来,代码动态分析如下:
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 开始的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*100 + file_content)),大于或等于 1999, 并且(file_content*100 + file_content)> 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 <= 0x17, file_content <= 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~file_content 为字符串:"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 开始为 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 的值
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 的值
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比较
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 的值
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 的值
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 == 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 为 '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       ; 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
00401D1C    50                     push    eax
00401D1D    E8 6EF5FFFF            call    00401290                      ; 设置注册失败的信息
00401D22    83C4 04                  add   esp, 4
00401D25    C3                     retn
总共有10多次验证,我们把注册文件当作一个字符数组来説明,file_content[] 表示这个校验文件的内容,下标为文件内偏移量。
(1)以下等式全部要成立(call 00401E90)
file_content == 0x06
file_content == 0x0A
file_content == 0x15
file_content == 0x07
file_content == 0x13
file_content == 0x10
file_content == 0x0A
file_content == 0x72
file_content == 0x0C
file_content == 0x00

(2)以下全部相等(call 00401F80)
file_content == 0x07
file_content == 0x20
file_content == 0x34
file_content == 0x3E
file_content == 0x09
file_content == 0x07
file_content == 0x0A

(3) (call 00402130)
file_content == 0x08
file_content == 0x00
file_content == 0x00
file_content == 0x05

(4) (call 00402260)
file_content == 0x08
file_content == 0x04
file_content == 0x00
file_content == 0x12
file_content == 0x0A
file_content == 0x12
file_content == 0x03
file_content == 0x12
file_content == 0x02
file_content == 0x2C
file_content == 0x43
file_content == 0x08
file_content == 0x02
file_content == 0x2A
file_content == 0x0A
file_content == 0x44
file_content == 0x54

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

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

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

CrackMe 这段文字保存在CrackMe的以下内存区域:
0040D10053 55 50 50 4F 52 54 20 54 48 45 20 53 4F 46 54SUPPORT THE SOFT
0040D11057 41 52 45 20 41 55 54 48 4F 52 53 20 42 59 20WARE AUTHORS BY
0040D12042 55 59 49 4E 47 20 54 48 45 20 50 52 4F 47 52BUYING THE PROGR
0040D13041 4D 53 20 49 46 20 59 4F 55 20 55 53 45 20 54AMS IF YOU USE T
0040D14048 45 4D 20 41 46 54 45 52 20 43 52 41 43 4B 49HEM AFTER CRACKI
0040D1504E 47 20 54 48 45 4D 21                        NG THEM!

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

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

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

(11~14) 与 10类似,都是校验和的验证。见附件中的分析。

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

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

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

如果全校验成功,则会调用 call 004012E0,设置注册成功状态,如下代码:
004012E0    56                     push    esi
004012E1    6A 01                  push    1                              ; 将 "Request" 按钮设置为可用状态
004012E3    8B7424 0C                mov   esi, dword ptr          ; hDlg
004012E7    68 EC030000            push    3EC                            ; "Request" ControlID
004012EC    56                     push    esi
004012ED    FF15 A0124100            call    dword ptr              ; USER32.GetDlgItem
004012F3    50                     push    eax
004012F4    FF15 9C124100            call    dword ptr              ; USER32.EnableWindow
004012FA    66:C705 84EA4000 6300    mov   word ptr , 63          ; 设置注册成功标志
00401303    A1 B4D04000            mov   eax, dword ptr       ; 校验次数 0x0F
00401308    8B15 B0EA4000            mov   edx, dword ptr       ; 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              ; 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              ; USER32.SetClassLongA
00401327    6A 00                  push    0
00401329    8B0D B0EA4000            mov   ecx, dword ptr       ; 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              ; call USER32.DialogBoxParamA(),显示成功注册对话框
0040133E    5E                     pop   esi
0040133F    C3                     retn


其中 为校验次数,如果全部成功,则 == 0x0F,如果强行暴破跳过前面的校验,这个值就不正确,因为这个值是在前面10多个校验函数中修改的。
并且在 0x004012FA 处设置 的值为 0x63,表示注册校验成功!!!
分析就到这里,下面是注册机源码,使用 Dev-C++ 调试通过:
#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;/// 操作系统ID,Windows9x系列才有,Windows7以后无此注册表键值

char regFile;

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位系统

"ProductID"="12345-12345678-1234-5678"

Windows 64位系统

"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\n\"ProductID\"=\"12345-12345678-1234-5678\"\n";
      char registry_x64[] = "Windows Registry Editor Version 5.00\n\n\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 开始的null结尾的字符串长度大于3字节, 小于 31 字节
/// 字符串内容不限,只要长度大于3字节, 小于 31 字节即可。
    char * p1 = & regFile;
    strcpy(p1, ""); /// 注意:长度不能大于31个字符,否则会栈缓冲溢出,覆盖函数返回地址。

/// call    00401E90      ; 判断 注册文件 前10字节必须为 06 0A 15 07 13 10 0A 72 0C 00
    regFile = 0x06;
    regFile = 0x0A;
    regFile = 0x15;
    regFile = 0x07;
    regFile = 0x13;
    regFile = 0x10;
    regFile = 0x0A;
    regFile = 0x72;
    regFile = 0x0C;
    regFile = 0x00;

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

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

/// call    00402260      ; 注册文件第22~38字节判断:08 04 00 12 0A 12 03 12 02 2C 43 08 02 2A 0A 44 54
    regFile = 0x08;
    regFile = 0x04;
    regFile = 0x00;
    regFile = 0x12;
    regFile = 0x0A;
    regFile = 0x12;
    regFile = 0x03;
    regFile = 0x12;
    regFile = 0x02;
    regFile = 0x2C;
    regFile = 0x43;
    regFile = 0x08;
    regFile = 0x02;
    regFile = 0x2A;
    regFile = 0x0A;
    regFile = 0x44;
    regFile = 0x54;

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

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

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

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

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

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

      checkValue5 = sum5;
      regFile = sum5;

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

/// call    00403190       ; 检查注册文件的最后一个字符 file_content 为 'R', 即 0x52
    regFile = '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 调用即可避免报错并能完成脱壳。


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

u223110 发表于 2019-7-22 08:37

感谢分享

troyli 发表于 2019-7-22 08:45

感谢分享 膜拜

nsdllwyb 发表于 2019-7-22 09:10

感谢分享,这才是技术贴

52FYKX 发表于 2019-7-22 18:00

感谢分享,这才是技术贴

天空藍 发表于 2019-7-22 22:16

1.为什么oep会有那些代码?是一种混淆吗?
2.为什么使用分析代码可以揭穿?

solly 发表于 2019-7-22 22:24

天空藍 发表于 2019-7-22 22:16
1.为什么oep会有那些代码?是一种混淆吗?
2.为什么使用分析代码可以揭穿?

1、不是,因为OD加载crackme后,壳代码运行前那里会是数据节属性,壳代码解密并写入代码后再改成代码节属性(大概就是加个可执行之类的),但 OD 并没有刷新,还是认为它为数据,所以只要再“分析代码”一下就刷新了。
2、见第1点。

天空藍 发表于 2019-7-24 00:22

solly 发表于 2019-7-22 22:24
1、不是,因为OD加载crackme后,壳代码运行前那里会是数据节属性,壳代码解密并写入代码后再改成代码节属 ...

原来是有关于WINPE的知识,哈哈,这本书我还没开始嗑,此书内容超丰富,一看到那么多字就懒了XD。等我哪时候心血来潮再来埋首研读。感谢解答,虽然听解释之后还是很懵XD

32110 发表于 2019-7-27 16:23

受教了,感谢分享

JACKFANS 发表于 2019-7-27 16:34


感谢分享
页: [1]
查看完整版本: 160 个 CrackMe 之 160 Torn@do.3 注册分析和注册机生成注册文件