一个月前,我得到了一个新任务,要求我对某系统的License文件验证过程进行破解,当时,对逆向工程完全不懂得我真是手足无措,还好我找到了看雪论坛,在一个月的一边学习,一边尝试后,终于将任务完成,现在将我这一个月的分析过程大体讲出来,也许会对其他人在今后的工作提供一个思路的方向.我会将这个过程尽量写得简洁,但是其实一个月来我走了很多弯路的
我用到的工具有:ultraedit ,ollydbg ,peid
另外,由于这项工程还算是比较机密的东西,因此我会将里面被我认为有必要的关键地方进行修改,但不影响破解的过程,只是将一些关键词语换掉.
首先,这个工程针对的系统并不是windows,而是在其他机器上的系统,现在我把它假设为打印机系统(但我实际上做的不是打印机).一个打印机有很多功能,比方说,打印是他必须有的功能,而复印或者自动化复印打印,则是一些需要通过License文件来进行验证的才能获得到的功能.因此,我们要想破解这个系统,则需要先将打印机的硬盘进行一份Ghost,然后在我们自己的电脑上尝试对他进行分析.
现在,我遇到了我的第一个问题,直接将硬盘接到电脑上启动,运行到一半还没开完机,就会报出一个错误"系统环境变量错误"(这个错误实际上是英文),想象很容易理解,本来这个硬盘是应该加载在打印机上的,现在我们在台机上将它作为系统盘运行他必然会导致错误.因此,我们只好将他作为从属盘来进行分析.
将这个硬盘接到电脑上,可以看到他有三个分区,一个为main分区,一个为data分区,一个为backup分区,很容易理解,主盘,数据盘,备份盘,那么重点工作肯定是在main分区中进行.
我的第一个思路是,现在,我的手上有一个正确的License文件,里面肯定有验证码了,因此,我想先找到他是什么,于是,动用ultraedit打开它,发现它里面有类似如下的关键内容:
ASD 打印功能 DSA 系统号=123456 ASD 产品号=789 DSA aaaaaaaaaaaaaaaaa DSA CHECK=OK
对这个内容进行分析,ASD DSA 这类东西是被我修改过的,我判断应该也是一些格式方面的东西,"打印功能"则是这个License所针对的他所能打开的软件功能,系统号是这台机子的编号,产品号则应该是这个软件的编号,然后aaaaaaaaaaaaaaaaa则应该是这个License码.
我当初的想法是,既然这个License里面有所需要进行的License码,那么肯定他也被储藏在了main盘里的什么地方,然后那个文件相当重要,(其实后来证明这个思路明显有问题,不过他也确实给我带来了通向成功的一条路).因此,我调用ultraedit的搜索功能在main盘里面搜索那个aaaaaaaaaaaaaaaaa,被我找到了大概8个文件,很可惜的是他们其中的7个完全没用,但是却浪费了我好几天的研究时间,但是其中的一个文件带给了我一条道路.这是一个IE浏览器文件,用ultraedit打开它,前半段显示了很多JAVA语言,我反正是不太懂了,但是在整个语言的末尾,却有一个很重要的信息:
系统环境变量="C:\FILEA\FILEB\FILEC\License.DAT"
这个地方有关键字License.DAT,那么他肯定有用,因此,我又一次用ultraedit搜索包含C:\FILEA\FILEB\FILEC\License.DAT关键路径的文件,这一次,工作看似很顺利,我搜索到了大概20多个包含这个关键路径的文件,但是其中只有一个是EXE文件.(其实后来的深入分析证明我这次运气超级好,因为整个系统里的文件调用一般都是分段的一个地址一个地址的路径黏贴加起来的,只有这里是整个路径都包含了).于是,我开始研究这个EXE文件,这时才终于开始了OD的分析路程.
首先,对这个文件进行字符串查看,结果如下,因为太多,只贴一部分,这部分是我的最初分析的入手点.
文本字串参考位于 恢复:.text
地址 反汇编 文本字串
00407CE0 PUSH 恢复.004377E0 ASCII "读取系统信息失败."
00407DFB PUSH 恢复.004377C8 ASCII "回滚数据备份"
00407E2B PUSH 恢复.004377B4 ASCII "备份用户数据..."
00407E46 PUSH 恢复.004377A8 ASCII "恢复..."
00407E8A PUSH 恢复.00437794 ASCII "恢复失败..."
00407EBB PUSH 恢复.0043775C ASCII "恢复License信息,这将会用掉一些时间..."
00407ED4 PUSH 恢复.00437730 ASCII "恢复License信息失败..."
00407F03 PUSH 恢复.00437708 ASCII "恢复用户自定义信息..."
00407F1C PUSH 恢复.004376D4 ASCII "恢复用户自定义信息失败..."
00407F42 PUSH 恢复.00437698 ASCII "恢复成功..."
首先,我是从"读取系统信息失败."这一点入手的,因为最开始,我打算把程序完整的跑一下,结果到了这里就会失败,因此来看这附近的代码段
00407CD5 |. E8 51A2FFFF CALL 恢复.00401F2B
00407CDA |. 85C0 TEST EAX,EAX
00407CDC |. 75 1F JNZ SHORT 恢复.00407CFD
00407CDE |. 6A 01 PUSH 1
00407CE0 |. 68 E0774300 PUSH 恢复.004377E0 ; ASCII "读取系统信息失败."
可以很容易的看出来 CALL 恢复.00401F2B 这一句是用来读取系统信息的,读取成功与否的结果保存在EAX,然后根据情况进行跳转,本来我打算强行跳过这句的,可是发现这基本不能实现,如果强行跳过这句话,会由于缺少很多信息而导致程序基本上走几句就会直接程序异常出错.所以强行跳转不能实现,不过不用灰心,观察一下可以发现,这个CALL其实就是整个程序的关键执行代码部分,不过由于他是从407C3B开始,到4080BE结束的,刚才尝试了下将代码贴出来,不过由于太长导致行文很不好看,所以只把关键的部分贴出来.观察一下代码的中下部可以发现
00407EBB PUSH 恢复.0043775C ASCII "恢复License信息,这将会用掉一些时间..."
00407ED4 PUSH 恢复.00437730 ASCII "恢复License信息失败..."
00407F03 PUSH 恢复.00437708 ASCII "恢复用户自定义信息..."
00407F1C PUSH 恢复.004376D4 ASCII "恢复用户自定义信息失败..."
00407F42 PUSH 恢复.00437698 ASCII "恢复成功..."
这5个信息都是在这个CALL中,而且"恢复License信息,这将会用掉一些时间..."以及"恢复License信息失败..."一看就知道跟我们的目标相当相关,因此研究它的附近代码:
00407EBB |. 68 5C774300 PUSH 恢复.0043775C ; ASCII "Restore license information... It will takes minutes..."
00407EC0 |. 8BCE MOV ECX,ESI
00407EC2 |. E8 24FDFFFF CALL 恢复.00407BEB ; 这里是将上面的那个信息显示出来
00407EC7 |. 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-10]
00407ECA |. E8 34B7FFFF CALL 恢复.00403603 ; 调用一个CALL,结果导致EAX的赋值来控制跳转
00407ECF |. 85C0 TEST EAX,EAX
00407ED1 |. 75 18 JNZ SHORT 恢复.00407EEB
00407ED3 |. 50 PUSH EAX
00407ED4 |. 68 30774300 PUSH 恢复.00437730 ; ASCII "FAILED to restore license information..."
于是我们发现,CALL 恢复.00403603这一句相当重要,出来后的结果直接决定程序的显示信息,可惜的是我们不能将它强行设置为1,因为这个跳转实际上只是决定程序的显示信息为什么,而不能决定License的验证,也就是说即使这里我们让他强行跳转,他也只是把正确的信息显示出来,而不会带来正确的结果,因此,我们需要进入这个CALL来继续研究
同样,这个CALL从403603开始,到403799结束,我只把关键代码写出来
00403759 |. 50 PUSH EAX ; /Path
0040375A |. FF15 18D34200 CALL DWORD PTR DS:[<&SHLWAPI.PathFileExi>; \PathFileExistsA
00403760 |. 5F POP EDI
00403761 |. 5E POP ESI
00403762 |. 85C0 TEST EAX,EAX
00403764 |. 5B POP EBX
00403765 |. 75 02 JNZ SHORT 恢复.00403769
00403767 |. C9 LEAVE
00403768 |. C3 RETN
00403769 |> 8B4D F8 MOV ECX,DWORD PTR SS:[EBP-8]
0040376C |. 6A 00 PUSH 0 ; /Arg6 = 00000000
0040376E |. 68 98724300 PUSH 恢复.00437298 ; |Arg5 = 00437298 ASCII " 1"
00403773 |. 68 E0930400 PUSH 493E0 ; |Arg4 = 000493E0
00403778 |. 68 10000008 PUSH 8000010 ; |Arg3 = 08000010
0040377D |. 8D85 F4FEFFFF LEA EAX,DWORD PTR SS:[EBP-10C] ; 这里将路径地址再次给到EAX
00403783 |. 68 90724300 PUSH 恢复.00437290 ; |Arg2 = 00437290 ASCII "Default"
00403788 |. 50 PUSH EAX ; |Arg1
00403789 |. E8 B9000000 CALL 恢复.00403847 ; \恢复.00403847
这里我们可以看出来程序片段的流程为首先,EAX中存放的是一个文件的具体地址,在CALL DWORD PTR DS:[<&SHLWAPI.PathFileExi>处对这个路径地址的正确性进行判断,非零则跳转调用一个6个参数的CALL,其中参数1就是路径地址,经过再对CALL 恢复.00403847的研究发现,这个CALL是调用了EAX所指向的路径上的一个可执行文件,而这个文件的返回结果将直接影响整个CALL的最终的EAX赋值,可惜,我们不能跳过这个文件的调用而将结果强行赋值为1,因为这样只会让我们仅仅是得到单纯的正确信息而没有正确结果.不过,我们应该去研究这个可执行文件,因为这个可执行文件的名字叫做"License信息.exe"
根据EAX中存放的路径地址找到文件"License信息.exe",当直接用OD打开他时,程序会报一个"找不到"License匹配信息,DLL"(当然实际上这个库文件不是这个名字,只是后来我根据他的作用在写这篇文章时命名的)"的错误,不用慌,全main盘搜索并找到他,将它复制到"License信息.exe"文件所在的文件夹下,OD成功打开"License信息.exe".开始分析"License信息.exe"
分析"License信息.exe"我不再进行叙述,因为后来的研究证明其实分析它我走了一个特大的弯路,因为其实我根本不需要去分析他的,而应该直接分析"License匹配信息,DLL"才对.不过在分析"License信息.exe"的过程中,我又熟悉了对OD的一些操作,找到了几个好的功能介绍给大家,这就是查找中的"查找模块间的调用"这个功能,尤其当文件调用了一些其他自定的库文件时,这个功能相当好用,因为在指导有这个功能后,我直接发现它调用了"License匹配信息,DLL"库文件中的两个函数,一个叫"产生License密钥",另一个叫做"安装License密钥",在找到了这两个函数后,我又走了一个弯路,就是去研究这两个函数,其实我应该研究的是"License匹配信息,DLL"才对
当最终发现正确的方法后,我开始用OD来分析"License匹配信息,DLL",当打开库文件时,我们不要慌张,因为库文件看似没有入手点,其实不然,这里我要向大家推荐OD的另一个非常好使的功能,查找功能里的"查找当前模块中的名称"这个功能,一打开这个功能,会把当前库文件中所有调用的函数名写出来,当然,会显得非常多,不过不要慌张,仔细看的话只有几个,就是那些名称前面有问号作为前缀的函数,那些,就是当前模块的自定义函数,把他们列出来如下:
名称=?删除所有License信息@@GGGGGGGGGGGGG
名称?删除License信息@@GGGGGGGGGGGGG@@@Z
名称?产生License密钥@@GGGGGGGGGGGGG@@@Z
名称?得到第一个License信息@@GGGGGGGGGGGGG@@@Z
名称?得到下一个License信息@@GGGGGGGGGGGGG@@@Z
名称?初始化License信息@@GGGGGGGGGGGGG@@@Z
名称?安装License@@GGGGGGGGGGGGG@@@Z
名称安装License记忆@@GGGGGGGGGGGGG@@@Z
名称?License是否有效@@GGGGGGGGGGGGG@@@Z
名称?更新注册信息@@GGGGGGGGGGGGG@@@Z
很显然,最重要的一个函数就是 ?License是否有效@ 函数,因此来对他进行研究
进入这个函数后会发现,这个函数的构造非常简单,只有一个传入参数,我们就假设他为用来进行License匹配的参数.这个函数有5个CALL,其中第一个是判断参数是否为空,空则EAX=0,然后返回,然后对参数指针进行一个移位,对其现在所指向的字符串进行长度判断,不为6则EAX=-2,然后返回,这之后再次对参数指针进行移位,对其现在所指向的字符串进行判断,如果不在"00000000"以及"99999999"之间的则对EAX进行赋值并且程序返回,但是这里要注意,错误的话EAX的赋值不是负数也不是0,而是1!!于是程序到了最后一个CALL,这个CALL相当深,但是此时我们不需要看它的内容,我们直接看它后面的操作:
008736E4 . F7D8 NEG EAX
008736E6 . 1BC0 SBB EAX,EAX
008736E8 . 5F POP EDI
008736E9 . 24 F0 AND AL,0F0
008736EB . 5E POP ESI
008736EC . 40 INC EAX
008736ED . 5B POP EBX
008736EE . C2 0400 RETN 4
这里我们可以看到,加入这个CALL返回时EAX=1,那么程序的最终结果为EAX=1,而这与前面"00000000"以及"99999999"之间的判断的错误结果返回值一样,那么这个CALL的返回结果肯定不是1,而应该是0,当EAX=0时,我们从8736E4开始分析,很显然,EAX的最终结果将变为FFFFFFF1,因此,我们让这个函数的返回结果改为在入口时强行将程序跳转到8736DF,并将代码改为MOV EAX,1;然后保存修改"License匹配信息,DLL"库文件.之后在进行开机验证.
实际的上机结果证明,这次的爆破成功,不过其实在这个"License匹配信息,DLL"文件修改后还是出了点小差错的,因为当时我所研究与修改的库文件所针对的版本是那个系统的2.0版本,而实际的机子上的版本却是4.0版本,库文件进行了一些加密与修改,不过正确的流程还是这样,我成功的将需要用向商家购买才能得到的24个外加功能添加到了机器系统上.
回看我的这次爆破过程,其实走了很多弯路,特别是对"License信息.exe"文件其实对我的工作延误了很久,如果我直接就对"License匹配信息,DLL"库文件进行研究的话,说不定工作早就完成了,不过弯路的走来也让我的汇编代码分析能力上了一个档次,我在分析第一个文件时,基本上每条代码都要加注释,而基本分析完"License信息.exe"之后,我可以说基本上10句代码我才需要加一条简单的注释.另外,还发现了"查找模块间的调用"以及"查找当前模块下的名称"这两个相当实用的功能,感觉收获颇丰.
纵观这次爆破,我总结的经验就是顺藤摸瓜,查找一切与License可能相关的东西进行研究,并尤其要注意文件与文件之间的调用,特别是一些不认识的库文件要重点研究,说不定关键的匹配信息就在里面.
当然,在这里我也要感谢看雪论坛在我这一个月中的工作帮助,在论坛上我找到了太多的东西对我有所帮助.当然,我也希望这篇文章能赢得斑斑给我一个注册码的心情,毕竟假使不算发这篇原创帖,我也有了20贴的发帖量以及100KX的虚拟货币,希望能够得到一个邀请码来进行更大的发展,再次感谢~~
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)