一、前言

本文是逆向分析CM4系列的最后一篇,我会将该游戏的序列号验证机制分析完毕,进而编写出注册码生成器。

二、分析第二个验证循环

延续上一篇文章的内容,来到如下代码处:

图1

上述代码并没有特别需要注意的地方,只是知道了接下来的循环需要执行4次。下面就是重要的验证部分:

图2

这是注册码中第二组四个字符的生成代码,主要是利用[ebp+var_20]进行运算,将结果作为字符串的偏移值,从而得到注册码。回顾一下,这里的[ebp+var_20]是之前运算所得到的余数,可见这个游戏的验证过程中的取余运算还是比较多的。接下来的两段代码,与图2代码较为类似:

图3

图4

上述两段代码在取余并获取相应字符的同时,还更改了[ebp+var_20]、[ebp+var_2C]与[ebp+var_38]中的值,用于接下来的运算,由于比较简单,这里就不再赘述。

至此,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[4];              // 用于临时保存前四位验证码的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[4][4] = { "0" };// 这个二维数组保存最终得出的注册码char letter[37] = { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" };// 字母表,用于生成注册码
//
// 这里是注册码算法的第一处验证循环,这里通过四重循环,来不断
// 验证各种不同的ASCII码值的组合,也就是从0000到ZZZZ,从而生成
// 第一组的验证码(4个字符)
//// 此处循环生成第一组验证码的第一个字符for ( i = 48; i <= 90; i++ ){     if( i >= 58 && i <= 64 ) continue;// 如果注册码是数字,则减去48if( i >= 48 && i <= 57 ){tmp[0] = i - 48;}// 如果注册码是大写字母,则减去55else{tmp[0] = i - 55;}// 此处循环生成第一组验证码的第二个字符for ( j = 48; j <= 90; j++ ){if( j >= 58 && j <= 64 ) continue;// 如果注册码是数字,则减去48if( j >= 48 && j <= 57 ){tmp[1] = j - 48;}// 如果注册码是大写字母,则减去55else{tmp[1] = j - 55;}// 此处循环生成第一组验证码的第三个字符for ( m = 48; m <= 90; m++ ){if( m >= 58 && m <= 64 ) continue;// 如果注册码是数字,则减去48if( m >= 48 && m <= 57 ){tmp[2] = m - 48;}// 如果注册码是大写字母,则减去55else{tmp[2] = m - 55;}// 此处循环生成第一组验证码的第四个字符for ( n = 48; n <= 90; n++ ){if( n >= 58 && n <= 64 ) continue; // 如果注册码是数字,则减去48if( n >= 48 && n <= 57 ){tmp[3] = n - 48;}// 如果注册码是大写字母,则减去55else{tmp[3] = n - 55;}var_14 = 0;// 按照算法进行运算for( a = 3; a >= 0; a-- ){var_14 *= 36;var_14 += tmp[a];}if (var_14 % 36 != 0 ){                                                                        // loc_4132CBNum = GetNum(var_14);var_20 = Num % var_9C;temp = var_20;temp %= 36;if ( temp == 0 ){continue;}else{// loc_41330FNum = GetNum(var_20);var_2C = Num % var_9C;temp = var_2C;temp %= 36;if ( temp == 0 ){continue;}else{// loc_413353Num = GetNum(var_2C);var_38 = Num % var_9C;temp = var_38;temp %= 36;if ( temp == 0 ){continue;}else{                   // 第一组(前四个)注册码验证完毕并赋值Reg[0][0] = i;Reg[0][1] = j;Reg[0][2] = m;Reg[0][3] = n;
//
// 这里是注册码算法的第二处验证循环,这里通过之前运算的结果,
// 经过运算得到余数(edx),作为letter[]字母表的偏移,从而生成
// 注册码字符
//// loc_4133C5,第二处循环算法for( b = 0; b < 4; b++ ){c = 1;edx = var_20 % 36;Reg[c][b] = letter[edx];c += 1;   temp = var_20 / 36;var_20 = temp;edx = var_2C % 36;Reg[c][b] = letter[edx];c += 1;temp = var_2C / 36;var_2C = temp;edx = var_38 % 36;Reg[c][b] = letter[edx];temp = var_38 / 36;var_38 = temp;}// 输出已经运算完毕的注册码for ( a = 0; a <= 3; a++){for( b = 0; b <= 3; b++ ){printf("%c", Reg[a][b]);                                      }if(a != 3){printf("-");}}printf("\n");}}}count--;                                         }if ( count == 0 ){getchar();return 0;}}}}}   return 0;
}

结合之前的分析,代码并不难理解,只是各种验证与循环比较多。这里我只在乎实现,而不考虑代码的优化等问题。

四、程序测试

这里我先生成10个注册码。由于后三组注册码字符是严格取决于第一组注册码字符的取值的,而第一组注册码字符的取值范围是在0000到FFFF之间,那么我这里生成的10个注册码其实也就所有注册码中的前十个,运行结果如下:

图5 所生成的前10个注册码

为了测试这些注册码,我们无需重新安装游戏,因为游戏在安装时会在注册表中建立相应的键值,用于保存注册码,而游戏每次启动又会查询注册表获取该注册码,所以我们只需修改该键值即可:

图6

我不可能验证所有注册码,但是验证这十个,结果是可行的,那么可以认为上面的程序是可行的,这里不再赘述。

五、后记

这三篇关于CM4逆向分析的文章,可以说是我到目前为止写作时间跨度最长的了。本系列的 上篇,我是在研究完 FIFA07与 《仙剑奇侠传》之后就打算写作的,大概是在今年6月中旬的时候。那时写了个初稿,也就是通过“爆破”的方式来绕过验证机制,想继续写注册机的编写部分,无奈水平有限,一直拖到现在才完成。这大半年的时间也是我的知识水平增长最快的半年,要不然我依旧不能分析出CM4的验证机制。这三篇文章的中篇和下篇,是利用了一个多星期的时间突击完成的,这段时间每天只有很少的时间用于分析,而且分析过程中也走了非常多的弯路,也正是因为这些弯路,我才能够在文章中始终展示出一片坦途。因为是第一次分析注册码验证机制,没有经验,耗时也比较长。但是我的收获却是巨大的。我也希望各位读者也能够从这些文章中有所收获,多多练习,多多思考,不断尝试,将自己所学,真正运用于自己的身边以及自己的工作中。

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

  1. Linux用户安全及Linux PAM验证机制

    一.Linux身份验证 1.用户与系统管理员     >用户分为系统管理员与普通管理用户两大类.            >每个用户在系统中都有唯一的用户名,是用户使用系统的凭证.      ...

  2. ASP.NET身份验证机制membership入门——配置篇(1){转}

    几乎所有的系统中都会使用到访问控制和角色管理这样的功能,例如:新建.修改.删除用户和角色,为用户分配角色,管理角色中的用户等等.于是MS在ASP.NET 2.0开始,实现了这些功能,使得我们在开发中, ...

  3. 黑客攻防技术宝典Web实战篇第2版—第6章 攻击验证机制

    6.1 验证技术 1.执行验证采用不同的技术 6.2 验证机制设计缺陷 6.2.1 密码保密性不强 1.应用程序常常使用的密码形式 6.2.2 蛮力攻击登录 6.2.3 详细的失败消息 1.尝试不同用 ...

  4. ASP.Net 2.0窗体身份验证机制详解(FormsAuthentication) (转载)

    ASP.Net 2.0窗体身份验证机制详解(FormsAuthentication) 收藏 转自:http://www.aspxclub.com/l12/c_3689.html 本篇文章介绍了在ASP ...

  5. [系统安全] 二十二.PE数字签名之(下)微软证书漏洞CVE-2020-0601复现及Windows验证机制分析

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...

  6. java实现登录验证机制的技术_基于token的登陆验证机制

    session简介 做过Web开发的程序员应该对Session都比较熟悉,Session是一块保存在服务器端的内存空间,一般用于保存用户的会话信息. 用户通过用户名和密码登陆成功之后,服务器端程序会在 ...

  7. Web应用程序的身份验证机制

    身份验证是大多数网站的基本要求. 但是,有许多机制可以实现身份验证,并且它们之间不是很互换. 根据业务需求,开发人员需要为其应用程序选择最合适的身份验证方法. 除非人们很好地了解机制之间的差异,否则这 ...

  8. 渗透测试方法论4---测试验证机制

    文章目录 一.了解验证机制 二.数据攻击 (一).测试密码强度 (二).测试用户名枚举 (三).测试密码猜测的适应性 三.特殊功能 (一).测试账户恢复功能(自己改密码) (二).测试"记住 ...

  9. 看我如何绕过Lastpass双因素验证机制

    本文讲的是看我如何绕过Lastpass双因素验证机制,在某次红队测试中,我发现了一种在Lastpass中绕过双因素验证(2FA)的方法.不幸的是,这一发现是在Tavis Ormandy曝光Lastpa ...

最新文章

  1. html溢出攻击,溢出(漏洞)攻击的实现及防御
  2. c语言中void跟argv,argc和argv []在C语言中
  3. 剑桥 2020 人工智能全景报告,未来 8 项AI趋势预测
  4. B站面试官炫耀身价过亿,贬低北邮应试者引热议!不知北邮毕业的B站创始人作何感想?
  5. 偏心率e用于描述某一轨道与圆轨道的区别
  6. OCP12C题库,71sql的使用
  7. maven常用命令(编译、测试、运行、打包、安装、部署)
  8. Session的原理,大型网站中Session方面应注意什么?
  9. Cookie 解说(分类、用途、缺陷、功能 ...)
  10. 论文浅尝 | ICLR 2020 - 一文全览知识图谱研究
  11. 注册域名需要资格吗_域名对于企业商标注册的重要性你知道吗?
  12. HTML5促使本地应用向Web迁移
  13. 使用css让文字两端对齐
  14. PHP Fatal error: Declaration of Hyperf\Framework\SymfonyEventDispatcher::dispatch($event) must be c
  15. 中国农业全要素生产率(1949-2020年)
  16. 一键生成App图标所有尺寸的三个酷站分享
  17. 最好的开源网络入侵检测工具
  18. 乐助手电脑版 v3.0.4.33284 官方版
  19. 学籍管理查找学生信息 c语言,【C语言期末实训】学生学籍管理系统
  20. RFID基本理论与操作实验

热门文章

  1. mysql连接所有字段_mysql:重命名连接的所有字段
  2. 单片机红外遥控格力空调
  3. 格力空调红外编码分析
  4. 这些证书可以在复试时加分!有机会一定要考!
  5. linux常见命令:查看端口是否被占用
  6. jq图片懒加载,支持background-image背景图
  7. 每日一题:讲一讲你理解的微服务架构?
  8. purge mysql_MySQL:Innodb purge线程略解
  9. 虫师 python_python 多线程就这么简单 - 虫师
  10. 高斯径向基函数(RBF)神经网络