好友
阅读权限 35
听众
最后登录 1970-1-1
solly
发表于 2019-7-21 19:31
本帖最后由 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 位系统 下需要导入的:
[HTML] 纯文本查看 复制代码
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"
在 Win 32 位系统 下,导入注册表信息为: [HTML] 纯文本查看 复制代码
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"
其键值部分可以改成自己想要的。长度大于1,最好小于32字符。
处理好注册表后,继续向后分析,在处理完注册表信息后,CrackMe 创建了一个标准 Windows API 窗口程序,因此,我们来找其 Windows 窗口消息回调函数 WndProc,再次回到 WinMain() 入口(0x00403270),在其后面就可看到 WndProc 函数(0x00403400),如下图第4行:
跟随 WndProc,来到其入口:
具体框架代码如下所示:
[Asm] 纯文本查看 复制代码
00403400 . 8B4C24 08 mov ecx, dword ptr [esp+8]
00403404 . 56 push esi
00403405 . 83F9 01 cmp ecx, 1 ; WM_CREATE; Switch (cases 1..111)
00403408 . 74 2F je short 00403439
0040340A . 83F9 02 cmp ecx, 2 ; WM_DESTROY
0040340D . 0F84 84000000 je 00403497
00403413 . 81F9 11010000 cmp ecx, 111 ; WM_COMMAND
00403419 . 0F84 86000000 je 004034A5
0040341F . 8B4424 14 mov eax, dword ptr [esp+14] ; Default case of switch 00403405
00403423 . 8B5424 10 mov edx, dword ptr [esp+10]
00403427 . 8B7424 08 mov esi, dword ptr [esp+8]
0040342B . 50 push eax ; /lParam
0040342C . 52 push edx ; |wParam
0040342D . 51 push ecx ; |Message
0040342E . 56 push esi ; |hWnd
0040342F . FF15 D4124100 call dword ptr [<&user32.DefWindowProcA>] ; \DefWindowProcA
00403435 . 5E pop esi
00403436 . C2 1000 retn 10
先看看初始化的 WM_CREATE 消息处理,跟随 je 0x00403439,如下:
可以看到,最后一段代码向自己 Post 了一条自定义的 WM_COMMAND 消息,其wParam == 0x77777777。如下所示:
[Asm] 纯文本查看 复制代码
00403473 > \8B7424 08 mov esi, dword ptr [esp+8]
00403477 . 6A 00 push 0 ; /lParam = 0
00403479 . C680 C8EC4000 00 mov byte ptr [eax+40ECC8], 0 ; |
00403480 . 68 77777777 push 77777777 ; |wParam = 77777777
00403485 . 68 11010000 push 111 ; |Message = WM_COMMAND
0040348A . 56 push esi ; |hWnd
0040348B . FF15 D8124100 call dword ptr [<&user32.PostMessageA>] ; \PostMessageA
00403491 . 33C0 xor eax, eax
既然是 WM_COMMAND 消息,我们回到 WndProc,跟随 WM_COMMAND 消息处理的 je 0x004034A5,如下:
代码很简单,只要判断出 wParam == 0x77777777,就创建一个对话框,并显示,这个就主界面对话框,并且,当对话框关闭后,就调用 DestroyWindow() 退出 CrackMe。如下所示:
[Asm] 纯文本查看 复制代码
004034A5 > \817C24 10 77777777 cmp dword ptr [esp+10], 77777777 ; Case 111 (WM_COMMAND) of switch 00403405
004034AD . 75 21 jnz short 004034D0
004034AF . 8B7424 08 mov esi, dword ptr [esp+8]
004034B3 . 6A 00 push 0 ; /lParam = NULL
004034B5 . 68 E0344000 push 004034E0 ; |DlgProc = [url=mailto:Torn@do_.004034E0]Torn@do_.004034E0[/url]
004034BA . A1 B0EA4000 mov eax, dword ptr [40EAB0] ; |
004034BF . 56 push esi ; |hOwner
004034C0 . 6A 65 push 65 ; |pTemplate = 65
004034C2 . 50 push eax ; |hInst => NULL
004034C3 . FF15 94124100 call dword ptr [<&user32.DialogBoxParamA>] ; \DialogBoxParamA
004034C9 . 56 push esi ; /hWnd
004034CA . FF15 E0124100 call dword ptr [<&user32.DestroyWindow>] ; \DestroyWindow
004034D0 > 33C0 xor eax, eax
004034D2 . 5E pop esi
004034D3 . C2 1000 retn 10
从上面的图中可以到,DlgProc 为 0x004034E0,我们跟随立即数,来到 DlgProc,如下图所示:
具体代码如下:
[Asm] 纯文本查看 复制代码
004034E0 . 8B4424 08 mov eax, dword ptr [esp+8]
004034E4 . 56 push esi
004034E5 . 83F8 02 cmp eax, 2 ; WM_DESTROY; Switch (cases 2..111)
004034E8 . 74 70 je short 0040355A
004034EA . 3D 10010000 cmp eax, 110 ; WM_INITDIALOG
004034EF . 74 0D je short 004034FE
004034F1 . 3D 11010000 cmp eax, 111 ; WM_COMMAND
004034F6 . 74 46 je short 0040353E
004034F8 . 33C0 xor eax, eax ; Default case of switch 004034E5
004034FA . 5E pop esi
004034FB . C2 1000 retn 10
先看看 WM_INITDIALOG 消息处理过程,跟随 je 004034FE,如下:
可以看到,最后有一段如下代码:
[Asm] 纯文本查看 复制代码
0040351A . 6A 00 push 0 ; /Enable = FALSE, 高置成禁用状态
0040351C . 68 EC030000 push 3EC ; |/ControlID = 3EC (1004.)
00403521 . 56 push esi ; ||hWnd
00403522 . FF15 A0124100 call dword ptr [<&user32.GetDlgItem>] ; |\GetDlgItem
00403528 . 50 push eax ; |hWnd
00403529 . FF15 9C124100 call dword ptr [<&user32.EnableWindow>] ; \EnableWindow
0040352F . 56 push esi
00403530 . E8 5BE6FFFF call 00401B90 ; 注册验证
00403535 . 83C4 04 add esp, 4
00403538 . 33C0 xor eax, eax
调用 EnableWindow() 将 “Request” 按钮设置成禁用状态了。
然后,调用 call 0x00401B90 函数,这个函数就是注册文件校验函数,后面具体分析,先跟随进去看看:
我们在函数开始点下一个断点 。等下动态分析时,便会断下来。
我们再回到 DlgProc,找到 WM_COMMAND 消息处理代码(0x0040353E)如下:
[Asm] 纯文本查看 复制代码
0040353E > \8B4424 10 mov eax, dword ptr [esp+10] ; Case 111 (WM_COMMAND) of switch 004034E5
00403542 . 25 FFFF0000 and eax, 0FFFF
00403547 . 83F8 02 cmp eax, 2 ; Switch (cases 2..3ED)
0040354A . 74 20 je short 0040356C
0040354C . 3D EC030000 cmp eax, 3EC
00403551 . 74 2F je short 00403582
00403553 . 3D ED030000 cmp eax, 3ED
00403558 . 74 72 je short 004035CC
0040355A > A1 48EA4000 mov eax, dword ptr [40EA48] ; Default case of switch 00403547
0040355F . 50 push eax ; /hObject => NULL
00403560 . FF15 EC114100 call dword ptr [<&gdi32.DeleteObject>] ; \DeleteObject
00403566 . 33C0 xor eax, eax
00403568 . 5E pop esi
00403569 . C2 1000 retn 10
0040356C > 8B7424 08 mov esi, dword ptr [esp+8] ; Case 2 of switch 00403547
00403570 . 6A 00 push 0 ; /Result = 0
00403572 . 56 push esi ; |hWnd
00403573 . FF15 C4124100 call dword ptr [<&user32.EndDialog>] ; \EndDialog
00403579 . B8 01000000 mov eax, 1
0040357E . 5E pop esi
0040357F . C2 1000 retn 10
00403582 > 66:833D 84EA4000 6>cmp word ptr [40EA84], 63 ; Case 3EC of switch 00403547
0040358A . 6A 00 push 0 ; /Enable = FALSE
0040358C . 74 1D je short 004035AB ; |
0040358E . 8B7424 0C mov esi, dword ptr [esp+C] ; |
00403592 . 68 EC030000 push 3EC ; |/ControlID = 3EC (1004.)
00403597 . 56 push esi ; ||hWnd
00403598 . FF15 A0124100 call dword ptr [<&user32.GetDlgItem>] ; |\GetDlgItem
0040359E . 50 push eax ; |hWnd
0040359F . FF15 9C124100 call dword ptr [<&user32.EnableWindow>] ; \EnableWindow
004035A5 . 33C0 xor eax, eax
004035A7 . 5E pop esi
004035A8 . C2 1000 retn 10
004035AB > 8B7424 0C mov esi, dword ptr [esp+C] ; |
004035AF . 68 E0364000 push 004036E0 ; |DlgProc = [url=mailto:Torn@do_.004036E0]Torn@do_.004036E0[/url]
004035B4 . 56 push esi ; |hOwner
004035B5 . A1 B0EA4000 mov eax, dword ptr [40EAB0] ; |
004035BA . 6A 68 push 68 ; |pTemplate = 68
004035BC . 50 push eax ; |hInst => NULL
004035BD . FF15 94124100 call dword ptr [<&user32.DialogBoxParamA>] ; \DialogBoxParamA
004035C3 . B8 01000000 mov eax, 1
004035C8 . 5E pop esi
004035C9 . C2 1000 retn 10
004035CC > 8B7424 08 mov esi, dword ptr [esp+8] ; Case 3ED of switch 00403547
004035D0 . 6A 00 push 0 ; /lParam = NULL
004035D2 . 68 F0354000 push 004035F0 ; |DlgProc = [url=mailto:Torn@do_.004035F0]Torn@do_.004035F0[/url]
004035D7 . A1 B0EA4000 mov eax, dword ptr [40EAB0] ; |
004035DC . 56 push esi ; |hOwner
004035DD . 6A 67 push 67 ; |pTemplate = 67
004035DF . 50 push eax ; |hInst => NULL
004035E0 . FF15 94124100 call dword ptr [<&user32.DialogBoxParamA>] ; \DialogBoxParamA
004035E6 . B8 01000000 mov eax, 1
004035EB . 5E pop esi
004035EC . C2 1000 retn 10
上面代码有一关键判断,如下所示:
[Asm] 纯文本查看 复制代码
00403582 > \66:833D 84EA4000 6>cmp word ptr [40EA84], 63 ; "Reqest" Handler,[0x0040EA84] == 0x63 表示注册成功; Case 3EC of switch 00403547
0040358A . 6A 00 push 0 ; /Enable = FALSE
0040358C . 74 1D je short 004035AB ; |如果 [0x0040EA84] == 0x63 则去显示成功对话框,不相等则将 Request 设为禁用状态
可见注册成功的标志就是 [0x0040EA84] == 0x63。
下面的进一步动态分析 CrackMe 的行为,首先看看注册表读写部分。
[Asm] 纯文本查看 复制代码
; 读取注册表 ProductId 键值
;
004032E8 68 84D74000 push 0040D784 ; ASCII "ProductId"
004032ED 68 58D74000 push 0040D758 ; ASCII "SOFTWARE\Microsoft\Windows\CurrentVersion"
004032F2 68 02000080 push 80000002
004032F7 E8 A4E6FFFF call 004019A0 ; 读取键值,eax 为返回地址
004032FC 83C4 0C add esp, 0C
004032FF BF 60D14000 mov edi, 0040D160 ; 保存 ProductId 的缓冲区
00403304 50 push eax ; str
00403305 68 28D74000 push 0040D728 ; ASCII "%s"
0040330A 68 60D14000 push 0040D160 ; buffer
0040330F FF15 B8124100 call dword ptr [4112B8] ; USER32.wsprintfA,将 ProductId 存在 [0x0040D160]
00403315 66:BA 0100 mov dx, 1
00403319 83C4 0C add esp, 0C
0040331C B9 FFFFFFFF mov ecx, -1
00403321 2BC0 sub eax, eax
00403323 F2:AE repne scas byte ptr es:[edi]
00403325 F7D1 not ecx
00403327 49 dec ecx ; 取得 ProductId 的长度
00403328 83F9 01 cmp ecx, 1
0040332B 72 2C jb short 00403359 ; 小于1字节表示出错
0040332D 0FBFC2 movsx eax, dx
00403330 66:42 inc dx
00403332 BF 60D14000 mov edi, 0040D160 ; ProductID 字符串
00403337 8A88 5FD14000 mov cl, byte ptr [eax+40D15F]
0040333D 80C1 05 add cl, 5 ; 加密 ProductID 字符串,就是每个字符的 ASCII 码值加上5
00403340 8888 C7D14000 mov byte ptr [eax+40D1C7], cl ; 在 [0x0040D1C6] 存入加密后的 ProductID
00403346 B9 FFFFFFFF mov ecx, -1
0040334B 2BC0 sub eax, eax
0040334D F2:AE repne scas byte ptr es:[edi]
0040334F 0FBFC2 movsx eax, dx
00403352 F7D1 not ecx
00403354 49 dec ecx ; 加密的数据长度
00403355 3BC8 cmp ecx, eax
00403357 ^ 73 D4 jnb short 0040332D
从注册表读取 ProductId 后,会进行加密,加密后 ProductId 变成 "6789:26789:;<=267892:;<= "。
原始的 ProductId 为:"12345-12345678-1234-5678 "。
前面我们在文件校验函数下了断点,CrackMe 初始化时就会调用,OD便会断下来,代码动态分析如下:
[Asm] 纯文本查看 复制代码
00401B90 6A 00 push 0
00401B92 E8 99FEFFFF call 00401A30 ; 无用的验证
00401B97 83C4 04 add esp, 4
00401B9A 6A 00 push 0
00401B9C E8 9FF7FFFF call 00401340 ; 无用的验证
00401BA1 83C4 04 add esp, 4
00401BA4 6A 00 push 0
00401BA6 E8 85F8FFFF call 00401430 ; 无用的验证
00401BAB 83C4 04 add esp, 4
00401BAE 6A 00 push 0
00401BB0 E8 ABF9FFFF call 00401560 ; 无用的验证
00401BB5 83C4 04 add esp, 4
00401BB8 6A 00 push 0
00401BBA E8 21FBFFFF call 004016E0 ; 无用的验证
00401BBF 83C4 04 add esp, 4
00401BC2 6A 00 push 0
00401BC4 E8 87FBFFFF call 00401750 ; 无用的验证
00401BC9 83C4 04 add esp, 4
00401BCC 6A 00 push 0
00401BCE E8 DDFBFFFF call 004017B0 ; 无用的验证
00401BD3 83C4 04 add esp, 4
00401BD6 6A 00 push 0
00401BD8 E8 63FCFFFF call 00401840 ; 无用的验证
00401BDD 83C4 04 add esp, 4
00401BE0 6A 00 push 0
00401BE2 E8 09FDFFFF call 004018F0 ; 无用的验证
00401BE7 83C4 04 add esp, 4
00401BEA 6A 00 push 0
00401BEC E8 3F010000 call 00401D30 ; 判断 CrackMe 目录中是否存在文件 "REGISTRATION.DAT"
00401BF1 83C4 04 add esp, 4
00401BF4 85C0 test eax, eax ; eax == 0 表示文件不存在
00401BF6 0F84 1C010000 je 00401D18
00401BFC 6A 00 push 0
00401BFE E8 BD010000 call 00401DC0 ; 判断 CrackMe 目录中文件 "REGISTRATION.DAT" 的长度是否为 1024 字节
00401C03 83C4 04 add esp, 4
00401C06 85C0 test eax, eax ; eax == 0 表示文件长度不正确
00401C08 0F84 0A010000 je 00401D18
00401C0E 6A 00 push 0
00401C10 E8 1BFEFFFF call 00401A30 ; 判断注册文件内容 file_content[153] 开始的null结尾的字符串长度大于3字节, 小于 32 字节
00401C15 83C4 04 add esp, 4
00401C18 85C0 test eax, eax
00401C1A 0F84 F8000000 je 00401D18
00401C20 6A 00 push 0
00401C22 E8 69020000 call 00401E90 ; 判断 注册文件 前10字节必须为 06 0A 15 07 13 10 0A 72 0C 00
00401C27 83C4 04 add esp, 4
00401C2A 85C0 test eax, eax
00401C2C 0F84 E6000000 je 00401D18
00401C32 6A 00 push 0
00401C34 E8 47030000 call 00401F80 ; 注册文件第11~17字节判断,必须等于串:07 20 34 3E 09 07 0A
00401C39 83C4 04 add esp, 4
00401C3C 85C0 test eax, eax
00401C3E 0F85 D4000000 jnz 00401D18
00401C44 6A 00 push 0
00401C46 E8 E5040000 call 00402130 ; 注册文件第18~21字节判断,必须等于串:08 00 00 05
00401C4B 83C4 04 add esp, 4
00401C4E 85C0 test eax, eax
00401C50 0F84 C2000000 je 00401D18
00401C56 6A 00 push 0
00401C58 E8 03060000 call 00402260 ; 注册文件第22~38字节判断:08 04 00 12 0A 12 03 12 02 2C 43 08 02 2A 0A 44 54
00401C5D 83C4 04 add esp, 4
00401C60 85C0 test eax, eax
00401C62 0F85 B0000000 jnz 00401D18
00401C68 6A 00 push 0
00401C6A E8 41090000 call 004025B0 ; 注册文件第81~84字节处理,转换成时间(年份):(file_content[82]*100 + file_content[83])),大于或等于 1999, 并且(file_content[82]*100 + file_content[83])> 2019(系统当前年份)
00401C6F 83C4 04 add esp, 4
00401C72 85C0 test eax, eax
00401C74 0F84 9E000000 je 00401D18
00401C7A 6A 00 push 0
00401C7C E8 0F0B0000 call 00402790 ; 检查 file_content[84] <= 0x17, file_content[85] <= 0x3B,时 : 分
00401C81 83C4 04 add esp, 4
00401C84 85C0 test eax, eax
00401C86 0F84 8C000000 je 00401D18
00401C8C 6A 00 push 0
00401C8E E8 0D0C0000 call 004028A0 ; file_content[208]~file_content[296] 为字符串:"SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!"
00401C93 83C4 04 add esp, 4
00401C96 85C0 test eax, eax
00401C98 74 7E je short 00401D18
00401C9A 6A 00 push 0
00401C9C E8 FF0C0000 call 004029A0 ; file_content[336] 开始为 ProductID 的加密字符串(根据注册表中的字符串数据变量)
00401CA1 83C4 04 add esp, 4
00401CA4 85C0 test eax, eax
00401CA6 75 70 jnz short 00401D18
00401CA8 6A 00 push 0
00401CAA E8 110E0000 call 00402AC0 ; 注册文件前100字节校验和检查,并且 ((checkSum xor 1999)/10) 小于 128,等于 file_content[944] 的值
00401CAF 83C4 04 add esp, 4
00401CB2 85C0 test eax, eax
00401CB4 74 62 je short 00401D18
00401CB6 6A 00 push 0
00401CB8 E8 230F0000 call 00402BE0 ; 同上一函数,并加上验证 file_content[945] 的值
00401CBD 83C4 04 add esp, 4
00401CC0 85C0 test eax, eax
00401CC2 74 54 je short 00401D18
00401CC4 6A 00 push 0
00401CC6 E8 45100000 call 00402D10 ; 功能同上一函数,校验和与 file_content[946]比较
00401CCB 83C4 04 add esp, 4
00401CCE 85C0 test eax, eax
00401CD0 74 46 je short 00401D18
00401CD2 6A 00 push 0
00401CD4 E8 87110000 call 00402E60 ; 对前面3个校验和进行校验,校验结果等于 file_content[947] 的值
00401CD9 83C4 04 add esp, 4
00401CDC 85C0 test eax, eax
00401CDE 74 38 je short 00401D18
00401CE0 6A 00 push 0
00401CE2 E8 89120000 call 00402F70 ; 注册文件前384字节校验和检查,校验结果等于 file_content[948] 的值
00401CE7 83C4 04 add esp, 4
00401CEA 85C0 test eax, eax
00401CEC 75 2A jnz short 00401D18
00401CEE 6A 00 push 0
00401CF0 E8 8B130000 call 00403080 ; 对前面5次校验结果的和进行校验,file_content[949] == sqrt(sum(checkSum1+checkSum2+checkSum3+checkSum4+checkSum5))
00401CF5 83C4 04 add esp, 4
00401CF8 85C0 test eax, eax
00401CFA 74 1C je short 00401D18
00401CFC 6A 00 push 0
00401CFE E8 8D140000 call 00403190 ; 检查注册文件的最后一个字符 file_content[1023] 为 'R', 即 0x52
00401D03 83C4 04 add esp, 4
00401D06 85C0 test eax, eax
00401D08 74 0E je short 00401D18
00401D0A 8B4424 04 mov eax, dword ptr [esp+4] ; hDlg
00401D0E 50 push eax
00401D0F E8 CCF5FFFF call 004012E0 ; 设置注册成功的信息
00401D14 83C4 04 add esp, 4
00401D17 C3 retn
00401D18 8B4424 04 mov eax, dword ptr [esp+4]
00401D1C 50 push eax
00401D1D E8 6EF5FFFF call 00401290 ; 设置注册失败的信息
00401D22 83C4 04 add esp, 4
00401D25 C3 retn
总共有10多次验证,我们把注册文件当作一个字符数组来説明,file_content[] 表示这个校验文件的内容,下标为文件内偏移量。
[HTML] 纯文本查看 复制代码
(1)以下等式全部要成立(call 00401E90)
file_content[0] == 0x06
file_content[1] == 0x0A
file_content[2] == 0x15
file_content[3] == 0x07
file_content[4] == 0x13
file_content[5] == 0x10
file_content[6] == 0x0A
file_content[7] == 0x72
file_content[8] == 0x0C
file_content[9] == 0x00
(2)以下全部相等(call 00401F80)
file_content[10] == 0x07
file_content[11] == 0x20
file_content[12] == 0x34
file_content[13] == 0x3E
file_content[14] == 0x09
file_content[15] == 0x07
file_content[16] == 0x0A
(3) (call 00402130)
file_content[17] == 0x08
file_content[18] == 0x00
file_content[19] == 0x00
file_content[20] == 0x05
(4) (call 00402260)
file_content[21] == 0x08
file_content[22] == 0x04
file_content[23] == 0x00
file_content[24] == 0x12
file_content[25] == 0x0A
file_content[26] == 0x12
file_content[27] == 0x03
file_content[28] == 0x12
file_content[29] == 0x02
file_content[30] == 0x2C
file_content[31] == 0x43
file_content[32] == 0x08
file_content[33] == 0x02
file_content[34] == 0x2A
file_content[35] == 0x0A
file_content[36] == 0x44
file_content[37] == 0x54
(5) (call 004025B0)
注册文件第81~84字节处理,转换成时间(年份),大于或等于 1999
file_content[80] 无判断
(file_content[82]*100 + file_content[83])> 2019(当前年份)
file_content[81]>=0x04, 4, 月份
(6) (call 00402790)
检查 file_content[84] <= 0x17, file_content[85] <= 0x3B,0x17 == 23,0x3B == 59,这个校验就是 小于 “23时59分” 的意思
(7) (call 004028A0)
file_content[208]~file_content[296] 为字符串:"SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!"
CrackMe 这段文字保存在CrackMe的以下内存区域:
0040D100 53 55 50 50 4F 52 54 20 54 48 45 20 53 4F 46 54 SUPPORT THE SOFT
0040D110 57 41 52 45 20 41 55 54 48 4F 52 53 20 42 59 20 WARE AUTHORS BY
0040D120 42 55 59 49 4E 47 20 54 48 45 20 50 52 4F 47 52 BUYING THE PROGR
0040D130 41 4D 53 20 49 46 20 59 4F 55 20 55 53 45 20 54 AMS IF YOU USE T
0040D140 48 45 4D 20 41 46 54 45 52 20 43 52 41 43 4B 49 HEM AFTER CRACKI
0040D150 4E 47 20 54 48 45 4D 21 NG THEM!
(8) (call 004029A0)
file_content[336] 开始为变形后的注册表值 ProductID (ASCII "6789:26789:;<=267892:;<=")
(9) (call 00402AC0)
注册文件前100字节校验和检查,并且 ((checkSum xor 1999)/10) 小于 128,等于 file_content[944] 的值
(10) (call 00402BE0)
同上一函数,并加上验证 file_content[945] 的值
(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,设置注册成功状态,如下代码:
[Asm] 纯文本查看 复制代码
004012E0 56 push esi
004012E1 6A 01 push 1 ; 将 "Request" 按钮设置为可用状态
004012E3 8B7424 0C mov esi, dword ptr [esp+C] ; hDlg
004012E7 68 EC030000 push 3EC ; "Request" ControlID
004012EC 56 push esi
004012ED FF15 A0124100 call dword ptr [4112A0] ; USER32.GetDlgItem
004012F3 50 push eax
004012F4 FF15 9C124100 call dword ptr [41129C] ; USER32.EnableWindow
004012FA 66:C705 84EA4000 6300 mov word ptr [40EA84], 63 ; 设置注册成功标志
00401303 A1 B4D04000 mov eax, dword ptr [40D0B4] ; 校验次数 0x0F
00401308 8B15 B0EA4000 mov edx, dword ptr [40EAB0] ; hInstance
0040130E 66:05 C800 add ax, 0C8
00401312 0FB7C8 movzx ecx, ax
00401315 51 push ecx ; ResourceID: 0x00D7
00401316 52 push edx ; hInstance
00401317 FF15 E8124100 call dword ptr [4112E8] ; USER32.LoadIconA
0040131D 50 push eax
0040131E 6A F2 push -0E ; GCL_HICON, Replaces a handle to the icon associated with the class.
00401320 56 push esi
00401321 FF15 EC124100 call dword ptr [4112EC] ; USER32.SetClassLongA
00401327 6A 00 push 0
00401329 8B0D B0EA4000 mov ecx, dword ptr [40EAB0] ; hInstance
0040132F 68 E0364000 push 004036E0 ; DlgProc
00401334 56 push esi
00401335 6A 68 push 68
00401337 51 push ecx
00401338 FF15 94124100 call dword ptr [411294] ; call USER32.DialogBoxParamA(),显示成功注册对话框
0040133E 5E pop esi
0040133F C3 retn
其中 [0x0040D0B4] 为校验次数,如果全部成功,则 [0x0040D0B4] == 0x0F,如果强行暴破跳过前面的校验,这个值就不正确,因为这个值是在前面10多个校验函数中修改的。
并且在 0x004012FA 处设置 [0x0040EA84] 的值为 0x63,表示注册校验成功!!!
分析就到这里,下面是注册机源码,使用 Dev-C++ 调试通过:
[Asm] 纯文本查看 复制代码
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <windows.h>
int checkValue1 = 0; /// 0x0040D1A8
int checkValue2 = 0; /// 0x0040D1AC
int checkValue3 = 0; /// 0x0040D1B0
int checkValue4 = 0; /// 0x0040D1B4
int checkValue5 = 0; /// 0x0040D1B8
int checkCode1 = 0; /// 0x0040EA94
int checkCode2 = 0; /// 0X0040EABC
char ProductID[256]; /// 操作系统ID,Windows9x系列才有,Windows7以后无此注册表键值
char regFile[1024];
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
BOOL IsWow64();
int makeRegistryFile();
int getProductID(char * defaultID);
int makeRegInfo();
int saveRegInfo();
int main(int argc, char** argv) {
memset(ProductID, 0, 256);
memset(regFile, 0, 1024);
/**
Windows 32位系统
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"
Windows 64位系统
[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion]
"ProductID"="12345-12345678-1234-5678"
**/
char productID[] = "12345-12345678-1234-5678"; /// 注册表键值
getProductID(productID);
makeRegInfo();
saveRegInfo();
return 0;
}
int getProductID(char * defaultID) {
/// "12345-12345678-1234-5678" ===> "6789:26789:;<=267892:;<="
char key[] = "ProductId";
char subKey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion";
HKEY h = HKEY_LOCAL_MACHINE; /// 0x80000002
HKEY hKey = (HKEY)0xFFFFFFFF;
DWORD keyType, cbData = 0, n = 0;
DWORD s = RegOpenKeyExA(h, subKey, 0, KEY_READ, &hKey);
if(s == ERROR_SUCCESS) {
s = RegQueryValueExA(hKey, key, NULL, NULL, NULL, &cbData); /// 返回缓冲区大小
if(s == ERROR_SUCCESS) {
if(cbData>255) {
cbData = 255; //// 防止溢出
}
n = cbData;
s = RegQueryValueExA(hKey, key, NULL, &keyType, (LPBYTE)ProductID, &cbData);
}
}
RegCloseKey(hKey);
if(n<=0) {
strcpy(ProductID, defaultID);
printf("ERROR: ProductID is NOT EXISTS in Registry.\n");
makeRegistryFile(); //// 生成注册表文件
}
return 0;
}
int makeRegistryFile() {
char registry_x86[] = "Windows Registry Editor Version 5.00\n\n[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion]\n\"ProductID\"=\"12345-12345678-1234-5678\"\n";
char registry_x64[] = "Windows Registry Editor Version 5.00\n\n[HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion]\n\"ProductID\"=\"12345-12345678-1234-5678\"\n";
char * reg = NULL;
if(IsWow64()) {
reg = registry_x64; //// 64位系统
} else {
reg = registry_x86; //// 32位系统
}
FILE * f = fopen("ProductID.reg", "w");
int n = fwrite(reg, strlen(reg), 1, f);
fflush(f);
fclose(f);
if(n==1) {
printf("Make registry file successed, please import registry file: ProductID.reg\n");
}
return 0;
}
BOOL IsWow64() {
BOOL bIsWow64 = FALSE;
//IsWow64Process is not available on all supported versions of Windows.
//Use GetModuleHandle to get a handle to the DLL that contains the function
//and GetProcAddress to get a pointer to the function if available.
fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
if(NULL != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
//handle error
printf("get os bit error\n"); /// 出错了
}
}
return bIsWow64;
}
int makeRegInfo() {
/// call 00401A30 ; 判断注册文件内容 file_content[153] 开始的null结尾的字符串长度大于3字节, 小于 31 字节
/// 字符串内容不限,只要长度大于3字节, 小于 31 字节即可。
char * p1 = & regFile[153];
strcpy(p1, "[Cracked by solly, 2019-07-11.]"); /// 注意:长度不能大于31个字符,否则会栈缓冲溢出,覆盖函数返回地址。
/// call 00401E90 ; 判断 注册文件 前10字节必须为 06 0A 15 07 13 10 0A 72 0C 00
regFile[0] = 0x06;
regFile[1] = 0x0A;
regFile[2] = 0x15;
regFile[3] = 0x07;
regFile[4] = 0x13;
regFile[5] = 0x10;
regFile[6] = 0x0A;
regFile[7] = 0x72;
regFile[8] = 0x0C;
regFile[9] = 0x00;
/// call 00401F80 ; 注册文件第11~17字节判断,必须等于串:07 20 34 3E 09 07 0A
regFile[10] = 0x07;
regFile[11] = 0x20;
regFile[12] = 0x34;
regFile[13] = 0x3E;
regFile[14] = 0x09;
regFile[15] = 0x07;
regFile[16] = 0x0A;
/// call 00402130 ; 注册文件第18~21字节判断,必须等于串:08 00 00 05
regFile[17] = 0x08;
regFile[18] = 0x00;
regFile[19] = 0x00;
regFile[20] = 0x05;
/// call 00402260 ; 注册文件第22~38字节判断:08 04 00 12 0A 12 03 12 02 2C 43 08 02 2A 0A 44 54
regFile[21] = 0x08;
regFile[22] = 0x04;
regFile[23] = 0x00;
regFile[24] = 0x12;
regFile[25] = 0x0A;
regFile[26] = 0x12;
regFile[27] = 0x03;
regFile[28] = 0x12;
regFile[29] = 0x02;
regFile[30] = 0x2C;
regFile[31] = 0x43;
regFile[32] = 0x08;
regFile[33] = 0x02;
regFile[34] = 0x2A;
regFile[35] = 0x0A;
regFile[36] = 0x44;
regFile[37] = 0x54;
/// call 004025B0 ; 注册文件第81~84字节处理,转换成时间的年份(file_content[82]*100 + file_content[83])),大于或等于 1999,
/// 实际上 (file_content[82]*100 + file_content[83]) > 2019(系统当前年份) 才可以。
/// 以下填充的是 9999-12-31
regFile[80] = 0x1F; // 31 /// 日
regFile[81] = 0x0C; // 12 /// 月
regFile[82] = 0x63; // 99 /// 年
regFile[83] = 0x63; // 99 /// 年
/// call 00402790 ; 检查 file_content[84] <= 0x17, file_content[85] <= 0x3B
regFile[84] = 0x17; // 23 /// 时
regFile[85] = 0x3B; // 59 /// 分
//regFile[86] = 0x3B; // 59 /// 秒,可不填充,没有用到
regFile[86] = 0x1E; // 30 /// 秒,不能填充 0x3B,因为刚好会导致后面的检查码为 0x1A,引起 fread()函数出错,读取文件不完整
/// call 004028A0 ; file_content[208]~file_content[296] 为固定字符串:
/// "SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!"
char * p2 = & regFile[208];
strcpy(p2, "SUPPORT THE SOFTWARE AUTHORS BY BUYING THE PROGRAMS IF YOU USE THEM AFTER CRACKING THEM!");
/// call 004029A0 ; file_content[336] 开始为 ProductID 的加密字符串
int n = strlen(ProductID); /// 注册表中的产品ID(明文)
for(int i=0; i<n; i++) {
regFile[336 + i] = ProductID[i] + 5; /// 保存密文,加密就是 ASCII 码 + 0x05
}
/// call 00402AC0 ; 注册文件前100字节校验和检查,
/// 并且 ((checkSum xor 1999)/10) 小于 128,等于 file_content[944] 的值
int sum1 = 0;
for(int i=0; i<100; i++) { //// 0x64
sum1 += (int)regFile[i];
}
int check1 = checkCode1 * checkCode1;
int check2 = (checkCode2 >= 10) ? checkCode2 : 10;
sum1 = ((sum1 ^ 1999) | check1) / check2;
int j = 0x30;
while(sum1 > 127) {
sum1 -= 127;
regFile[j++] = -127; //// 校正校验值 < 128
}
checkValue1 = sum1;
regFile[944] = sum1;
/// call 00402BE0 ; 同上一函数,并加上验证 file_content[945] 的值
int sum2 = sum1 * sum1 / 0x54;
checkValue2 = sum2;
regFile[945] = sum2;
/// call 00402D10 ; 功能同上一函数,校验和与file_content[946]比较
int sum3 = 0;
for(int i=0; i<384; i++) { /// 0x180
sum3 += (int)regFile[i];
}
// int k = 0x130;
// while(sum3 > 127) {
// sum3 -= 127;
// regFile[k++] = -127; //// 校正校验值 < 128
// }
sum3 = (sum3 ^ (sum2 * sum1)) / 534; /// 0x216;
checkValue3 = sum3;
regFile[946] = sum3;
/// call 00402E60 ; 对前面3个校验和进行校验,校验结果等于 file_content[947] 的值
int sum4 = (checkValue1 + checkValue2 + checkValue3);
sum4 *= (checkValue3 / checkValue2);
sum4 *= (checkValue1 / checkValue2);
sum4 /= 10;
checkValue4 = sum4;
regFile[947] = sum4;
/// call 00402F70 ; 注册文件前384字节校验和检查,校验结果等于 file_content[948] 的值
int sum5 = 0;
for (int i=0; i<384; i++) { /// 0x180
sum5 += (int)regFile[i];
}
sum5 = (sum5 ^ 0xFF) / 500; /// 0x01F4
checkValue5 = sum5;
regFile[948] = sum5;
/// call 00403080 ; 对前5次校验结果的和进行校验,
/// file_content[949] == sqrt(sum(checkSum1+checkSum2+checkSum3+checkSum4+checkSum5))
int sum6 = checkValue1 + checkValue2 + checkValue3 + checkValue4 + checkValue5;
sum6 = (int)sqrt(sum6);
regFile[949] = sum6;
/// call 00403190 ; 检查注册文件的最后一个字符 file_content[1023] 为 'R', 即 0x52
regFile[1023] = 'R'; /// 0x52
}
int saveRegInfo() {
FILE * f = fopen("REGISTRATION.DAT", "wb");
size_t n = fwrite(regFile, 1024, 1, f);
fflush(f);
fclose(f);
if(n == 1) {
printf("save registration data file successed! \ncopy \"REGISTRATION.DAT\" to directory of CrackMe.\n");
} else {
printf("save registration data file failured!\n");
}
}
运行注册机,生成注册文件“REGISTRATION.DAT”,并放在与 CrackMe 相同的目录中,启动 CrackMe,首先就会弹出注册成功的对话框,如下所示:
点“OK”后,进入主界面,如下图所示:
其"Request"按钮的状态不再是“禁用”状态了。
完毕!!!
附注:在Windows XP 下脱壳时,会报一个错误,如下:
脱壳代码中有一个 call 做了校验,脱壳时跳过这个 call 调用即可避免报错并能完成脱壳。
附件是前面校验函数调用的子函数分析,太长了,不贴出来了,当作附件下载。
免费评分
参与人数 8 威望 +2
吾爱币 +13
热心值 +8
收起
理由
小_Ta
+ 1
+ 1
谢谢@Thanks!
Hmily
+ 2
+ 7
+ 1
感谢发布原创作品,吾爱破解论坛因你更精彩!
pk8900
+ 1
+ 1
用心讨论,共获提升!写的非常详细!
fei8255
+ 1
+ 1
用心讨论,共获提升!
天空藍
+ 1
+ 1
用心讨论,共获提升!
kwan8888
+ 1
+ 1
欢迎分析讨论交流,吾爱破解论坛有你更精彩!
GKQ
+ 1
+ 1
欢迎分析讨论交流,吾爱破解论坛有你更精彩!
FleTime
+ 1
欢迎分析讨论交流,吾爱破解论坛有你更精彩!
查看全部评分