逆向工程第004篇:跨越CM4验证机制的鸿沟(中)
一、前言
在上一篇文章的最后,我已经找出了关键的CALL语句,那么这篇文章我就带领大家来一步一步地分析这个CALL。我会将我的思路完整地展现给大家,因此分析过程可能略显冗长,我会分为两篇文章进行讨论。在整个分析过程中,我也会把我所遇到的瓶颈展示出来,毕竟我在实际分析时,也并不是一帆风顺的,遇到瓶颈属于正常情况,关键是在于应该怎么解决。考虑到绝大部分读者手中应该是没有CM4这款游戏,并没有实际动手进行逆向分析的机会,所以我在文章中添加了足够多的解释与截图,让大家仅仅是看这篇文章,就能够领会逆向分析的基本方法。我希望能够将我的思想阐述清楚,令每一位对逆向工程有兴趣的读者都能有所收获。
二、分析程序成功验证的流程
上文说到,真正影响验证结果的是位于0x0012FCEB处的值,如果该值为0,那么说明验证失败,而影响该值的是位于0x0041DB3处的CALL 004112F8这个语句的返回值,即eax中保存的值。那么我们可以进入这个CALL查看一下,并留意eax中的值的变化。
这个CALL里面只有一条语句,就是一个无条件跳转,它会跳转到0x00413080处的语句,利用IDA Pro可以查看一下该函数的流程图:
图1 函数0x00413080的流程图
由图1可以发现,该函数中拥有非常多的分支结构,很符合注册码验证的程序特征。对此,我个人的习惯是从后向前进行探索。由图中我所标出的红框中的内容可以发现,程序的所有流程,最终都要汇聚于一处,而再向上观察,可以发现一共有八个小方框,或者可以理解为一共有八种验证结果,那么由此逆推,应当可以发现正确的注册码的验证流程。这里应当先分析红框中的反汇编代码。首先是最后汇聚处的代码:
图2函数0x00413080最后一部分代码
由于这是函数0x00413080最后的代码,所以我们只需要关心包含有eax的代码即可,因为这里决定了eax中的值究竟是多少,从而最终决定验证是否成功。我们很容易就发现了两条包含有eax的语句,我已经用红框标注出来了,分别是eax的入栈与出栈的语句。可以理解为这里是为了保护eax中的值。可能有读者会问,图2中倒数第5个语句的CALL,其返回值是否会影响eax中的值呢?其实是不会的,如果不放心,当然可以用OD执行一遍,看看执行完这个CALL之后,EAX寄存器的值有没有变为红色(红色表示该寄存器的值有变动)。其实图2中最后的这几句代码的意思是恢复栈帧,让堆栈保持平衡,这里不再详细说明。
那么我们继续向上分析,来到那八个小方框处,首先看一下第一个方框处的代码:
图3 分析loc_413155处的代码
我们依旧只关心eax,可以发现图中有两处代码包含有eax,程序首先将0x0这个值赋给[ebp+var_1D1],然后又将[ebp+var_1D1]中保存的值赋给al,那么很明显,此时的eax的值就变成了0,可以认为这第一个小方框(loc_413155)是验证失败时才会执行的,因为它间接地将0值赋给了eax。按照这个思想分析接下来的7个小方框,最终可以发现唯一的一个疑似验证成功的小方框内的反汇编代码:
图4 疑似验证成功处的代码
之所以认为这里应该就是验证成功处的流程,是因为eax的值被间接地赋值为了0x01,而这就会直接影响之后的验证流程,使得程序会认为验证成功。那么我们就可以按照这条线索不断向上查找,最终能够发现正确的程序流程。因为由上至下,是有多种路径的,而由下至上,路径是唯一的,所以很容易就能够确定路径。结合图1,可以确定流程如下:
图5 成功验证的流程
在图5中,我已经用红框标出了正确的验证流程,这是非常重要的线索,依据这个,我们就能够确定在每条分支判断中,应该执行哪条语句才能够达到最终的目标。这也是IDA Pro的Graph overview界面带来的好处。需要说明的是,图中绿色箭头代表跳转验证成功时所走的路径,而红色箭头代表验证失败时所走的路径,蓝色代表无条件跳转执行。
三、分析程序的验证算法
在图1中可以发现,一共有两处循环操作,那么可以认为这就是程序的验证机制所在,如下图所示:
按照顺序,我们先分析位于图6中的左侧的第一个循环。其起始处的反汇编代码如下:
图7 第一处循环起始处的反汇编代码
这里没有什么需要注意的,仅仅是一些初始化,可以知道程序首先要验证4个字符。需注意的是,图7中的loc_4131CE处的代码在验证第一个字符时是不执行的。接下来有:
图8
依据图8中的代码可以知道,验证程序会将注册码的ASCII码值减去0x30,然后比较该值与9的大小。由此可以认为程序是在验证用户所输入的注册码是否为数字。如果是数字(小于等于9),则将该值保存在[ebp+var_230]的位置,如果不是数字(大于9,即英文字母),则将该值减去7后,保存在[ebp+var_230]的位置。这里无论注册码是数字还是字母,保存完毕后都要跳转到loc_41322C处的语句继续执行:
图9
由图9的代码我们可以确定,CM4的注册码(前四个字符)要求只能够是由数字或者大写字母组成。因为结合图5可以知道,位于0x0041323F处的代码jg short loc_41324A一旦跳转实现,那么程序就验证失败,而这里主要是验证注册码是不是小写字母。同理位于0x00413248处的代码也会跳转到验证失败的位置。所以可知以上代码是在验证所输入字符的合法性。那么如果编写注册机,代码可以如下编写:
for ( i = 48; i <= 90; i++ )
{
if( i >= 58 && i <= 64 ) continue;
// 如果注册码是数字,则减去48
if( i >= 48 && i <= 57 )
{
tmp[0] = i - 48;
}
// 如果注册码是大写字母,则减去55
else
{
tmp[0] = i - 55;
}
for ( j = 48;j <= 90; j++ ) {}
……
}
由于是要生成四个字符,所以可以使用四重for循环,循环的内容就是ASCII码值。
程序继续执行。接下来如果通过了字符合法性的验证,就来到了loc_41326B的位置:
图10也就是真正来到了算法运算的代码处。首先是将[ebp+var_B4]中的值与0x24相乘,该地址初始值为0,它也会保存着最终的运算结果。乘完之后,再加上[ebp+var_A8]中的值,该地址中保存有注册码的ASCII码值减去0x30(如果是数字)或者减去0x37(如果是大写字母)之后的值。一加一乘,还是很简单很好理解的。代码如下:
var_14 = 0;
for( a = 3; a >= 0; a-- )
{
var_14 *= 36;
var_14 += tmp[a];
}
最后程序会无条件跳转到loc_4131CE处的代码,它在我们的图7中,作用是继续验证下一个注册码字符。
那么当第一组的4个注册码都验证(运算)完毕后,结合图7的最后一句代码与图8的第一句代码可以知道,程序会跳转到loc_413291处的代码执行:
图11
图11中的代码是对注册码第一组的四个字符的运算结果的验证,它是将运算结果与0x24相除,如果有余数,那么验证成功,并跳转到loc_4132CB处继续执行。至此,我们对程序验证机制的第一个循环分析完毕。
四、遇到瓶颈
以上分析还是比较顺利的,但是接下来我就遇到了一个瓶颈,继续分析:
这段代码的第一处反汇编语句出现了[ebp+var_60],这个在我之前的分析中并未出现,这里需要将[ebp+var_60]中保存的值与我们之前运算得出的结果即[ebp+var_14]进行相加,将求和结果当做地址,取该地址中的内容再进行运算。最后需要取余数,并保存在[ebp+var_20]中,再进行下一步的运算。所以首先应当搞清楚[ebp+var_60]里面究竟是什么,利用OD查看一下:
图13
通过对数据窗口的分析可知,这里保存的似乎是一个PE文件,因为它很是符合一个PE文件的特征,因为接下来的验证过程需要从该PE文件中的特定偏移处([ebp+var_14])取数据进行运算,所以有必要搞清楚这个PE文件的来历。因为不同的验证码,它们位于第一重循环运算的结果,即[ebp+var_14]是不相同的,那么也就会获得不同的偏移值,获得不同的位于该偏移的数据,从而得出不同的验证结果。这么来看,这个PE文件有点像一个“密码本”,找到这个“密码本”的来历,为我所用,才能够成功编写出注册码生成器。分析至此,下一步的工作就是从头开始寻找。
其实这个说着容易,我最初分析的时候可是花费了不少时间。这个PE文件的生成,应当是在第一重验证循环之前,那么有必要分析图1中最开始的那几个CALL语句,于是就找到了以下内容:
图14
可见程序在此调用了CreateFile()这个API函数,打开了名为“cm4.epe”的文件,由于没有指定文件路径,所以可以肯定该文件就位于和游戏可执行文件同一个目录下。进入游戏目录查找,果然能够找到,利用十六进制编辑软件Hex Editor Neo打开查看一下:
图15
图15和图13大致对比一下,其数据是一致的,那么就可以认为“密码本”就是这个“cm4.epe”文件。也许这个文件还有其它功用,但是这里不进行深究,只要知道需要用其来获取注册码的数据即可。
五、继续进行运算
接下来的代码和图12中的基本一致:
图16
图17
以上两段代码同样需要“密码本”的帮助,不同的是每次的偏移值都不同,这里不再赘述。需要注意的是,图12、图16和图17中的[ebp+var_20]、[ebp+var_2C]和[ebp+var_38]中的值非常重要,直接影响下一步的验证过程,请大家通过截图搞明白这个三个数值的来历。这会在下一篇文章中进行分析。
六、小结
如果读者随着本文的分析过程,一步一步地看下来,可能会觉得这一切十分顺利,似乎没有什么困难,所谓的瓶颈也不过如此。但是事实上,由于我在分析的时候对很多事情是不知道的,一切都在摸着石头过河,总会卡在某处而不知所措,这些困难是在文章中难以体现的。也正是因为这样,如果想精通逆向分析,平时就需要大量的练习,才能在实际的分析过程中,一帆风顺。
逆向工程第004篇:跨越CM4验证机制的鸿沟(中)的更多相关文章
- 逆向工程第003篇:跨越CM4验证机制的鸿沟(上)
一.前言 <冠军足球经理>系列作为一款拟真度极高的足球经营类游戏,赢得过无数赞誉,而CM4可以说是这个传奇的起点.但是在游戏安装过程中,当用户输入完序列号之后,程序并不会对用户的输入进行真 ...
- 逆向工程第005篇:跨越CM4验证机制的鸿沟(下)
一.前言 本文是逆向分析CM4系列的最后一篇,我会将该游戏的序列号验证机制分析完毕,进而编写出注册码生成器. 二.分析第二个验证循环 延续上一篇文章的内容,来到如下代码处: 图1 上述代码并没有特别需 ...
- 逆向project第005篇:跨越CM4验证机制的鸿沟(下)
一.前言 本文是逆向分析CM4系列的最后一篇,我会将该游戏的序列号验证机制分析完成,进而编写出注冊码生成器. 二.分析第二个验证循环 延续上一篇文章的内容,来到例如以下代码处: 图1 上述代码并没有特 ...
- 逆向project第003篇:跨越CM4验证机制的鸿沟(上)
一.前言 <冠军足球经理>系列作为一款拟真度极高的足球经营类游戏.赢得过无数赞誉,而CM4可以说是这个传奇的起点. 可是在游戏安装过程中.当用户输入完序列号之后.程序并不会对用户的输入进行 ...
- 黑客攻防技术宝典web实战篇:攻击验证机制习题
猫宁!!! 参考链接:http://www.ituring.com.cn/book/885 随书答案. 1. 在测试一个使用joe和pass证书登录的Web应用程序的过程中,在登录阶段,在拦截代理服务 ...
- 通过扩展改善ASP.NET MVC的验证机制[实现篇]
原文:通过扩展改善ASP.NET MVC的验证机制[实现篇] 在<使用篇>中我们谈到扩展的验证编程方式,并且演示了本解决方案的三大特性:消息提供机制的分离.多语言的支持和多验证规则的支持, ...
- 通过扩展改善ASP.NET MVC的验证机制[使用篇]
原文:通过扩展改善ASP.NET MVC的验证机制[使用篇] ASP.NET MVC提供一种基于元数据的验证方式是我们可以将相应的验证特性应用到作为Model实体的类型或者属性/字段上,但是这依然具有 ...
- 本版本延续MVC中的统一验证机制~续的这篇文章,本篇主要是对验证基类的扩展和改善(转)
本版本延续MVC中的统一验证机制~续的这篇文章,本篇主要是对验证基类的扩展和改善 namespace Web.Mvc.Extensions { #region 验证基类 /// <summary ...
- ASP.NET 身份验证机制
ASP.NET提供了3种认证方式:windows身份验证:IIS根据应用程序的设置执行身份验证.要使用这种验证方式,在IIS中必须禁用匿名访问.Forms验证 :用Cookie来保存 ...
随机推荐
- ElasticSearch学习笔记(超详细)
文章目录 初识ElasticSearch 什么是ElasticSearch ElasticSearch特点 ElasticSearch用途 ElasticSearch底层实现 ElasticSearc ...
- SpringMVC-02 第一个SpringMVC程序
SpringMVC-02 第一个SpringMVC程序 第一个SpringMVC程序 配置版 新建一个Moudle , springmvc-02-hello,确定依赖导入进去了 1.配置web.xml ...
- 使用createrepo构建本地yum仓库
rpm包安装的时候会有很多软件会出现因为其他依赖包没有,而导致安装失败的情况.一般可以连接外网的时候我们直接使用 yum 进行安装,可以为我们解决依赖包关系,但是很多工作环境下是没有外网的,内网情况下 ...
- 关于,java-webservice接口,根据服务端,自动生成客户端调用时,响应时间慢
我这边遇到的问题,是在和对方进行webservice接口交互的时候,用工具,调用对方的webservice接口,对方响应很快.但是用java生成的客户端调用就会很慢才得到响应.大概有5分钟左右. 这里 ...
- FreeBSD 发布 2020 年 Q3 季度报告
FreeBSD 几日前发布 Q3 季度报告,介绍了在过去第三季度里 FreeBSD 完成的工作和相关项目,涉及到架构支持.内核改进.持续集成和驱动程序优化等. 列举部分如下: FreeBSD 基金会目 ...
- cocos 向左滚动公告
properties:{ lblNotice:[cc.Node], speed:1, curtext:null }, start (){ this.getNotice(); }, getNotic ...
- 5、Spring教程之依赖注入
概念 依赖注入(Dependency Injection,DI). 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 . 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装 ...
- [换根DP][倍增]luogu P5666 树的重心
题面 https://www.luogu.com.cn/problem/P5666 分析 对于一棵以i为根的树来说,它的重心必然在其size大于等于sumsize/2的子树中. 那么断掉一条边e(u, ...
- C# - 实现类型的比较
IComparable<T> .NET 里,IComparable<T>是用来作比较的最常用接口. 如果某个类型的实例需要与该类型的其它实例进行比较或者排序的话,那么该类型就可 ...
- Android Studio 之 制作 Nine-Patch 图片(.9图片)
•引言 9.png 可以保证图片在合适的位置进行局部拉伸,避免了图片全局缩放造成的图片变形问题. 但是由于Android Studio对于.9图片的检查更加严格,所以不符合AS要求的.9图片会带来很多 ...