逆向project第005篇:跨越CM4验证机制的鸿沟(下)
一、前言
本文是逆向分析CM4系列的最后一篇,我会将该游戏的序列号验证机制分析完成,进而编写出注冊码生成器。
二、分析第二个验证循环
延续上一篇文章的内容,来到例如以下代码处:
图1
上述代码并没有特别须要注意的地方。仅仅是知道了接下来的循环须要运行4次。
以下就是重要的验证部分:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaW9pb19qeQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
图2
这是注冊码中第二组四个字符的生成代码。主要是利用[ebp+var_20]进行运算。将结果作为字符串的偏移值,从而得到注冊码。回想一下,这里的[ebp+var_20]是之前运算所得到的余数。可见这个游戏的验证过程中的取余运算还是比較多的。接下来的两段代码,与图2代码较为类似:
图3
图4
上述两段代码在取余并获取对应字符的同一时候,还更改了[ebp+var_20]、[ebp+var_2C]与[ebp+var_38]中的值。用于接下来的运算。因为比較简单,这里就不再赘述。
三、编写注冊码生成器
结合之前的分析。我们非常easy就能够编写出注冊机。
可是要注意,我们在生成注冊码的时候。是须要利用“cm4.epe”这个文件的。须要将二者放置在同一文件夹。让注冊机程序便于读取“password本”中的内容以进行运算,代码例如以下:
#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;
// 假设注冊码是数字,则减去48
if( i >= 48 && i <= 57 )
{
tmp[0] = i - 48;
}
// 假设注冊码是大写字母,则减去55
else
{
tmp[0] = i - 55;
}
// 此处循环生成第一组验证码的第二个字符
for ( j = 48; j <= 90; j++ )
{
if( j >= 58 && j <= 64 ) continue;
// 假设注冊码是数字,则减去48
if( j >= 48 && j <= 57 )
{
tmp[1] = j - 48;
}
// 假设注冊码是大写字母。则减去55
else
{
tmp[1] = j - 55;
}
// 此处循环生成第一组验证码的第三个字符
for ( m = 48; m <= 90; m++ )
{
if( m >= 58 && m <= 64 ) continue;
// 假设注冊码是数字,则减去48
if( m >= 48 && m <= 57 )
{
tmp[2] = m - 48;
}
// 假设注冊码是大写字母,则减去55
else
{
tmp[2] = m - 55;
}
// 此处循环生成第一组验证码的第四个字符
for ( n = 48; n <= 90; n++ )
{
if( n >= 58 && n <= 64 ) continue;
// 假设注冊码是数字,则减去48
if( n >= 48 && n <= 57 )
{
tmp[3] = n - 48;
}
// 假设注冊码是大写字母,则减去55
else
{
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_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[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个注冊码事实上也就全部注冊码中的前十个,执行结果例如以下:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaW9pb19qeQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
图5 所生成的前10个注冊码
为了測试这些注冊码,我们无需又一次安装游戏,由于游戏在安装时会在注冊表中建立对应的键值。用于保存注冊码。而游戏每次启动又会查询注冊表获取该注冊码。所以我们仅仅需改动该键值就可以:
图6
我不可能验证全部注冊码,可是验证这十个。结果是可行的。那么能够觉得上面的程序是可行的,这里不再赘述。
五、后记
这三篇关于CM4逆向分析的文章,可以说是我到眼下为止写作时间跨度最长的了。本系列的上篇,我是在研究完FIFA07与《仙剑奇侠传》之后就打算写作的,大概是在今年6月中旬的时候。那时写了个初稿,也就是通过“爆破”的方式来绕过验证机制,想继续写注冊机的编写部分,无奈水平有限。一直拖到如今才完毕。这大半年的时间也是我的知识水平增长最快的半年,要不然我依然不能分析出CM4的验证机制。这三篇文章的中篇和下篇。是利用了一个多星期的时间袭击完毕的。这段时间每天仅仅有非常少的时间用于分析,并且分析过程中也走了非常多的弯路,也正是由于这些弯路,我才可以在文章中始终展示出一片坦途。由于是第一次分析注冊码验证机制,没有经验,耗时也比較长。可是我的收获却是巨大的。我也希望各位读者也可以从这些文章中有所收获,多多练习,多多思考,不断尝试,将自己所学。真正运用于自己的身边以及自己的工作中。
逆向project第005篇:跨越CM4验证机制的鸿沟(下)的更多相关文章
- 逆向project第003篇:跨越CM4验证机制的鸿沟(上)
一.前言 <冠军足球经理>系列作为一款拟真度极高的足球经营类游戏.赢得过无数赞誉,而CM4可以说是这个传奇的起点. 可是在游戏安装过程中.当用户输入完序列号之后.程序并不会对用户的输入进行 ...
- 逆向工程第005篇:跨越CM4验证机制的鸿沟(下)
一.前言 本文是逆向分析CM4系列的最后一篇,我会将该游戏的序列号验证机制分析完毕,进而编写出注册码生成器. 二.分析第二个验证循环 延续上一篇文章的内容,来到如下代码处: 图1 上述代码并没有特别需 ...
- 逆向工程第003篇:跨越CM4验证机制的鸿沟(上)
一.前言 <冠军足球经理>系列作为一款拟真度极高的足球经营类游戏,赢得过无数赞誉,而CM4可以说是这个传奇的起点.但是在游戏安装过程中,当用户输入完序列号之后,程序并不会对用户的输入进行真 ...
- 逆向工程第004篇:跨越CM4验证机制的鸿沟(中)
一.前言 在上一篇文章的最后,我已经找出了关键的CALL语句,那么这篇文章我就带领大家来一步一步地分析这个CALL.我会将我的思路完整地展现给大家,因此分析过程可能略显冗长,我会分为两篇文章进行讨论. ...
- 逆向project第004篇:令计算器程序显示汉字(下)
一.前言 钩子技术是一项很有有用价值的技术.在Windows下HOOK技术的方法比較多,使用比較灵活,常见的应用层的HOOK方法有Inline HOOK(详见<反病毒攻防研究第012篇:利用In ...
- 黑客攻防技术宝典web实战篇:攻击验证机制习题
猫宁!!! 参考链接:http://www.ituring.com.cn/book/885 随书答案. 1. 在测试一个使用joe和pass证书登录的Web应用程序的过程中,在登录阶段,在拦截代理服务 ...
- Android 逆向project 实践篇
Android逆向project 实践篇 上篇给大家介绍的是基础+小Demo实践. 假设没有看过的同学能够进去看看.(逆向project 初篇) 本篇主要给大家介绍怎样反编译后改动源代码, 并打包执行 ...
- Android Cocos2dx引擎 prv.ccz/plist/so等优化缓存文件,手把手ida教你逆向project反编译apk库等文件
前段时间在 Android play 上看到一个非常牛逼的 3D 动态天气预报,效果真的非常炫.二话不说动手 dex2jar.bat/apktool 发现这并没 有什么卵用,在核心的地方看见 nati ...
- 通过扩展改善ASP.NET MVC的验证机制[实现篇]
原文:通过扩展改善ASP.NET MVC的验证机制[实现篇] 在<使用篇>中我们谈到扩展的验证编程方式,并且演示了本解决方案的三大特性:消息提供机制的分离.多语言的支持和多验证规则的支持, ...
随机推荐
- vagrant多节点配置
1.vagrantfile的配置 Vagrant.configure("2") do |config| config.vm.box = "xinjieLinux" ...
- 7款Flash和Javascript网页视频播放器
Flash和javascript网页视频播放器.梦想中的视频播放器是这样的: 支持所有格式,兼容所有主流视频网站,支持播放列表.视频缩略图.全屏播放.画面调节.预加载.体积贼小,功能贼多……. 也许你 ...
- NYOJ 8 一种排序(comparator排序)
一种排序 时间限制: 3000 ms | 内存限制: 65535 KB 难度: 3 描述 现在有很多长方形,每一个长方形都有一个编号,这个编号可以重复:还知道这个长方形的宽和长,编号.长.宽都 ...
- javaScript几个操作数组增减元素的函数
1. shift:删除原数组第一项,并返回删除元素的值:如果数组为空则返回undefined 2. unshift:将参数添加到原数组开头,并返回数组的长度 var a = [1,2,3,4,5]; ...
- [Python爬虫] 之六:Selenium 常用控件用法
Selenium 常用控件用法 1.文本框 上图中,如何定位搜索文本框,并输入搜索内容进行搜索 首先:利用方法 find_element_by_xpath定位元素:inputElements = se ...
- Kubenertes资源分配之Request和Limit解析
收录待用,修改转载已取得腾讯云授权 Kubernetes是一个容器集群管理平台,Kubernetes需要统计整体平台的资源使用情况,合理地将资源分配给容器使用,并且要保证容器生命周期内有足够的资源来保 ...
- VS2012利用Wix打包问题
在用VS2012打包的时候,忽然发现没有像VS2010一样可以本地打包的项目模板,于是找了N多资料后,发现现在微软在推荐用WIX打包. 在折腾WIX打包生成界面的时候,遇到了一个很纠结的问题. Unr ...
- WinForm 窗口缩放动画效果
using System; using System.Collections.Generic; using System.Text; using System.Threading; using Sys ...
- HDU 1710 Binary Tree Traversals(二叉树)
题目地址:HDU 1710 已知二叉树先序和中序求后序. #include <stdio.h> #include <string.h> int a[1001], cnt; ty ...
- ReSharper的功能真的很强大主要是针对代码规范和优化,园子里介绍的也不少,如果你没有安装,那我只能表示你们会相见恨晚
二.ReSHarper 代码规范.单元测试.... ReSharper的功能真的很强大,主要是针对代码规范和优化,园子里介绍的也不少,如果你没有安装,那我只能表示你们会相见恨晚! 1.像命名不规范,f ...