首先申明,本人菜鸟一个,在这个CM中被迷住了不少时间,还是问原作者才得到了答案,所以请你们不要找我什么破解啊收费之类的!我一开始以为是利用钩子来处理MessageBox的,结果陷入泥潭,前面一片黑暗,费尽心思得到的却是一大堆垃圾代码……不得不佩服作者的思路以及MFC的强大啊,再次感叹:定势思维害死人!
下面开始讲述痛苦历程:
首先,用OD一加载CM就会立即退出,选上插件的功能亦然。查看软件发现里面有一个tls节。用IDA加载能看到tls中有两个回调函数:
看Callback0的部分伪代码:
……
这里利用被调试的软件具有调试权限的特点来反调试,这个anti只要用HideOD就能越过。
第二个回调函数的反汇编代码:
.text:00401630 TlsCallback_1 proc near ; DATA XREF: .rdata:004335FC o
.text:00401630 push 0 ; lpModuleName
.text:00401632 call ds:GetModuleHandleA
.text:00401638 mov ecx, [eax+3Ch]
.text:0040163B mov ecx, [ecx+eax+28h] ; 得到软件的OEP地址
.text:0040163F add ecx, eax
.text:00401641 xor eax, eax
.text:00401643
.text:00401643 loc_401643: ; CODE XREF: TlsCallback_1+1D j
.text:00401643 cmp byte ptr [eax+ecx], 0CCh
.text:00401647 jz short loc_401650
.text:00401649 inc eax
.text:0040164A cmp eax, 0Ah ; 测试前10个字节是否是0xcc断点,是的话直接ExitProcess
.text:0040164D jl short loc_401643
.text:0040164F retn
修改
.text:00401630 push 0 ; lpModuleName
为retn,直接返回,即能越过此anti,从而让OD正常加载!
然后是进入代码校验,下断GetModuleHandleA [ESP+4]==0 按住F9看堆栈中函数的领空:
来到函数调用处,即来到了校验函数部分:
00401660 /$ 83EC 0C sub esp, 0C
00401663 |. 53 push ebx
00401664 |. 56 push esi
00401665 |. 6A 00 push 0 ; /pModule = NULL
00401667 |. FF15 98324300 call dword ptr [<&KERNEL32.GetModuleH>; \GetModuleHandleA
0040166D |. 8BD8 mov ebx, eax
0040166F |. 8B73 3C mov esi, dword ptr [ebx+3C] ; 得到e_flag
00401672 |. 8B441E 1C mov eax, dword ptr [esi+ebx+1C] ; eax=sizeofcode
00401676 |. 8B541E 2C mov edx, dword ptr [esi+ebx+2C] ; edx=baseofcode
0040167A |. 03F3 add esi, ebx ; esi=_IMAGE_NT_HEADERS
0040167C |. 50 push eax
0040167D |. 03D3 add edx, ebx ; edx=baseofcode+baseofimage
0040167F |. E8 2C010000 call 004017B0 ; 计算检验和
00401684 |. 3B46 58 cmp eax, dword ptr [esi+58] ; 与esi.CheckSum进行比较
00401687 |. 74 08 je short 00401691 ; 此处若不跳转,直接返回校验失败
00401689 |. 5E pop esi
0040168A |. 33C0 xor eax, eax ; 校验失败,返回0
0040168C |. 5B pop ebx
0040168D |. 83C4 0C add esp, 0C
00401690 |. C3 retn
看校验和的计算过程为:
004017B0 /$ 51 push ecx
004017B1 |. 53 push ebx
004017B2 |. 8B5C24 0C mov ebx, dword ptr [esp+C] ; ebx=sizeofcode
004017B6 |. 56 push esi ; _IMAGE_NT_HEADERS
004017B7 |. 57 push edi
004017B8 |. 33C9 xor ecx, ecx
004017BA |. 33F6 xor esi, esi
004017BC |. 33FF xor edi, edi
004017BE |. 33C0 xor eax, eax
004017C0 |. 83FB 02 cmp ebx, 2
004017C3 |. 894C24 0C mov dword ptr [esp+C], ecx
004017C7 |. 7C 32 jl short 004017FB
004017C9 |. 4B dec ebx
004017CA |. 55 push ebp
004017CB |. EB 03 jmp short 004017D0
004017CD | 8D49 00 lea ecx, dword ptr [ecx]
004017D0 |> 0FB60C02 /movzx ecx, byte ptr [edx+eax] ; 得到代码段的第i个字节,设为a,ecx=a
004017D4 |. 8BE9 |mov ebp, ecx ; ebp=ecx=a=codeseg[i]
004017D6 |. C1E5 06 |shl ebp, 6 ; ebp=a<<6
004017D9 |. 33E9 |xor ebp, ecx ; ebp=a<<6^a
004017DB |. 0FB64C02 01 |movzx ecx, byte ptr [edx+eax+1] ; ecx=b=codeseg[i+1]
004017E0 |. 03F5 |add esi, ebp ; sum1=esi+=ebp
004017E2 |. 8BE9 |mov ebp, ecx ; ebp=b
004017E4 |. C1E5 06 |shl ebp, 6 ; ebp=b<<6
004017E7 |. 33E9 |xor ebp, ecx ; ebp=b<<6^b
004017E9 |. 83C0 02 |add eax, 2 ; i+=2
004017EC |. 03FD |add edi, ebp ; sum2=edi+=ebp
004017EE |. 3BC3 |cmp eax, ebx
004017F0 |.^ 7C DE \jl short 004017D0
004017F2 |. 8B5C24 18 mov ebx, dword ptr [esp+18]
004017F6 |. 8B4C24 10 mov ecx, dword ptr [esp+10]
004017FA |. 5D pop ebp
004017FB |> 3BC3 cmp eax, ebx
004017FD |. 7D 0B jge short 0040180A
004017FF |. 0FB60410 movzx eax, byte ptr [eax+edx]
00401803 |. 8BC8 mov ecx, eax
00401805 |. C1E1 06 shl ecx, 6
00401808 |. 33C8 xor ecx, eax
0040180A |> 8D0437 lea eax, dword ptr [edi+esi] ; eax=edi+esi=sum1+sum2
0040180D |. 5F pop edi
0040180E |. 5E pop esi
0040180F |. 03C1 add eax, ecx
00401811 |. 5B pop ebx
00401812 |. 59 pop ecx
00401813 \. C2 0400 retn 4
翻译成C语言即
While(i < len(codeseg[i])){
x = codeseg[i]<<6^codeseg[i]
y = codeseg[i+1]<<6^codeseg[i+1]
i+=2;
}
Sum = x+y; //计算的校验和
00401691 |> \8BB6 80000000 mov esi, dword ptr [esi+80] ; esi.IMAGE_DIRECTORY_ENTRY_IMPORT
00401697 |. 55 push ebp
00401698 |. 03F3 add esi, ebx ; esi=输入表实际地址
0040169A |. 57 push edi
0040169B |> 8B46 10 /mov eax, dword ptr [esi+10] ; eax=IAT
0040169E |. 897424 14 |mov dword ptr [esp+14], esi
004016A2 |. 85C0 |test eax, eax
004016A4 |. 75 17 |jnz short 004016BD
004016A6 |. 3946 08 |cmp dword ptr [esi+8], eax
004016A9 |. 75 12 |jnz short 004016BD
004016AB |. 3946 0C |cmp dword ptr [esi+C], eax
004016AE |. 75 0D |jnz short 004016BD
004016B0 |. 3906 |cmp dword ptr [esi], eax
004016B2 |. 75 09 |jnz short 004016BD
004016B4 |. 3946 04 |cmp dword ptr [esi+4], eax
004016B7 |. 0F84 AD000000 |je 0040176A
004016BD |> 8B3E |mov edi, dword ptr [esi] ; edi=OriginalFirstThunk
004016BF |. 8D2C18 |lea ebp, dword ptr [eax+ebx]
004016C2 |. 8B45 00 |mov eax, dword ptr [ebp] ; eax=函数的绝对地址
004016C5 |. 03FB |add edi, ebx
004016C7 |. 85C0 |test eax, eax
004016C9 |. 0F84 93000000 |je 00401762
004016CF |. 90 |nop
004016D0 |> 33C9 |/xor ecx, ecx
004016D2 |> 8A10 ||/mov dl, byte ptr [eax] ; 取函数的第1字节,x
004016D4 |. 80F2 12 |||xor dl, 12 ; x^12h ==0DE的话,说明该函数被下了断点
004016D7 |. 80FA DE |||cmp dl, 0DE
004016DA |. 0F84 B8000000 |||je 00401798 ; 如果相等,则x=0xcc
004016E0 |. 41 |||inc ecx
004016E1 |. 40 |||inc eax
004016E2 |. 83F9 05 |||cmp ecx, 5 ; 总共比较5个字节
004016E5 |.^ 7C EB ||\jl short 004016D2
004016E7 |. F707 00000080 ||test dword ptr [edi], 80000000 ; 测试是否是以序号方式引入函数
004016ED |. 75 62 ||jnz short 00401751 ; 如果按序号方式引入的函数,就跳过
004016EF |. BA 3C524400 ||mov edx, 0044523C
004016F4 |> 8B07 ||/mov eax, dword ptr [edi]
004016F6 |. 807C18 02 00 |||cmp byte ptr [eax+ebx+2], 0
004016FB |. 8D4418 02 |||lea eax, dword ptr [eax+ebx+2] ; 得到函数名
004016FF |. C74424 10 000>|||mov dword ptr [esp+10], 0
00401707 |. 74 24 |||je short 0040172D
00401709 |. 8DA424 000000>|||lea esp, dword ptr [esp]
00401710 |> 0FB608 |||/movzx ecx, byte ptr [eax]
00401713 |. 8BF1 ||||mov esi, ecx
00401715 |. C1E6 07 ||||shl esi, 7
00401718 |. 33F1 ||||xor esi, ecx ; a=fname[i]<<7^fname[i]
0040171A |. 017424 10 ||||add dword ptr [esp+10], esi ; s+=a
0040171E |. C14C24 10 02 ||||ror dword ptr [esp+10], 2
00401723 |. 40 ||||inc eax
00401724 |. 8038 00 ||||cmp byte ptr [eax], 0
00401727 |.^ 75 E7 |||\jnz short 00401710
00401729 |. 8B7424 14 |||mov esi, dword ptr [esp+14]
0040172D |> 8B4C24 10 |||mov ecx, dword ptr [esp+10]
00401731 |. 394A FC |||cmp dword ptr [edx-4], ecx ; 将函数名计算结果与指定值比较
00401734 |. 75 10 |||jnz short 00401746
00401736 |. 8B45 00 |||mov eax, dword ptr [ebp]
00401739 |. 8902 |||mov dword ptr [edx], eax ; [edx]保存函数绝对地址
0040173B |. 896A 08 |||mov dword ptr [edx+8], ebp ; [edx+8]保存函数指针
0040173E |. 8B4D 00 |||mov ecx, dword ptr [ebp]
00401741 |. 8B01 |||mov eax, dword ptr [ecx]
00401743 |. 8942 04 |||mov dword ptr [edx+4], eax ; [edx+4]中保存函数的前四字节
00401746 |> 83C2 14 |||add edx, 14
00401749 |. 81FA 8C524400 |||cmp edx, 0044528C
0040174F |.^ 7C A3 ||\jl short 004016F4
00401751 |> 8B45 04 ||mov eax, dword ptr [ebp+4]
00401754 |. 83C5 04 ||add ebp, 4
00401757 |. 83C7 04 ||add edi, 4
0040175A |. 85C0 ||test eax, eax
0040175C |.^ 0F85 6EFFFFFF |\jnz 004016D0
00401762 |> 83C6 14 |add esi, 14 ; 得到下一张输入表
00401765 |.^ E9 31FFFFFF \jmp 0040169B
00401779 |. 51 push ecx
0040177A |. 6A 40 push 40
0040177C |. 6A 04 push 4
0040177E |. 56 push esi
0040177F |. FFD0 call eax ; 通过VirtualProtect更改MessageBox地址属性
00401781 |. 85C0 test eax, eax
00401783 |. 74 06 je short 0040178B
00401785 |. C706 20184000 mov dword ptr [esi], 00401820 ; 更改属性成功,替换MessageBox函数地址为新函数地址
0040178B |> 5F pop edi
0040178C |. 5D pop ebp
0040178D |. 5E pop esi
0040178E |. B8 01000000 mov eax, 1 ; 返回1表示校验成功无断点且未改代码
00401793 |. 5B pop ebx
00401794 |. 83C4 0C add esp, 0C
这里也就是为什么修改代码以后有无论怎么也得不到注册成功的结果。因为如果没有通过校验的话,MessageBox函数未被指定函数替换,造成第二部分注册码验证根本没有进行……,
然后对GetDlgItemTextA下断,来到第一部分的验证函数OnOK():
004014B0 /. 55 push ebp
004014B1 |. 8BEC mov ebp, esp
004014B3 |. 83E4 F8 and esp, FFFFFFF8
004014B6 |. 83EC 18 sub esp, 18
004014B9 |. 53 push ebx
004014BA |. 55 push ebp
004014BB |. 56 push esi
004014BC |. 57 push edi
004014BD |. 8BE9 mov ebp, ecx
004014BF |. 33FF xor edi, edi
004014C1 |. FF15 9C324300 call dword ptr [<&KERNEL32.GetTickCou>; [GetTickCount
004014C7 |. 6A 28 push 28 ; /Arg3 = 00000028
注意此处有一个GetTickCount()函数,用来计算验证的时间!
0040151C |. 3885 80000000 cmp byte ptr [ebp+80], al ; 注册码每八位必须用-隔开
00401522 |. 0F85 E9000000 jnz 00401611 ; 不是此格式立即报错!下同……
00401528 |. 3885 89000000 cmp byte ptr [ebp+89], al
0040152E |. 0F85 DD000000 jnz 00401611
00401534 |. 3885 92000000 cmp byte ptr [ebp+92], al
0040153A |. 0F85 D1000000 jnz 00401611
00401540 |. 8BC3 mov eax, ebx
00401542 |. 8D50 01 lea edx, dword ptr [eax+1]
00401545 |> 8A08 /mov cl, byte ptr [eax]
00401547 |. 40 |inc eax
00401548 |. 84C9 |test cl, cl
0040154A |.^ 75 F9 \jnz short 00401545
0040154C |. 2BC2 sub eax, edx
0040154E |. 83F8 23 cmp eax, 23 ; 判断注册码的长度是否等于23h=35位,否则报错!
00401557 |. BE 48524400 mov esi, 00445248 ; 开始计算过程
0040155C |. 8D6424 00 lea esp, dword ptr [esp]
00401560 |> 6A 10 /push 10
00401562 |. 6A 00 |push 0
00401564 |. 53 |push ebx
00401565 |. E8 4BD50100 |call 0041EAB5 ; 将每组的八位注册码转化为16进制数到EAX
0040156A |. 3346 F0 |xor eax, dword ptr [esi-10] ; 与四个常数相异或的值设为a,b,c,d
四个常数的值:
0040156D |. 83C6 14 |add esi, 14
00401570 |. 8946 EC |mov dword ptr [esi-14], eax
00401573 |. 83C4 0C |add esp, 0C
00401576 |. 83C3 09 |add ebx, 9
00401579 |. 81FE 98524400 |cmp esi, 00445298
0040157F |.^ 7C DF \jl short 00401560
00401581 |. A1 48524400 mov eax, dword ptr [445248]
00401586 |. 0FAFC7 imul eax, edi
00401589 |. 0FAFC7 imul eax, edi ; eax=ax^2
0040158C |. 8B35 70524400 mov esi, dword ptr [445270]
00401592 |. 2B35 84524400 sub esi, dword ptr [445284] ; esi=c-d
00401598 |. 99 cdq
00401599 |. 8BD8 mov ebx, eax ; ebx = ax^2
0040159B |. A1 5C524400 mov eax, dword ptr [44525C] ; eax=b
004015A0 |. 0FAFC7 imul eax, edi ; eax=b*x
004015A3 |. 895424 24 mov dword ptr [esp+24], edx ; [esp+24] = (int64)(b*x)>>32
004015A7 |. 99 cdq
004015A8 |. 894424 18 mov dword ptr [esp+18], eax ; [esp+18]=b*x
004015AC |. 895424 1C mov dword ptr [esp+1C], edx ; [esp+1c]=(int64)(b*x)>>32
004015B0 |. FF15 9C324300 call dword ptr [<&KERNEL32.GetTickCou>; [GetTickCount
004015B6 |. 8BC8 mov ecx, eax
004015B8 |. 2B4C24 14 sub ecx, dword ptr [esp+14] ; 获取计算注册码的时间差值
004015BC |. 8BC6 mov eax, esi ; EAX=c-d
004015BE |. 0FAFC6 imul eax, esi ; EAX=(c-d)^2
004015C1 |. 99 cdq
004015C2 |. 2B4424 18 sub eax, dword ptr [esp+18] ; eax=(c-d)^2-b*x
004015C6 |. 1B5424 1C sbb edx, dword ptr [esp+1C] ; 高位亦相减
004015CA |. 03C3 add eax, ebx ; eax=(c-d)^2-b*x+ax^2
004015CC |. 135424 24 adc edx, dword ptr [esp+24]
004015D0 |. 0BC2 or eax, edx ; (c-d)^2-b*x+ax^2==0
004015D2 |. 75 3D jnz short 00401611
004015D4 |. A1 48524400 mov eax, dword ptr [445248] ; eax=a
004015D9 |. 8B15 5C524400 mov edx, dword ptr [44525C] ; edx=b
004015DF |. 8BF0 mov esi, eax
004015E1 |. 0FAFF7 imul esi, edi ; esi=a*x
004015E4 |. 03F6 add esi, esi ; esi=2a*x
004015E6 |. 3BF2 cmp esi, edx ; 2a*x==b
004015E8 |. 75 27 jnz short 00401611
004015EA |. 85D0 test eax, edx ; a != b
004015EC |. 74 23 je short 00401611
004015EE |. 81F9 E8030000 cmp ecx, 3E8 ; 计算时间小于3E8毫秒=1000毫秒=1秒? 不成立失败!
004015F4 |. 7D 1B jge short 00401611 ; 大于1秒也没戏了
这一段所要满足的验证条件是:
1、(c-d)^2-b*x+a*x^2==0
2、2*a*x == b
3、a != b
然后再往下走,将会进入替换后的伪MessageBox函数部分,这里将会进行第二部分验证:
F7进入,将会来到替换后的部分,如果先前的代码验证未通过,就不会进行二部分验证了,直接是“还差一小步……!”实际差得远啊!
00401820 . 83EC 1C sub esp, 1C
00401823 . A1 20164400 mov eax, dword ptr [441620]
00401828 . 33C4 xor eax, esp
0040182A . 894424 18 mov dword ptr [esp+18], eax
0040182E . 8B4424 20 mov eax, dword ptr [esp+20]
00401832 . 8B0D 3C524400 mov ecx, dword ptr [44523C] ; USER32.MessageBoxA
00401838 . 56 push esi
00401839 . 8B7424 2C mov esi, dword ptr [esp+2C]
0040183D . 803E 00 cmp byte ptr [esi], 0
0040185B . 8BCE mov ecx, esi
0040185D . 8D49 00 lea ecx, dword ptr [ecx]
00401860 > 0FB611 movzx edx, byte ptr [ecx] ; 对"恭喜"或"遗憾"字样进行计算
00401863 . 8BFA mov edi, edx
00401865 . C1E7 07 shl edi, 7
00401868 . 33FA xor edi, edx
0040186A . 017C24 08 add dword ptr [esp+8], edi
0040186E . C14C24 08 02 ror dword ptr [esp+8], 2
00401873 . 41 inc ecx
00401874 . 8039 00 cmp byte ptr [ecx], 0
00401877 .^ 75 E7 jnz short 00401860
00401879 . 817C24 08 8A1>cmp dword ptr [esp+8], 45001E8A ; 如果计算结果不等于“恭喜”计算结果,则“遗憾”了~!
00401881 . 0F85 8A000000 jnz 00401911 ; 如果相等,则进行第二部分验证
00401887 . 8B15 70524400 mov edx, dword ptr [445270] ; edx=c
0040188D . 8B3D 84524400 mov edi, dword ptr [445284] ; edi=d
00401893 . 8B0D 68524400 mov ecx, dword ptr [445268] ; ecx=x
00401899 . 53 push ebx
0040189A . 55 push ebp
0040189B . 8BDA mov ebx, edx
0040189D . 2BDF sub ebx, edi ; ebx=c-d
0040189F . 8D2C89 lea ebp, dword ptr [ecx+ecx*4] ; ebp=5*x
004018A2 . 3BDD cmp ebx, ebp ; (c-d)==5*x?
004018A4 . 5D pop ebp
004018A5 . 5B pop ebx
004018A6 . 75 69 jnz short 00401911 ; 不相等就完蛋
004018A8 . 03FA add edi, edx ; edi=c+d
004018AA . 8D14CD 000000>lea edx, dword ptr [ecx*8] ; edx=8*x
004018B1 . 2BD1 sub edx, ecx ; edx=7*x
004018B3 . 3BFA cmp edi, edx ; c+d==7*x?
004018B5 . 75 5A jnz short 00401911 ; 不相等就完蛋
在这里得到第二部分的验证条件是:
4、c-d==5*x
5、c+d==7*x
将前面的三个条件和后面的两个条件相结合:
1、(c-d)^2-b*x+a*x^2==0
2、2*a*x == b
3、a != b
4、c-d==5*x
5、c+d==7*x
解方程组得到:
a=25
b=50x
c=6x
d=x
注意这里的abcd是注册码的四个部分与四个常数相异或的结果,所以要得到注册码就得用abcd分别与四个常数再次相异或,这样要写出注册机也比较容易了!
总结:虽然作者本人说是娱乐,但是可以看出这个CM还是非常难破解的,存在至少三处以上的手段来反逆向,而且是一个方程组,使得算法更加晦涩难懂,像我这样数学不大好的人基本上是迷茫状态。如果再将GetModuleHandleA改为非直接调用,代码校验部分会变得更隐蔽,逆向难度会进一步增加……个人观点!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)