0x00:简介——————————————————————————————————————————————————————————————————不得不说,随着时代的发展,游戏产业在近几年的互联网潮流中越来越扮演者重要的地位,与之而来的不仅有网络游戏公司,还有游戏灰色产业的暗流涌动。在游戏产业的发展中,诞生了一大批所谓的“外x挂”开发人员,他们不断的利用游戏的漏洞,在违法牟利的同时,也促进了游戏安全行业的进步。
同时,在游戏安全的对抗中,诞生了以下几种技术以防止游戏作弊的发生:⒈数据检测:对基础的游戏数据进行校验,例如坐标是否违规越界地图(坐标瞬移功能),人物短时间位移距离是否过大(人物加速功能)等等⒉CRC检测:基于游戏程序代码的检验,例如将人物移动中判断障碍物的je条件跳转修改为jmp强制跳转(人物穿墙功能)等等⒊封包检测:将游戏数据封包进行校验,防止利用封包漏洞实现违规操作,例如之前的穿X火线强登(可以登录任意账号)等等⒋机器检测:现在鹅厂 安全组好像换人了 ,游戏机器码封的都挺狠,一封就十年,不过道高一尺,魔高一丈,目前依然不够完善,很多朋友还是可以Pass⒌Call检测:非法调用Call导致校验值非法,例如攻击Call的严格校验(角色扮演游戏自动打怪脚本都是调用Call的)等等⒍堆栈检测:该检测归于调用Call过程中产生的问题⒎文件检测:对于游戏本地文件的检测,例如之前穿X火线几年前风靡一时的REZ文件(快刀秒杀,穿墙,遁地,飞天)等等⒏模块检测:很多外x挂采用“注入”的形式,所以模块检测在游戏安全对抗中也扮演着极其重要的作用⒐特征检测:这个主要检测典型的使用“易语言”开发的程序,或者部分外x挂市场比较大的毒瘤程序,或者菜单绘制(imgui绘制)等等⒑调试检测:针对调试器和调试行为的检测,对OllyDbg,CheatEngine等调试器特征和调试行为的检测等。⒒游戏保护:主要是利用R3各种反调试技术以及驱动层的HOOK等技术实现的游戏保护,例如鹅厂的TP等等可能还有一些我暂时还没想到的,哥哥们可以在下方补充~
0x01:步入今天的正题—CRC检测——————————————————————————————————————————————————————————————————首先,我们今天要讲的是游戏的CRC检测,所以为了能让下面的内容让大家理解,我们先来准备一下CRC检测的基础知识吧:⒈百度百科给我们CRC的解释
CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
来源:url/item/crc%E6%A0%A1%E9%AA%8C/url
⒉维基百科给我们CRC的解释
循环冗余校验(英语:Cyclic redundancy check,通称“CRC”)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。生成的数字在传输或者存储之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。一般来说,循环冗余校验的值都是32位的整数。由于本函数易于用二进制的计算机硬件使用、容易进行数学分析并且尤其善于检测传输通道干扰引起的错误,因此获得广泛应用。此方法是由W. Wesley Peterson于1961年发表。
来源:[url]/wiki/%E5%BE%AA%E7%92%B0%E5%86%97%E9%A4%98%E6%A0%A1%E9%A9%97
我们总结一下上面的百科解释:CRC是一种校验算法并且该算法被广泛应用于文件,数据等的校验。不过好像对于逆向来说还是有些模糊,那么现在就让我们打开看论坛发布的《加密与解密(第四版)》第279页,看一下其中的解释:
通过翻阅书籍,我们发现实际上CRC算法有很多种,而我们运用在软件加密中的CRC算法为CRC32算法。哇,这本书,兄弟萌,你们看,这本书,简直就是逆向界的圣经啊,别犹豫了!买他!书本有价!知识无价!哈哈,开个玩笑,在这里对所有参与《加密与解密》书籍工作的朋友致敬!
#include <Windows.h>
#include <stdio.h>
int
crc = NULL;
int
have_crc_table = NULL;
unsigned
int
crc32_table[256];
//生成具有256个元素的CRC32表
void
Crc_Make_Table()
{
have_crc_table = 1;
for
(
int
i = 0; i < 256; i++)
{
crc = i;
for
(
int
j = 0; j < 8; j++)
{
if
(crc & 1)
crc = (crc >> 1) ^ 0xEDB88320;
//CRC32 多项式的值,也可以是0x04C11DB7
else
crc >>= 1;
}
crc32_table[i] = crc;
//生成并存储CRC32数据表
}
}
//根据CRC32数据表计算内存或文件CRC校验码
unsigned
int
Calc_Crc32(unsigned
int
crc,
char
*Data,
int
len)
{
crc = 0xFFFFFFFF;
//将CRC初始化为-1
//判断CRC32表是否生成
if
(!have_crc_table)
Crc_Make_Table();
for
(
int
i = 0; i < len; i++)
{
crc = (crc >> 8) ^ crc32_table[(crc ^ Data[i]) & 0xff];
}
return
~crc;
}
int
main()
{
SetConsoleTitleA(
"Crc检测过掉学习工具 Ver1.0 提供方:小迪Xiaodi"
);
printf
(
"\n\n"
);
printf
(
"使用CE工具->添加地址0x402000->查找访问并尝试过掉检测!\n\n"
);
printf
(
"如果修改主程序模块,将会提示 “CRC代码校验检测到您修改了代码!”:\n\n\n\n\n"
);
//初始内存校验值
unsigned
int
uMainMoudleSumA = Calc_Crc32(0, (
char
*)0x400000, 0x1F000);
//400000- 41D000
//while循环开启CRC检测
while
(1)
{
//CRC循环检测内存实时校验值
unsigned
int
TmpCrcSum = Calc_Crc32(0, (
char
*)0x400000, 0x1F000);
if
(TmpCrcSum != uMainMoudleSumA)
{
//封号处理-掉线处理
MessageBoxA(NULL,
"CRC代码校验检测到您修改了代码!"
,
"Caption"
, MB_OK);
}
//为了方便,我在这里使用的Sleep函数控制检测的周期
Sleep(2000);
}
getchar
();
return
0;
}
最后,我们在生成文件的时候,要注意以下几个问题:
⒈静态编译和去除优化等的设置
⒉CRC校验函数传入参数的设置:代码处:
//计算内存校验值
Calc_Crc32(0, (char*)0x400000, 0x1F000);
在这里尤其要注意传入的第三个参数,他代表了一个校验的范围,那么这个位置,我们如何确定呢?①:先编译生成文件,我生成的是Release版本:
②:用PE相关的工具确定程序主模块镜像的大小,在这里我使用的是PEID v0.95工具:
复制粘贴一下镜像大小,传入一下就可以了,这个参数不可以乱填,否则会造成数据溢出,导致程序崩溃。0x03:对自己的CRC程序的防护测试——————————————————————————————————————————————————————————————————⒈运行自己的程序并用CheatEngine添加地址:
⒉修改代码测试:由于0x00402000处于代码段的位置,所以我们修改数值就相当于修改了代码,也就相应的触发了代码CRC的校验
⒊我们将末尾的数字“2”改为“3” ,直接触发了CRC检测:
0x04:对自己的CRC程序的分析——————————————————————————————————————————————————————————————————①首先,在攻击前,我们要知道代码的CRC检测是针对代码段的②代码段是用来执行的,正常情况下不会有其他数据访问代码段,被访问的大多是数据段,代码段被访问,很可疑就是CRC检测③此处说的“访问”的概念,大家可以通过CheatEngine工具中的“找出是什么访问了这个地址”来理解开干!
⒈针对0x402000这个地址,在CheatEngine工具中鼠标右键,查找访问,操作如下:
2.检测出现了!
我们记录一下该处的访问代码:
0040103F - movsx ecx,byte ptr [eax]
因为Ollydbg的调试体验要更好一些,所以我们记录地址:0x40103F,并转到Ollydbg去分析:
Ollydbg舒服就舒服在还能把你的代码分析出来,太强了!
⒊下断走一遍流程刚开始的时候,注意观察eax:
单步往下执行,下面会有个强制性的向上跳转:
继续执行走到初始位置:
总结:发现eax由0x400000变成了0x400001,也就是说,它在循环的递增检测所有范围内的内存代码数据⒋返回上一层观查一下函数通常,大家可能会CTRL+F9返回上一层,或者按如下图中返回:
这样做后就发现无论如何都无法返回,那应该怎样做呢?很简单,我们可以从堆栈中返回,堆栈窗口有个神奇的功能就是返回数据:
对着“返回到”敲下回车键,抽个烟的时间:
奇迹竟然发生了:
瞧瞧我们看见了什么?①我们自己写的CRC检验函数②检验函数上面就是我们的参数,起始检测地址和检测范围③我们自己写的信息框函数和Sleep函数这个位置,相当于代码中的:
//初始内存校验值
unsigned
int
uMainMoudleSumA = Calc_Crc32(0, (
char
*)0x400000, 0x1F000);
//400000- 41D000
//while循环开启CRC检测
while
(1)
{
//CRC循环检测内存实时校验值
unsigned
int
TmpCrcSum = Calc_Crc32(0, (
char
*)0x400000, 0x1F000);
if
(TmpCrcSum != uMainMoudleSumA)
{
//封号处理-掉线处理
MessageBoxA(NULL,
"CRC代码校验检测到您修改了代码!"
,
"Caption"
, MB_OK);
}
Sleep(2000);
}
对比图:
怎么样,是不是很刺激?
0x05:对自己的CRC程序的攻击测试——————————————————————————————————————————————————————————————————在这里呢,我们简单的讲几种过掉的姿势:⒈跳转jmp直接Pass
分析:这个位置直接Pass掉下方的CRC校验函数,直接跳转到了getchar函数,过于简单粗暴,仅适用于该程序,不适用于网络游戏哦~
⒉Nop大法
Nop大法尤其要注意Nop一定要Nop彻底!另外一定要堆栈平衡!否则被检测或者程序崩溃!⒊分析代码更改判断条件下断看eax,eax根据代码结合内层函数得知,是我们的CRC校验值:内层函数:
单步执行,根据我们观察eax,发现该处是初始校验值和实时
校验值的比较处:
那么我们干脆一不做二不休,直接cmp ecx,ecx,让他跳转永远相等,就永远不检测了:
当然还有其他的一些方法,在这里大家可以开动想象力,自行实践哦~总结:①要充分观察寄存器窗口数据的变化②注意疑似校验值的数据以及校验值的判断和计算③Nop要彻底,并且保证堆栈平衡④大家自由发挥
0x06:对某厂商的网络FPS游戏实战CRC对抗测试——————————————————————————————————————————————————————————————————⒈看一下游戏的样子:
该游戏可实现除草功能,地图除草方法:CheatEngine工具搜索字节数组:55 8B EC 8B 45 08 83 EC 08 8B 48 10 8B 01,找到的地址减去0x22,对该地址nop即可实现除草
⒉分析一下除草地址的检测由于我们是搜索特征得到的,该游戏的除草功能也是通过修改代码段nop实现的,所以触发了代码的CRC校验检测,符合我们今天讲的知识查找访问,发现四条访问地址,这个即为我们的CRC检测的访问:
查看相关的汇编代码:
发现:①疑似加密密匙的东西②所有的四条访问均来源于同一个代码段,观看代码得知属于同一个Call,也就是检测Call③由于该检测Call被厂商修改过,所以属于比较特殊的变形CRC⒊下断检测的代码,观察寄存器
只能推断EAX有可能是校验的次数,也就校验的大小,其他的寄存器并无法得知是否是校验值⒋去函数的头部看一看头部下断:eax疑似校验值,edx疑似校验大小,也就是循环的次数
继续运行,发现eax和edx同时变化:
edx在这里变化了两次,第一次为我们的计次,第二次为edi的数据,我们尝试修改头部的数据进行攻击测试:[由于涉及游戏安全和平衡,在这里,我不将写入的数据进行公开,大家有兴趣的可以自行研究实践]下hook处理:①分配内存写数据因为是jmp 5字节hook,所以要注意写法
②跳转至hook位置
OK,接下来就是漫长的等待~只要CheatEngine工具的访问不出现新的访问,那么检测就过掉了北京时间比较准确:
CRC一直没有访问,尝试开启除草功能:
开启后效果:
几分钟过去了,CRC依然没有访问,玩家死亡依然没有访问:
继续开一局游戏:
蹲个坑的时间~
然后就被人打死了~
至此,该功能检测被过掉,检测稳定了.
0x07:总结⒈外x挂和反外x挂的斗争依然在继续,各大游戏厂商必须加强游戏的检测防护,避免外x挂产业泛滥!2.多层嵌套的检测是必须的,在嵌套的同时,还应该注意代码混淆的程度也要加大!3.务必加强对CheatEngine等工具的检测,一旦发现,就封号处理,提高逆向难度!游戏逆向仅限于技术交流,切勿用于违法犯罪,切勿开发违法犯罪的相关外x挂、辅x助等工具!