solly 发表于 2019-5-29 15:13

160 个 CrackMe 之 126 - NZC.1 手动脱壳和注册算法追踪及注册机实现

本帖最后由 solly 于 2019-5-29 23:47 编辑

160 个 CrackMe 之 126 - NZC.1 手动脱壳和注册算法追踪及注册机实现
CrackMe集合中的第126个 NZC.1 在 Windows 10 下是无法直接运行的,但在OD中可以载入,表示其可以在Windows10下运行,只是运行时执行了非法的指令(或访问了无效的内存),一般这些指令都是由那些老掉牙的AntiDebug检查用的,主要是针对SoftICE检查,直接执行了 int 68,int 3 等指令。
首先,我们检查一下软件壳。

显示有未知的壳。节的情况如下:

最后有一个 .defiler 的节,Defiler有掩蔽, 遮蔽之意,看来这个节就是壳了。


因为壳是未知的,只有手动脱壳,下面进行手动脱过程的讲解,内容有点长,有点长,有点长,截图就有50多个。


首先用OD载入 NZC.1.exe,入口第1条指令就是一个跳转指令,如下图,而且代码看起来也很乱,还有无效指令,如下图:


先不要运行,按回车跟随,来到下一条指令,如下图:

看到第2条执行的指令码为”EB 02“,也就是一条 JMP 指令,但跳转位置是在另一指令中间,并且上图中有好几个 "EB 02" 指令码及显示无效的指令,这些都符合”花指令“特征,因此,我们先处理这些花指令,便于我们后面的分析。
按”-“号,回退到程序入口,如下图:

点击“右键”,选择菜单项“去除花指令”=>“ObSiDiUm”,也就是子菜单的第1项,执行完后,弹出对话框,告诉我们去除了多少花指令:

去除花指令后,代码界面一下子多出了好多的 “NOP” 指令,有效指令一下子少了很多,如下图:


因为默认情况,直接执行去除花指令的操作只处理 0x400个字节内的花指令,并没有扫描全部代码,我们手动拖动滚动条来到0x0040D400处,果然没有处理完毕,还有一些花指令:

再次在这里("0x0040D400“)右键菜单执行一次前面的操作,这次又去除了41 条花指令:

去除花指令的步骤先到此暂停,后面还有去除花指令的操作,不过现在无法执行,因为那些代码还是加密的,如下图所示,在没解密前也去不了花指令了:




现在我们开始跟踪CM的壳代码。


再次回到原点,如下图:

按几次F8,慢点按,按快了就可能要重新来一次了。
来到下图位置:

这里有一个常用的手法,就是 call 下一条代码(指令码为 E8 00000000),然后一个 pop 指令将返回地址取出来,这个就是用来取得指令区的当前执行指令的地址的,这里相当于取得0x0040D246这个地址,并存放到 EBP 中了,所以执行这样的 call 调用时,一定要按F7进入,因为这个call是不会返回来了的。
F7进入后,再按F8往下走,来到这里:


来到这个指令位置 0x0040D25B call 0x0040D100,不了解功能,只有按F7跟踪进去,在进去前,先在这个call的下一条指令上按F2下一个中断,防止不小心按了F9返回时没停下来(后面有些进入call的情况也可以这样处理),返回来后再禁用或取消这个断点。

进入后又是一个调用,老办法,再次按 F7 进入调用,仔细看,其实就是调用了后面10个字节后的指令。


这个调用功能很简单,就是寻找系统库 Kernel32.dll 文件在内存中位置,这个位置也就是该dll的 hInstance,后面的代码需要用到这个 hInstance。
因为 Windows 加载程序或dll一般都是按 0x10000 大小对齐内存的,所以只要找到一个库中的地址,这里是在栈中读取的用于返回KERNEL32.BaseThreadInitThunk 的地址,并去掉低16位,形成 0x75460000的对齐地址,以此为基础,寻找 Kernel32.dll在内存中的存放位置,并取得 hInstance。同时,这两次call调用是利用同一个 retn 指令返回去的。
这两层 call 调用代码很短,几个F8后就找到hInstance并返回了,再次F8往下走,来到第1个解码代码处:


这里是对数据区的字符串资源进行解码,定位到JNZ指令的下一条指令,按F4执行解码循环,解码结果如下:


解码出来的是一些API函数名和库名。后面要用到这些名字,取得API的的入口地址,下面是关键代码和数据变化情况:
解码数据1:
0040D29E    B9 66000000         mov   ecx, 66                                                      ; 开始数据解码,API函数库和函数名等字符串数据
0040D2A3    8D9D F8284000       lea   ebx, dword ptr                                     ; ebx == 0x0040D8CC, EBX ==>解码开始位置
0040D2A9    300B                xor   byte ptr , cl                                             ; CL == 0x66
0040D2AB    8003 40             add   byte ptr , 40
0040D2AE    43                  inc   ebx
0040D2AF    49                  dec   ecx
0040D2B0    85C9                test    ecx, ecx                                                       ; ecx=0x66,长度为0x66字节
0040D2B2^ 75 F5               jnz   short 0040D2A9

解码前:
0040D8CC61 40 50 73 50 4E 43 5E 7A 79 6E 7E 69 6A 98 48a@PsPNC^zyn~ij楬
0040D8DC7A 7A 64 76 7C 91 5C 60 6F 69 40 62 68 7B 69 75zzdv|慲oi@bh{iu
0040D8EC7F 44 84 4E 67 72 73 1E 19 18 3E 14 02 38 F8 32D凬grs>8?
0040D8FC0E 1C 00 23 00 1E 13 0A 1D 1E EC 38 0F 1D 3C 0E.#..?<
0040D90C0B 00 16 E3 30 04 01 3B 0E 2F 33 38 3F 2A 2B 1A .?;/38?*+
0040D91C33 38 3B 21 2B D1 05 3C 2B 3F FF F9 CA 02 2D 3538;!+?<+??-5
0040D92C28 20 28 F0 F0 C1                              ( (痧?.
解码后:
0040D8CC47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00 5FGetProcAddress._
0040D8DC6C 6F 70 65 6E 00 4C 6F 61 64 4C 69 62 72 61 72lopen.LoadLibrar
0040D8EC79 41 00 4D 65 73 73 61 67 65 42 6F 78 41 00 45yA.MessageBoxA.E
0040D8FC78 69 74 50 72 6F 63 65 73 73 00 53 65 74 54 69xitProcess.SetTi
0040D90C6D 65 72 00 52 65 61 64 50 72 6F 63 65 73 73 4Dmer.ReadProcessM
0040D91C65 6D 6F 72 79 00 55 73 65 72 33 32 00 4B 65 72emory.User32.Ker
0040D92C6E 65 6C 33 32 00                              nel32.
将API解码出来后,下一步是寻找这些API的入口地址了,一路F8来到 0x0040D2E3 ,如下图:

来到0x0040D2E3后,又是一个 call 调用,我们按F7 跟进去,并F8来到下面:


这一段代码的主要功能就是在 Kernel32.dll 中的导出表中的函数名列表中寻找 LoadLibraryA()函数名,并计算得到其入口地址。这里会用前面寻到 kernel32 的 hInstance,并会对其进行PE文件格式判断,然后遍历导出表中的函数名列表,找到需要的函数并计算该函数的入口地址,代码如下,我把花指令的NOP去掉了,所以指令地址不是连续的了:
0040D005    90                  nop                                                ; Get_API_Address Proc
0040D009    58                  pop   eax
0040D00E    5B                  pop   ebx
0040D013    5F                  pop   edi
0040D019    59                  pop   ecx
0040D01E    50                  push    eax
0040D024    8BD3                  mov   edx, ebx                                 ; ebx == hInstance
0040D02A    66:813B 4D5A          cmp   word ptr , 5A4D                     ; 'MZ'
0040D033    0F85 C4000000         jnz   0040D0FD
0040D03E    0FB743 3C             movzx   eax, word ptr
0040D046    03D8                  add   ebx, eax
0040D04D    66:813B 5045          cmp   word ptr , 4550                     ; 'PE'
0040D056    0F85 A1000000         jnz   0040D0FD
0040D060    8B5B 78               mov   ebx, dword ptr
0040D067    03DA                  add   ebx, edx                                 ; edx=0x756D0000,Kernel32.dll的hInstance
0040D06D    0BFF                  or      edi, edi                                 ; EDI ===> "LoadLibraryA", "GetProcAddress"
0040D073    74 78               je      short 0040D0ED
0040D079    8B73 20               mov   esi, dword ptr
0040D080    03F2                  add   esi, edx
0040D086    8B4B 18               mov   ecx, dword ptr                   ; ecx == 0x643
0040D08D    53                  push    ebx
0040D092    33DB                  xor   ebx, ebx
0040D094    90                  nop                                                ; 循环开始位置
0040D098    AD                  lods    dword ptr                             ; eax ===> 内存区缓冲区,全0
0040D09D    03C2                  add   eax, edx                                 ; eax == 7576B1F2, EAX ==>"AcquireSRWLockExclusive"
0040D0A3    56                  push    esi
0040D0A4    57                  push    edi
0040D0A9    87FE                  xchg    esi, edi                                 ; ESI ===> "LoadLibraryA", "GetProcAddress"
0040D0AF    97                  xchg    eax, edi                                 ; EDI ===> "AcquireSRWLockExclusive"
0040D0B4    AC                  lods    byte ptr                            ; al = 0x4C, "LoadLibraryA"的第1个字母的ASCII码值
0040D0B9    0AC0                  or      al, al                                     ; 检查导入表是否结束
0040D0C0    75 0B               jnz   short 0040D0CD
0040D0C2    90                  nop
0040D0C6    803F 00               cmp   byte ptr , 0
0040D0C9    74 0B               je      short 0040D0D6
0040D0CB    EB 07               jmp   short 0040D0D4
0040D0CD    3807                  cmp   byte ptr , al                         ; 检查 是否等于 al == 'L', 查找 LoadLibraryA 的位置
0040D0CF    75 03               jnz   short 0040D0D4
0040D0D1    47                  inc   edi
0040D0D2^ EB DC               jmp   short 0040D0B0                           ; 循环比较字符串是否"LoadLibraryA"
0040D0D4    B0 01               mov   al, 1
0040D0D6    5F                  pop   edi
0040D0D7    5E                  pop   esi
0040D0D8    0AC0                  or      al, al
0040D0DA    74 06               je      short 0040D0E2                           ; al=0则中断循环,表示找到了"LoadLibraryA"
0040D0DC    43                  inc   ebx
0040D0DD^ E2 B5               loopd   short 0040D094                           ; 循环结束位置
0040D0DF    5B                  pop   ebx
0040D0E0    EB 1B               jmp   short 0040D0FD
0040D0E2    93                  xchg    eax, ebx                                 ; 循环中断跳出到这里,计算LoadLibraryA的入口地址
0040D0E3    5B                  pop   ebx
0040D0E4    8B73 24               mov   esi, dword ptr
0040D0E7    03F2                  add   esi, edx
0040D0E9    0FB70C46            movzx   ecx, word ptr
0040D0ED    2B4B 10               sub   ecx, dword ptr
0040D0F0    8B73 1C               mov   esi, dword ptr
0040D0F3    03F2                  add   esi, edx
0040D0F5    91                  xchg    eax, ecx
0040D0F6    8B4486 04             mov   eax, dword ptr
0040D0FA    03C2                  add   eax, edx                                 ; eax == LoadLibraryA的入口地址
0040D0FC    C3                  retn                                             ; Get_API_Address Proc 结束
0040D0FD    33C0                  xor   eax, eax
0040D0FF    C3                  retn                                             ; Get_API_Address Proc 结束


代码执行完后,会将 LoadLibraryA()函数的入口地址置于 EAX 中并返回,如果没有找到该函数,EAX=0。

从函数返回后,取得了 Kernel32.LoadLibraryA()的入口,然后通过调用 LoadLibraryA("User32"),取得 User32.dll的 hInstance,如下图所示。

然后,再次调用该函数:LoadLibraryA("Kernel32"),取得 Kernel32 的 hInstance,不过这个好象有点多余了,前面已经取得了这个 hInstance 了。


上面取得的 hInstance 与前面 0x0040D25B call 0x0040D100 取得的 hInstance 是一样的。
再次几个F8,来到0x0040D369,这里再次调用 call 0x0040D005,这次是从 User32 中取 GetProcAddress() 函数的地址了,如下图:

取得 GetProcAddress()的入口地址后,通过与LoadLibraryA()得到的 hInstance 配合,就可以得到其它几个函数(如 MessageBoxA(), ExitProcess(), SetTimer(), ReadProcessMemory(), _lopen() 等几个函数)的入口地址了,这一段代码比较长,多按 F8 向下走:

取得这些系统 API 调用的入口后,再F8向下走,来到 0x0040D472,就可以看到程序将调用 SetTimer()函数设置一个定时器(我们需要跳过这段代码的执行,减少调试时的干扰)这时可以看到还有4个push 指令,这些指令也不要执行,如下图:

通过在OD中改变EIP的值就可以跳过定时器的调用,如下:


那几个给 SetTimer() 设置参数的 Push 指令也要跳过,我们在 0x0040D493 的位置点击右键,选择“此处为新 EIP”就可以跳过 SetTimer()函数的调用。
点击后,OD会弹出一个提示对话框,选择“是(Y)”即可,如下所示:

跳过定时器后,按 F8 来到 0x0040D4AE 处,如下图,这是一段解码程序,这里一共有三个解码循环,第1个也是解码字符串资源,第2、3个是解码代码:

第1段代码如下,去掉了无用的NOP 指令,因此指令地址不连续了:
0040D497    8D9D 78284000         lea   ebx, dword ptr                 ; ebx = 0x0040D84C,开始第2次数据解码,解码反SoftICE调试字符串等
0040D4A2    8D8D CB284000         lea   ecx, dword ptr                 ; ecx = 0x0040D89F
0040D4AC    2BCB                  sub   ecx, ebx                                 ; 长度 0x53 字节, 解码数据
0040D4AE    90                  nop                                                ; 循环开始
0040D4B2    8033 64               xor   byte ptr , 64
0040D4B9    8003 65               add   byte ptr , 65
0040D4C0    C003 66               rol   byte ptr , 66
0040D4C8    43                  inc   ebx
0040D4CD^ E2 DF               loopd   short 0040D4AE;
解码前:
0040D84C37 A1 33 23 23 23 33 23 23 27 23 23 23 27 23 FF7?###3##'###'#?
0040D85C01 45 40 DF DF 3A CC 3C 48 54 CC 00 E4 38 08 7FE@哌:?HT??
0040D86C40 E4 7F 48 54 50 24 28 54 00 37 FF B0 3C 54 7B@?HTP$(T.7??T{
0040D87C7F B4 44 4C 58 7F 54 00 0C 34 44 7F 8C 3C 50 08碊LXT..4D?P
0040D88CA4 CC D4 7F 1C 54 4C 58 7B FF 68 68 37 68 8C A4ぬ?TLX{?hh7h尋
0040D89CCC D4 FF
解码后:
0040D84C2E 8A 2F 2B 2B 2B 2F 2B 2B 2A 2B 2B 2B 2A 2B 00.?+++/++*+++*+.
0040D85CB2 A1 62 08 08 F0 43 6F 64 65 43 72 79 70 74 20病b餋odeCrypt
0040D86C62 79 20 64 65 66 69 6C 65 72 2E 00 4E 6F 65 21by defiler..Noe!
0040D87C20 4D 61 63 68 20 65 72 73 6D 61 20 53 6F 66 74   Mach ersma Soft
0040D88C49 43 45 20 77 65 63 68 21 00 5C 5C 2E 5C 53 49ICE wech!.\\.\SI
0040D89C43 45 00                                       CE.


然后就是2段代码解码的循环,第1段如下所示,去掉了无用的“NOP”指令:
0040D4CF    B9 78284000         mov   ecx, 00402878                              ; ASCII "rr"
0040D4D8    B8 7C254000         mov   eax, 0040257C
0040D4E1    2BC8                  sub   ecx, eax                                 ; 长度 0x2FC和 可变密钥2,两层功能
0040D4E7    8D9D 7C254000         lea   ebx, dword ptr                 ; ebx = 0x0040D550,指向解码代码的一个Proc,不过这个Proc需要先解码
0040D4F1    8A95 EB284000         mov   dl, byte ptr                   ; ss: == 0xB4,解码密钥1
0040D4FC    90                  nop                                                ; 代码解码循环1开始
0040D501    3013                  xor   byte ptr , dl                         ; 解码1,固定密钥, dl = 0xB4
0040D508    300B                  xor   byte ptr , cl                         ; 解码2, 可变密钥, ecx=0x2D9, 0x2D8
0040D50E    43                  inc   ebx
0040D513    49                  dec   ecx
0040D518    85C9                  test    ecx, ecx
0040D51E^ 75 DC               jnz   short 0040D4FC                           ; 代码解码循环1结束
第2段,也去掉了无用的"NOP"指令了:
0040D524    B9 78284000         mov   ecx, 00402878                              ; ASCII "rr"
0040D52D    B8 7C254000         mov   eax, 0040257C
0040D536    2BC8                  sub   ecx, eax                                 ; ecx = 0x02FC, 解码长度和可变密钥
0040D53C    8D9D 8B254000         lea   ebx, dword ptr                 ; ebx = 0x0040D55F, 第2个代码解码的开始地址
0040D546    8A95 88284000         mov   dl, byte ptr                   ; dl = 0xB2, 固定密钥
0040D550    C00B 04               ror   byte ptr , 4
0040D553    3013                  xor   byte ptr , dl
0040D555    43                  inc   ebx
0040D556    49                  dec   ecx
0040D557    85C9                  test    ecx, ecx
0040D559^ 75 F5               jnz   short 0040D550                           ; 解码完成后,再次去花指令(开始位置:0x0040D55B,长度:0x0300,93个)

运行到 0x0040D55B 后(我们可以在 0x0040D55B 处选中该行,按 F4 直接运行到这里),如下图:

这个时候,我们又看到一堆“EB 02”指令了,这是代码解码后,花指令也解码出来了,前面去花指令并不会把这些加密的花指令去掉,所以我们再进行一次去花指令的操作,这次不要在右键菜单上操作,因为那个默认长度是 0x400,可能会超出本节的内存范围,导致OD 都会被操作系统关闭。所以,我们要从主菜单的“插件”菜单中来调用去花指令的功能:

如上图所示,选择“去除花指令”,会弹出一个对话框:

我们在上面填好开始地址和长度,长度的计算:由前面节信息可知为0x932字节,减去开始地址:0x55B,为0x3D7,由于后面还会有一些变量的空间或无用空间,我们只取0x300试试。
点“确定”,完成花指令的去除:

又干掉了一批花指令,一下子代码又变得清新了:

可以看到一个 INT 68 指令,就是这个指令,导致其不能在 Windows NT 系列的系统上运行,执行到这里就会被关闭,所以我们也要跳过这个指令,如下所示:

我们执行到 0x0040D56F,给 ah 赋值后就可以了,然后跳过 INT 68 指令,由于 ECX 不为 0,下面这几个对 ecx 的检测和跳转指令也不用执行了:

直接来到 0x0040D59A 处,设置为新的 EIP,如下所示:

由于 ah 前面赋值为 0x43,也就不会等于 0xF386了,如下所示,跳转会成功:


这段指令整理后如下,是一个循环执行 int 68 指令的代码。

0040D564    B9 FFFF0100         mov   ecx, 1FFFF                                 ; 调用 int 68 次数
0040D569    90                  nop                                                ; 开始循环检测
0040D56D    B4 43               mov   ah, 43                                     ; 功能号
0040D572    90                  nop
0040D573    CD 68               int   68                                       ; 中断调用,检测 SoftICE 是否加载,对 OD 没有用,但会引起NT内核系统关闭CM。
0040D575    90                  nop
0040D579    49                  dec   ecx
0040D57F    85C9                  test    ecx, ecx
0040D585^ 75 E2               jnz   short 0040D569                           ; 循环调用 int 68
0040D58C    85C9                  test    ecx, ecx
0040D592    0F85 96020000         jnz   0040D82E                                 ; 跳转到错误地址去了
0040D59C    66:3D 86F3            cmp   ax, 0F386                                  ; 如果 SoftICE 在运行,int 68调用将把 ax 设值为 0xF386
0040D5A5    0F85 97000000         jnz   0040D642                                 ; 如果没有检测到SoftICE, 就开始跳转去解码原始代码了。
0040D5AB    90                  nop

如果上面代码最后没有跳转,就会去执行一段覆盖CM壳代码的操作代码,以及显示一个失败对话框。代码如下:
0040D5AF    C785 84284000 22000000   mov   dword ptr , 22
0040D5BE    8DBD 6E264000            lea   edi, dword ptr                 ; edi == 0x0040D642
0040D5C8    B9 FF000000            mov   ecx, 0FF                                 ; exc 为循环次数
0040D5D1    F2:AA                  repne   stos byte ptr es:                     ; 覆盖代码区内容,清成 AL 中的值, 直到 ecx 变为 0
0040D5D7    B9 D7254000            mov   ecx, 004025D7
0040D5E0    BB 95254000            mov   ebx, 00402595
0040D5EA    2BCB                     sub   ecx, ebx                                 ; exc 为循环次数
0040D5F0    8DBD 95254000            lea   edi, dword ptr                 ; edi ===> 0x0040D5F6
0040D5FA    F2:AA                  repne   stos byte ptr es:                     ; 覆盖代码区内容,清成 AL 中的值, 直到 ecx 变为 0
0040D5FF    90                     nop
0040D600    8D8D A4284000            lea   ecx, dword ptr                 ; ecx ==> "Noe! Mach ersma SoftICE wech!"
0040D60A    8D9D 8E284000            lea   ebx, dword ptr                 ; ebx ==> "CodeCrypt by defiler."
0040D614    6A 30                  push    30                                       ; 显示信息
0040D61B    53                     push    ebx
0040D620    51                     push    ecx
0040D626    6A 00                  push    0
0040D62C    FF95 D3284000            call    dword ptr                      ; call MessageBoxA
0040D636    FF95 D7284000            call    dword ptr                      ; ExitProcess
0040D640    33ED                     xor   ebp, ebp                                 ; 清0了本地变量基址指针

由于ah=0x43,AX != 0xF386,不会执行上面的代码,我们成功跳转到了新的解码代码,来到 0x0040D642,如下图所示:

这里是对CM的原有代码进行解密,多次解密后,就会跳转到 OEP,准备执行 CM 原有的程序代码,到这个时候也可以脱壳了。
下面具体説明,下图是对原始代码进行第1次解密,如下所示:

第1次解密后,还有一次对SoftICE 的检查,就是通过打开"\\\\.\\SICE"文件句柄来实现的,如下图所示。

不过我们用的是OD,这个检查无效,我们可以执行,并且能够完成检查也不会引起异常。
接下来是第2次对原有代码进行重复解密运算,如下图所示:

搞完2次解密后,原始代码还没有出来,这个时候,CM壳又对整个壳代码进行了一次8位"校验和"的运算处理,如下图:

按常理来説,这个校验是包括了花指令在内的校验运算,不过,经过测试,去除花指令后的“校验和”与没有去除花指令的“校验和”是一样的,都是0x98。后来观察,有些花指令中间无用数据并不一样,有几个是不同的,可能是作者调整了的。。。。降低点工作量(不过我在带花指令的情况也试了下,确实是一样的“校验和”)。
“校验和”计算完后,来到下面:

CM壳并不对”校验和“进行检查,直接保存到内存变量,为什么呢,因为,还要用这个”校验和“对原始代码继续进行解码操作,没错,用这个”校验和“作为密钥来解码代码,如下图,dl中就是这个”校验和“的值:

上面一波解码操作完成后,才正式完成壳的全部解码功能,可以看到原始未加密的代码了:

以及原始代码的入口,即OEP了,到这个时候,壳终于脱掉了:

上面这一波操作的壳代码如下,去掉了无用的“NOP"指令:
0040D642    8B8D 84284000            mov   ecx, dword ptr                 ; ecx == 0x00001000,开始解码非壳代码
0040D64D    8B85 7C284000            mov   eax, dword ptr                 ; eax == 0x00400000
0040D657    0385 80284000            add   eax, dword ptr                 ; eax == 00401000, 指向非壳代码区
0040D661    8BD8                     mov   ebx, eax                                 ; ebx == eax == 0x00401000
0040D668    66:51                  push    cx                                       ; 开始第1次循环解码非壳代码,字节数:ecx == 0x1000
0040D66A    8A8D 8D284000            mov   cl, byte ptr                   ; cl == 0xF0, key
0040D670    000B                     add   byte ptr , cl                         ; 解码1
0040D677    D20B                     ror   byte ptr , cl                         ; 解码2
0040D679    66:59                  pop   cx
0040D67F    43                     inc   ebx
0040D680^ E2 E6                  loopd   short 0040D668
0040D686    9C                     pushfd
0040D68C    58                     pop   eax                                        ; eax = flag register == 0x0216
0040D691    F6C4 01                  test    ah, 1                                    ; ah == 0x02, 不等于 0x01
0040D698    0F85 90010000            jnz   0040D82E
0040D69E    8D8D C2284000            lea   ecx, dword ptr                 ; ecx ===> "\\\\.\\SICE"
0040D6A8    6A 40                  push    40
0040D6AE    51                     push    ecx                                        ; ecx===>"\\\\.\\SICE"
0040D6B3    FF95 E3284000            call    dword ptr                      ; call _lopen("\\\\.\\SICE"),检查 SoftICE 是否运行
0040D6BD    83F8 FF                  cmp   eax, -1                                    ; eax == 0xFFFFFFFF,表示 SoftICE 没有运行
0040D6C4^ 0F85 E1FEFFFF            jnz   0040D5AB                                 ; 不等-1表示SICE在运行,跳去破坏代码、显示消息并退出
0040D6CF    8B8D 84284000            mov   ecx, dword ptr                 ; ecx == 0x00010000, 解码长度,开始第2次非壳代码解码
0040D6DA    8B85 7C284000            mov   eax, dword ptr                 ; eax == 0x00400000,基址
0040D6E4    0385 80284000            add   eax, dword ptr                 ; eax == 0x00401000, 指向非壳代码
0040D6EF    8BD8                     mov   ebx, eax                                 ; ebx = eax = 0x00401000, p
0040D6F5    8A95 88284000            mov   dl, byte ptr                   ; dl == 0xB2, key2
0040D6FF    33C0                     xor   eax, eax                                 ; key1=0
0040D705    FEC0                     inc   al                                       ; key1++
0040D70B    3003                     xor   byte ptr , al
0040D711    C00B 05                  ror   byte ptr , 5
0040D718    3013                     xor   byte ptr , dl
0040D71E    C003 07                  rol   byte ptr , 7
0040D725    43                     inc   ebx                                        ; p++
0040D72A    49                     dec   ecx                                        ; i--
0040D730    85C9                     test    ecx, ecx                                 ; i>0
0040D737^ 75 CC                  jnz   short 0040D705
0040D741    8B85 7C284000            mov   eax, dword ptr                 ; 基址(eax == 0x00400000),开始第3次非壳代码解码
0040D74B    0385 80284000            add   eax, dword ptr
0040D755    8BD8                     mov   ebx, eax                                 ; ebx == 0x00401000, p
0040D75B    8B03                     mov   eax, dword ptr                      ; 读取指令码
0040D762    3985 89284000            cmp   dword ptr , eax                ; == 0x080862A1,检查第2次解码是否正确
0040D76C    0F85 BC000000            jnz   0040D82E                                 ; 相等则解码正确
0040D777    33C0                     xor   eax, eax                                 ; checkSum = 0
0040D779    8D8D 78284000            lea   ecx, dword ptr                 ; ecx == 0x0040D84C,OEP
0040D782    90                     nop                                                ; 下面是对壳代码进行校验和(checkSum)运算
0040D783    8D9D 2C204000            lea   ebx, dword ptr [ebp+<&MFC42.#3346_CWinThre>; ebx == 0x0040D000, 指向壳的入口地址,(ebp == 0x0000AFD4)
0040D789    2BCB                     sub   ecx, ebx                                 ; ecx == 0x0000084C, 循环次数
0040D78B    0203                     add   al, byte ptr                          ; 循环对壳代码字节进行求和,这个是对壳的修改校验(因为去除花指令,求和后的值可能是错的),也是下面解码用的密钥
0040D78D    43                     inc   ebx
0040D78E    49                     dec   ecx
0040D793    85C9                     test    ecx, ecx
0040D795^ 75 F4                  jnz   short 0040D78B
0040D79A    90                     nop
0040D79B    8885 EB284000            mov   byte ptr , al                  ; al == 0x98,保存“校验和”,因去除了花指令,该值不一定准确(后来验证是正确的)
0040D7A1    8B8D 84284000            mov   ecx, dword ptr                 ; ecx == 0x00010000,解码长度
0040D7AC    8B85 7C284000            mov   eax, dword ptr                 ; eax == 0x00400000,基址
0040D7B6    0385 80284000            add   eax, dword ptr                 ; eax == 0x00401000,解码开始地址
0040D7C1    8BD8                     mov   ebx, eax                                 ; pointer p
0040D7C7    8A95 EB284000            mov   dl, byte ptr                   ; dl == 0x98, 解码密钥,也是前面求得的壳代码的校验和(checkSum)
0040D7D1    33C0                     xor   eax, eax                                 ; n = 0
0040D7D6    90                     nop
0040D7D7    FEC0                     inc   al                                       ; n++,开始循环解码,第3次解码
0040D7DD    D20B                     ror   byte ptr , cl                         ; cl, 可变密解(循环变量)
0040D7E4    3013                     xor   byte ptr , dl                         ; dl 为壳代码的校验和(checkSum)
0040D7EA    2813                     sub   byte ptr , dl                         ; dl 为壳代码的校验和(checkSum)
0040D7F0    43                     inc   ebx
0040D7F5    49                     dec   ecx
0040D7FB    85C9                     test    ecx, ecx
0040D802^ 75 D3                  jnz   short 0040D7D7
0040D804    90                     nop
0040D808    90                     nop                                                ; 全部解码完成,准备转去OEP
0040D809    8B85 78284000            mov   eax, dword ptr                 ; eax = 0x00401A50, OEP
0040D817    5A                     pop   edx
0040D81C    59                     pop   ecx
0040D822    5B                     pop   ebx
0040D827    5D                     pop   ebp
0040D828    FFE0                     jmp   eax                                        ; goto OEP
0040D82A    90                     nop

下面,F8完 JMP EAX 后,我们正式进入 CM 的原始代码区了,如下图所示:


这个时候就可以开始脱壳了,如下图所示,进行脱壳操作:

弹出脱壳对话框,全部默认设置,直接点击”Dump“即可!


脱壳完成后,我们直接 F9 运行CM的代码,由于跳过了int68指令,CM可以正常进入了,看到了亲切的窗口界面:

还有一个就是对序列号的验证了(上图中水印太大,挡住了两个按钮,汗!!!)。
下面我们先找一下进行注册码验证的代码在哪里。
回到OD界面,看一下代码,这个CM是VC+MFC框架编写的,可以看到大量 MFC 对象的操作代码。
最早做过MFC4~6版本下应用开发的应该知道,那个时候MFC是通过事件和成员变量的映射来处理消息的,映射好界面控件和成员变量之间的关系后,只要执行 MFC 框架的 CWnd::UpdateData()就可以将界面中输入的值,自动赋给前面映射的成员变量,所以,我们先找找这个 UpdateData()函数,很快,我们就找到了,并且也只出现了一次:


可以肯定,这段代码就是注册码处理验证的地方了。
在这个函数的前面下一个断点,就可以进行下一步的注册码验证操作了。


回到 CM 界面。
输入一个用户名和注册码假码"787878787878",如下图:

点击”OK!“(按钮被水印挡住了)就会断下来,进入OD调试,跟踪注册码的算法,具体跟踪、分析放一起了,如下所示:
00401410   .6A FF                  push    -1                                                      ;消息处理映射函数
00401412   .68 181D4000            push    00401D18                                                ;SE 处理程序安装
00401417   .64:A1 00000000         mov   eax, dword ptr fs:
0040141D   .50                     push    eax
0040141E   .64:8925 00000000       mov   dword ptr fs:, esp
00401425   .83EC 38                sub   esp, 38
00401428   .53                     push    ebx                                                   ;开始计算注册码
00401429   .55                     push    ebp
0040142A   .8BE9                   mov   ebp, ecx
0040142C   .56                     push    esi
0040142D   .57                     push    edi
0040142E   .8D4C24 14            lea   ecx, dword ptr
00401432   .E8 67050000            call    <jmp.&MFC42.#540_CString::CString>
00401437   .33DB                   xor   ebx, ebx
00401439   .8D4C24 10            lea   ecx, dword ptr
0040143D   .895C24 50            mov   dword ptr , ebx                                 ; 字符串计数器
00401441   .E8 58050000            call    <jmp.&MFC42.#540_CString::CString>
00401446   .8D4C24 20            lea   ecx, dword ptr
0040144A   .C64424 50 01         mov   byte ptr , 1                                    ; 字符串计数器
0040144F   .E8 4A050000            call    <jmp.&MFC42.#540_CString::CString>
00401454   .8D4C24 30            lea   ecx, dword ptr
00401458   .C64424 50 02         mov   byte ptr , 2
0040145D   .E8 3C050000            call    <jmp.&MFC42.#540_CString::CString>
00401462   .8D4C24 1C            lea   ecx, dword ptr
00401466   .C64424 50 03         mov   byte ptr , 3
0040146B   .E8 2E050000            call    <jmp.&MFC42.#540_CString::CString>
00401470   .6A 01                  push    1
00401472   .8BCD                   mov   ecx, ebp
00401474   .C64424 54 04         mov   byte ptr , 4                                    ;字符串计数器
00401479   .E8 80050000            call    <jmp.&MFC42.#6334_CWnd::UpdateData>                     ;读取数据给相应控件的映射变量(MFC映射成员变量)
0040147E   .8D45 60                lea   eax, dword ptr
00401481   .8D4C24 14            lea   ecx, dword ptr
00401485   .50                     push    eax
00401486   .E8 6D050000            call    <jmp.&MFC42.#858_CString::operator=>                  ;str1, eax==0x0019F4C8, ===> 用户名,solly
0040148B   .8D4D 64                lea   ecx, dword ptr
0040148E   .51                     push    ecx
0040148F   .8D4C24 14            lea   ecx, dword ptr
00401493   .E8 60050000            call    <jmp.&MFC42.#858_CString::operator=>                  ;str2, eax==0x0019F4C4, ===> 注册码,"787878787878"
00401498   .8B7C24 14            mov   edi, dword ptr                                  ;edi ===> 用户名,"solly"
0040149C   .83C9 FF                or      ecx, FFFFFFFF
0040149F   .33C0                   xor   eax, eax
004014A1   .68 50304000            push    00403050                                                ;ASCII "Ocbk~mxy`mxecb6Ucy+zi,o~mogih,ai-"
004014A6   .F2:AE                  repne   scas byte ptr es:
004014A8   .F7D1                   not   ecx
004014AA   .49                     dec   ecx                                                   ;int len = ecx == strlen(str1),用户名长度
004014AB   .8BF1                   mov   esi, ecx                                                ;esi = ecx = length(username) == 5,用户名长度
004014AD   .8D4C24 24            lea   ecx, dword ptr
004014B1   .E8 E2040000            call    <jmp.&MFC42.#860_CString::operator=>                  ;str3, eax==0x0019F4D4, ===>"Ocbk~mxy`mxecb6Ucy+zi,o~mogih,ai-"
004014B6   .68 4C304000            push    0040304C
004014BB   .8D4C24 34            lea   ecx, dword ptr
004014BF   .E8 D4040000            call    <jmp.&MFC42.#860_CString::operator=>                  ;str4, eax==0x0019F4E4, ===> "4",用于检查SN最后一位是否为"8"
004014C4   .8D4C24 2C            lea   ecx, dword ptr
004014C8   .E8 D1040000            call    <jmp.&MFC42.#540_CString::CString>                      ;eax == 0x0019F4E0
004014CD   .8D4C24 18            lea   ecx, dword ptr
004014D1   .C64424 50 05         mov   byte ptr , 5
004014D6   .E8 C3040000            call    <jmp.&MFC42.#540_CString::CString>                      ;eax == 0x0019F4CC
004014DB   .8D4C24 28            lea   ecx, dword ptr
004014DF   .C64424 50 06         mov   byte ptr , 6
004014E4   .E8 B5040000            call    <jmp.&MFC42.#540_CString::CString>                      ;eax==0x0019F4DC
004014E9   .8D4C24 24            lea   ecx, dword ptr
004014ED   .C64424 50 07         mov   byte ptr , 7
004014F2   .E8 A7040000            call    <jmp.&MFC42.#540_CString::CString>                      ;eax==0x0019F4D8
004014F7   .8D5424 10            lea   edx, dword ptr                                  ;edx = sn ===> "787878787878"
004014FB   .8D4C24 2C            lea   ecx, dword ptr
004014FF   .52                     push    edx
00401500   .C64424 54 08         mov   byte ptr , 8                                    ;字符串计数器
00401505   .E8 EE040000            call    <jmp.&MFC42.#858_CString::operator=>                  ;str5, eax==0x0019F4E0, ===>"787878787878"
0040150A   .68 48304000            push    00403048                                                ; ===> "-"
0040150F   .8D4C24 14            lea   ecx, dword ptr                                  ; ===> 注册码假码"787878787878"
00401513   .895C24 38            mov   dword ptr , ebx
00401517   .E8 D6040000            call    <jmp.&MFC42.#2764_CString::Find>                        ;在序列号中查找分隔符"-"
0040151C   .8BF8                   mov   edi, eax                                                ;没找到分隔符,则eax == 0xFFFFFFFF,否则为"-"号的位置值
0040151E   .83FF FF                cmp   edi, -1                                                 ;位置值 保存于 EDI
00401521   .75 0D                  jnz   short 00401530
00401523   .8B45 00                mov   eax, dword ptr                                     ;eax == 0x00402350
00401526   .8BCD                   mov   ecx, ebp
00401528   .FF50 60                call    dword ptr                                     ;call , MFC42.#2446_CWnd::DestroyWindow
0040152B   .E9 E8010000            jmp   00401718                                                ;序列号格式不对,结束
00401530   >8D4C24 10            lea   ecx, dword ptr                                  ; ===> 注册码假码"7878-8787878",手动加了一个"-"
00401534   .51                     push    ecx
00401535   .8D4C24 2C            lea   ecx, dword ptr
00401539   .E8 BA040000            call    <jmp.&MFC42.#858_CString::operator=>                  ;str6, eax==0x0019F4DC, ===> 注册码"7878-8787878"
0040153E   .8D5424 38            lea   edx, dword ptr
00401542   .57                     push    edi                                                   ;长度(left)
00401543   .52                     push    edx                                                   ;buffer
00401544   .8D4C24 30            lea   ecx, dword ptr                                  ;引用 str6
00401548   .E8 9F040000            call    <jmp.&MFC42.#4129_CString::Left>                        ;str7, eax == 0x0019F4EC, ==>"7878"
0040154D   .8B00                   mov   eax, dword ptr                                     ;eax ===> "7878"
0040154F   .8D4C24 3C            lea   ecx, dword ptr                                  ;ecx == 0x0019F4F0, 保存结果 str_sn1
00401553   .6A 0A                  push    0A                                                      ; /radix = A (10.)
00401555   .51                     push    ecx                                                   ; |endptr
00401556   .50                     push    eax                                                   ; |str_sn1
00401557   .FF15 CC214000          call    dword ptr [<&MSVCRT.strtol>]                            ; \MSVCRT.strtol(),转成整数 sn1
0040155D   .8BD8                   mov   ebx, eax                                                ;eax==0x1EC6 = 7878
0040155F   .83C4 0C                add   esp, 0C                                                 ;恢复 esp 堆栈指针
00401562   .8D4C24 38            lea   ecx, dword ptr                                  ;ecx == 0x0019F4EC, ===> "7878"
00401566   .895C24 40            mov   dword ptr , ebx                                 ;保存 sn1
0040156A   .E8 51030000            call    <jmp.&MFC42.#800_CString::~CString>                     ;删除字符串"7878"
0040156F   .8D5424 2C            lea   edx, dword ptr
00401573   .8D4C24 18            lea   ecx, dword ptr
00401577   .52                     push    edx
00401578   .E8 7B040000            call    <jmp.&MFC42.#858_CString::operator=>                  ;str8, eax == 0x0019F4CC, ===> 注册码假码"7878-8787878"
0040157D   .47                     inc   edi                                                   ;edi=5, 指向"-"号的下一个字符位置
0040157E   .8D4424 38            lea   eax, dword ptr                                  ;str9
00401582   .57                     push    edi                                                   ;起始位置(mid)
00401583   .50                     push    eax
00401584   .8D4C24 20            lea   ecx, dword ptr
00401588   .E8 59040000            call    <jmp.&MFC42.#4277_CString::Mid>                         ;str9 = str8.Mid(n) ===>
0040158D   .8B00                   mov   eax, dword ptr                                     ;eax == 0x00BC0DE0 ===> "8787878",SN的后半截, 保存为 str_sn2
0040158F   .8D4C24 3C            lea   ecx, dword ptr                                  ;ecx==0x0019F4F0
00401593   .51                     push    ecx                                                   ; /endptr
00401594   .50                     push    eax                                                   ; |str_sn2
00401595   .FF15 D4214000          call    dword ptr [<&MSVCRT.strtod>]                            ; \strtod(), 转成双精度 sn2 保存在浮点寄存器ST0, ST0 == 8787878.000
0040159B   .DA6424 48            fisub   dword ptr                                     ;sn2 = sn2 - sn1, ST0 == 8780000.000
0040159F   .83C4 08                add   esp, 8                                                ;恢复 esp 堆栈指针
004015A2   .8D4C24 38            lea   ecx, dword ptr                                  ;ecx == 0x0019F4EC, ===> "8787878"
004015A6   .DD5C24 40            fstp    qword ptr                                     ;保存 ST0 在 , 8字节
004015AA   .E8 11030000            call    <jmp.&MFC42.#800_CString::~CString>                     ;删除"8787878"
004015AF   .8D5424 14            lea   edx, dword ptr                                  ; ===> "solly"
004015B3   .8D4C24 24            lea   ecx, dword ptr                                  ;ecx == 0x0019F4D8
004015B7   .52                     push    edx
004015B8   .E8 3B040000            call    <jmp.&MFC42.#858_CString::operator=>                  ;eax == 0x0019F4D8, ===>"solly"
004015BD   .DD4424 40            fld   qword ptr                                     ;载入sn2到ST0, == 8780000.000
004015C1   .DC1D 28244000          fcomp   qword ptr                                     ;ST0浮点数与常量47391894.0比较, == 47391894.000
004015C7   .DFE0                   fstsw   ax
004015C9   .F6C4 41                test    ah, 41                                                ;SN2需要大于47391894.000
004015CC   .74 0D                  je      short 004015DB
004015CE   .8B45 00                mov   eax, dword ptr
004015D1   .8BCD                   mov   ecx, ebp
004015D3   .FF50 60                call    dword ptr                                     ;MFC42.#2446_CWnd::DestroyWindow
004015D6   .E9 3D010000            jmp   00401718                                                ;退出
004015DB   >8B7C24 24            mov   edi, dword ptr                                  ;edi ==> "solly", SN2>47391894.000,来到这里
004015DF   .83C9 FF                or      ecx, FFFFFFFF                                           ;int len = -1
004015E2   .33C0                   xor   eax, eax                                                ;char a=0
004015E4   .83F3 6F                xor   ebx, 6F                                                 ;sn1 = ebx = sn1 xor 0x6F = 0x1EA9 = 7849
004015E7   .8A5437 FF            mov   dl, byte ptr                               ;dl = name
004015EB   .895C24 40            mov   dword ptr , ebx                                 ; = sn1 == 0x00053297
004015EF   .F2:AE                  repne   scas byte ptr es:                                  ;计算 name 的长度
004015F1   .DB4424 40            fild    dword ptr                                     ;ST0 = sn1 = 7849
004015F5   .F7D1                   not   ecx
004015F7   .49                     dec   ecx                                                   ;int len2 = name.Length() = 5,用户名的长度
004015F8   .83E2 7F                and   edx, 7F                                                 ;edx = edx and 0x7F, int 变 char
004015FB   .0FAFCA               imul    ecx, edx                                                ;int lc = len2 * (char)name = 0x025D,用户名长度x用户名最后一个字符
004015FE   .33D2                   xor   edx, edx                                                ;int i=0
00401600   .85F6                   test    esi, esi                                                ;len>0, 检查用名名长度>0, len == esi, 其在 0x004014AB处赋值
00401602   .7E 1C                  jle   short 00401620                                          ;for(int sum=0, int i=0; i<n; i++) {
00401604   .8B7C24 14            mov   edi, dword ptr                                  ;edi ===> "solly"
00401608   >8A043A               mov   al, byte ptr                                   ;a = name
0040160B   .8B5C24 34            mov   ebx, dword ptr                                  ; == 0x00
0040160F   .83E0 7F                and   eax, 7F                                                 ;a = a and 0x7F
00401612   .0FAFC1               imul    eax, ecx                                                ;a = a * lc
00401615   .03D8                   add   ebx, eax                                                ;sum = sum + a;
00401617   .42                     inc   edx                                                   ;i++;
00401618   .3BD6                   cmp   edx, esi                                                ;i<n;
0040161A   .895C24 34            mov   dword ptr , ebx                                 ;循环结束时,sum = 0x00053287
0040161E   .^ 7C E8                  jl      short 00401608                                          ;}
00401620   >8B4424 34            mov   eax, dword ptr                                  ;eax = sum = 0x00053287 = 340615
00401624   .8B4C24 30            mov   ecx, dword ptr                                  ;ecx = 0x00BC0D90, ecx ===> str4
00401628   .83F0 10                xor   eax, 10                                                 ;int sum2 = sum xor 0x10 = 0x00053297 = 340631
0040162B   .894424 40            mov   dword ptr , eax                                 ;保存 sum2, 用于与输入的SN1比较
0040162F   .8A01                   mov   al, byte ptr                                     ;al = '4', str4,用于检查SN最后一位是否为"8"
00401631   .DB4424 40            fild    dword ptr                                     ;ST0 = 340631, ST1=原ST0=sn1=7849
00401635   .D9C9                   fxch    st(1)                                                   ;交换ST0, ST1
00401637   .D8D9                   fcomp   st(1)                                                   ;比较大小
00401639   .34 0C                  xor   al, 0C                                                ;int a4 = al = al xor 0x0C = 0x38, 异或后变成字符"8"
0040163B   .884424 34            mov   byte ptr , al                                 ;保存 a4
0040163F   .DFE0                   fstsw   ax                                                      ;ax==0x3920
00401641   .F6C4 40                test    ah, 40                                                ;sum2必须等于sn1
00401644   .DDD8                   fstp    st                                                      ;清空ST0
00401646   .0F84 C4000000          je      00401710                                                ;(sum2 != sn1)则退出
0040164C   .8B5424 34            mov   edx, dword ptr                                  ;edx == a4 == (char)0x00053238(0x38 == "4" xor 0x0C)
00401650   .6A 01                  push    1                                                       ;1个字节
00401652   .52                     push    edx                                                   ;a4
00401653   .8D4C24 40            lea   ecx, dword ptr
00401657   .E8 84030000            call    <jmp.&MFC42.#536_CString::CString>                      ;str11, eax == 0x0019F4EC, (char) == 0x38 ===>"8",CString(TCHAR ch,int n = 1);
0040165C   .8D4424 40            lea   eax, dword ptr                                  ;eax = sum = 0x00053297
00401660   .6A 01                  push    1
00401662   .50                     push    eax                                                   ;指向返回值缓冲区的指针 char **
00401663   .8D4C24 20            lea   ecx, dword ptr                                  ;buffer ; = str2 ===> "7878-8787878"
00401667   .C64424 58 09         mov   byte ptr , 9                                    ;字符串计数器
0040166C   .E8 69030000            call    <jmp.&MFC42.#5710_CString::Right>                     ;str12 = Right(sn, 1), eax = 0x0019F4F4, ="8", 注册码的最后一位字符
00401671   .8B4C24 38            mov   ecx, dword ptr                                  ;ecx == 0x00BC0DE0 ===> "8"(0x34 xor 0x0C后的字符)
00401675   .8B00                   mov   eax, dword ptr                                     ;eax == 0x00B30E30 ===> "8" (注册码的最后一位字符)
00401677   .51                     push    ecx                                                   ; /strParam2
00401678   .50                     push    eax                                                   ; |strParam1
00401679   .FF15 D8214000          call    dword ptr [<&MSVCRT._mbscmp>]                           ; \MSVCRT._mbcmp(), 比较SN最后一位是否为'8',相等返回 0
0040167F   .83C4 08                add   esp, 8
00401682   .8D4C24 40            lea   ecx, dword ptr                                  ;str11, ecx = 0x0019F4F4, 准备删除该字符串
00401686   .85C0                   test    eax, eax                                                ;检查字符串比较结果
00401688   .0F94C3               sete    bl                                                      ;相等时,bl = 1
0040168B   .E8 30020000            call    <jmp.&MFC42.#800_CString::~CString>                     ;清空 str11
00401690   .8D4C24 38            lea   ecx, dword ptr
00401694   .C64424 50 08         mov   byte ptr , 8                                    ;字符串计数
00401699   .E8 22020000            call    <jmp.&MFC42.#800_CString::~CString>
0040169E   .84DB                   test    bl, bl                                                ;检查字符串比较结果,bl==1表示相等
004016A0   .74 6E                  je      short 00401710                                          ;bl等于0,则退出,bl等于1则显示 Crack 成功!!!
004016A2   .8B7C24 20            mov   edi, dword ptr                                  ;一个加密静态初始化的字符串 ===> "Ocbk~mxy`mxecb6Ucy+zi,o~mogih,ai-"
004016A6   .83C9 FF                or      ecx, FFFFFFFF
004016A9   .33C0                   xor   eax, eax                                                ;'\0'
004016AB   .33F6                   xor   esi, esi                                                ;int i=0
004016AD   .F2:AE                  repne   scas byte ptr es:
004016AF   .F7D1                   not   ecx
004016B1   .49                     dec   ecx                                                   ;计算长度
004016B2   .8BF9                   mov   edi, ecx                                                ;edi 为长度
004016B4   .85FF                   test    edi, edi
004016B6   .7E 43                  jle   short 004016FB
004016B8   >8B5424 20            mov   edx, dword ptr                                  ;str3, edx == 0x00BC0D40 ===> "Ocbk~mxy`mxecb6Ucy+zi,o~mogih,ai-"
004016BC   .8D4C24 1C            lea   ecx, dword ptr                                  ;ecx == 0x0019F4D0,临时结果
004016C0   .8A0416               mov   al, byte ptr                                   ;al = str
004016C3   .8D5424 40            lea   edx, dword ptr                                  ;str11, edx == 0x0019F4F4, 临时存放解码后CString字符
004016C7   .34 0C                  xor   al, 0C                                                ;str = str xor 0x0C
004016C9   .884424 34            mov   byte ptr , al                                 ;保存字符 str
004016CD   .8B4424 34            mov   eax, dword ptr                                  ;eax ===> str
004016D1   .50                     push    eax
004016D2   .51                     push    ecx
004016D3   .52                     push    edx
004016D4   .E8 FB020000            call    <jmp.&MFC42.#923_operator+>                           ;连接字符串,eax == 0x0019F4F4, ===> 解码后的字符串
004016D9   .50                     push    eax
004016DA   .8D4C24 20            lea   ecx, dword ptr
004016DE   .C64424 54 0A         mov   byte ptr , 0A                                 ;字符串计数
004016E3   .E8 10030000            call    <jmp.&MFC42.#858_CString::operator=>                  ;字符串赋值,str13 = "C"
004016E8   .8D4C24 40            lea   ecx, dword ptr
004016EC   .C64424 50 08         mov   byte ptr , 8                                    ;字符串计数
004016F1   .E8 CA010000            call    <jmp.&MFC42.#800_CString::~CString>                     ;清除临时字符串2个
004016F6   .46                     inc   esi                                                   ;i++
004016F7   .3BF7                   cmp   esi, edi
004016F9   .^ 7C BD                  jl      short 004016B8                                          ;循环解码得到字符串 ===> "Congratulation:You've cracked me!"
004016FB   >8B4424 1C            mov   eax, dword ptr
004016FF   .6A 40                  push    40
00401701   .68 38304000            push    00403038                                                ;ASCII " Crackme"
00401706   .50                     push    eax
00401707   .8BCD                   mov   ecx, ebp
00401709   .E8 C0020000            call    <jmp.&MFC42.#4224_CWnd::MessageBoxA>                  ;显示 Crack 成功
0040170E   .EB 08                  jmp   short 00401718
00401710   >8B55 00                mov   edx, dword ptr
00401713   .8BCD                   mov   ecx, ebp
00401715   .FF52 60                call    dword ptr                                     ;MFC42.#2446_CWnd::DestroyWindow
00401718   >8D4C24 24            lea   ecx, dword ptr
0040171C   .C64424 50 07         mov   byte ptr , 7                                    ;字符串计数
00401721   .E8 9A010000            call    <jmp.&MFC42.#800_CString::~CString>
00401726   .8D4C24 28            lea   ecx, dword ptr
0040172A   .C64424 50 06         mov   byte ptr , 6
0040172F   .E8 8C010000            call    <jmp.&MFC42.#800_CString::~CString>
00401734   .8D4C24 18            lea   ecx, dword ptr
00401738   .C64424 50 05         mov   byte ptr , 5
0040173D   .E8 7E010000            call    <jmp.&MFC42.#800_CString::~CString>
00401742   .8D4C24 2C            lea   ecx, dword ptr
00401746   .C64424 50 04         mov   byte ptr , 4
0040174B   .E8 70010000            call    <jmp.&MFC42.#800_CString::~CString>
00401750   .8D4C24 1C            lea   ecx, dword ptr
00401754   .C64424 50 03         mov   byte ptr , 3
00401759   .E8 62010000            call    <jmp.&MFC42.#800_CString::~CString>
0040175E   .8D4C24 30            lea   ecx, dword ptr
00401762   .C64424 50 02         mov   byte ptr , 2
00401767   .E8 54010000            call    <jmp.&MFC42.#800_CString::~CString>
0040176C   .8D4C24 20            lea   ecx, dword ptr
00401770   .C64424 50 01         mov   byte ptr , 1
00401775   .E8 46010000            call    <jmp.&MFC42.#800_CString::~CString>
0040177A   .8D4C24 10            lea   ecx, dword ptr
0040177E   .C64424 50 00         mov   byte ptr , 0
00401783   .E8 38010000            call    <jmp.&MFC42.#800_CString::~CString>
00401788   .8D4C24 14            lea   ecx, dword ptr
0040178C   .C74424 50 FFFFFFFF   mov   dword ptr , -1
00401794   .E8 27010000            call    <jmp.&MFC42.#800_CString::~CString>
00401799   .8B4C24 48            mov   ecx, dword ptr
0040179D   .5F                     pop   edi
0040179E   .5E                     pop   esi
0040179F   .5D                     pop   ebp
004017A0   .5B                     pop   ebx
004017A1   .64:890D 00000000       mov   dword ptr fs:, ecx
004017A8   .83C4 44                add   esp, 44
004017AB   .C3                     retn
004017AC      90                     nop

根据上面对算法的分析,写出注册机,经Dev-C++调试通过,代码如下:
#include "stdio.h"
#include "String.h"

void getCode(char * name);

int main() {

    char name;

    printf("Keygen for NZC.1\n");

    printf("Input your name: ");

    gets(name);

    getCode(name);

    return 0;
}

void getCode(char * name) {
    int len = strlen(name);
    int lc = len * (char)name;
    int sum = 0;
    for (int i=0; i<len; i++) {
      sum += lc * (char)(name & 0x7F);
    }

    int sn1 = sum ^ 0x10 ^ 0x6F;
    int sn2 = sn1 + 47391894 + 2; // (sn2-sn1)>47391894, 加2是保证下面将尾数变成8后还是大于47391894
    sn2 = sn2 + (8 - (sn2 % 10)); //// 将个位数变为8
   
    printf("serial: %d-%d\n", sn1, sn2);
}

运算结果如下:
Keygen for NZC.1
Input your name: solly
serial: 340728-47732628
--------------------------------
Process exited after 1.482 seconds with return value 0
请按任意键继续. . .

我们再次在 CM 中输入上面得序列号,得到CM的提示如下:

分析完毕!
后面是脱壳后的文件信息。

solly 发表于 2019-5-29 16:30

本帖最后由 solly 于 2019-5-29 16:56 编辑

补充一下关于那个定时器的説明,定时器也是用于反调试,检查本 CM 所用的API是否被 BPX 中断,如有中断,则破坏CM代码并引起异常,导致系统关闭CM。
生成定时器的代码:
0040D467    8D8D 67214000            lea   ecx, dword ptr                            ; ecx 指向指令代码区的地址,ecx==0x0040D13B, Timer回调函数
0040D472    51                     push    ecx                                                   ; lpTimerFunc, ecx==0x0040D13B,指令区地址 TimerProc
0040D478    68 F4010000            push    1F4                                                   ; uElapse == 500ms
0040D481    6A 00                  push    0                                                       ; nIDEvent
0040D487    6A 00                  push    0                                                       ; hWnd
0040D48D    FF95 DB284000            call    dword ptr                                   ; call User32.SetTimer(0,0,500, 0x0040D13B)
0040D493    90                     nop
定时器的回调代码:
0040D13B    90                     nop                                                             ; 0x0040D467处的指令中的ecx 指向这里, 定时器回调函数 TimerProc
0040D14C    60                     pushad
0040D151    E8 05000000            call    0040D15B
0040D156    00000000               dd      0x00000000                                              ; 0x0040D1CF处指令引用这里,保存数据用
0040D15A    00                     db      0x00                                                    ; 0x0040D207处指令引用这里,保存数据用
;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0040D15B    5D                     pop   ebp                                                   ; 取得call后的返回地址
0040D160    81ED 82214000            sub   ebp, 00402182
0040D16A    33C9                     xor   ecx, ecx                                                ; i = 0
0040D170    8D940D CB284000          lea   edx, dword ptr                          ; edx=0040D89F, edx ==> API 入口
0040D17B    8B12                     mov   edx, dword ptr                                     ; ==> LoadLibraryA, GetProcAddress, MessageBoxA, ExitProcess, SetTimer, ReadProcessMemory, _lopen
0040D181    85D2                     test    edx, edx
0040D188    74 39                  je      short 0040D1C3
0040D18E    E8 5F000000            call    0040D1F2                                                ; 读取系统API(LoadLibraryA等)第1个字节,防 Int3 调试
0040D197    80BD 86214000 CC         cmp   byte ptr , 0CC                              ; != 0xCC, 反INT3调试
0040D1A3    74 28                  je      short 0040D1CD
0040D1A9    C685 86214000 00         mov   byte ptr , 0                              ; 置 0x0040D15A 为 0, 隐藏0xCC 反调试检查
0040D1B5    83C1 04                  add   ecx, 4                                                ; i++,指向下一个 API 入口
0040D1BD^ EB B1                  jmp   short 0040D170                                          ; 循环继续
0040D1C3    90                     nop                                                             ; TimerProc 检查 API 正常后,来到这里
0040D1C7    61                     popad
0040D1CC    C3                     retn                                                            ; TimerProc 结束
;----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0040D1CD    90                     nop                                                             ; TimerProc 内如果检测到 API 被 BPX 中断,则来到这里
0040D1D1    8DBD 87214000            lea   edi, dword ptr                            ; edi == 0x0040D15B, 指向上面的代码
0040D1DB    B9 91000000            mov   ecx, 91
0040D1E4    33C0                     xor   eax, eax
0040D1EA    F2:AA                  repne   stos byte ptr es:                                  ; 覆盖破坏上面的代码,共破坏0x91字节
0040D1EC    90                     nop                                                             ; 会一直覆盖到这里, 不留一点痕迹
0040D1F0    8B00                     mov   eax, dword ptr                                     ; 因为eax==0,这里是非法内存访问,导至程序会被系统关闭
;----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0040D1F2    60                     pushad                                                          ; TimerProc 函数中 0x0040D18E 处的代码调用这里
0040D1F3    B8 FFFFFF7F            mov   eax, 7FFFFFFF
0040D1FC    8D8D 82214000            lea   ecx, dword ptr                            ; ECX == 0x0040D156
0040D207    8D9D 86214000            lea   ebx, dword ptr                            ; EBX == 0x0040D15A
0040D211    51                     push    ecx                                                   ; lpNumberOfBytesRead , ReadProcMemory 返回读了多少字节数据
0040D217    6A 01                  push    1                                                       ; nSize
0040D21D    53                     push    ebx                                                   ; lpBuffer,ReadProcMemory读取的内容
0040D222    52                     push    edx                                                   ; lpBaseAddress, 指向 LoadLibraryA() 等函数第一个字节的指令
0040D227    50                     push    eax                                                   ; hProcess
0040D22C    FF95 DF284000            call    dword ptr                                   ; call ReadProcessMemory,用于防止本CM所调用API的第1个指令字节是 0xCC(int3 中断)
0040D236    61                     popad
0040D237    C3                     retn


附件是前面所有截图的打包。

pk8900 发表于 2019-5-29 21:07

帖子加到我那个索引贴里了,写这个帖子估计得半天时间吧~~

solly 发表于 2019-5-29 21:22

pk8900 发表于 2019-5-29 21:07
帖子加到我那个索引贴里了,写这个帖子估计得半天时间吧~~

还不止半天,截图处理费时。文字大部分在跟踪时就写在OD中了。

kanxue2018 发表于 2019-5-29 23:36

非常好的练习资料,感谢楼主

cpj1203 发表于 2019-5-30 01:01

受益良多

学士天下 发表于 2019-5-30 08:25

谢谢,让我好好学一下

AAQZzx 发表于 2019-5-30 10:20

支持 楼主辛苦了

wapj152321 发表于 2019-6-13 07:44

谢谢分享

solly 发表于 2019-6-13 12:12

liphily 发表于 2019-6-13 11:20
%D2%BB%BF%B4%BE%CD%B6%AE%A3%AC%D2%BB%D7%F6%BE%CD%B7%CF%A1%A3%0D%0A%D1%DB%A3%BA%CE%D2%BB%E1%C1%CB%0D% ...

看多了就会了 ;www
一看就懂,一做就废。
眼:我会了
脑:你不会{:1_935:}

页: [1] 2 3
查看完整版本: 160 个 CrackMe 之 126 - NZC.1 手动脱壳和注册算法追踪及注册机实现