【文章标题】: 我爱MV1.3.0破解和算法分析手迹
【文章作者】: Tocky
【下载地址】: http://shareware.skycn.com/soft/23615.htm
【使用工具】: OllyDBG PEID LordPE 
【软件介绍】: 『我爱MV』是一款方便快捷的MV制作软件,它拥有独创的动感歌词技术,实现漂亮大方、精确同步的卡拉OK字幕效果,配合几十种转场特效和灵活的文字动画,产生富有个性、动感十足的MV,不失为送给恋人、亲朋好友的一份别致的礼物!
【操作平台】: Windows XP SP3
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!小弟初来看雪,希望和各位多多交流,第一次写破文,不对之处请指出。还望哪位朋友能给个邀请码,先行谢过。
--------------------------------------------------------------------------------
【详细过程】
破解之旅开始,信心指数 0  

软件安装好后,试运行了下,点击帮助-->输入注册码,弹出这个界面

注意看红色字体部分,呵呵,软件作者想的真周到呢,连软件的注册验证方式完完本本的都告诉我们了,只差没说下注册算法了呢(事实上注册算法也比较简单,下文会分析到)

OK,填写下注册信息吧,点击注册按钮

也不知道软件作者说的真的假的,咱还是具体验证下吧。打开注册表,搜索下注册名Tocky和注册码123456789 (一般建议使用自己设定的复杂点的数字和用户名,以防注册表中其他重复数据的干扰,这里我可以保证我的注册表中没有可以干扰的重复数据,所以就暂用他拉)


看到这,哈哈,注册码信息已经写入注册表了,并且明文存储没有加密,这样我们就不用去辛辛苦苦的去找软件的注册验证方式拉,刚开始破解信心指数就猛的+1 

关掉软件,上PEID来查下壳吧

PEID查壳显示nothing found * ,是不是未知壳呢?看链接器版本显示8.0,查看区块,没有什么异常,初步断定这是VC++8.0写的软件,并且没有加壳。事实上以我个人经验来说,VC++7.0以上版本写的软件,PEID大都会显示nothing found *,这是因为VC++7.0以上版本写的软件的入口特征都不一样,和VC++6.0固定的入口特征完全不同,PEID还识别不了。这样简单的判断后,原来不是未知壳,破解信心指数再次+1 

OD载入软件,点击运行。。。等等,出现了异常(如果你的OD异常选项没有忽略所有异常的话)
这是怎么回事呢?是不是软件有加壳呢?还是软件自身产生了这些异常?试一试忽略所有异常和忽略最近发生的异常,对软件进行调试,发现可以正常调试,初步判定软件并没有加壳,出现异常是软件自身产生的。这里需要特别说明的是,判断软件是否加壳有很多方法,有时需要多种方法的结合(例如看输入表有没加密,有没增加可疑的区块等等),但是更多的时候需要我们调试软件的经验和感觉,调试的软件多了,见的壳多了,直觉都能告诉我们软件有没加壳。
这样判断下来,软件没有加壳,哈哈,信心指数陡然+3 

既然是通过注册表存储注册信息,那启动时必然要读取注册表,再加以判断。一般来说查询注册表用2个API函数RegQueryValueA和RegQueryValueExA,看下这个软件的输入表,里面只有一个注册表查询函数RegQueryValueExA,又省事多了啊,注册表存储注册码的键名是 Key ,这样我们可以下条件断点来到从注册表中获取注册码的地方。

命令行输入: bp RegQueryValueExA,[SRING [ESP+8]]=="Key"

说明:条件断点的知识可以在OD帮助手册中找到

中断下来啦,F2取消断点,ALT+F9执行到用户代码 ---->这个软件使用了mfc80.dll,如果你的OD没有导入mfc80.lib库的话,需要先导入下,这样才能看到mfc80.dll中的函数名,我导入mfc80.lib库以后,函数名显得有些乱码,原因不明,还望知道的朋友告诉我一声。

来到这里:

代码:
00410ED5  |.  FF15 0C804400 CALL DWORD PTR DS:[<&ADVAPI32.RegOpenKeyExA>]     ; \RegOpenKeyExA
00410EDB  |.  3BC3          CMP EAX,EBX
00410EDD  |.  0F85 13010000 JNZ MV_Build.00410FF6
00410EE3  |.  8B7424 14     MOV ESI,DWORD PTR SS:[ESP+14]
00410EE7  |.  8D4C24 14     LEA ECX,DWORD PTR SS:[ESP+14]
00410EEB  |.  51            PUSH ECX
00410EEC  |.  8D5424 28     LEA EDX,DWORD PTR SS:[ESP+28]
00410EF0  |.  52            PUSH EDX
00410EF1  |.  68 64BB4400   PUSH MV_Build.0044BB64                            ;  ASCII "Key"
一直往下走,直到这里堆栈中出现了机器码,很有可能某处CALL就要比较了
0041103F  |.  E8 CC570200   CALL MV_Build.00436810                            ;  取机器码函数,堆栈中出现机器码

走到这里:
代码:
00411088  |.  E8 93560200   CALL MV_Build.00436720                            ;  算注册码关键函数,将注册码压栈了且栈中有机器码
0041108D  |.  83C4 08       ADD ESP,8
00411090  |.  8BCD          MOV ECX,EBP
00411092  |.  C68424 300100>MOV BYTE PTR SS:[ESP+130],2
0041109A  |.  FF15 0C854400 CALL DWORD PTR DS:[<&MFC80.#??B?$CSimpleStringT@D>;  MFC80.#?GetString@?$CSimpleStringT@D$00@ATL@@QBEPBDXZ_3397
004110A0  |.  50            PUSH EAX                                          ;  EAX=机器码
004110A1  |.  8D4C24 20     LEA ECX,DWORD PTR SS:[ESP+20]                     ;  ecx=经过计算后得到的注册码的地址
004110A5  |.  FF15 20854400 CALL DWORD PTR DS:[<&MFC80.#?Compare@?$CStringT@D>;  MFC80.#?Compare@?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QBEHPBD@Z_1482

MFC80.#?Compare@?$CStringT 这个字符串比较函数很可疑,往上找到 CALL MV_Build.00436720  ,因为经过这个CALL以后就有了这个字符串比较函数,所以这个CALL就显得很可疑,跟进去,动态跟踪以后,发现他的确是关键CALL,因为这个CALL非常简单,将注册码经过运算以后得到一个新的注册码,而这个新的注册码就和机器码做一个明码比较

程序的注册基本机制就初步明了了,再看程序是如何验证的。一般来说程序验证是否注册必然要用到if(){}else{}语句,而汇编就表现为je/jne/jb/ja等等,只不多这个判断的地方可能在执行某一功能的时候,也可能在程序初始化的时候。在这么一大段CALL里面,我没有看到类似JN/JNZ等条件判断语句,于是大胆的推测,程序在初始化的时候计算注册码和机器码进行比较,然后将比较结果存储到一个全局变量中,在执行某一功能的时候再判断此全局变量即可。


为了验证我的推测,我重新加载了程序,开始动态调试,从00411088开始
代码:
004110A5  |.  FF15 20854400 CALL DWORD PTR DS:[<&MFC80.#?Compare@?$CStringT@D>;  MFC80.#?Compare@?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QBEHPBD@Z_1482
004110AB      F7D8          NEG EAX                                           ;  比较是否注册成功,重点,相等返回eax=0,否则返回>0或< 0
004110AD      1BC0          SBB EAX,EAX
004110AF      83C0 01       ADD EAX,1
004110B2      8D4C24 1C     LEA ECX,DWORD PTR SS:[ESP+1C]
004110B6  |.  8987 A4000000 MOV DWORD PTR DS:[EDI+A4],EAX                     ;  这个地址存放是否注册版本的标志
当比较字符串函数结束后,发现程序对EAX进行了几步运算,并且最终EAX=0,将EAX的值存放到[EDI+A4]中,此时信息窗口提示:
EAX=00000000
DS:[0045EC14]=00000000

破解信心再次膨胀性的+3,0045EC14很有可能就是存放注册标志的全局变量,而且,0045EC14地址在.data 段,这就更能加强我的判断了,为了证实我的判断,运行程序,对0045EC14下内存访问字节断点,然后点击帮助按纽,发现OD 如我所愿的中断了,好吧,继续运行,程序显示没有注册。

试试改变0045EC14的值,看程序会不会显示已经注册。

关闭注册窗口,再次点击帮助按纽,OD中断下来,在数据窗口中将0045EC14处的值改为01,然后运行,点击输入注册码

太令人兴奋啦,程序显示已经注册啦,并且注册按纽已经变灰,说明我上面的推测完全正确!至此爆破成功。信心指数暴增100+ 



再回过头来看看软件的注册算法吧。(出于对软件作者的尊重,算法部分我只大概说下流程和给出算法部分的些关键注释,请自行查看)

软件注册码必须和机器码相同形式,因为是对注册码的每一个字符经过运算后得到另一个字符,最终形成一个新的注册码,此注册码与机器码进行字符串比较

注册算法CALL :00411088  |.  E8 93560200   CALL MV_Build.00436720 

代码:
00436720  /$  6A FF         PUSH -1
00436722  |.  68 53614400   PUSH MV_Build.00446153
00436727  |.  64:A1 0000000>MOV EAX,DWORD PTR FS:[0]
0043672D  |.  50            PUSH EAX
0043672E  |.  83EC 08       SUB ESP,8
00436731  |.  53            PUSH EBX
00436732  |.  56            PUSH ESI
00436733  |.  57            PUSH EDI
00436734  |.  A1 CCEA4500   MOV EAX,DWORD PTR DS:[45EACC]
00436739  |.  33C4          XOR EAX,ESP
0043673B  |.  50            PUSH EAX
0043673C  |.  8D4424 18     LEA EAX,DWORD PTR SS:[ESP+18]
00436740  |.  64:A3 0000000>MOV DWORD PTR FS:[0],EAX
00436746  |.  33F6          XOR ESI,ESI
00436748  |.  897424 14     MOV DWORD PTR SS:[ESP+14],ESI
0043674C  |.  8B5C24 28     MOV EBX,DWORD PTR SS:[ESP+28]
00436750  |.  8BCB          MOV ECX,EBX
00436752  |.  C74424 20 010>MOV DWORD PTR SS:[ESP+20],1
0043675A  |.  FF15 E8844400 CALL DWORD PTR DS:[<&MFC80.#??0?$CStringT@DV?$Str>;  MFC80.#??0?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ_310
00436760  |.  8D4C24 2C     LEA ECX,DWORD PTR SS:[ESP+2C]
00436764  |.  C74424 14 010>MOV DWORD PTR SS:[ESP+14],1
0043676C  |.  FF15 10854400 CALL DWORD PTR DS:[<&MFC80.#?GetLength@?$CSimpleS>;  MFC80.#?GetLength@?$CSimpleStringT@D$00@ATL@@QBEHXZ_2902
00436772  |.  8BF8          MOV EDI,EAX
00436774  |.  85FF          TEST EDI,EDI                                      ;  判断注册码长度是否为0
00436776  |.  7E 6C         JLE SHORT MV_Build.004367E4
00436778  |.  EB 06         JMP SHORT MV_Build.00436780
0043677A  |   8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX]
00436780  |>  56            /PUSH ESI                                         ;  注册码字符串数组下标
00436781  |.  8D4C24 30     |LEA ECX,DWORD PTR SS:[ESP+30]                    ;  ECX=从注册表取出的注册码地址
00436785  |.  FF15 D0814400 |CALL DWORD PTR DS:[<&MFC80.#?GetAt@?$CSimpleStri>;  取单个字符MFC80.#?GetAt@?$CSimpleStringT@D$00@ATL@@QBEDH@Z_2451
0043678B  |.  3C 2D         |CMP AL,2D                                        ;  判断AL是否"-"
0043678D  |.  884424 10     |MOV BYTE PTR SS:[ESP+10],AL
00436791  |.  74 3D         |JE SHORT MV_Build.004367D0
00436793  |.  8B4424 10     |MOV EAX,DWORD PTR SS:[ESP+10]
00436797  |.  6A 00         |PUSH 0
00436799  |.  50            |PUSH EAX
0043679A  |.  B9 C8ED4500   |MOV ECX,MV_Build.0045EDC8
0043679F  |.  FF15 10864400 |CALL DWORD PTR DS:[<&MFC80.#?Find@?$CStringT@DV?>;  MFC80.#?Find@?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QBEHDH@Z_2271
004367A5  |.  8BC8          |MOV ECX,EAX
004367A7  |.  83F9 FF       |CMP ECX,-1                                       ;  比较是否在字串中获取到字符的位置
004367AA  |.  7E 24         |JLE SHORT MV_Build.004367D0                      ;  下面开始真正计算注册码
004367AC  |.  2BCE          |SUB ECX,ESI                                      ;  ECX=字符位置
004367AE  |.  B8 B1133BB1   |MOV EAX,B13B13B1
004367B3  |.  F7E9          |IMUL ECX
004367B5  |.  C1FA 03       |SAR EDX,3
004367B8  |.  8BC2          |MOV EAX,EDX
004367BA  |.  C1E8 1F       |SHR EAX,1F
004367BD  |.  03C2          |ADD EAX,EDX
004367BF  |.  6BC0 1A       |IMUL EAX,EAX,1A
004367C2  |.  03C8          |ADD ECX,EAX
004367C4  |.  79 03         |JNS SHORT MV_Build.004367C9
004367C6  |.  83C1 1A       |ADD ECX,1A
004367C9  |>  80C1 41       |ADD CL,41                                        
004367CC  |.  884C24 10     |MOV BYTE PTR SS:[ESP+10],CL                      ; CL是新计算出的注册字符
004367D0  |>  8B4C24 10     |MOV ECX,DWORD PTR SS:[ESP+10]
004367D4  |.  51            |PUSH ECX
004367D5  |.  8BCB          |MOV ECX,EBX
004367D7  |.  FF15 D4814400 |CALL DWORD PTR DS:[<&MFC80.#?AppendChar@?$CSimpl>;  MFC80.#?AppendChar@?$CSimpleStringT@D$00@ATL@@QAEXD@Z_1258
004367DD  |.  83C6 01       |ADD ESI,1
004367E0  |.  3BF7          |CMP ESI,EDI                                      ;  EDI是注册码长度,ESI是计数器
004367E2  |.^ 7C 9C         \JL SHORT MV_Build.00436780
004367E4  |>  8D4C24 2C     LEA ECX,DWORD PTR SS:[ESP+2C]                     ;  已经算出注册码
004367E8  |.  FF15 DC844400 CALL DWORD PTR DS:[<&MFC80.#??1?$CStringT@DV?$Str>;  MFC80.#??1?$CStringT@DV?$StrTraitMFC_DLL@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ_578
004367EE  |.  8BC3          MOV EAX,EBX
004367F0  |.  8B4C24 18     MOV ECX,DWORD PTR SS:[ESP+18]
004367F4  |.  64:890D 00000>MOV DWORD PTR FS:[0],ECX
004367FB  |.  59            POP ECX
004367FC  |.  5F            POP EDI
004367FD  |.  5E            POP ESI
004367FE  |.  5B            POP EBX
004367FF  |.  83C4 14       ADD ESP,14
00436802  \.  C3            RETN


如果你耐着性子看完这么多,你的信心指数是不是也大增呢?