ioio_jy 发表于 2015-1-3 00:58

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

一、前言      本文是逆向分析CM4系列的最后一篇,我会将该游戏的序列号验证机制分析完毕,进而编写出注册码生成器。
二、分析第二个验证循环      延续上一篇文章的内容,来到如下代码处:图1      上述代码并没有特别需要注意的地方,只是知道了接下来的循环需要执行4次。下面就是重要的验证部分:图2      这是注册码中第二组四个字符的生成代码,主要是利用进行运算,将结果作为字符串的偏移值,从而得到注册码。回顾一下,这里的是之前运算所得到的余数,可见这个游戏的验证过程中的取余运算还是比较多的。接下来的两段代码,与图2代码较为类似:图3图4上述两段代码在取余并获取相应字符的同时,还更改了、与中的值,用于接下来的运算,由于比较简单,这里就不再赘述。    至此,CM4的注册码验证机制彻底分析完毕,那么接下来就可以开始注册码生成器的编写了。
三、编写注册码生成器    结合之前的分析,我们很容易就可以编写出注册机。但是要注意,我们在生成注册码的时候,是需要利用“cm4.epe”这个文件的,需要将二者放置在同一目录,让注册机程序便于读取“密码本”中的内容以进行运算,代码如下:#include<stdio.h>
#include<windows.h>
//////////////////////////////////////////////////////////////
// GetNum函数用于计算cm4.epe文件中相应偏移值处的DWORD大小的
// 十六进制数值,用于接下来的运算,该函数有一个参数var,保存
// 有偏移值
//////////////////////////////////////////////////////////////
DWORD GetNum( DWORD dwOffset )
{
    HANDLE hFile = NULL;
        DWORD dwSigNum = 0;         // 用于保存位于偏移位置的DWORD字节的内容
        DWORD dwNum = 0;            // 恒为0,用作ReadFile的参数
        // 打开名为cm4.epe的文件,该文件与本程序应处于同一目录下
        hFile = CreateFile("cm4.epe",
                               GENERIC_READ,
                                           0,
                                           NULL,
                                           OPEN_EXISTING,
                                           FILE_ATTRIBUTE_NORMAL,
                                           NULL
                      );
        // 如果文件打开失败,则提示出错信息并退出
        if (hFile == INVALID_HANDLE_VALUE)
        {
         printf("Could not open cm4.epe\n");
         return 0;
        }
    // 设置文件指针到指定的位置
        SetFilePointer(hFile, dwOffset, 0, FILE_BEGIN);
        // 读取起始于文件指针位置的十六进制代码,读取长度为4个字节(DWORD)
        ReadFile(hFile, &dwSigNum, sizeof(DWORD), &dwNum, NULL);
   
        CloseHandle(hFile);
        return dwSigNum;
}

int main()
{
    int a, b, c;             // 用于控制循环次数
        int i, j, m, n;          // 用于保存第一组验证码的四个ASCII码值
        int count = 10;          // 用于保存生成的注册码的组数
        int tmp;            // 用于临时保存前四位验证码的ASCII码减去0x30或0x37后的值
        int temp;                // 用于保存临时的运算结果
        int edx;               // 用于保存运算的余数
       
        DWORD Num;               // 用于保存位于cm4.epe相应偏移处的十六进制代码
        DWORD var_9C = 0x800000; // 这是一个固定的值,作为之后验证中的除数
        DWORD var_14;            // 用于保存第一循环算法最终运算的结果
        DWORD var_20 = 0;
        DWORD var_2C = 0;
        DWORD var_38 = 0;      // 这三个变量用于保存第二循环算法中的运算结果       
       
        char Reg = { "0" };// 这个二维数组保存最终得出的注册码
        char letter = { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" };// 字母表,用于生成注册码
//////////////////////////////////////////////////////////////
// 这里是注册码算法的第一处验证循环,这里通过四重循环,来不断
// 验证各种不同的ASCII码值的组合,也就是从0000到ZZZZ,从而生成
// 第一组的验证码(4个字符)
//////////////////////////////////////////////////////////////
        // 此处循环生成第一组验证码的第一个字符
        for ( i = 48; i <= 90; i++ )
        {          
                if( i >= 58 && i <= 64 ) continue;
                // 如果注册码是数字,则减去48
                if( i >= 48 && i <= 57 )
                {
                  tmp = i - 48;
                }
                // 如果注册码是大写字母,则减去55
                else
                {
                        tmp = i - 55;
                }
                // 此处循环生成第一组验证码的第二个字符
                for ( j = 48; j <= 90; j++ )
                {
                  if( j >= 58 && j <= 64 ) continue;
                        // 如果注册码是数字,则减去48
                        if( j >= 48 && j <= 57 )
                        {
                          tmp = j - 48;
                        }
                        // 如果注册码是大写字母,则减去55
                        else
                        {
                          tmp = j - 55;
                        }
                        // 此处循环生成第一组验证码的第三个字符
                        for ( m = 48; m <= 90; m++ )
                        {
                          if( m >= 58 && m <= 64 ) continue;
                                // 如果注册码是数字,则减去48
                          if( m >= 48 && m <= 57 )
                                {
                                tmp = m - 48;
                                }
                          // 如果注册码是大写字母,则减去55
                          else
                                {
                                tmp = m - 55;
                                }
                                // 此处循环生成第一组验证码的第四个字符
                                for ( n = 48; n <= 90; n++ )
                                {
                                  if( n >= 58 && n <= 64 ) continue;
                                        // 如果注册码是数字,则减去48
                                if( n >= 48 && n <= 57 )
                                        {
                                    tmp = n - 48;
                                        }
                                // 如果注册码是大写字母,则减去55
                                else
                                        {
                                    tmp = n - 55;
                                        }
                                  var_14 = 0;
                                        // 按照算法进行运算
                                        for( a = 3; a >= 0; a-- )
                                        {
                                          var_14 *= 36;
                        var_14 += tmp;
                                        }
                                        if (var_14 % 36 != 0 )
                                        {                                                                                                                                         
                        // loc_4132CB
                        Num = GetNum(var_14);
                        var_20 = Num % var_9C;
                                                temp = var_20;
                                                temp %= 36;
                                                if ( temp == 0 )
                                                {
                                                  continue;
                                                }
                                                else
                                                {
                                                  // loc_41330F
                            Num = GetNum(var_20);
                            var_2C = Num % var_9C;
                                                        temp = var_2C;
                                                        temp %= 36;
                                                        if ( temp == 0 )
                                                        {
                                                        continue;
                                                        }
                                                  else
                                                        {
                                                          // loc_413353
                              Num = GetNum(var_2C);
                              var_38 = Num % var_9C;
                                                                temp = var_38;
                                                                temp %= 36;
                                                          if ( temp == 0 )
                                                                {
                                                            continue;
                                                                }
                                                        else
                                                                {                                       
                                                                        // 第一组(前四个)注册码验证完毕并赋值
                                                                        Reg = i;
                                                                        Reg = j;
                                                                        Reg = m;
                                                                        Reg = n;
//////////////////////////////////////////////////////////////
// 这里是注册码算法的第二处验证循环,这里通过之前运算的结果,
// 经过运算得到余数(edx),作为letter[]字母表的偏移,从而生成
// 注册码字符
//////////////////////////////////////////////////////////////
                                    // loc_4133C5,第二处循环算法
                                                                        for( b = 0; b < 4; b++ )
                                                                        {
                                                                          c = 1;
                                                                                edx = var_20 % 36;
                                                                                Reg = letter;
                                                                               
                                                                                c += 1;       
                                                                          temp = var_20 / 36;
                                                                                var_20 = temp;
                                                                                edx = var_2C % 36;
                                                                                Reg = letter;
                                                                               
                                                                                c += 1;
                                                                                temp = var_2C / 36;
                                                                                var_2C = temp;
                                                                                edx = var_38 % 36;
                                                                                Reg = letter;
                                                                                temp = var_38 / 36;
                                                                                var_38 = temp;
                                                                        }
                                                                        // 输出已经运算完毕的注册码
                                                                        for ( a = 0; a <= 3; a++)
                                                                        {
                                                                          for( b = 0; b <= 3; b++ )
                                                                                {
                                                                                  printf("%c", Reg);                                                                         
                                                                                }
                                                                                if(a != 3)
                                                                                {
                                                                                  printf("-");
                                                                                }
                                                                        }
                                                                        printf("\n");
                                                                }
                                                        }
                                                }
                                                count--;                                                                                       
                                        }
                                        if ( count == 0 )
                                        {
                                          getchar();
                                                return 0;
                                        }
                                }
                        }
                }
        }       
        return 0;
}结合之前的分析,代码并不难理解,只是各种验证与循环比较多。这里我只在乎实现,而不考虑代码的优化等问题。
四、程序测试      这里我先生成10个注册码。由于后三组注册码字符是严格取决于第一组注册码字符的取值的,而第一组注册码字符的取值范围是在0000到FFFF之间,那么我这里生成的10个注册码其实也就所有注册码中的前十个,运行结果如下:图5 所生成的前10个注册码    为了测试这些注册码,我们无需重新安装游戏,因为游戏在安装时会在注册表中建立相应的键值,用于保存注册码,而游戏每次启动又会查询注册表获取该注册码,所以我们只需修改该键值即可:图6      我不可能验证所有注册码,但是验证这十个,结果是可行的,那么可以认为上面的程序是可行的,这里不再赘述。
五、小结      我在分析的过程中走了非常多的弯路,也正是因为这些弯路,我才能够在文章中始终展示出一片坦途。但是我的收获却是巨大的。我也希望各位读者也能够从这些文章中有所收获,多多练习,多多思考,不断尝试,将自己所学,真正运用于自己的身边,自己的工作中。

qq1239996785 发表于 2015-1-3 01:00

占个沙发给JY个赞

w394634587 发表于 2015-1-3 01:54

前排沙发出售瓜子 冰糖 矿泉水 {:300_961:}

plpplppl 发表于 2015-1-3 08:23

厉害了   精华作品

yyz219 发表于 2015-1-3 10:11

榻榻米 发表于 2015-1-3 19:04

能够真正用心将逆向分析这么清晰的 现在实在不多了楼主显示是个人才~

www52pojiecn 发表于 2015-1-3 21:14

推精华! 特别喜欢楼主第004篇的静态分析部分,理解了静态码条件下如何追踪

含蕊 发表于 2015-1-3 21:24

这个还真不错{:1_937:}

ioio_jy 发表于 2015-1-3 21:27

www52pojiecn 发表于 2015-1-3 21:14
推精华! 特别喜欢楼主第004篇的静态分析部分,理解了静态码条件下如何追踪

多谢你的肯定。这个“精华”是怎么评的?

www52pojiecn 发表于 2015-1-3 21:32

ioio_jy 发表于 2015-1-3 21:27
多谢你的肯定。这个“精华”是怎么评的?

应该是版主决定吧!
页: [1] 2
查看完整版本: 逆向工程第005篇:跨越CM4验证机制的鸿沟(下)