Absolut Chess v1.5 注册算法分析
Absolut Chess v1.5 是一个国际象棋软件。下载的版本是演示版,有 10 次使用次数限制,否则将不能运行。演示版不能输入注册码(但实际上功能存在的,被隐藏掉了)。
补丁部分
首先是去掉使用次数限制。
10 次以后会跳出一个框,在 DialogBoxParamA 上设断点顺利截到。
0040F9EF 833D 50834300 00 cmp dword ptr [438350], 0
0040F9F6 0F84 39000000 je 0040FA35
0040F9FC 6A 00 push 0 ; /lParam = NULL
0040F9FE 68 0F104000 push 0040100F ; |DlgProc = AbsolutC.0040100F
0040FA03 8B45 08 mov eax, dword ptr [ebp+8] ; |
0040FA06 50 push eax ; |hOwner
0040FA07 68 30874300 push 00438730 ; |pTemplate = "DLG_BUY"
0040FA0C A1 848B4500 mov eax, dword ptr [458B84] ; |
0040FA11 50 push eax ; |hInst => 00400000
0040FA12 FF15 E0284700 call dword ptr [<&USER32.DialogB>; /DialogBoxParamA
0040FA18 8945 E4 mov dword ptr [ebp-1C], eax
0040FA1B 837D E4 00 cmp dword ptr [ebp-1C], 0
0040FA1F 0F85 10000000 jnz 0040FA35
0040FA25 6A 00 push 0 ; /lParam = 0
0040FA27 6A 00 push 0 ; |wParam = 0
0040FA29 6A 02 push 2 ; |Message = WM_DESTROY
0040FA2B 8B45 08 mov eax, dword ptr [ebp+8] ; |
0040FA2E 50 push eax ; |hWnd
0040FA2F FF15 E4284700 call dword ptr [<&USER32.SendMes>; /SendMessageA
0040FA35 833D 985E4400 00 cmp dword ptr [445E98], 0
这部分第一行可以看到,根据 [438350] 是否为 0 来判断要不要显式使用次数已到的框。
再往上看:
0040F8D9 833D 948C4500 0A cmp dword ptr [458C94], 0A
0040F8E0 0F8E 0A000000 jle 0040F8F0
0040F8E6 C705 50834300 01000 mov dword ptr [438350], 1
0040F8F0 C705 40834300 00000 mov dword ptr [438340], 0
0040F8FA E9 9F000000 jmp 0040F99E
如果 [458C94] <= 0Ah(10),就跳过给 [438350] 赋为 1 的那句。
由此推断,[458C94] 里面是当前使用次数。
重新开始调试,给 00458C94 设置内存写入硬件断点。
截到读取注册表写入内存变量部分如下:
0040E37E C785 0CFCFFFF 04000 mov dword ptr [ebp-3F4], 4
0040E388 8D85 0CFCFFFF lea eax, dword ptr [ebp-3F4]
0040E38E 50 push eax ; /pBufSize
0040E38F 8D85 10FCFFFF lea eax, dword ptr [ebp-3F0] ; |
0040E395 50 push eax ; |Buffer
0040E396 6A 00 push 0 ; |pValueType = NULL
0040E398 6A 00 push 0 ; |Reserved = NULL
0040E39A 68 48854300 push 00438548 ; |ValueName = "cvezes"
0040E39F 8B85 14FCFFFF mov eax, dword ptr [ebp-3EC] ; |
0040E3A5 50 push eax ; |hKey
0040E3A6 FF15 74254700 call dword ptr [<&ADVAPI32.RegQu>; /RegQueryValueExA
0040E3AC 8985 BCF7FFFF mov dword ptr [ebp-844], eax
0040E3B2 83BD BCF7FFFF 00 cmp dword ptr [ebp-844], 0
0040E3B9 0F85 16000000 jnz 0040E3D5
0040E3BF 8B85 10FCFFFF mov eax, dword ptr [ebp-3F0] ; 注册表中的值
0040E3C5 35 F4FE0E00 xor eax, 0EFEF4 ; 0xEFEF4^0xEFEF4=0, 0xEFEF4^0xEFEFF=0Ah
0040E3CA A3 948C4500 mov dword ptr [458C94], eax ; 写到内存变量
0040E3CF FF05 948C4500 inc dword ptr [458C94] ; 使用次数加1
于是可以把 eax, 0EFEF4 这据改掉,改为 xor eax, eax,这样每次都是第一次了。
第一处改动:0040E3C5 . 35 F4FE0E00 xor eax, 0EFEF4
改为
0040E3C5 33C0 xor eax, eax
0040E3C7 90 nop
0040E3C8 90 nop
0040E3C9 90 nop
文件修补:0000D7C5 35 F4 FE 0E 00 -> 33 C0 90 90 90
接下来是两处输入注册码的按钮。
先是 10 次使用满了跳出来的那个框里。
按下输入注册码按钮,出来一个框说演示版不能注册。这个框是 MessageBox。
在 MessageBoxA 上设断点,调用处:
0041F7F1 837D F4 02 cmp dword ptr [ebp-C], 2
0041F7F5 0F84 72FFFFFF je 0041F76D
看到 0041F76D 附近:
0041F76D > /6A 30 push 30 ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
0041F76F . |68 50954300 push 00439550 ; |Title = "Demo Version"
0041F774 . |68 60954300 push 00439560 ; |Text = "Operation do not allowed in this demo.",LF,LF,"Buy the full version online!",LF,LF,"http://www.overlans.com/shop.php",LF,LF,"Only $ 12"
0041F779 . |8B45 08 mov eax, dword ptr [ebp+8] ; |
0041F77C . |50 push eax ; |hOwner
0041F77D . |FF15 54284700 call dword ptr [<&USER32.MessageBoxA>>; /MessageBoxA
0041F783 . |68 D4954300 push 004395D4 ; /Title = "Microsoft Internet Explorer"
0041F788 . |6A 00 push 0 ; |Class = 0
0041F78A . |6A 00 push 0 ; |hAfterWnd = NULL
0041F78C . |6A 00 push 0 ; |hParent = NULL
0041F78E . |FF15 68284700 call dword ptr [<&USER32.FindWindowEx>; /FindWindowExA
0041F794 . |8945 FC mov dword ptr [ebp-4], eax
0041F797 . |6A 01 push 1 ; /IsShown = 1
0041F799 . |6A 00 push 0 ; |DefDir = NULL
0041F79B . |6A 00 push 0 ; |Parameters = NULL
0041F79D . |68 F0954300 push 004395F0 ; |FileName = "http://www.overlans.com/shop.php"
0041F7A2 . |68 14964300 push 00439614 ; |Operation = "open"
0041F7A7 . |8B45 FC mov eax, dword ptr [ebp-4] ; |
0041F7AA . |50 push eax ; |hWnd
0041F7AB . |FF15 1C284700 call dword ptr [<&SHELL32.ShellExecut>; /ShellExecuteA
0041F7B1 . |E9 27000000 jmp 0041F7DD
0041F7B6 . |6A 00 push 0 ; /lParam = NULL
0041F7B8 . |68 90114000 push 00401190 ; |DlgProc = AbsolutC.00401190
0041F7BD . |8B45 08 mov eax, dword ptr [ebp+8] ; |
0041F7C0 . |50 push eax ; |hOwner
0041F7C1 . |68 1C964300 push 0043961C ; |pTemplate = "DLG_LICENCA"
0041F7C6 . |A1 848B4500 mov eax, dword ptr [458B84] ; |
0041F7CB . |50 push eax ; |hInst => NULL
0041F7CC . |FF15 E0284700 call dword ptr [<&USER32.DialogBoxPar>; /DialogBoxParamA
0041F7D2 . |50 push eax ; /Result
0041F7D3 . |8B45 08 mov eax, dword ptr [ebp+8] ; |
0041F7D6 . |50 push eax ; |hWnd
0041F7D7 . |FF15 F8284700 call dword ptr [<&USER32.EndDialog>] ; /EndDialog
0041F7DD > |E9 2B000000 jmp 0041F80D
看到先跳出一个框,再打开一个网址。注意到紧跟在它后面的 0041F7B6,DLG_LICENCA 这个名字好像是我们期待的。把 0041F7F5 的跳转改为 je 0041F7B6 试试看,果然见到了那个框。
第二处改动:0041F7F5 0F 84 72 FF FF FF (je long 0041F76D) 改为 0F 84 BB FF FF FF (je long 0041F7B6) 也可改为 74 BF 90 90 90 90 (je short 0041F7B6)
文件修补:0001EBF7 72 -> BB
再是帮助菜单里的注册菜单项。
同样截 MessageBoxA,可以找到 00410594 这个函数是调用者。它显示 Demo 版不可注册,并打开网址。
跟上面一模一样,紧跟在其后的 004105DD 用于弹出注册框用于输入。
向上看,找到
00411666 8B85 FCFBFFFF mov eax, dword ptr [ebp-404] ; eax=[0012F9Ec]=0000001A
0041166C 33C9 xor ecx, ecx
0041166E 8A88 1F174100 mov cl, byte ptr [eax+41171F] ; cl=[41171F+1A]=[411739]=12
00411674 FF248D 7B1641 jmp dword ptr [ecx*4+41167B] ; [ecx*4+41167B]=[004116C3]=00410594
004116C3 处存放着这个地址。于是第三处改动:把 004116C3 处 94 05 41 00 改为 DD 05 41 00 就好了。文件修补:00010AC3 94 -> DD
下面来分析注册判断流程。
随便输注册码,截 MessageBoxA。下面这段是相关内容和分析:
0041FEE9 8D85 38FFFFFF lea eax, dword ptr [ebp-C8] ; 注册码 "1234"
0041FEEF 50 push eax ; 00401177 参数三
0041FEF0 8D45 9C lea eax, dword ptr [ebp-64] ; 用户名 "kjj"
0041FEF3 50 push eax ; 00421B30 参数
0041FEF4 E8 371C0000 call 00421B30 ; 求用户名长度?
0041FEF9 83C4 04 add esp, 4 ; 这里看出上面这个函数只有一个参数, "1234" 不是它的
0041FEFC 50 push eax ; eax=3, 00401177 参数二
0041FEFD 8D45 9C lea eax, dword ptr [ebp-64] ; [ebp-64]="kjj"
0041FF00 50 push eax ; 00401177 参数一
0041FF01 E8 7112FEFF call 00401177 ;
{
00401177 E9 F0CD0000 jmp 0040DF6C
0040DF6C 55 push ebp
0040DF6D 8BEC mov ebp, esp
0040DF6F 83EC 24 sub esp, 24
0040DF72 53 push ebx
0040DF73 56 push esi
0040DF74 57 push edi
0040DF75 837D 0C 00 cmp dword ptr [ebp+C], 0 ; [ebp+C]=00000003, 用户名长度
0040DF79 0F8F 07000000 jg 0040DF86
0040DF7F 33C0 xor eax, eax
0040DF81 E9 80000000 jmp 0040E006
0040DF86 8B45 10 mov eax, dword ptr [ebp+10] ; [ebp+10]=[0012F614]=0012F62C="1234"
0040DF89 50 push eax ; 004012A8 的参数
0040DF8A E8 1933FFFF call 004012A8 ; 判断是否是 20 个万能注册码之一
{
004012A8 E9 C3C50000 jmp 0040D870
0040D870 55 push ebp
0040D871 8BEC mov ebp, esp
0040D873 83EC 04 sub esp, 4
0040D876 53 push ebx
0040D877 56 push esi
0040D878 57 push edi
0040D879 C745 FC 00000 mov dword ptr [ebp-4], 0 ; 这里把 [ebp-4] 弄成 0 了
0040D880 E9 03000000 jmp 0040D888
0040D885 FF45 FC inc dword ptr [ebp-4] ; 循环首, [ebp-4] 加 1
0040D888 A1 0C834300 mov eax, dword ptr [43830C]
0040D88D 3945 FC cmp dword ptr [ebp-4], eax
0040D890 0F8D 34000000 jge 0040D8CA
0040D896 8B45 08 mov eax, dword ptr [ebp+8] ; eax=[0012F400]="1234", 输入的注册码
0040D899 50 push eax ; 参数二
0040D89A 8B45 FC mov eax, dword ptr [ebp-4] ; eax=[ebp-4]
; 1st: 刚才它被弄成 0 了, 于是 eax=0
0040D89D 03C0 add eax, eax ; eax=eax*2=[ebp-4]*2
0040D89F 8D0440 lea eax, dword ptr [eax+eax*2] ; eax=eax*3=[ebp-4]*6
0040D8A2 8D0480 lea eax, dword ptr [eax+eax*4] ; eax=eax*5=[ebp-4]*1E, 于是这里都是 0
0040D8A5 05 887F4300 add eax, 00437F88 ; 00437F88="Ab200001FRvGKF"
; 这里有一张表:
; 00437F88+ 0*1E: Ab200001FRvGKF
; 00437F88+ 1*1E: Ab20001Mdvl6Il
; 00437F88+ 2*1E: Ab20002qMv1oxy
; 00437F88+ 3*1E: Ab20003C9kz5Kj
; 00437F88+ 4*1E: Ab20004oXz3iYB
; 00437F88+ 5*1E: Ab20005v63uo5b
; 00437F88+ 6*1E: Ab20006xx6wmGk
; 00437F88+ 7*1E: Ab20007iXrxYgx
; 00437F88+ 8*1E: Ab20008CAO55EP
; 00437F88+ 9*1E: Ab20009TUwNMYe
; 00437F88+ A*1E: Ab20010askrZO9
; 00437F88+ B*1E: Ab20011Q6VvIYZ
; 00437F88+ C*1E: Ab200122PGq2NF
; 00437F88+ D*1E: Ab20013KuI3ByH
; 00437F88+ E*1E: Ab20014hGZnATH
; 00437F88+ F*1E: Ab20015CIZD4Ji
; 00437F88+10*1E: Ab20016qGqvpsn
; 00437F88+11*1E: Ab200179Fq3nts
; 00437F88+12*1E: Ab20018QLHV24h
; 00437F88+13*1E: Ab20019nbbma0w
0040D8AA 50 push eax ; 参数一, "Ab200001FRvGKF"
0040D8AB E8 F0410100 call 00421AA0 ; 这里面就是简单的字符串比较,害得我看了半天
0040D8B0 83C4 08 add esp, 8
0040D8B3 85C0 test eax, eax
0040D8B5 0F85 0A000000 jnz 0040D8C5
0040D8BB B8 01000000 mov eax, 1 ; 如果相同, 令 eax=1
0040D8C0 E9 0C000000 jmp 0040D8D1 ; 跳到后面, 跳出这个函数, 这个函数返回值就是 1 了
0040D8C5 E9 BBFFFFFF jmp 0040D885
0040D8CA 33C0 xor eax, eax
0040D8CC E9 00000000 jmp 0040D8D1
0040D8D1 5F pop edi
0040D8D2 5E pop esi
0040D8D3 5B pop ebx
0040D8D4 C9 leave
0040D8D5 C3 retn
}
0040DF8F 83C4 04 add esp, 4
0040DF92 85C0 test eax, eax
0040DF94 0F84 0A000000 je 0040DFA4 ; 如果不是万能注册码, 跳过下面两行
0040DF9A B8 01000000 mov eax, 1 ; 如果是万能注册码, 令 eax=1
0040DF9F E9 62000000 jmp 0040E006 ; 跳到后面, 跳出函数
; 下面是非万能注册码时的处理
0040DFA4 E8 2E31FFFF call 004010D7 ; 把字母和数字数了个遍
; [462658]=0000003E(62), al='Z'
0040DFA9 8D45 DC lea eax, dword ptr [ebp-24] ; [ebp-24]=[0012F5E0], 缓冲区, 参数三
0040DFAC 50 push eax
0040DFAD 8B45 0C mov eax, dword ptr [ebp+C] ; eax=3, 用户名长度, 参数二
0040DFB0 50 push eax
0040DFB1 8B45 08 mov eax, dword ptr [ebp+8] ; eax="kjj", 用户名, 参数一
0040DFB4 50 push eax
0040DFB5 E8 1133FFFF call 004012CB ; 算法在这里, 详细内容见下面
; [ebp-24]="CH&aIaJaIaIaJaIaIaKxA9"
0040DFBA 83C4 0C add esp, 0C
0040DFBD C745 FC 00000 mov dword ptr [ebp-4], 0
0040DFC4 E9 03000000 jmp 0040DFCC
0040DFC9 FF45 FC inc dword ptr [ebp-4] ; 这个循环用来比较注册码是否正确
0040DFCC 837D FC 12 cmp dword ptr [ebp-4], 12 ; 仅仅比较前 18 位
0040DFD0 0F8D 26000000 jge 0040DFFC
0040DFD6 8B45 FC mov eax, dword ptr [ebp-4]
0040DFD9 8B4D 10 mov ecx, dword ptr [ebp+10]
0040DFDC 0FBE0408 movsx eax, byte ptr [eax+ecx]
0040DFE0 8B4D FC mov ecx, dword ptr [ebp-4]
0040DFE3 0FBE4C0D DC movsx ecx, byte ptr [ebp+ecx-24]
0040DFE8 3BC1 cmp eax, ecx
0040DFEA 0F84 07000000 je 0040DFF7
0040DFF0 33C0 xor eax, eax
0040DFF2 E9 0F000000 jmp 0040E006
0040DFF7 E9 CDFFFFFF jmp 0040DFC9
0040DFFC B8 01000000 mov eax, 1
0040E001 E9 00000000 jmp 0040E006
0040E006 5F pop edi
0040E007 5E pop esi
0040E008 5B pop ebx
0040E009 C9 leave
0040E00A C3 retn
}
0041FF06 83C4 0C add esp, 0C ; 这里看出这个函数是三个参数
0041FF09 85C0 test eax, eax
0041FF0B 0F84 56000000 je 0041FF67 ; 无效注册码最后看注册算法这个函数。
004012CB E9 3DC80000 jmp 0040DB0D
最后来看算注册码的函数。
0040DB0D 55 push ebp
0040DB0E 8BEC mov ebp, esp
; 传入参数
; [ebp+8]: char *username
; [ebp+C]: int length
; [ebp+10]: char *buffer
0040DB10 83EC 10 sub esp, 10 ; 4 个局部变量, 记为 int var[4]
0040DB13 53 push ebx
0040DB14 56 push esi
0040DB15 57 push edi
0040DB16 C745 F0 03000 mov dword ptr [ebp-10], 3 ; [ebp-10]=0012F5B0=00000003, var[0]=3
0040DB1D 837D 0C 00 cmp dword ptr [ebp+C], 0 ; 判断用户名是否零长度
0040DB21 0F8F 05000000 jg 0040DB2C
0040DB27 E9 4F010000 jmp 0040DC7B
0040DB2C E8 A635FFFF call 004010D7 ; 又是这个函数, [462658]=0000003E, al='Z'
0040DB31 A0 807F4300 mov al, byte ptr [437F80] ; [437F80]='C'
0040DB36 8B4D 10 mov ecx, dword ptr [ebp+10] ; 缓冲区地址, 刚才的第三个参数
0040DB39 8801 mov byte ptr [ecx], al
0040DB3B A0 817F4300 mov al, byte ptr [437F81] ; [437F81]='H'
0040DB40 8B4D 10 mov ecx, dword ptr [ebp+10]
0040DB43 8841 01 mov byte ptr [ecx+1], al
0040DB46 A0 827F4300 mov al, byte ptr [437F82] ; [437F82]='&'
0040DB4B 8B4D 10 mov ecx, dword ptr [ebp+10]
0040DB4E 8841 02 mov byte ptr [ecx+2], al ; 把 "CH&" 拷到缓冲区
0040DB51 C745 F4 03000 mov dword ptr [ebp-C], 3 ; [ebp-C]=00000003, var[1]=3, var[1] 记为 i
0040DB58 E9 03000000 jmp 0040DB60 ; 进入循环
0040DB5D FF45 F4 inc dword ptr [ebp-C] ; 循环首
0040DB60 837D F4 12 cmp dword ptr [ebp-C], 12 ; 一直循环到 i>=12,循环 15 次
0040DB64 0F8D 97000000 jge 0040DC01
0040DB6A 8B45 F4 mov eax, dword ptr [ebp-C] ; eax=i=3
0040DB6D 99 cdq ; edx=0
0040DB6E F77D 0C idiv dword ptr [ebp+C] ; eax=i/length=1, edx=i%length=0
0040DB71 8955 FC mov dword ptr [ebp-4], edx ; var[3]=edx=i%length=0, var[3] 记为 r1
0040DB74 8B45 F4 mov eax, dword ptr [ebp-C] ; eax=i=3
0040DB77 0345 F0 add eax, dword ptr [ebp-10] ; eax=eax+var[0]=eax+3=6
0040DB7A 99 cdq ; edx=0
0040DB7B F77D 0C idiv dword ptr [ebp+C] ; eax=(i+3)/length=2, edx=(i+3)%length=0
0040DB7E 8955 F8 mov dword ptr [ebp-8], edx ; var[2]=edx=(i+3)%length=0, var[2] 记为 r2
0040DB81 8B45 F4 mov eax, dword ptr [ebp-C] ; eax=i=3
0040DB84 99 cdq ; edx=0
0040DB85 33C2 xor eax, edx ; eax=3
0040DB87 2BC2 sub eax, edx ; eax=3
0040DB89 83E0 01 and eax, 1 ; eax=1
0040DB8C 33C2 xor eax, edx ; eax=1
0040DB8E 2BC2 sub eax, edx ; eax=1
; 这段是判断 i 的奇偶
0040DB90 0F84 34000000 je 0040DBCA
0040DB96 8B45 F8 mov eax, dword ptr [ebp-8] ; eax=r2=0
0040DB99 8B4D 08 mov ecx, dword ptr [ebp+8] ; ecx=username
0040DB9C 0FBE0408 movsx eax, byte ptr [eax+ecx] ; eax=username[r2]
0040DBA0 8B4D FC mov ecx, dword ptr [ebp-4] ; ecx=r1
0040DBA3 8B55 08 mov edx, dword ptr [ebp+8] ; edx=username
0040DBA6 320411 xor al, byte ptr [ecx+edx] ; al=al^username[r1]=username[r2]^username[r2]=0
0040DBA9 33C9 xor ecx, ecx ; ecx=0
0040DBAB 8AC8 mov cl, al ; cl=al=0
0040DBAD 8BC1 mov eax, ecx ; eax=0
0040DBAF 99 cdq ; edx=0
0040DBB0 F73D 58264600 idiv dword ptr [462658] ; eax=eax/3E=0, edx=eax%3E=0
0040DBB6 8A82 60CA4600 mov al, byte ptr [edx+46CA60] ; al=chr[edx]='a'
; 46CA60: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ, 记为 char chr[62];
0040DBBC 8B4D F4 mov ecx, dword ptr [ebp-C] ; eax=i=3
0040DBBF 8B55 10 mov edx, dword ptr [ebp+10] ; edx=buffer
0040DBC2 880411 mov byte ptr [ecx+edx], al ; buffer[i]=al
; 小结(i 奇的时候)
; r1 = i % length;
; r2 = (i+3) % length;
; offset = (username[r]^username[r2]) % 0x5E;
; buffer[i] = chr[offset];
0040DBC5 E9 32000000 jmp 0040DBFC
0040DBCA 8B45 F8 mov eax, dword ptr [ebp-8] ; eax=r2=0
0040DBCD 8B4D 08 mov ecx, dword ptr [ebp+8] ; ecx=username
0040DBD0 0FBE0408 movsx eax, byte ptr [eax+ecx] ; eax=username[r2]
0040DBD4 8B4D FC mov ecx, dword ptr [ebp-4] ; ecx=r1
0040DBD7 8B55 08 mov edx, dword ptr [ebp+8] ; edx=username
0040DBDA 0FBE0C11 movsx ecx, byte ptr [ecx+edx] ; eax=username[r1]
0040DBDE 23C1 and eax, ecx ; eax=username[r2]&username[r1]
0040DBE0 33C9 xor ecx, ecx
0040DBE2 8AC8 mov cl, al
0040DBE4 8BC1 mov eax, ecx ; eax 取最后两位
0040DBE6 99 cdq
0040DBE7 F73D 58264600 idiv dword ptr [462658]
0040DBED 8A82 60CA4600 mov al, byte ptr [edx+46CA60]
0040DBF3 8B4D F4 mov ecx, dword ptr [ebp-C]
0040DBF6 8B55 10 mov edx, dword ptr [ebp+10]
0040DBF9 880411 mov byte ptr [ecx+edx], al ; buffer[i]=al
; 小结(i 偶的时候)
; r1 = i % length;
; r2 = (i+3) % length;
; offset = (username[r]&username[r2]) % 0x5E;
; buffer[i] = chr[offset];
0040DBFC E9 5CFFFFFF jmp 0040DB5D ; 循环尾
0040DC01 6A 00 push 0
0040DC03 E8 D8390100 call 004215E0 ; 生成一个随机值
0040DC08 83C4 04 add esp, 4
0040DC0B 50 push eax ; 这个值作为下面的用的值
0040DC0C E8 8F390100 call 004215A0 ; 仅仅设置一个初始值而已,看了好久,太浪费时间了
0040DC11 83C4 04 add esp, 4
0040DC14 E8 97390100 call 004215B0
0040DC19 99 cdq
0040DC1A F73D 58264600 idiv dword ptr [462658]
0040DC20 8A82 60CA4600 mov al, byte ptr [edx+46CA60]
0040DC26 8B4D 10 mov ecx, dword ptr [ebp+10]
0040DC29 8841 12 mov byte ptr [ecx+12], al ; 加一个字符(但是注册码仅仅比较前面 18 位,这些是没用的)
0040DC2C E8 7F390100 call 004215B0
0040DC31 99 cdq
0040DC32 F73D 58264600 idiv dword ptr [462658]
0040DC38 8A82 60CA4600 mov al, byte ptr [edx+46CA60]
0040DC3E 8B4D 10 mov ecx, dword ptr [ebp+10]
0040DC41 8841 13 mov byte ptr [ecx+13], al ; 再加一个字符
0040DC44 E8 67390100 call 004215B0
0040DC49 99 cdq
0040DC4A F73D 58264600 idiv dword ptr [462658]
0040DC50 8A82 60CA4600 mov al, byte ptr [edx+46CA60]
0040DC56 8B4D 10 mov ecx, dword ptr [ebp+10]
0040DC59 8841 14 mov byte ptr [ecx+14], al ; 再加一个字符
0040DC5C E8 4F390100 call 004215B0
0040DC61 99 cdq
0040DC62 F73D 58264600 idiv dword ptr [462658]
0040DC68 8A82 60CA4600 mov al, byte ptr [edx+46CA60]
0040DC6E 8B4D 10 mov ecx, dword ptr [ebp+10]
0040DC71 8841 15 mov byte ptr [ecx+15], al ; 再加一个字符
0040DC74 8B45 10 mov eax, dword ptr [ebp+10]
0040DC77 C640 16 00 mov byte ptr [eax+16], 0 ; 结束符
0040DC7B 5F pop edi
0040DC7C 5E pop esi
0040DC7D 5B pop ebx
0040DC7E C9 leave
0040DC7F C3 retn
与最后面的 4 位生成有关的是 004215E0、004215A0、004215B0 这三个函数。由于经验不足,我在里面分析了好久。后来搞累了,随便乱输最后 4 位,发现总是可以的。不断的看软件自己生成的注册码,最后 4 位总是不一样的。然后回过头来看注册码比较的地方,是无视最后 4 位的,才知道那 4 位无关紧要。再再再后来用 IDA 看,原来是 _time、_srand、_rand 这三个函数……
把那些白看了的也贴出来吧
{
004215E0 83EC 10 sub esp, 10
004215E3 8D4424 00 lea eax, dword ptr [esp] ; buffer
004215E7 50 push eax ; pLocaltime
004215E8 FF15 10274700 call dword ptr [<&KERNEL32.GetLocalTime> ; GetLocalTime
; D8 07 06 00 06 00 07 00 0F 00 2D 00 03 00 2C 03
; typedef struct _SYSTEMTIME
; {
; WORD wYear; // 0x07D8=2008
; WORD wMonth; // 0x0006=6
; WORD wDayOfWeek; // 0x0007=6(Sat.)
; WORD wDay; // 0x0007=7
; WORD wHour; // 0x000F=15)
; WORD wMinute; // 0x002D=45
; WORD wSecond; // 0x0003=3
; WORD wMilliseconds; // 0x032C=812 (15:45:03.812)
; } SYSTEMTIME, *PSYSTEMTIME;
004215EE 8B4424 0C mov eax, dword ptr [esp+C]
004215F2 25 FFFF0000 and eax, 0FFFF ; 秒 00000023
004215F7 50 push eax
004215F8 33C0 xor eax, eax
004215FA 66:8B4424 0E mov ax, word ptr [esp+E] ; 分钟 00000021
004215FF 50 push eax
00421600 8B4424 10 mov eax, dword ptr [esp+10]
00421604 25 FFFF0000 and eax, 0FFFF ; 小时 00000011
00421609 50 push eax
0042160A 33C0 xor eax, eax
0042160C 66:8B4424 12 mov ax, word ptr [esp+12] ; 日期 00000007
00421611 50 push eax
00421612 33C0 xor eax, eax
00421614 66:8B4424 12 mov ax, word ptr [esp+12] ; 月份 00000006
00421619 50 push eax
0042161A 8B4424 14 mov eax, dword ptr [esp+14] ; 年份 000007D8
0042161E 25 FFFF0000 and eax, 0FFFF
00421623 50 push eax
00421624 E8 37100000 call 00422660
{
00422660 83EC 24 sub esp, 24
00422663 53 push ebx
00422664 56 push esi
00422665 8B7424 30 mov esi, dword ptr [esp+30] ; 年份, esi=000007D8
00422669 57 push edi
0042266A 55 push ebp
0042266B 81EE 6C070000 sub esi, 76C ; esi=esi-0000076C(1900)=0000006C(108)
; 记为 years
00422671 83FE 46 cmp esi, 46 ; 当前时间 1970 以前, eax=-1, 跳出
00422674 0F8C AE000000 jl 00422728
0042267A 81FE 8A000000 cmp esi, 8A ; 当前时间 2038 以后, 处理同上
00422680 0F8F A2000000 jg 00422728
00422686 8B5C24 3C mov ebx, dword ptr [esp+3C] ; ebx=月份=00000006
0042268A 8B2C9D D4A743 mov ebp, dword ptr [ebx*4+43A7D4] ; ebp=00000096
; 0043A7D4+1*4: FFFFFFFF -1
; 0043A7D4+2*4: 0000001E 30 +31
; 0043A7D4+3*4: 0000003A 58 +28
; 0043A7D4+4*4: 00000059 89 +31
; 0043A7D4+5*4: 00000077 119 +30
; 0043A7D4+6*4: 00000096 150 +31
; 0043A7D4+7*4: 000000B4 180 +30
; 0043A7D4+8*4: 000000D3 211 +31
; 0043A7D4+9*4: 0000016C 242 +31
; 0043A7D4+A*4: 00000110 272 +30
; 0043A7D4+B*4: 0000012F 303 +31
; 0043A7D4+C*4: 0000014D 333 +30
00422691 036C24 40 add ebp, dword ptr [esp+40] ; 加上日期, ebp=0000009D, 一年中的第几天
00422695 F7C6 03000000 test esi, 3 ; 年份是否为 4 的倍数
0042269B 75 06 jnz short 004226A3
0042269D 83FB 02 cmp ebx, 2 ; 月份是否为 2
004226A0 7E 01 jle short 004226A3
004226A2 45 inc ebp ; 加上一天, ebp=0000009E, 记为 yday
004226A3 E8 F8420000 call 004269A0
{
004269A0 833D 64A74300 cmp dword ptr [43A764], 0 ; [43A764]=1
004269A7 75 28 jnz short 004269D1 ; 直接跳出……忽悠人的?
004269A9 6A 0B push 0B
004269AB E8 F0C4FFFF call 00422EA0
004269B0 83C4 04 add esp, 4
004269B3 833D 64A74300 cmp dword ptr [43A764], 0
004269BA 75 0B jnz short 004269C7
004269BC E8 3F000000 call 00426A00
004269C1 FF05 64A74300 inc dword ptr [43A764]
004269C7 6A 0B push 0B
004269C9 E8 42C5FFFF call 00422F10
004269CE 83C4 04 add esp, 4
004269D1 C3 retn
}
004226A8 8D14F6 lea edx, dword ptr [esi+esi*8] ; edx=esi*9=0000033CC(972)=years*9
004226AB 8D04D6 lea eax, dword ptr [esi+edx*8] ; eax==esi*49h=00001ECC(7884)=years*73d
004226AE 8B4C24 44 mov ecx, dword ptr [esp+44] ; ecx=小时=11, 记为 hour
004226B2 8D1480 lea edx, dword ptr [eax+eax*4] ; edx=eax*5=000099FC(39420)=years*365d
004226B5 8D46 FF lea eax, dword ptr [esi-1] ; eax=0000006B(107)=years-1
004226B8 03D5 add edx, ebp ; edx=00009A9A=years*366d-1
004226BA 83E0 FC and eax, FFFFFFFC ; eax 低两位变 0, =(years-1)-(years-1)%4
004226BD 8D1490 lea edx, dword ptr [eax+edx*4] ; edx=(years*366-1)*4+((years-1)-(years-1)%4)
004226C0 8D3C52 lea edi, dword ptr [edx+edx*2] ; edi=(years*366-1)*12+((years-1)-(years-1)%4)*3
004226C3 8D1479 lea edx, dword ptr [ecx+edi*2] ; edx=(years*366-1)*24+((years-1)-(years-1)%4)*6+hour
004226C6 C1E2 02 shl edx, 2 ; edx=(years*366-1)*6+(years-1)/4*6+hour/4
004226C9 8D3C52 lea edi, dword ptr [edx+edx*2] ; edi=(years*366-1)*18+(years-1)/4*18+hour/4*3
004226CC 8B4424 4C mov eax, dword ptr [esp+4C] ; eax=秒, 记为 second
004226D0 8D14BF lea edx, dword ptr [edi+edi*4] ; edx=(years*366-1)*90+(years-1)/4*90+hour/4*15
004226D3 896C24 2C mov dword ptr [esp+2C], ebp ; [esp+2C]=yday
004226D7 035424 48 add edx, dword ptr [esp+48] ; edx+=分钟, mimute
; edx=(years*366-1)*90+(years-1)/4*90+hour/4*15+minute
004226DB 897424 24 mov dword ptr [esp+24], esi ; [esp+24]=years
004226DF C1E2 02 shl edx, 2 ; edx=(years*366-1)*90/4+(years-1)/4*90/4+hour/4*15/4+minute/4
004226E2 4B dec ebx ; yday=yday-1
004226E3 8D3C52 lea edi, dword ptr [edx+edx*2] ; edi=(years*366-1)*90/4*3+(years-1)/4*90/4*3+hour/4*15/4*3+minute/4*3
004226E6 895C24 20 mov dword ptr [esp+20], ebx ; [esp+20]=yday-1
004226EA 894C24 18 mov dword ptr [esp+18], ecx ; [esp+18]=hour
004226EE 8D14BF lea edx, dword ptr [edi+edi*4] ; edx=(years*366-1)*90/4*15+(years-1)/4*90/4*15+hour/4*15/4*15+minute/4*15
004226F1 0315 70A74300 add edx, dword ptr [43A770] ; [43A770]=FFFF8F80
; edx=(years*366-1)*90/4*15+(years-1)/4*90/4*15+hour/4*15/4*15+minute/4*15+0xFFFF8F80
004226F7 833D 74A74300 cmp dword ptr [43A774], 0 ; [43A774]=00000000
004226FE 8DBC02 808155 lea edi, dword ptr [edx+eax+7C558180]
; edi=(years*366-1)*90/4*15+(years-1)/4*90/4*15+hour/4*15/4*15+minute/4*15+0xFFFF8F80+second+0x7C558180
00422705 74 17 je short 0042271E
00422707 8D4424 10 lea eax, dword ptr [esp+10]
0042270B 50 push eax
0042270C E8 6F450000 call 00426C80
00422711 83C4 04 add esp, 4
00422714 85C0 test eax, eax
00422716 74 06 je short 0042271E
00422718 81EF 100E0000 sub edi, 0E10
0042271E 8BC7 mov eax, edi
00422720 5D pop ebp
00422721 5F pop edi
00422722 5E pop esi
00422723 5B pop ebx
00422724 83C4 24 add esp, 24
00422727 C3 retn
00422728 B8 FFFFFFFF mov eax, -1
0042272D 5D pop ebp
0042272E 5F pop edi
0042272F 5E pop esi
00422730 5B pop ebx
00422731 83C4 24 add esp, 24
00422734 C3 retn
}
00421629 8B4C24 2C mov ecx, dword ptr [esp+2C]
0042162D 83C4 18 add esp, 18
00421630 85C9 test ecx, ecx
00421632 74 02 je short 00421636
00421634 8901 mov dword ptr [ecx], eax
00421636 83C4 10 add esp, 10
00421639 C3 retn
}
补丁&注册机。(仅用于学习研究,请下载后24 小时内删除。)
(原发表于 CSDN:https://blog.csdn.net/cnStreamlet/article/details/2521622)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY