【联系方式】 QQ:不能留Q来着。。。。
【作者主页】 不好说啊
【破解声明】 师傅写的cm,不敢不玩,不过又不会玩。唉,只能写一点小小的分析了,如果有错的话不要介意啊
【软件名称】 新手CM 无壳无花 业界良心
【加壳信息】 VC
【软件简介】 一个很有趣的cm,带了一点掩眼法,需要多加一点心思才能搞定的啊
一个小小的cm,无壳无花,代码一目了然,只是可以参考的字符串不多,但还是有的,我们右键查找一下ASCII,会找到几条有趣的中文提示:
00401759 . 68 F48B4300 push 无壳无花.00438BF4 ; 用户名和序列号不可为空! ;判断输入的数据是否为空
00401712 . 68 D08B4300 push 无壳无花.00438BD0 ; |c:\reg.ini
00401741 . 68 DC8B4300 push 无壳无花.00438BDC ; 验证通过,请重启软件! ;要求重启程序进行验证
估计这附近就是关键的比较的地方了,我们先来猜测一下程序的验证流程:输入数据->验证数据->保存数据->重启验证。
应该就是这么几点的,于是我们就来在这一段的段首下段,输入好信息后,慢慢跟踪吧。
段首为 00401610,下F2断点,点注册,然后慢慢跟踪,确定每一个函数的大致作用
[Asm] 纯文本查看 复制代码
00401610 . 6A FF push -0x1
00401612 . 68 90264300 push 无壳无花.00432690
....... ;此处省略掉无关的代码
00401693 . 8D4C24 0C lea ecx,dword ptr ss:[esp+0xC]
00401697 . 51 push ecx
00401698 . 8D8E D0000000 lea ecx,dword ptr ds:[esi+0xD0]
0040169E . C64424 24 01 mov byte ptr ss:[esp+0x24],0x1
004016A3 . E8 C6870000 call <无壳无花.Edit_GetText> ; GetPass
004016A8 . 8D5424 10 lea edx,dword ptr ss:[esp+0x10]
004016AC . 52 push edx
004016AD . 8D8E 78010000 lea ecx,dword ptr ds:[esi+0x178]
004016B3 . E8 B6870000 call <无壳无花.Edit_GetText> ; GetName
004016B8 . 8B4424 0C mov eax,dword ptr ss:[esp+0xC]
004016BC . 68 678F4300 push 无壳无花.00438F67 ; push NULL
004016C1 . 50 push eax ; push Pass
004016C2 . C74424 1C 000>mov dword ptr ss:[esp+0x1C],0x0
004016CA . E8 FCDB0100 call <无壳无花.strcmp> ; 把Pass与空字符串比较,确定Pass是否为空
004016CF . 83C4 08 add esp,0x8
004016D2 . 85C0 test eax,eax
004016D4 . 0F95C0 setne al
004016D7 . 84C0 test al,al
004016D9 . 74 7A je short 无壳无花.00401755 ; 为空的话,跳向错误提示
004016DB . 8B4C24 10 mov ecx,dword ptr ss:[esp+0x10]
004016DF . 68 678F4300 push 无壳无花.00438F67 ; PUSH NULL
004016E4 . 51 push ecx ; push Name
004016E5 . E8 E1DB0100 call <无壳无花.strcmp> ; 吧Name与空字符串比较,确定Name是否为空
004016EA . 83C4 08 add esp,0x8
004016ED . 85C0 test eax,eax
004016EF . 0F95C0 setne al
004016F2 . 84C0 test al,al
004016F4 . 74 5F je short 无壳无花.00401755 ; 为空的话跳向失败
004016F6 . 8B5424 0C mov edx,dword ptr ss:[esp+0xC] ; 取Pass的长度
004016FA . 837A F4 14 cmp dword ptr ds:[edx-0xC],0x14 ; 与0x14比较,如果Pass的长度不够0x14,则跳向失败
004016FE . 75 65 jnz short 无壳无花.00401765 ; Pass长度为 0x14
00401700 . 6A 00 push 0x0 ; /hTemplateFile = NULL
00401702 . 68 80000000 push 0x80 ; |Attributes = NORMAL
00401707 . 6A 02 push 0x2 ; |Mode = CREATE_ALWAYS
00401709 . 6A 00 push 0x0 ; |pSecurity = NULL
0040170B . 6A 02 push 0x2 ; |ShareMode = FILE_SHARE_WRITE
0040170D . 68 00000040 push 0x40000000 ; |Access = GENERIC_WRITE
00401712 . 68 D08B4300 push 无壳无花.00438BD0 ; |c:\reg.ini
00401717 . FF15 9C324300 call dword ptr ds:[<&KERNEL32.CreateFile>; \CreateFileA
0040171D . 8BF8 mov edi,eax ; 下面就对文件C:\reg.ini开始写入注册信息。
0040171F . 6A 00 push 0x0 ; /pOverlapped = NULL
00401721 . 8D4424 18 lea eax,dword ptr ss:[esp+0x18] ; |
00401725 . 50 push eax ; |pBytesWritten
00401726 . 8B4424 14 mov eax,dword ptr ss:[esp+0x14] ; |
0040172A . 8B48 F4 mov ecx,dword ptr ds:[eax-0xC] ; |
0040172D . 51 push ecx ; |nBytesToWrite
0040172E . 50 push eax ; |Buffer
0040172F . 57 push edi ; |hFile
00401730 . FF15 98324300 call dword ptr ds:[<&KERNEL32.WriteFile>>; \WriteFile
00401736 . 57 push edi ; /hObject
00401737 . FF15 94324300 call dword ptr ds:[<&KERNEL32.CloseHandl>; \CloseHandle
0040173D . 6A 00 push 0x0
0040173F . 6A 00 push 0x0
00401741 . 68 DC8B4300 push 无壳无花.00438BDC ; 验证通过,请重启软件!
00401746 . 8BCE mov ecx,esi
00401748 . E8 E3640000 call 无壳无花.00407C30 ; 信息写入后退出程序
0040174D . 6A 00 push 0x0 ; /ExitCode = 0x0
0040174F . FF15 90324300 call dword ptr ds:[<&KERNEL32.ExitProces>; \ExitProcess
00401755 > 6A 00 push 0x0 ; 验证失败则继续循环
00401757 . 6A 00 push 0x0
00401759 . 68 F48B4300 push 无壳无花.00438BF4 ; 用户名和序列号不可为空!
0040175E . 8BCE mov ecx,esi
00401760 . E8 CB640000 call 无壳无花.00407C30
00401765 > C64424 20 00 mov byte ptr ss:[esp+0x20],0x0
0040176A . 8B4424 10 mov eax,dword ptr ss:[esp+0x10]
...............;此处省略掉无关的代码
004017C0 . 83C4 18 add esp,0x18
004017C3 . C3 retn
这样注册的流程就一目了然了,
①首先,读取Name与Pass,把他们与Null比较,如果其中有一个为空,则提示失败
②接着把Pass的长度与0x14比较,如果不等,则继续要求用户重新输入
③最后把读取到的信息保存到文件c:\reg.ini里面,待重启程序后继续重新进行验证。
于是,我们的目标也同样是非常明确的了。
①重启程序,并对CreateFileA之类的函数下断点。
②找到程序验证序列号的地方,并且找出他验证的方式
③爆破掉关键的地方,从而达到破解的目的。
重启程序,对文件操作一类的函数下断点,然后F9运行。那么,自然的,程序就停下来了。。。。。。。才对的。但是神奇的一幕出现了,到注册框出现以前,程序并没有调用到对文件操作的函数!!!
这下我可郁闷了,没办法,用程序监视一类的软件监视这个软件,果然,还是没有出现对reg.ini这个文件进行操作的记录。
也就是说,这个程序压根就没有想过读取这一个文件!!!!!
我哪个去,这又是为啥啊,的确是挺让人郁闷的,这让小菜该怎么办啊。
实在是没有思路了,于是我就从程序初始化到程序界面出来为止跟了一下,发现了一个非常有趣的地方。
[Asm] 纯文本查看 复制代码
004012F0 . 56 push esi
004012F1 . 57 push edi
004012F2 . 8B7C24 0C mov edi,dword ptr ss:[esp+0xC] ; 初始化各项
004012F6 . 8BF1 mov esi,ecx
004012F8 . 8D46 78 lea eax,dword ptr ds:[esi+0x78]
004012FB . 50 push eax
004012FC 68 EC030000 push 0x3EC
00401301 . 57 push edi
00401302 . E8 33BE0000 call <无壳无花.CreateControl> ; 注册button
00401307 . 8D8E D0000000 lea ecx,dword ptr ds:[esi+0xD0]
0040130D . 51 push ecx
0040130E . 68 EB030000 push 0x3EB
00401313 . 57 push edi
00401314 . E8 21BE0000 call <无壳无花.CreateControl> ; Edit
00401319 . 8D96 24010000 lea edx,dword ptr ds:[esi+0x124]
0040131F . 52 push edx
00401320 . 68 ED030000 push 0x3ED
00401325 . 57 push edi
00401326 . E8 0FBE0000 call <无壳无花.CreateControl> ; 注册button
0040132B . 8D86 78010000 lea eax,dword ptr ds:[esi+0x178]
00401331 . 50 push eax
00401332 . 68 EE030000 push 0x3EE
00401337 . 57 push edi
00401338 . E8 FDBD0000 call <无壳无花.CreateControl> ; Edit
0040133D . 81C6 CC010000 add esi,0x1CC
00401343 . 56 push esi
00401344 . 6A 02 push 0x2
00401346 . 57 push edi
00401347 . E8 EEBD0000 call <无壳无花.CreateControl> ; static
0040134C . 5F pop edi
0040134D . 5E pop esi
0040134E . C2 0400 retn 0x4
这一段是程序初始化各个控件的地方。这倒是没有什么关系,不过认真想了一下,就会发现有特别的地方了!
首先是两个Edit框,这倒是没有问题,一个用来输入Name,一个用来输入Pass。
问题就在于它创建了两个 按钮 button。
这不是很奇怪吗,为什么要创建两个呢?现在,我们来做一下假设,在MFC中,假如一个按钮button只能对应一个按钮事件,那么两个button不就对应了两个按钮事件吗?
假如每一个按钮事件对应了一种验证方式,那么不就有两种不同的验证方式了吗?
假如我们前面看到的验证方式是其中一种,那么另外一种是什么呢?话说回来,到底是谁规定了前面给出的验证方式就一定是正确的呢?如果前面那个是一个陷阱,那么不就表明另外一个按钮事件才是真正的验证地方吗?
所以,基于这样一个想法,我就来寻找一种激活另外一个按钮的按钮事件的方法。
寻找的方法有很多,我就不一一分析了。我说一下我的思路吧:
程序肯定会有判断倒是是激活哪一个按钮的机制。有可能是时钟,不过这个程序并没有时钟,所以这个Pass。
有可能是根据Name与Pass的数据来修改,所以我就对读取信息的相关函数下段了,就是前面我下过标签的Edit_GetText函数了,具体的位置在00409E6E,作用就是获取Edit框的文本数据了。
断点下好后,输入Name,然后切换到Pass的输入时,OD里面停下来了。原来是根据Name_Edit的输入焦点消失的时候才会激活的事件。怪不得程序要使用WH_CBT钩子了。
我们来看一下那个函数:
[Asm] 纯文本查看 复制代码
004017D0 . 6A FF push -0x1 ; NameEdit焦点消失
...............;继续省略一堆数据
0040181E . 8D4C24 08 lea ecx,dword ptr ss:[esp+0x8]
00401822 . 51 push ecx
00401823 . 8D8E 78010000 lea ecx,dword ptr ds:[esi+0x178]
00401829 . C74424 18 000>mov dword ptr ss:[esp+0x18],0x0
00401831 . E8 38860000 call <无壳无花.Edit_GetText> ; GetName
00401836 . 8B5424 08 mov edx,dword ptr ss:[esp+0x8]
0040183A . 52 push edx
0040183B . E8 E7D50100 call <无壳无花.strtoxl> ; 把Name(数字字符串)转换为数字
00401840 . 83C4 04 add esp,0x4
00401843 . 3D DE070000 cmp eax,0x7DE
00401848 . 75 17 jnz short 无壳无花.00401861 ; 如果等于7DE(2014)则不会跳转
0040184A . 6A 01 push 0x1
0040184C . 8D8E 24010000 lea ecx,dword ptr ds:[esi+0x124]
00401852 . E8 FFA40000 call <无壳无花.ShwoWindow> ; 使隐藏的按钮显示出来
00401857 . 6A 00 push 0x0
00401859 . 8D4E 78 lea ecx,dword ptr ds:[esi+0x78]
0040185C . E8 F5A40000 call <无壳无花.ShwoWindow> ; 并且隐藏原来的按钮
00401861 > C74424 14 FFF>mov dword ptr ss:[esp+0x14],-0x1
00401869 . 8B4424 08 mov eax,dword ptr ss:[esp+0x8]
.................;省略一堆代码
00401896 . 83C4 10 add esp,0x10
00401899 . C3 retn
很显然了,当NameEdit控件的焦点消失的时候,读取Name,并且判断Name是否为数字2014,如果是的话,就显示隐藏的按钮,并且隐藏原来的按钮。如果不是的话,就什么都不做。
这不就是我一直在寻找的地方吗?赶快在Name中输入2014,然后切换代码吧,这样正确的注册按钮与正确的验证机制就会出来了。这样一来,输入完Pass后,点注册,就能来的正确的注册地方了。
[Asm] 纯文本查看 复制代码
004018A0 . 6A FF push -0x1
........;看我省略一堆代码
004018F0 . 8D4C24 08 lea ecx,dword ptr ss:[esp+0x8]
004018F4 . 51 push ecx
004018F5 . 8D8E D0000000 lea ecx,dword ptr ds:[esi+0xD0]
004018FB . C74424 20 000>mov dword ptr ss:[esp+0x20],0x0
00401903 . E8 66850000 call <无壳无花.Edit_GetText> ; GetPass
00401908 . 8B4C24 08 mov ecx,dword ptr ss:[esp+0x8]
0040190C . 8B41 F4 mov eax,dword ptr ds:[ecx-0xC] ; 取得Pass的长度
0040190F . 83F8 0A cmp eax,0xA ; 用长度与0xa(10)比较
00401912 . 0F85 84000000 jnz 无壳无花.0040199C ; 不等于则跳向失败
00401918 . 51 push ecx
00401919 . E8 09D50100 call <无壳无花.strtoxl> ; 把Pass(数字的Str)转换为整形数据
0040191E . 83C4 04 add esp,0x4
00401921 . 3D 3F420F00 cmp eax,0xF423F ; 与0xF423F (999999)比较
00401926 . 7E 74 jle short 无壳无花.0040199C ; 比这个小的话就跳向失败
00401928 . 6A 00 push 0x0
0040192A . 6A FF push -0x1
0040192C . 8BCE mov ecx,esi
0040192E . E8 C3A20000 call <无壳无花.GetDlgItem> ; 获取图标控件的hwnd
00401933 . 8BC8 mov ecx,eax
00401935 . E8 1CA40000 call <无壳无花.ShwoWindow>
0040193A . C74424 10 000>mov dword ptr ss:[esp+0x10],0x0
00401942 . C74424 0C 044>mov dword ptr ss:[esp+0xC],无壳无花.00434D04 ; 0辕
0040194A . C64424 1C 01 mov byte ptr ss:[esp+0x1C],0x1
0040194F . E8 21B70000 call 无壳无花.0040D075
00401954 . 8B40 0C mov eax,dword ptr ds:[eax+0xC] ; 加载正确的图片
00401957 . 68 86000000 push 0x86 ; /RsrcName = 134.
0040195C . 50 push eax ; |hInst
0040195D . FF15 70344300 call dword ptr ds:[<&USER32.LoadBitmapA>>; \LoadBitmapA
00401963 . 50 push eax
00401964 . 8D4C24 10 lea ecx,dword ptr ss:[esp+0x10]
00401968 . E8 20CD0000 call 无壳无花.0040E68D
0040196D . 8B5424 10 mov edx,dword ptr ss:[esp+0x10]
00401971 . 8B86 EC010000 mov eax,dword ptr ds:[esi+0x1EC]
00401977 . 52 push edx ; /lParam
00401978 . 6A 00 push 0x0 ; |wParam = 0x0
0040197A . 68 72010000 push 0x172 ; |Message = STM_SETIMAGE
0040197F . 50 push eax ; |hWnd
00401980 . FF15 5C344300 call dword ptr ds:[<&USER32.SendMessageA>; \SendMessageA
00401986 . C64424 1C 00 mov byte ptr ss:[esp+0x1C],0x0
0040198B . 8D4C24 0C lea ecx,dword ptr ss:[esp+0xC]
0040198F . C74424 0C 044>mov dword ptr ss:[esp+0xC],无壳无花.00434D04 ; 0辕
00401997 . E8 54010000 call 无壳无花.00401AF0
0040199C > C74424 1C FFF>mov dword ptr ss:[esp+0x1C],-0x1 ; 失败
........;不重要的代码就不贴了
004019D1 . 83C4 18 add esp,0x18
004019D4 . C3 retn
这样,真正的验证段又出现了。
首先,获取Pass,然后用长度与0xA(10)比较。不等于的话就跳向失败。
然后,把Pass的字符串数据转换为整形数据,并且与0xF423F(999999)比较,比他小的话就跳向失败。
所以,输入1000000以上的10位数字就会成功的了,大家玩起吧。我就狠心一点,直接爆菊了。
版权声明:本文由F8LEFT原创与52pojie论坛,转载请保留以上信息
PS:很久没有玩过游戏了,手痒了,赶紧玩去了。各位如果觉得我写的文章有趣的话,请多多回复吧,不要老是潜水了啊。。。。。花费你们几分钟时间打几个字应该不过分吧?这篇破文我可是写了1个小时有多的的呵。