首页 > 代码库 > 逆向工程第006篇:跨越CM4验证机制的鸿沟(中)

逆向工程第006篇:跨越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 程序中的两处循环

        按照顺序,我们先分析位于图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 ( i = 48; i <= 90; i++ ) {}
    ……
}

        由于是要生成四个字符,所以可以使用四重for循环,循环的内容就是ASCII码值。

        程序继续执行。接下来如果通过了字符合法性的验证,就来到了loc_41326B的位置:

图10

        图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处继续执行。至此,我们对程序验证机制的第一个循环分析完毕。

  

四、遇到瓶颈

        以上分析还是比较顺利的,但是接下来我就遇到了一个瓶颈,继续分析:

图12

        这段代码的第一处反汇编语句出现了[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]中的值非常重要,直接影响下一步的验证过程,请大家通过截图搞明白这个三个数值的来历。这会在下一篇文章中进行分析。

 

六、小结

        如果读者随着本文的分析过程,一步一步地看下来,可能会觉得这一切十分顺利,似乎没有什么困难,所谓的瓶颈也不过如此。但是事实上,由于我在分析的时候对很多事情是不知道的,一切都在摸着石头过河,总会卡在某处而不知所措,这些困难是在文章中难以体现的。也正是因为这样,如果想精通逆向分析,平时就需要大量的练习,才能在实际的分析过程中,一帆风顺。

逆向工程第006篇:跨越CM4验证机制的鸿沟(中)