160 个 CrackMe 之 159 - Torn@do.2 脱壳修复、注册分析及注册机
本帖最后由 solly 于 2019-7-15 23:58 编辑160 个 CrackMe 之 159 - Torn@do.2 是一个加了壳的 CrackMe ,文件信息如下:
显示未知的壳,用 "scan /t" 重新扫描一下:
这次显示为 "EncryptPE" 了。而且其文件末尾为字符串“eEnCrypter! :) 2nd&mi”,没见过,只有手动脱壳了。
节信息如下:
看起来与我原来脱过的 043 Ding Boy 的壳很象,也是很多节,并依次对下一节解码,也就是节名为 ".Stone" 的节,都是解码用的。
在 Windows 10 下OD载入 CrackMe,如下图所示:
往下拖可以看到 0x0041F096 有一个 jmp eax,我们直接选择在这一条指令上按 F4 , 然后再 F8 就会跳转到下一节的解码代码,与这一节基本一样,也是到 jmp eax 处 F4, 再F8跳转到下一节的解码代码,这个操作有多次,一直来到下面代码处,如下图,才来到 OEP:
上图的 0x00403560 才是 OEP,到这里我们就可以脱壳了。
再往下拖一下,可以找到 WinMain() 函数的调用,如下图:
如上图,call 00401DE0 就是调用 WinMain() 函数。
我们先脱壳,脱壳后,我们运行一下,报一个错误,运行不了,错误提示如下所示:
是一个 API 函数的问题,这个 API 函数是 DefWindowProcA(),我们需要手动修复这个问题,我们重新来到 OEP。用“管理员身份运行” ImportREC,如下图所示:
可以看到,最后一行的 Thunk 的 Valid 为 NO,点击旁边的 ”Show Invalid" 按钮,如下图显示:
其中那条 NtdllDefWindowProc_A 就是要手动修复的,鼠标左键双击这一行,弹出手动导入表编辑对话框,如下图所示:
我们先在 "Module" 下拉列表中,将“ntdll.dll” 改成 "User32.dll",如下图所示:
然后在 Function 列表中找到“DefWindowProcA”这一行并选择,如上图所示,选好后按 “OK” 即可,这时显示如下图:
所有的函数都有效了,这时就是可以 "Fix Dump" 了,如下图所示,选择要修复的 Dump 文件即可。
这样操作后,就可以正常在 Windows 10 运行了,如果还不行,还可以用 LordPE 来 dump,不用 OD 的 dump 功能,如下图所示:
再按前面的方法修复,就应该可以在 Windows 10 下运行了。
脱壳后,OD 重新载入 F9 直接运行,来到其界面:
随便输入几个信息,点“Validate”,没有动静,按3次后,“Validate” 按钮不可用了,提示变成了:"TRY AGAIN!",如下图所示:
通过前面所述,我们知道WinMain() 函数入口在 0x00401DE0,我们浏览一下这个 WinMain()函数,可以看到这是一个标准的 Windows 窗口API应用的代码,最后面是消息循环处理。
从最前的4行代码,我们找到了 WndProc 的入口为 0x00401FD0,如下所示:
00401DE0 83EC 4C sub esp, 4C
00401DE3 C74424 00 30000000 mov dword ptr , 30
00401DEB C74424 04 03000000 mov dword ptr , 3
00401DF3 C74424 08 D01F4000 mov dword ptr , 00401FD0
我们跟随立即数,来到 0x00401FD0,如下所示,是 WndProc() 的主处理代码:
00401FD0 8B4C24 08 mov ecx, dword ptr ; nMessage
00401FD4 56 push esi
00401FD5 83F9 01 cmp ecx, 1 ; WM_CREATE
00401FD8 74 2F je short 00402009
00401FDA 83F9 02 cmp ecx, 2 ; WM_DESTROY
00401FDD 0F84 84000000 je 00402067
00401FE3 81F9 11010000 cmp ecx, 111 ; WM_COMMAND
00401FE9 0F84 86000000 je 00402075
00401FEF 8B4424 14 mov eax, dword ptr ; lParam
00401FF3 8B5424 10 mov edx, dword ptr ; wParam
00401FF7 8B7424 08 mov esi, dword ptr ; hWnd
00401FFB 50 push eax ; 默认消息处理
00401FFC 52 push edx
00401FFD 51 push ecx
00401FFE 56 push esi
00401FFF FF15 D4E24000 call dword ptr [<&USER32.DefWindowProcA>] ; ntdll.NtdllDefWindowProc_A
00402005 5E pop esi
00402006 C2 1000 retn 10
最后那个 call User32.DefWindowProcA 就是我们前面修复的那个 API 调用。
我们再次跟随 WM_CREATE 消息处理的“je 00402009”,来到 0x00402009,如下所示:
00402009 8B4424 14 mov eax, dword ptr
0040200D 68 F4010000 push 1F4
00402012 68 88C14000 push 0040C188 ; ASCII "F:\Downloads\crack\159_Torn@do.2"
00402017 6A 00 push 0
00402019 8B48 04 mov ecx, dword ptr
0040201C 890D F8BE4000 mov dword ptr , ecx
00402022 FF15 64E24000 call dword ptr [<&KERNEL32.GetModuleHandleA>] ; KERNEL32.GetModuleHandleA
00402028 50 push eax
00402029 FF15 68E24000 call dword ptr [<&KERNEL32.GetModuleFileNameA>] ; KERNEL32.GetModuleFileNameA
0040202F 85C0 test eax, eax
00402031 74 10 je short 00402043
00402033 B9 5C000000 mov ecx, 5C
00402038 3888 88C14000 cmp byte ptr , cl
0040203E 74 03 je short 00402043
00402040 48 dec eax
00402041^ 75 F5 jnz short 00402038
00402043 8B7424 08 mov esi, dword ptr
00402047 6A 00 push 0 ; lParam == 0x00000000
00402049 C680 88C14000 00 mov byte ptr , 0
00402050 68 77777777 push 77777777 ;wParam == 0x77777777
00402055 68 11010000 push 111 ;nMessage == WM_COMMAND
0040205A 56 push esi
0040205B FF15 E4E24000 call dword ptr [<&USER32.PostMessageA>] ; USER32.PostMessageA
00402061 33C0 xor eax, eax
00402063 5E pop esi
00402064 C2 1000 retn 10
可以看到,在处理 WM_CREATE 消息时,在最后,给窗口 Post 了一条自定义的 WM_COMMAND 消息。我们再次回到前面的 WndProc()框架函数,来到 WM_COMMAND 消息处理跳转 "je 00402075",跟随这个跳转,来到 WM_COMMAND 消息处理代码处:
00402075 817C24 10 77777777 cmp dword ptr , 77777777
0040207D 75 21 jnz short 004020A0
0040207F 8B7424 08 mov esi, dword ptr
00402083 6A 00 push 0
00402085 68 B0204000 push 004020B0
0040208A A1 F8BE4000 mov eax, dword ptr
0040208F 56 push esi
00402090 6A 65 push 65
00402092 50 push eax
00402093 FF15 E0E24000 call dword ptr [<&USER32.DialogBoxParamA>] ; USER32.DialogBoxParamA
00402099 56 push esi
0040209A FF15 F0E24000 call dword ptr [<&USER32.DestroyWindow>] ; USER32.DestroyWindow
004020A0 33C0 xor eax, eax
004020A2 5E pop esi
004020A3 C2 1000 retn 10
这个消息处理非常简单,就是只要wParam为自定义的 0x77777777 就显示 CrackMe 的主对话框,因此这里就是显示主界面的地方,我们也找到了 DlgProc() 的入口为 0x004020B0。
再次“跟随立即数”,来到 0x004020B0,对话框消息处理框架代码如下:
004020B0 8B4424 08 mov eax, dword ptr
004020B4 53 push ebx
004020B5 56 push esi
004020B6 83F8 02 cmp eax, 2 ; WM_DESTROY
004020B9 57 push edi
004020BA 0F84 C6000000 je 00402186
004020C0 3D 10010000 cmp eax, 110 ; WM_INITDIALOG
004020C5 74 13 je short 004020DA
004020C7 3D 11010000 cmp eax, 111 ; WM_COMMAND
004020CC 0F84 9D000000 je 0040216F
004020D2 33C0 xor eax, eax
004020D4 5F pop edi
004020D5 5E pop esi
004020D6 5B pop ebx
004020D7 C2 1000 retn 10
我们跟随 WM_INITDIALOG 消息处理跳转 "je 004020DA" 后,可以看到这样一段代码:
004020F6 6A 00 push 0 ; 0 - Disabled, 1 - Enabled
004020F8 8B35 A0E24000 mov esi, dword ptr [<&USER32.GetDlgItem>] ; USER32.GetDlgItem
004020FE 68 EC030000 push 3EC ; "Request" ID
00402103 53 push ebx ; hDlg
00402104 FFD6 call esi ; call GetDlgItem()
00402106 50 push eax ; Control hwnd
00402107 FF15 98E24000 call dword ptr [<&USER32.EnableWindow>] ; USER32.EnableWindow
这段代码就是在 CrackMe 启动时将 "Request" 按钮的状态改为 "Disabled" 状态的。
我们再回到 DlgProc(),跟随处理 WM_COMMAND 跳转 "je 0040216F",来到其处理框架代码:
0040216F 8B4424 18 mov eax, dword ptr
00402173 25 FFFF0000 and eax, 0FFFF
00402178 3D EB030000 cmp eax, 3EB ; Validate 按钮
0040217D 7F 1B jg short 0040219A
0040217F 74 54 je short 004021D5 ; 跳转去进行注册验证
00402181 83F8 02 cmp eax, 2 ; WM_CLOSE
00402184 74 37 je short 004021BD
00402186 A1 08BE4000 mov eax, dword ptr
0040218B 50 push eax
0040218C FF15 F4E14000 call dword ptr [<&GDI32.DeleteObject>] ; GDI32.DeleteObject
00402192 33C0 xor eax, eax
00402194 5F pop edi
00402195 5E pop esi
00402196 5B pop ebx
00402197 C2 1000 retn 10
可以看到,跟随 “ je 004021D5” 后就可以到达注册验证的代码了:
004021D5 > \8B5C24 10 mov ebx, dword ptr ;Case 3EB of switch 00402178
004021D9 .53 push ebx
004021DA .E8 31F1FFFF call 00401310 ;假的序列号验证,实际取用户名
004021DF .83C4 04 add esp, 4
004021E2 .8BF8 mov edi, eax ;eax ===> "solly"
004021E4 .B9 FFFFFFFF mov ecx, -1
004021E9 .2BC0 sub eax, eax
004021EB .F2:AE repne scas byte ptr es:
004021ED .F7D1 not ecx
004021EF .2BF9 sub edi, ecx
004021F1 .8BC1 mov eax, ecx
004021F3 .C1E9 02 shr ecx, 2
004021F6 .8BF7 mov esi, edi
004021F8 .BF 30BF4000 mov edi, 0040BF30 ;ASCII "solly"
004021FD .F3:A5 rep movs dword ptr es:, dword ptr
004021FF .8BC8 mov ecx, eax
00402201 .53 push ebx
00402202 .83E1 03 and ecx, 3
00402205 .F3:A4 rep movs byte ptr es:, byte ptr
00402207 .E8 74F1FFFF call 00401380 ;假的序列号验证,实际取公司名
0040220C .83C4 04 add esp, 4
0040220F .8BF8 mov edi, eax ;eax ===> "company"
00402211 .B9 FFFFFFFF mov ecx, -1
00402216 .2BC0 sub eax, eax
00402218 .F2:AE repne scas byte ptr es:
0040221A .F7D1 not ecx
0040221C .2BF9 sub edi, ecx
0040221E .8BD1 mov edx, ecx
00402220 .C1E9 02 shr ecx, 2
00402223 .8BF7 mov esi, edi
00402225 .BF E0BD4000 mov edi, 0040BDE0 ;ASCII "company"
0040222A .F3:A5 rep movs dword ptr es:, dword ptr
0040222C .8BCA mov ecx, edx
0040222E .53 push ebx
0040222F .83E1 03 and ecx, 3
00402232 .F3:A4 rep movs byte ptr es:, byte ptr
00402234 .E8 B7F1FFFF call 004013F0 ;假的序列号验证,实际取序列名
00402239 .83C4 04 add esp, 4
0040223C .8BF8 mov edi, eax ;eax ===> "78787878"
0040223E .B9 FFFFFFFF mov ecx, -1
00402243 .2BC0 sub eax, eax
00402245 .F2:AE repne scas byte ptr es:
00402247 .F7D1 not ecx
00402249 .2BF9 sub edi, ecx
0040224B .8BD1 mov edx, ecx
0040224D .C1E9 02 shr ecx, 2
00402250 .8BF7 mov esi, edi
00402252 .BF 28BE4000 mov edi, 0040BE28 ;ASCII "78787878"
00402257 .F3:A5 rep movs dword ptr es:, dword ptr
00402259 .8BCA mov ecx, edx
0040225B .83E1 03 and ecx, 3
0040225E .F3:A4 rep movs byte ptr es:, byte ptr
00402260 .53 push ebx ;EBX == 0x00B50DE8
00402261 .68 28BE4000 push 0040BE28 ;ASCII "78787878"
00402266 .68 E0BD4000 push 0040BDE0 ;ASCII "company"
0040226B .68 30BF4000 push 0040BF30 ;ASCII "solly"
00402270 .E8 7BFAFFFF call 00401CF0 ;真正的注册码验证位置
00402275 .83C4 10 add esp, 10
00402278 .66:833D 20BE4000 28 cmp word ptr , 28 ; 是否成功!
00402280 .75 1E jnz short 004022A0 ; == 0x28 表示成功!
00402282 .6A 00 push 0 ; /lParam = NULL
00402284 .A1 F8BE4000 mov eax, dword ptr ; |
00402289 .68 A0254000 push 004025A0 ; |DlgProc = Torn@do_.004025A0
0040228E .53 push ebx ; |hOwner
0040228F .6A 68 push 68 ; |pTemplate = 68
00402291 .50 push eax ; |hInst => NULL
00402292 .FF15 E0E24000 call dword ptr [<&user32.DialogBoxParamA> ; \DialogBoxParamA
00402298 .33C0 xor eax, eax
0040229A .5F pop edi
0040229B .5E pop esi
0040229C .5B pop ebx
0040229D .C2 1000 retn 10
004022A0 >66:A1 74A04000 mov ax, word ptr
004022A6 .66:40 inc ax
004022A8 .66:A3 74A04000 mov word ptr , ax
004022AE .66:3D 0300 cmp ax, 3 ; 尝试3次
004022B2 .75 32 jnz short 004022E6
004022B4 .6A 00 push 0 ; /Enable = FALSE
004022B6 .8B35 A0E24000 mov esi, dword ptr [<&user32.GetDlgItem> ; |USER32.GetDlgItem
004022BC .68 EB030000 push 3EB ; |/ControlID = 3EB (1003.)
004022C1 .53 push ebx ; ||hWnd
004022C2 .FFD6 call esi ; |\GetDlgItem
004022C4 .50 push eax ; |hWnd
004022C5 .8B3D 98E24000 mov edi, dword ptr [<&user32.EnableWindow> ; |USER32.EnableWindow
004022CB .FFD7 call edi ; \EnableWindow
004022CD .68 68A04000 push 0040A068 ; /Text = "TRY AGAIN!"
004022D2 .68 EE030000 push 3EE ; |ControlID = 3EE (1006.)
004022D7 .53 push ebx ; |hWnd
004022D8 .FF15 F8E24000 call dword ptr [<&user32.SetDlgItemTextA> ; \SetDlgItemTextA
004022DE .66:A1 74A04000 mov ax, word ptr
004022E4 .EB 0C jmp short 004022F2
004022E6 >8B3D 98E24000 mov edi, dword ptr [<&user32.EnableWindow> ;USER32.EnableWindow
004022EC .8B35 A0E24000 mov esi, dword ptr [<&user32.GetDlgItem> ;USER32.GetDlgItem
004022F2 >66:A3 74A04000 mov word ptr , ax
004022F8 .66:3D 0300 cmp ax, 3 ; 尝试3次
004022FC .7E 21 jle short 0040231F
004022FE .6A 00 push 0
00402300 .68 EB030000 push 3EB
00402305 .53 push ebx
00402306 .FFD6 call esi
00402308 .50 push eax
00402309 .FFD7 call edi
0040230B .6A 40 push 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
0040230D .68 98A74000 push 0040A798 ; |Title = "Message from TORN@DO"
00402312 .68 10A74000 push 0040A710 ; |Text = "You're using a window editing tool like THE CUSTOMISER!",LF,"You have had 3 chances for entering the right code!",LF,"Do you think that's fair?"
00402317 .6A 00 push 0 ; |hOwner = NULL
00402319 .FF15 DCE24000 call dword ptr [<&user32.MessageBoxA>] ; \MessageBoxA
0040231F >33C0 xor eax, eax
00402321 .5F pop edi
00402322 .5E pop esi
00402323 .5B pop ebx
00402324 .C2 1000 retn 10
从上面的我的注释中,可以看到,前面三次都是没有用的验证,迷惑用的,实际用途只是取界面上输入的三个字符串。直正的验证是如下代码处:
00402260 .53 push ebx ;EBX == 0x00B50DE8
00402261 .68 28BE4000 push 0040BE28 ;ASCII "78787878"
00402266 .68 E0BD4000 push 0040BDE0 ;ASCII "company"
0040226B .68 30BF4000 push 0040BF30 ;ASCII "solly"
00402270 .E8 7BFAFFFF call 00401CF0 ;真正的注册码验证位置
00402275 .83C4 10 add esp, 10
00402278 .66:833D 20BE4000 28 cmp word ptr , 28 ; 是否成功!
00402280 .75 1E jnz short 004022A0 ; == 0x28 表示成功!
上面的 call 00401CF0 才是真正的验证代码,其代码下:
00401CF0/$53 push ebx
00401CF1|.56 push esi
00401CF2|.8B5C24 14 mov ebx, dword ptr ;序列号
00401CF6|.57 push edi
00401CF7|.55 push ebp
00401CF8|.53 push ebx
00401CF9|.E8 F2FCFFFF call 004019F0 ;strcmp(),判断是否空串
00401CFE|.83C4 04 add esp, 4
00401D01|.53 push ebx ;ebx ===> SN
00401D02|.E8 19FAFFFF call 00401720 ;sn~sn,固定字符 "IDC40"
00401D07|.83C4 04 add esp, 4
00401D0A|.83F8 01 cmp eax, 1
00401D0D|.74 0E je short 00401D1D
00401D0F|.66:C705 20BE4000 FFFF mov word ptr , 0FFFF
00401D18|.5D pop ebp
00401D19|.5F pop edi
00401D1A|.5E pop esi
00401D1B|.5B pop ebx
00401D1C|.C3 retn
00401D1D|>8B6C24 20 mov ebp, dword ptr
00401D21|.8B7C24 18 mov edi, dword ptr
00401D25|.8B7424 14 mov esi, dword ptr
00401D29|.55 push ebp
00401D2A|.53 push ebx
00401D2B|.57 push edi
00401D2C|.56 push esi
00401D2D|.E8 EEFCFFFF call 00401A20 ;无用校验
00401D32|.83C4 10 add esp, 10
00401D35|.53 push ebx
00401D36|.E8 55FAFFFF call 00401790 ;sn~sn,固定字符 "ETBL"
00401D3B|.83C4 04 add esp, 4
00401D3E|.83F8 01 cmp eax, 1
00401D41|.74 0E je short 00401D51
00401D43|.66:C705 20BE4000 FFFF mov word ptr , 0FFFF
00401D4C|.5D pop ebp
00401D4D|.5F pop edi
00401D4E|.5E pop esi
00401D4F|.5B pop ebx
00401D50|.C3 retn
00401D51|>53 push ebx
00401D52|.E8 89FAFFFF call 004017E0 ;序列号最后4字符,当前时间校验
00401D57|.83C4 04 add esp, 4
00401D5A|.83F8 01 cmp eax, 1
00401D5D|.74 0E je short 00401D6D
00401D5F|.66:C705 20BE4000 FFFF mov word ptr , 0FFFF
00401D68|.5D pop ebp
00401D69|.5F pop edi
00401D6A|.5E pop esi
00401D6B|.5B pop ebx
00401D6C|.C3 retn
00401D6D|>55 push ebp
00401D6E|.53 push ebx
00401D6F|.57 push edi
00401D70|.56 push esi
00401D71|.E8 FAFDFFFF call 00401B70 ;无用校验
00401D76|.83C4 10 add esp, 10
00401D79|.A1 80C34000 mov eax, dword ptr ;FirstInstallDateTime转换成字符串后的长度
00401D7E|.50 push eax
00401D7F|.53 push ebx
00401D80|.E8 EBFAFFFF call 00401870 ;注册表FirstInstallDateTime验证,但由于的BUG,变成固定数字(1702505)的验证
00401D85|.83C4 08 add esp, 8
00401D88|.83F8 01 cmp eax, 1
00401D8B|.74 0E je short 00401D9B
00401D8D|.66:C705 20BE4000 FFFF mov word ptr , 0FFFF
00401D96|.5D pop ebp
00401D97|.5F pop edi
00401D98|.5E pop esi
00401D99|.5B pop ebx
00401D9A|.C3 retn
00401D9B|>55 push ebp
00401D9C|.53 push ebx
00401D9D|.57 push edi
00401D9E|.56 push esi
00401D9F|.E8 8CFEFFFF call 00401C30 ;无用校验
00401DA4|.83C4 10 add esp, 10
00401DA7|.53 push ebx ;ebx ===> sn
00401DA8|.E8 63FBFFFF call 00401910 ;注册信息验证,包括注册表的 RegisteredOwner、输入的用户名和公司名验证
00401DAD|.83C4 04 add esp, 4
00401DB0|.83F8 01 cmp eax, 1
00401DB3|.74 0E je short 00401DC3
00401DB5|.66:C705 20BE4000 FFFF mov word ptr , 0FFFF
00401DBE|.5D pop ebp
00401DBF|.5F pop edi
00401DC0|.5E pop esi
00401DC1|.5B pop ebx
00401DC2|.C3 retn
00401DC3|>55 push ebp
00401DC4|.E8 97F6FFFF call 00401460 ;设置成功状态
00401DC9|.83C4 04 add esp, 4
00401DCC|.5D pop ebp
00401DCD|.5F pop edi
00401DCE|.5E pop esi
00401DCF|.5B pop ebx
00401DD0\.C3 retn
这里説明一下,从上面代码中可以看到,有一个时间验证(call 004017E0),代码如下:
004017E0/$83EC 04 sub esp, 4
004017E3|.33C9 xor ecx, ecx
004017E5|.8D4424 01 lea eax, dword ptr
004017E9|.56 push esi
004017EA|.8B5424 0C mov edx, dword ptr ;edx ===> SN
004017EE|.57 push edi
004017EF|.8BFA mov edi, edx ;edi ===> SN
004017F1|.884C24 08 mov byte ptr , cl
004017F5|.66:8908 mov word ptr , cx
004017F8|.8848 02 mov byte ptr , cl
004017FB|.B9 FFFFFFFF mov ecx, -1
00401800|.2BC0 sub eax, eax
00401802|.F2:AE repne scas byte ptr es:
00401804|.F7D1 not ecx ;ecx = len(sn)+1
00401806|.8BFA mov edi, edx
00401808|.2BC0 sub eax, eax
0040180A|.8A4C11 FB mov cl, byte ptr ;sn
0040180E|.884C24 08 mov byte ptr , cl
00401812|.B9 FFFFFFFF mov ecx, -1
00401817|.F2:AE repne scas byte ptr es:
00401819|.F7D1 not ecx ;ecx = len(sn)+1
0040181B|.8BFA mov edi, edx
0040181D|.8A4411 FC mov al, byte ptr ;sn
00401821|.B9 FFFFFFFF mov ecx, -1
00401826|.884424 09 mov byte ptr , al
0040182A|.2BC0 sub eax, eax
0040182C|.F2:AE repne scas byte ptr es:
0040182E|.F7D1 not ecx ;ecx = len(sn)+1
00401830|.8BFA mov edi, edx
00401832|.2BC0 sub eax, eax
00401834|.8A4C11 FD mov cl, byte ptr ;sn
00401838|.884C24 0A mov byte ptr , cl
0040183C|.B9 FFFFFFFF mov ecx, -1
00401841|.F2:AE repne scas byte ptr es:
00401843|.F7D1 not ecx ;ecx = len(sn)+1
00401845|.8D4424 08 lea eax, dword ptr
00401849|.8A5411 FE mov dl, byte ptr ;sn
0040184D|.885424 0B mov byte ptr , dl
00401851|.50 push eax
00401852|.E8 291C0000 call 00403480 ;atoi(sn)
00401857|.83C4 04 add esp, 4
0040185A|.8BF0 mov esi, eax ;esi = 0
0040185C|.E8 3FFEFFFF call 004016A0 ;GetLocalTime
00401861|.2BC6 sub eax, esi ;eax = 0x0000081C = 2076
00401863|.5F pop edi
00401864|.5E pop esi
00401865|.83F8 01 cmp eax, 1
00401868|.1BC0 sbb eax, eax
0040186A|.83C4 04 add esp, 4
0040186D|.F7D8 neg eax
0040186F\.C3 retn
其中又有一个调用(call 004016A0),就是根据当前时间生成一段注册验证,对注册码最后4个数字进行验证:
004016A0/$83EC 10 sub esp, 10
004016A3|.8D4424 00 lea eax, dword ptr
004016A7|.56 push esi
004016A8|.50 push eax ; /pLocaltime
004016A9|.FF15 6CE24000 call dword ptr [<&kernel32.GetLocalTime>] ; \GetLocalTime
004016AF|.33D2 xor edx, edx
004016B1|.33C0 xor eax, eax
004016B3|.66:8B5424 06 mov dx, word ptr ;月份 7
004016B8|.33C9 xor ecx, ecx
004016BA|.66:8B4424 0A mov ax, word ptr ;日 6
004016BF|.0FAFD0 imul edx, eax ;EDX = 0x2A
004016C2|.66:8B4C24 0E mov cx, word ptr ;分钟,cx = 0x09
004016C7|.8B4424 0C mov eax, dword ptr ;eax = 0x00090003
004016CB|.25 FFFF0000 and eax, 0FFFF ;小时 eax = 3
004016D0|.0FAFC1 imul eax, ecx ;eax == 0x1B
004016D3|.0FBE0D 60BF4000 movsx ecx, byte ptr ;RegisteredOwner,注册表项字符串的第1个字符的 ASCII 值。
004016DA|.03D0 add edx, eax ;edx = 0x2A + 0x1B
004016DC|.8B4424 04 mov eax, dword ptr ;ax = 2019
004016E0|.2BD1 sub edx, ecx ;ecx == RegisteredOwner
004016E2|.25 FFFF0000 and eax, 0FFFF
004016E7|.8D3402 lea esi, dword ptr ;esi = edx + eax + 0x45 + 0x7E3 = 69 + 2019 = 2088
004016EA|.56 push esi
004016EB|.E8 A0FBFFFF call 00401290 ;检查 SoftICE (Win9x) 调试
004016F0|.83C4 04 add esp, 4
004016F3|.85C0 test eax, eax
004016F5|.74 06 je short 004016FD
004016F7|.81C6 43010000 add esi, 143
004016FD|>56 push esi
004016FE|.E8 CDFBFFFF call 004012D0 ;检查 SoftICE (NT) 调试
00401703|.83C4 04 add esp, 4
00401706|.85C0 test eax, eax
00401708|.74 06 je short 00401710
0040170A|.81C6 71020000 add esi, 271
00401710|>8BC6 mov eax, esi
00401712|.5E pop esi
00401713|.83C4 10 add esp, 10
00401716\.C3 retn
也就是説,其验证时间精准到了分钟,生成的序列号有效时间最多60秒,如果注册机生成序列号与你输入序列号不是在同一分钟数字时(不是指1分钟内)序列号就是无效的,也就是説当输入序列号时,只要秒钟跨过了第59秒,就得重新生成序列号。
注册码前面两段是固定的,由 call 00401720 和 call 00401790 实现的,比较简单,这里不对其进行分析。
call 00401870 是对注册表中的一个值进行验证,这样,序列号与具体机器有关,不是通用的了,必须要有注册机才能搞定注册。这个调用是处理注册表中FirstInstallDateTime 这个值的,不过这个值只有在 Windows 9x 系统下才有,Win NT 系列,如 Windows 10 下就没有这个注册表项。另外 CrackMe 还要用到一个注册表项: RegisteredOwner,这个也只有在 Windows 9x 下才有。这个调用的主要代码如下(其前面有一段代码中没有用的,迷惑用的,不贴上来了):
00401870/$83EC 64 sub esp, 64
00401873|.53 push ebx
00401874|.56 push esi
00401875|.57 push edi
00401876|.33DB xor ebx, ebx ;int i = 0;
00401878|.8D7C24 0D lea edi, dword ptr
0040187C|.55 push ebp
0040187D|.33C0 xor eax, eax
0040187F|.B9 18000000 mov ecx, 18
00401884|.885C24 10 mov byte ptr , bl ;sn_time = '\0'
00401888|.F3:AB rep stos dword ptr es: ;len 0x63
0040188A|.66:AB stos word ptr es:
0040188C|.AA stos byte ptr es:
0040188D|.33F6 xor esi, esi ;int j = 0;
0040188F|.8B6C24 7C mov ebp, dword ptr ;FirstInstallDateTime 转换成字符串后的长度 len(FirstInstallDateTime) = 7
00401893|.3BE8 cmp ebp, eax
00401895|.74 24 je short 004018BB
00401897|.8B5424 78 mov edx, dword ptr ;edx ===> SN
0040189B|>8BFA /mov edi, edx
0040189D|.B9 FFFFFFFF |mov ecx, -1
004018A2|.2BC0 |sub eax, eax
004018A4|.F2:AE |repne scas byte ptr es:
004018A6|.F7D1 |not ecx
004018A8|.49 |dec ecx ;len(sn)
004018A9|.46 |inc esi ;j ++
004018AA|.2BCD |sub ecx, ebp ;len(sn) - len(company)
004018AC|.03CB |add ecx, ebx ;c = len(sn) - len(company) + i
004018AE|.43 |inc ebx ;i++
004018AF|.3BF5 |cmp esi, ebp ;j<len(company)
004018B1|.8A4411 FB |mov al, byte ptr ;sn
004018B5|.884434 0F |mov byte ptr , al
004018B9|.^ 72 E0 \jb short 0040189B
004018BB|>8D4424 10 lea eax, dword ptr ;sn_time == sn段
004018BF|.50 push eax
004018C0|.E8 BB1B0000 call 00403480 ;atoi(FirstInstallTime) = 0x0019FA69 = 1702505
004018C5|.83C4 04 add esp, 4
004018C8|.8BF0 mov esi, eax
004018CA|.A1 FCBE4000 mov eax, dword ptr ; == 0x0019FA74 == 1702516, FirstInstallTime
004018CF|.894424 10 mov dword ptr , eax ;eax == 1702516
004018D3|.C74424 14 00000000 mov dword ptr , 0
004018DB|.DF6C24 10 fild qword ptr
004018DF|.D9C0 fld st
004018E1|.E8 731C0000 call 00403559 ;tanh(1702516) = 1.0000
004018E6|.DC0D 20904000 fmul qword ptr ;1.0 * 11.00
004018EC|.DEE9 fsubp st(1), st ;1702516-11=1702505
004018EE|.E8 590D0000 call 0040264C
004018F3|.2BC6 sub eax, esi ;eax = 0x0019FA69 = 1702505
004018F5|.5D pop ebp
004018F6|.5F pop edi
004018F7|.83F8 01 cmp eax, 1
004018FA|.1BC0 sbb eax, eax
004018FC|.5E pop esi
004018FD|.F7D8 neg eax
004018FF|.5B pop ebx
00401900|.83C4 64 add esp, 64
00401903\.C3 retn
就是将 FirstInstallDateTime 进行简单计算后,与输入的序列号相关字段进行比较,相等即通过。
还有一个调用(call 00401910)就是对我们在界面上输入的用户名和公司名进行校验,当中还包括注册表项 RegisteredOwner 的验证,如下:
00401910/$83EC 64 sub esp, 64
00401913|.33C0 xor eax, eax
00401915|.B9 18000000 mov ecx, 18 ;ecx == 0x18 == 24
0040191A|.56 push esi
0040191B|.C64424 04 00 mov byte ptr , 0
00401920|.57 push edi
00401921|.8D7C24 09 lea edi, dword ptr
00401925|.F3:AB rep stos dword ptr es:
00401927|.66:AB stos word ptr es:
00401929|.BE 0C000000 mov esi, 0C ;int i = 12
0040192E|.B9 FFFFFFFF mov ecx, -1
00401933|.AA stos byte ptr es:
00401934|.8B5424 70 mov edx, dword ptr ;edx ===> SN
00401938|.2BC0 sub eax, eax
0040193A|.8BFA mov edi, edx ;edi ===> SN
0040193C|.F2:AE repne scas byte ptr es:
0040193E|.F7D1 not ecx
00401940|.49 dec ecx ;ecx = len(sn) = 0x21 = 33
00401941|.2B0D 80C34000 sub ecx, dword ptr ;FirstInstallDateTime 转换成字符串后的长度
00401947|.83E9 05 sub ecx, 5 ;5 (当前时间的长度 "-nnnn")
0040194A|.3BCE cmp ecx, esi ;len(sn) - 12 <= 12
0040194C|.76 24 jbe short 00401972
0040194E|>8A4432 FF /mov al, byte ptr ;sn
00401952|.46 |inc esi ;i++
00401953|.8BFA |mov edi, edx ;edi ====> sn
00401955|.B9 FFFFFFFF |mov ecx, -1
0040195A|.884434 FB |mov byte ptr , al ;tmp = sn
0040195E|.2BC0 |sub eax, eax
00401960|.F2:AE |repne scas byte ptr es:
00401962|.F7D1 |not ecx
00401964|.49 |dec ecx
00401965|.2B0D 80C34000 |sub ecx, dword ptr ;FirstInstallDateTime 转换成字符串后的长度
0040196B|.83E9 05 |sub ecx, 5 ;5 (当前时间的长度 "-nnnn")
0040196E|.3BCE |cmp ecx, esi
00401970|.^ 77 DC \ja short 0040194E
00401972|>8D4424 08 lea eax, dword ptr ;eax ===> "398378258"
00401976|.50 push eax
00401977|.E8 041B0000 call 00403480 ;atoi()
0040197C|.83C4 04 add esp, 4
0040197F|.8BF8 mov edi, eax ;eax == 0x17BEC512 == 398378258
00401981|.E8 0AFCFFFF call 00401590 ;RegisteredOwner索引check
00401986|.8BF0 mov esi, eax ;eax == 0x14 == 20
00401988|.E8 B3FBFFFF call 00401540 ;RegisteredOwner内容check
0040198D|.33F0 xor esi, eax ;eax == 0x01BA == 442, esi = 0x01AE == 430
0040198F|.68 E0BD4000 push 0040BDE0 ;ASCII "ite company"
00401994|.68 30BF4000 push 0040BF30 ;ASCII "solly88"
00401999|.E8 42FCFFFF call 004015E0 ;注册名和公司名checkcode
0040199E|.83C4 08 add esp, 8
004019A1|.03C6 add eax, esi ;long a = checkcode + regOwner_check
004019A3|.8D0CC0 lea ecx, dword ptr ;ecx == a * 9
004019A6|.8D14C8 lea edx, dword ptr ;edx == a * 73
004019A9|.8D0C52 lea ecx, dword ptr ;ecx == a * 219 = 778983
004019AC|.C1E1 02 shl ecx, 2 ;ecx == a * 876
004019AF|.2BC8 sub ecx, eax ;ecx == a * 875
004019B1|.C1E1 06 shl ecx, 6 ;ecx == a * 875*64 = a * 56000
004019B4|.2BC8 sub ecx, eax ;ecx == a * 55999
004019B6|.03C9 add ecx, ecx ;ecx == a * 111998
004019B8|.894C24 08 mov dword ptr , ecx ;ecx == 0x17BEBFB6 = 398376886
004019BC|.C74424 0C 00000000 mov dword ptr , 0
004019C4|.DF6C24 08 fild qword ptr
004019C8|.D9C0 fld st
004019CA|.D9FE fsin ;sin(398376886) = -0.6866350580165284495
004019CC|.DC0D 28904000 fmul qword ptr ; = 1999.0000, sin()*1999 = -1372.5834809750404020
004019D2|.DEE9 fsubp st(1), st ;st == 398378258.58348095420 = 398376886 - (-1372.5834809750404020)
004019D4|.E8 730C0000 call 0040264C ;eax == 0x17BEC512 == 398378258
004019D9|.2BC7 sub eax, edi ;edi = atoi()
004019DB|.5F pop edi
004019DC|.5E pop esi
004019DD|.83F8 01 cmp eax, 1
004019E0|.1BC0 sbb eax, eax
004019E2|.83C4 64 add esp, 64
004019E5|.F7D8 neg eax
004019E7\.C3 retn
其中,对用户名和公司名处理的调用(call 004015E0)如下所示:
004015E0/$83EC 08 sub esp, 8
004015E3|.33D2 xor edx, edx ;long sum = 0;
004015E5|.B9 FFFFFFFF mov ecx, -1
004015EA|.53 push ebx
004015EB|.66:BB 0100 mov bx, 1 ;int i = 1
004015EF|.56 push esi
004015F0|.8B7424 14 mov esi, dword ptr ;esi ===> "solly"
004015F4|.57 push edi
004015F5|.8BFE mov edi, esi ;edi ===> "solly"
004015F7|.2BC0 sub eax, eax
004015F9|.F2:AE repne scas byte ptr es:
004015FB|.F7D1 not ecx
004015FD|.49 dec ecx ;ecx == 用户名长度
004015FE|.83F9 01 cmp ecx, 1
00401601|.76 2F jbe short 00401632
00401603|>0FBFC3 /movsx eax, bx ;i
00401606|.0FBE4430 FF |movsx eax, byte ptr ;eax == name
0040160B|.8BC8 |mov ecx, eax ;ecx == name
0040160D|.C1E0 02 |shl eax, 2 ;eax == name * 4
00401610|.66:43 |inc bx ;i++
00401612|.8BFE |mov edi, esi ;edi ===> "solly"
00401614|.8D04C0 |lea eax, dword ptr ;eax == name * 36, (4*8+4)
00401617|.8D0CC1 |lea ecx, dword ptr ;ecx == name * 289,(36*8+1)
0040161A|.8D04C9 |lea eax, dword ptr ;eax == name * 2601, (289*9)
0040161D|.B9 FFFFFFFF |mov ecx, -1
00401622|.03D0 |add edx, eax ;sum += eax;
00401624|.2BC0 |sub eax, eax
00401626|.F2:AE |repne scas byte ptr es:
00401628|.0FBFC3 |movsx eax, bx ;i
0040162B|.F7D1 |not ecx
0040162D|.49 |dec ecx ;len(solly)
0040162E|.3BC8 |cmp ecx, eax ;i<len(name)
00401630|.^ 77 D1 \ja short 00401603 ;sum = 0x00118ACA
00401632|>66:BB 0100 mov bx, 1 ;int i = 1
00401636|.8B7424 1C mov esi, dword ptr ;esi ===> "company"
0040163A|.8BFE mov edi, esi ;edi ===> "company"
0040163C|.B9 FFFFFFFF mov ecx, -1
00401641|.2BC0 sub eax, eax
00401643|.F2:AE repne scas byte ptr es:
00401645|.F7D1 not ecx
00401647|.49 dec ecx ;len(company)
00401648|.83F9 01 cmp ecx, 1
0040164B|.76 21 jbe short 0040166E
0040164D|>0FBFC3 /movsx eax, bx ;i
00401650|.66:43 |inc bx ;i++
00401652|.8BFE |mov edi, esi ;edi ===> "company"
00401654|.0FBE4C30 FF |movsx ecx, byte ptr ;company
00401659|.03D1 |add edx, ecx ;sum += company
0040165B|.B9 FFFFFFFF |mov ecx, -1
00401660|.2BC0 |sub eax, eax
00401662|.F2:AE |repne scas byte ptr es:
00401664|.0FBFC3 |movsx eax, bx ;i
00401667|.F7D1 |not ecx
00401669|.49 |dec ecx ;len(company)
0040166A|.3BC8 |cmp ecx, eax ;i<len(company)
0040166C|.^ 77 DF \ja short 0040164D ;sum == 0x00118D48
0040166E|>8D0492 lea eax, dword ptr ;eax = sum * 5
00401671|.8D0C42 lea ecx, dword ptr ;ecx = sum * 11
00401674|.894C24 0C mov dword ptr , ecx ;ecx == 0x00C11218 == 12653080
00401678|.C74424 10 00000000 mov dword ptr , 0
00401680|.DF6C24 0C fild qword ptr
00401684|.D9FA fsqrt ;sqrt(12653080) == 3557.1168100021682220
00401686|.E8 C10F0000 call 0040264C ;eax == 0x0DE5 == 3557
0040168B|.5F pop edi
0040168C|.5E pop esi
0040168D|.5B pop ebx
0040168E|.83C4 08 add esp, 8
00401691\.C3 retn
另外对 RegisteredOwner 的验证有两次,比较简单,见注册机,不在此分析。另外,前面时间验证中,也用到了 RegisteredOwner 字符串的第1个字符(RegisteredOwner)的 ASCII 码值。
因为其有对注册表项进行验证,所以其需要读取注册表中的数据,不过,其在读取 FirstInstallDateTime 有问题,并没有取得这个值,而是取得保存这个值的地址,将这个地址参与了序列号的计算,这段取注册表信息的代码如下:
00401E59|.68 F8A64000 push 0040A6F8 ;ASCII "FirstInstallDateTime"
00401E5E|.68 CCA64000 push 0040A6CC ;ASCII "SOFTWARE\Microsoft\Windows\CurrentVersion"
00401E63|.68 02000080 push 80000002
00401E68|.E8 43F6FFFF call 004014B0 ;读注册表,返回结果是一个地址,存于 EAX 并返回。
00401E6D|.83C4 0C add esp, 0C
00401E70|.8B2D B4E24000 mov ebp, dword ptr [<&user32.wsprintfA>] ;USER32.wsprintfA
00401E76|.50 push eax ; /<%lu>,这里有Bug,传入的不是读取的时间,而是指向时间的地址指针,固定为 0x0019FA74
00401E77|.68 C8A64000 push 0040A6C8 ; |Format = "%lu",此格式是将返回地址当作一个无符号长整数进行操作
00401E7C|.68 90BF4000 push 0040BF90 ; |s = Torn@do_.0040BF90
00401E81|.FFD5 call ebp ; \wsprintfA
00401E83|.83C4 0C add esp, 0C
00401E86|.BF 90BF4000 mov edi, 0040BF90 ;ASCII "1702516"
00401E8B|.68 90BF4000 push 0040BF90 ;ASCII "1702516"
00401E90|.E8 EB150000 call 00403480 ;atoi(pointer)
00401E95|.83C4 04 add esp, 4
00401E98|.B9 FFFFFFFF mov ecx, -1
00401E9D|.A3 FCBE4000 mov dword ptr , eax ;保存 0x0019FA74,实际是一个地址指针,不是注册表的 FirstInstallDateTime
00401EA2|.2BC0 sub eax, eax
00401EA4|.F2:AE repne scas byte ptr es:
00401EA6|.F7D1 not ecx
00401EA8|.49 dec ecx ;前面timestamp字符串的长度
00401EA9|.68 B8A64000 push 0040A6B8 ;ASCII "RegisteredOwner"
00401EAE|.68 CCA64000 push 0040A6CC ;ASCII "SOFTWARE\Microsoft\Windows\CurrentVersion"
00401EB3|.890D 80C34000 mov dword ptr , ecx ;保存时间字符串的长度
00401EB9|.68 02000080 push 80000002
00401EBE|.E8 EDF5FFFF call 004014B0 ;读注册表
问题出在调用 wsprintf() 这个函数的参数,因为 Call 004014B0 这个函数读取注册表信息后,返回的是一个地址,而 wsprintf()中,引用的是这个地址,所以转换成字符串也是一个地址值,不是真正的 FirstInstallDateTime的值,如果要修改这个 Bug,可以进行以下修改,如下图所示:
将 0x00401E6D处的 add esp, 0C 改成 mov eax, ,然后在下面 0x00401E83 处的 add esp, 0C 改成 add esp, 18,这样堆栈也平衡了,Bug也消除了。
基本的注册验证过程分析到这里,用注册机生成注册码,并抓紧时间输入(粘贴),校验正确时,显示如下:
一定要注意时间,最好在当前分钟的前 0~30秒内生成序列号,后30秒来输入注册码并验证,不然可能序列号就过期了。
另外,在 Windows 10 之类的新版 Windows 下,缺少注册表项,需要导入以下信息,不然也通不过 CrackMe 的注册验证,64位系统如下:
Windows Registry Editor Version 5.00
"FirstInstallDateTime"=hex:c1,b1,98,4c
"RegisteredOrganization"="ite"
"RegisteredOwner"="solly"
"ProductId"="51163-030-0389753-07447"
32位系统如下:
Windows Registry Editor Version 5.00
"FirstInstallDateTime"=hex:c1,b1,98,4c
"RegisteredOrganization"="ite"
"RegisteredOwner"="solly"
"ProductId"="51163-030-0389753-07447"
当然,键值可以改成自己的,键名不能改动。
分析完毕,下面是注册机源码,使用 Dev-C++调试通过:
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <windows.h>
union CBData{
DWORD n;
byte buffer;
};
CBData readKey(HKEY hKey, char * path, char * key);
void getRegInfo();
void getPart1();
void getPart2();
long getRegTimeStamp(int index);
long getCheckRegOwner();
long getCheck(char * name, char * company, int index);
void getCurrDateTime(int index);
bool testSoftICE95();
bool testSoftICENT();
char sn;
CBData data;
long nFirstInstallDateTime = 0;
long nLenOfFirstInstallDateTime = 0;
char sFirstInstallDateTime;
char RegisteredOwner;
char RegisteredOrganization;
char ProductId;
int main(int argc, char** argv) {
memset(sn, 0, 48);
memset(sFirstInstallDateTime, 0, 128);
memset(RegisteredOwner, 0, 128);
memset(RegisteredOrganization, 0, 128);
memset(ProductId, 0, 128);
getRegInfo();
getPart1();
sn = '-';
getPart2();
sn = '-';
char name[] = "solly88";
char company[] = "ite company";
int n = getCheck(name, company, 11);
sn = '-';
int m = getRegTimeStamp(10+n+2);
sn = '-';
/// 最后4字节为当前时间检查
getCurrDateTime(10+n+3+m);
printf("\n SN = %s\n", sn);
return 0;
}
void getPart1() {
sn = 0x0D ^ 0x49;
sn = 0x07 ^ 0x44;
sn = 0x0D ^ 0x39;
sn = 0x1D ^ 0x54;
sn = 0x09 ^ 0x39;
}
void getPart2() {
sn = 0x06 ^ 0x52;
sn = 0x07 ^ 0x45;
sn = 0x1F ^ 0x53;
sn = 0x11 ^ 0x54;
}
/**
***/
/// FirstInstallTime
long getRegTimeStamp(int index) {
/// 0040BF90:1702516
//long a = 1702505;
////
//long FirstInstallDateTime = 1702516; /// 实际上是一个指向保存FirstInstallDateTime的内存地址 Win10
//long FirstInstallDateTime = 6617404; /// 实际上是一个指向保存FirstInstallDateTime的内存地址 Win98
//long FirstInstallDateTime = 6682940; /// 实际上是一个指向保存FirstInstallDateTime的内存地址 Win98_unpacked
long FirstInstallDateTime = nFirstInstallDateTime; /// 正常时是一个 Timestamp
long a = FirstInstallDateTime - tanh(FirstInstallDateTime) * 11.0;/// 当在win10下取的是地址时,这里是固定值 1702505
//char str_a;
sprintf(sFirstInstallDateTime, "%d", a);
long n = strlen(sFirstInstallDateTime);
for(int i=0; i<n; i++ ) {
sn = sFirstInstallDateTime;
}
return n;
}
long getCheckRegOwner() {// Proc_00401590 和 Proc_00401540
long sum1 = 0;
long sum2 = 0;
long n = strlen(RegisteredOwner);
for(int i=1; i<n; i++) {
sum1 += i*2;
sum2 += RegisteredOwner;
}
//printf("check reg owner: %d - %d\n", sum1, sum2);
return sum1 ^ sum2;
}
long getCheck(char * name, char * company, int index) {/// Proc_004015E0
long checkbase = getCheckRegOwner();
long sum = 0;
int n = strlen(name);
for(int i=0; i<n-1; i++) {
sum += name * 2601;
}
int m = strlen(company);
for(int i=0; i<m-1; i++) {
sum += company;
}
double x = sum * 11;
int code1 = (long)sqrt(x);
////
//printf("check code1: %d, %d\n", sum, code1);
long a = code1 + checkbase;
long c = a * 111998;
double y = c - sin(c) * 1999.0;
long code2 = (long)y;
//printf("check code2: %d\n", code2);
char code_str;
sprintf(code_str, "%d", code2);
int len = strlen(code_str);
for(int i=0; i<len; i++) {
sn = code_str;
}
return len;
}
bool testSoftICE95() {
HANDLE h = CreateFile("\\\\.\\SICE",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if(h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
return true;
}
return false;
}
bool testSoftICENT() {
HANDLE h = CreateFile("\\\\.\\NTICE",
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if(h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
return true;
}
return false;
}
/// Current date and time
void getCurrDateTime(int index) {
long a = RegisteredOwner;
time_t timer;
struct tm * st;
time(&timer);
st = localtime(&timer);
long t = (st->tm_year + 1900) + ((st->tm_mon + 1) * st->tm_mday - a) + st->tm_hour * st->tm_min;
///// SoftICE 检查,修正时间检查值(基本无用)
bool isExistsSICE95 = testSoftICE95();
bool isExistsSICENT = testSoftICENT();
if (isExistsSICE95) {
t += 0x143;
}
if (isExistsSICENT) {
t += 0x271;
}
char str_t;
itoa(t, str_t, 10);
sn = str_t;
sn = str_t;
sn = str_t;
sn = str_t;
///
//printf("time: %d-%d-%d %d:%d\n", st->tm_year+1900, st->tm_mon+1, st->tm_mday, st->tm_hour, st->tm_min) ;
}
CBData readKey(HKEY hKey, char * path, char * key) {
HKEY h = (HKEY)-1;
DWORD cbData;
DWORD regType = -1;
data.n = 1;
long s = RegOpenKeyEx(hKey, path, 0, KEY_READ, &h);
if( s != ERROR_SUCCESS) {
data.n = -1;
return data;
}
s = RegQueryValueEx(h, key, NULL, NULL, NULL, &cbData);
if(s == ERROR_SUCCESS) {
RegQueryValueEx(h, key, NULL, & regType, data.buffer, &cbData);
}
RegCloseKey(h);
return data;
}
void getRegInfo() {
char subKey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion";
char key1[] = "FirstInstallDateTime";
char key2[] = "RegisteredOwner";
char key3[] = "RegisteredOrganization";
char key4[] = "ProductId";
CBData cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key1);
nFirstInstallDateTime = cb.n;
struct tm * tm_reg = localtime((time_t *)&nFirstInstallDateTime);
sprintf(sFirstInstallDateTime, "%lu", nFirstInstallDateTime);
nLenOfFirstInstallDateTime = strlen(sFirstInstallDateTime);
printf(" DateTime: %d-%d-%d %d:%d:%d\n", tm_reg->tm_year + 1900, tm_reg->tm_mon + 1,
tm_reg->tm_mday, tm_reg->tm_hour, tm_reg->tm_min, tm_reg->tm_sec);
printf("Timestamp: %d,len=%d\n", nFirstInstallDateTime, nLenOfFirstInstallDateTime);
////
cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key2);
strcpy(RegisteredOwner, (char *)cb.buffer);
////
cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key3);
strcpy(RegisteredOrganization, (char *)cb.buffer);
////
cb = (CBData)readKey(HKEY_LOCAL_MACHINE, subKey, key4);
strcpy(ProductId, (char *)cb.buffer);
printf(" ProductId: %s\n", ProductId);
printf(" RegisteredOwner: %s\n", RegisteredOwner);
printf("RegisteredOrganization: %s\n", RegisteredOrganization);
}
160 个 CrackMe 之 078 - fireworx.10 注册算法中的变量初始化 BUG 分析
本帖最后由 solly 于 2019-8-5 22:51 编辑CrackMe 078 是一个 Delphi 编译的 CrackMe,也是加壳的,OD 加载后,入口代码是以下4行代码:
00429318 >90 nop
00429319 90 nop
0042931A 90 nop
0042931B 75 00 jnz short 0042931D
0042931D- E9 DE5C0400 jmp 0046F000
最后的跳转指令是跳转到壳代码入口,如下所示,解壳代码也简单:
0046F000 60 pushad
0046F001 E8 00000000 call 0046F006
0046F006 5D pop ebp
0046F007 81ED EAA84300 sub ebp, 0043A8EA
0046F00D B8 E4A84300 mov eax, 0043A8E4
0046F012 03C5 add eax, ebp
0046F014 2B85 78AD4300 sub eax, dword ptr
0046F01A 8985 84AD4300 mov dword ptr , eax
0046F020 80BD 6EAD4300 00 cmp byte ptr , 0
0046F027 75 15 jnz short 0046F03E
0046F029 FE85 6EAD4300 inc byte ptr
0046F02F E8 1D000000 call 0046F051
0046F034 E8 73020000 call 0046F2AC
0046F039 E8 0A030000 call 0046F348
0046F03E 8B85 70AD4300 mov eax, dword ptr
0046F044 0385 84AD4300 add eax, dword ptr
0046F04A 894424 1C mov dword ptr , eax
0046F04E 61 popad
0046F04F FFE0 jmp eax
在 0x0046F04F jmp eax 处,按 F4 就脱壳了,再 F8 就到了 OEP,可以进行脱壳,脱壳后也需要象 159 一样,进行 API 导入表手动修正,就是修正 DefWindowProc() 的引用。
正常启动后,界面如下:
如果成功注册,会显示以下界面:
当什么都不输入时,点击“OK”图片,会引发其第一个BUG,如下图所示:
我们随便输入一些数据,进行验证:
但是这个 CrackMe 的注册验证中,用到了一个未有效初始化的值,ESI 中的值,这个值应该是一个 Delphi 函数的 Sender 参数值,不过直接将其当作求累加和的基数,得不到正确的注册码。这可能是其第二个 Bug。
其事件处理代码如下:
00423F88 .53 push ebx
00423F89 .56 push esi
00423F8A .57 push edi
00423F8B .83C4 E8 add esp, -18
00423F8E .8BFA mov edi, edx
00423F90 .8BF0 mov esi, eax ; ESI = EAX = Sender,这里初始化的 ESI。
00423F92 .8BD7 mov edx, edi
00423F94 .8BC6 mov eax, esi
00423F96 .8B08 mov ecx, dword ptr
00423F98 .FF51 F0 call dword ptr
00423F9B .F646 40 02 test byte ptr , 2
00423F9F .74 09 je short 00423FAA
00423FA1 .33D2 xor edx, edx
00423FA3 .8BC6 mov eax, esi
00423FA5 .E8 8AEAFFFF call 00422A34
00423FAA >F646 44 02 test byte ptr , 2
00423FAE .74 3B je short 00423FEB
00423FB0 .66:8366 44 FD and word ptr , 0FFFD
00423FB5 .8BD4 mov edx, esp
00423FB7 .8B47 08 mov eax, dword ptr
00423FBA .E8 7D25FEFF call 0040653C
00423FBF .FF7424 04 push dword ptr
00423FC3 .FF7424 04 push dword ptr
00423FC7 .8D5424 10 lea edx, dword ptr
00423FCB .8BC6 mov eax, esi
00423FCD .8B08 mov ecx, dword ptr
00423FCF .FF51 44 call dword ptr
00423FD2 .8D4424 10 lea eax, dword ptr ; |
00423FD6 .50 push eax ; |pRect
00423FD7 .E8 9423FEFF call <jmp.&user32.PtInRect> ; \PtInRect
00423FDC .85C0 test eax, eax
00423FDE .74 0B je short 00423FEB
00423FE0 .8BC6 mov eax, esi
00423FE2 .66:BB ECFF mov bx, 0FFEC
00423FE6 .E8 25EEFDFF call 00402E10 ; 这里会间接去 call Image2Click()
00423FEB >8BD7 mov edx, edi
00423FED .33C9 xor ecx, ecx
00423FEF .8BC6 mov eax, esi
00423FF1 .E8 56FFFFFF call 00423F4C
00423FF6 .83C4 18 add esp, 18
00423FF9 .5F pop edi
00423FFA .5E pop esi
00423FFB .5B pop ebx
00423FFC .C3 retn
上面是Image控件的事件处理框架代码,通过 call 00402E10 调用Click处理代码,进行注册验证,验证代码如下:
00453E94/.55 push ebp
00453E95|.8BEC mov ebp, esp
00453E97|.6A 00 push 0
00453E99|.6A 00 push 0
00453E9B|.6A 00 push 0
00453E9D|.53 push ebx
00453E9E|.56 push esi
00453E9F|.57 push edi
00453EA0|.8BF8 mov edi, eax ;Sender 参数
00453EA2|.33C0 xor eax, eax
00453EA4|.55 push ebp ;指向本函数返回地址
00453EA5|.68 B63F4500 push 00453FB6 ;SEH
00453EAA|.64:FF30 push dword ptr fs: ;Save old SEH
00453EAD|.64:8920 mov dword ptr fs:, esp ;Install new SEH
00453EB0|.8D55 FC lea edx, dword ptr ;接收指向用户名缓冲区的指针
00453EB3|.8B87 C8020000 mov eax, dword ptr ;Owner 参数,控件对象实例
00453EB9|.E8 5AE9FCFF call 00422818 ;TControl.GetText(), 取得用户名,eax 为长度
00453EBE|.33DB xor ebx, ebx ;int i = 0
00453EC0|>8B45 FC /mov eax, dword ptr ;eax ===> 用户名: "solly",最多19位,多出部分无效。
00453EC3|.0FB64418 FF |movzx eax, byte ptr ;name,从delphi字符串长度最高位字节起(0x00),即 name, movzx 无符号扩展
00453EC8|.03F0 |add esi, eax ;esi 初始化值等于 0x02308CFC(Win10 64bit),sum += Ord(name)
00453ECA|.8D55 F8 |lea edx, dword ptr ;保存返回地址的指针
00453ECD|.8BC6 |mov eax, esi ;只有esi变成负数才有可能转换成11字符的字符串
00453ECF|.E8 9437FBFF |call 00407668 ;IntToStr()
00453ED4|.8D55 F4 |lea edx, dword ptr
00453ED7|.8B87 D0020000 |mov eax, dword ptr
00453EDD|.E8 36E9FCFF |call 00422818 ;取得注册码假码"7878787878"
00453EE2|.8B45 F4 |mov eax, dword ptr ;eax ===> 输入的注册码
00453EE5|.8A00 |mov al, byte ptr ;sn
00453EE7|.8B55 F8 |mov edx, dword ptr ;edx ===> 计算的注册码
00453EEA|.3A02 |cmp al, byte ptr ;假码第一个字符与name求和字符串第一个字符比较 sn == IntToStr(sum)
00453EEC|.0F85 9F000000 |jnz 00453F91
00453EF2|.8B45 F4 |mov eax, dword ptr ;eax ===> 输入的注册码
00453EF5|.8A40 01 |mov al, byte ptr ;sn
00453EF8|.8B55 F8 |mov edx, dword ptr ;edx ===> 计算的注册码
00453EFB|.3A42 01 |cmp al, byte ptr
00453EFE|.0F85 8D000000 |jnz 00453F91
00453F04|.8B45 F4 |mov eax, dword ptr ;eax ===> 输入的注册码
00453F07|.8A40 02 |mov al, byte ptr ;sn
00453F0A|.8B55 F8 |mov edx, dword ptr ;edx ===> 计算的注册码
00453F0D|.3A42 02 |cmp al, byte ptr
00453F10|.75 7F |jnz short 00453F91
00453F12|.8B45 F4 |mov eax, dword ptr
00453F15|.8A40 03 |mov al, byte ptr ;sn
00453F18|.8B55 F8 |mov edx, dword ptr
00453F1B|.3A42 03 |cmp al, byte ptr
00453F1E|.75 71 |jnz short 00453F91
00453F20|.8B45 F4 |mov eax, dword ptr
00453F23|.8A40 04 |mov al, byte ptr ;sn
00453F26|.8B55 F8 |mov edx, dword ptr
00453F29|.3A42 04 |cmp al, byte ptr
00453F2C|.75 63 |jnz short 00453F91
00453F2E|.8B45 F4 |mov eax, dword ptr
00453F31|.8A40 05 |mov al, byte ptr ;sn
00453F34|.8B55 F8 |mov edx, dword ptr
00453F37|.3A42 05 |cmp al, byte ptr
00453F3A|.75 55 |jnz short 00453F91
00453F3C|.8B45 F4 |mov eax, dword ptr
00453F3F|.8A40 06 |mov al, byte ptr ;sn
00453F42|.8B55 F8 |mov edx, dword ptr
00453F45|.3A42 06 |cmp al, byte ptr
00453F48|.75 47 |jnz short 00453F91
00453F4A|.8B45 F4 |mov eax, dword ptr
00453F4D|.8A40 07 |mov al, byte ptr ;sn
00453F50|.8B55 F8 |mov edx, dword ptr
00453F53|.3A42 07 |cmp al, byte ptr
00453F56|.75 39 |jnz short 00453F91
00453F58|.8B45 F4 |mov eax, dword ptr
00453F5B|.8A40 08 |mov al, byte ptr ;sn
00453F5E|.8B55 F8 |mov edx, dword ptr
00453F61|.3A42 08 |cmp al, byte ptr
00453F64|.75 2B |jnz short 00453F91
00453F66|.8B45 F4 |mov eax, dword ptr
00453F69|.8A40 09 |mov al, byte ptr ;sn
00453F6C|.8B55 F8 |mov edx, dword ptr
00453F6F|.3A42 09 |cmp al, byte ptr
00453F72|.75 1D |jnz short 00453F91
00453F74|.8B45 F4 |mov eax, dword ptr
00453F77|.8A40 0A |mov al, byte ptr ;sn
00453F7A|.8B55 F8 |mov edx, dword ptr
00453F7D|.3A42 0A |cmp al, byte ptr ;总共11次字符比较,只有当 ESI 的值为负数(-1000000000以下)可以转换成11位字符串
00453F80|.75 0F |jnz short 00453F91
00453F82|.A1 EC654500 |mov eax, dword ptr
00453F87|.8B00 |mov eax, dword ptr
00453F89|.8B10 |mov edx, dword ptr
00453F8B|.FF92 CC000000 |call dword ptr ;显示成功的消息
00453F91|>43 |inc ebx
00453F92|.83FB 13 |cmp ebx, 13
00453F95|.^ 0F85 25FFFFFF \jnz 00453EC0
00453F9B|.33C0 xor eax, eax
00453F9D|.5A pop edx
00453F9E|.59 pop ecx
00453F9F|.59 pop ecx
00453FA0|.64:8910 mov dword ptr fs:, edx
00453FA3|.68 BD3F4500 push 00453FBD
00453FA8|>8D45 F4 lea eax, dword ptr
00453FAB|.BA 03000000 mov edx, 3
00453FB0|.E8 57F8FAFF call 0040380C
00453FB5\.C3 retn
00453FB6 .^ E9 EDF2FAFF jmp 004032A8
00453FBB .^ EB EB jmp short 00453FA8
00453FBD .5F pop edi
00453FBE .5E pop esi
00453FBF .5B pop ebx
00453FC0 .8BE5 mov esp, ebp
00453FC2 .5D pop ebp
00453FC3 .C3 retn
序列号就是对用户名的所有字符的 ASCII 码值求累加和,并与esi中的值相加,再转换成一个10进制的字符串。
而Bug问题出在 0x00453EC8add esi, eax 这一行,这里 esi 没有在事件函数内初始化,而是直接引用的函数外传入的值(应该是上一层函数 Sender 对象的地址值),并且在不同的操作系统环境,有没有脱壳等不同情况下,其值不固定,所以这应该算是一个BUG,不然,无论怎么处理也得不到11位注册码来进行校验。根据上面的比较代码,注册码有11位,只有当 esi 为负数时(至少小于-1000000000)才可能通过 IntToStr() 函数转换成11位注册码。但是,地址值 0x80000000 ~ 0xFFFFFFFF 都是操作系统的地址空间,应用程序中不会产生 0x80000000 以上的变量地址值,所以,就算通过内存注册机方式,这个 crackme 基本上也没有可能通过其验证算法。
只有修改 esi 的初始值在 0x80000000 以上、0xC4653600以下时才可能生成11位的序列号,至于怎么改,就不讨论了,这里只指出这可能是一个 bug。 谢谢楼主分享 我怎么什么多看不懂啊,有没有基础点的教程啊,,我刚开始学{:1_907:}
谢谢楼主分享 学学思路,任重道远啊。自己一上手就蒙了 谢谢楼主分享 楼主是把160个CrackMe都制服了啊 谢谢楼主分享 多谢楼主教程 积累经验是一切的一切{:1_893:}
页:
[1]
2