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: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
帖子加到我那个索引贴里了,写这个帖子估计得半天时间吧~~
还不止半天,截图处理费时。文字大部分在跟踪时就写在OD中了。 非常好的练习资料,感谢楼主 受益良多 谢谢,让我好好学一下 支持 楼主辛苦了 谢谢分享 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:}