《冠军足球经理》系列作为一款拟真度极高的足球经营类游戏,赢得过无数赞誉,而CM4可以说是这个传奇的起点。但是在游戏安装过程中,当用户输入完序列号之后,程序并不会对用户的输入进行真伪判断,只有等到安装完毕,进入游戏之后,通过游戏是否正常显示才能够得知。而如果遇到用户不小心输入错误的情况,那么只能卸载游戏重新安装,这就会造成很大的麻烦。
为解决这个问题,这次的研究会以两篇文章的篇幅通过两种方式对这个游戏进行逆向分析,一种是采用常见的“爆破”手段,也就是本文所讨论的方法;而另一种则是编写出CM4的注册码生成器,这会在下一篇文章中进行讨论。后者需要依据反汇编代码分析出游戏的验证机制,这可以说是任何逆向研究的最终目标,也是本次研究的最终目标。
图1 CM4游戏封面
图2 CM4安装程序的序列号输入界面
而在本研究的过程中,我尝试将序列号输入为“0123456789ABCDEF”,每一位输入不同的字符,这样便于后面的逆向跟踪。这种方式往往也运用于缓冲区溢出的检测(可见《反病毒攻防研究第003篇:简单程序漏洞的利用》)。当游戏安装完毕后,进入游戏。程序会先进行序列号的验证,如果之前的序列号输入错误,那么就会出现错误提示对话框。
图3 进入游戏之前提示的错误信息
这里当我们单击“确定”之后,依然能够进入游戏,但是游戏却无法正常显示,在我的电脑中,凡是出现文字的部分,均会以乱码显示。虽然说对于老玩家而言,依然可以通过各个按钮的位置来确定每个按钮的功能,但是依然会造成很大的麻烦,所以在此很有必要对这一问题进行逆向研究。
图4 进入游戏中的乱码显示
一般来说,想要找到软件的验证语句的位置,则需要运用动态或者静态反汇编工具,得到它的反汇编代码,分析该软件的运行机理,一步一步跟踪执行,直至弹出如图3所示的“CDKEY检查”对话框。这个对话框一定是由一个CALL语句调用出来的,那么这个CALL语句必然包含了相应的验证机制,就有必要进入这个CALL语句,进一步分析。
对于这次的研究对象——CM4来说同样如此,根据惯例,我们有必要先对这款软件进行查壳的操作,这是一切逆向工作的起始,毕竟有壳的软件是必要进行脱壳之后才能进一步分析的。这里我使用的是PEiD:
图5 对CM4进行查壳操作
根据查壳的结果可以看到,本软件是采用Microsoft Visual C++ 8.0编写的Debug版本,并没有加壳,那么接下来就可以按照上述思想开始分析了。直接单步运行进行检查,很快就可以定位到调用错误提示对话框的CALL语句,其相对地址为0x0041495C(当然真正调用MessageBox()语句的 CALL还在这个CALL的内部)。
图6 找到CDKEY检查的CALL
按F7进入这个CALL之后,继续单步执行,来到相对地址为0x00411E3C位置处的语句,由于经过上一步(test eax,eax)的验证后ZF标志位为1(两个eax的值相等),于是跳转实现,会直接跳转到0x00411F26的位置。
图7 跳转至对话框的语句
经过观察可以发现,所跳转到的0x00411F26的位置就是调用MessageBox()函数的位置,而它也正是“CDKEY检查”对话框。
图8 对话框调用函数位置
分析至此,就可以采取相应的手段来避开这个对话框,使游戏正常运行。
“爆破”最常用的方式是修改关键跳,对于本软件来说,就是上面分析出来的相对地址为0x00411E3C位置(图7)处的“je00411F26”(相等则跳转)语句。正是由于这个跳转实现,使得CDKEY检查对话框(图3)弹出。依据这个就可以进行修改了。
在此情况下通常有两种方式可以达到目的。一种是可以考虑改变条件跳转语句的条件。可以将je修改为jnz(不等于0则跳转),这样程序可以顺序执行下去,而不会跳转到错误提示窗口。
图9 修改跳转语句的条件
另一种是可以直接将跳转语句删除,用nop予以取代,也可以达到避免产生跳转的效果。(注意:将反汇编代码修改为nop并不是对所有程序都有效,其目的只是避免跳转,从而继续下一条语句执行。具体情况需要具体分析)。
图10 nop掉跳转语句
采用上述任何一种方式修改之后,进入游戏,可以发现错误提示窗口不再显示,并且游戏中的文字也正常了。
图11 修改后进入游戏
当然,“爆破”的手段五花八门,可供修改的位置以及方式多种多样,在此也就不再赘述。但是有一点在此需要强调,在一般的软件逆向分析中,有一种去除NAG窗口的方式是把MessageBox()函数的第一个参数(在反汇编代码中是第四个参数)的值修改为一个无意义的值,比如1,这样就会导致消息窗口创建失败,从而也就不会显示MessageBox了。
图12 修改MessageBox的hWnd参数的值
但是在本软件中,采用这种方式虽然能够去除错误窗口,但是进入游戏后依旧显示乱码(如图4所示),可见,此时虽然不再提示CDKEY检查错误对话框,但是验证依旧失败,说明验证流程已经执行了。因此在本程序中,仅仅修改hWnd这个参数是无法达到目的的。
采取“爆破”手段虽然能够解决问题,但是却修改了程序的源代码,因此并不是高明的手段。而类似于这种软件的逆向分析的最高目标,就是通过分析其序列号的验证机制,从而编写出序列号生成器,这样不会破坏程序的原始代码,也对程序有了比较深刻的理解。当然关于序列号验证算法的逆向分析与序列号生成器的程序编写,会放到下一篇文章中讨论,这里只是去寻找验证机制的反汇编代码位置。
首先可以肯定的是,序列号验证模块的入口代码一定是一个CALL语句,而且它必然在判断是否执行错误提示窗口代码(图7)的上方(可能在上方很远处)。对于本程序来说,这个CALL的返回值保存在eax中,而这个eax正好就在弹出“CDKEY检查”窗口反汇编代码的正上方:
图13 序列号验证返回值判断
一般来说,序列号的验证就是通过一系列的算法对序列号进行检验,验证结束以后,接下来的语句就可以通过返回值,也就是保存在eax中的值的情况进行判断,以决定程序接下来的走向。有些不一样的是,本程序并不是对eax中的内容直接进行判断,而是将[ebp-19d]中的值经填充后再赋给eax进行验证,那么有理由相信,用于验证的CALL语句的真正返回值会保存在[ebp-19d]中,它才是最为关键的位置。逆向分析时就需要时刻留意这个位置的值如何发生变化。换算成相对地址,也就是0x0012FCEB。
不断向上回溯查找可以发现,整个流程中会出现非常多的CALL,但是不必每一个CALL都要跟进查看,只要留意该CALL语句执行完毕后是否改变了0x0012FCEB位置的值即可。可以紧盯数据窗口中的0x0012FCEB位置,看其究竟是受到哪个CALL的影响由断点“CC”变成了非零值(关于断点“CC”的说明,可查看《技术面试问题汇总第001篇:猎豹移动反病毒工程师part1》中问题1末尾处的补充知识)。按照这种思想,很快就可以找到关键CALL:
图14 影响返回值的关键CALL
相对地址为0x0041DB3的CALL语句执行后,其返回值赋给了[ebp-19d],将该位置的值由CC变成了00。
图15 数据窗口中查看0x0012FCEB
这样一来,接下来的工作就是进入相对地址为0x0041DB3的CALL语句中进行分析了。对于这个CALL内部原理的分析,我会放在下一篇文章中。
原文地址:http://blog.csdn.net/ioio_jy/article/details/40021179