软件与游戏安全之CRC检测与绕过

admin 2025-12-23 15:53:17 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入解析了游戏反作弊中的CRC检测机制,通过代码演示了循环冗余校验如何监控内存完整性以防止修改。文章详细阐述了CRC对逆向分析的阻碍,并提供了多种绕过思路,包括使用硬件断点、修改检测逻辑跳转指令、内存拷贝欺骗及挂起检测线程,具有很强的实战指导意义。 综合评分: 85 文章分类: 逆向分析,二进制安全,实战经验


cover_image

软件与游戏安全之CRC检测与绕过

原创

ovouec

Web安全入门

2025年12月19日 17:23 广东

最近干兼职事多,今天难得休息一天,更新一期之前学习的CRC笔记,在此之前我们先了解一下以下几种反游戏作弊的技术:

  说过检测之前我们先了解一下什么是crc检测。CRC,中文名称为循环冗余校验,CRC属于密码学一类算法,常用于数据校验,一般会用来检测程序是否被脱壳或者被修改,以达到防破解的目的。

我们看看百度百科给我们CRC的解释:

总结来说就是:CRC是一种校验算法且该算法能够被广泛应用于文件,数据等的一种校验。

而我们常见的,游戏中的CRC检测(循环冗余校验)是一种常见的反作弊机制,用于确保游戏数据的完整性和正确性,防止玩家通过修改内存或本地文件来获取不公平优势。‌

CRC代码检测原理

我们先来看基础的CRC代码实现:

#include&nbsp;<Windows.h>#include&nbsp;<stdio.h>

int&nbsp;crc =&nbsp;NULL;int&nbsp;have_crc_table =&nbsp;NULL;unsigned&nbsp;int&nbsp;crc32_table[256];
//生成具有256个元素的CRC32表void&nbsp;Crc_Make_Table(){    have_crc_table =&nbsp;1;
for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;256; i++)    {       crc = i;for&nbsp;(int&nbsp;j =&nbsp;0; j <&nbsp;8; j++)     {if&nbsp;(crc &&nbsp;1)             crc = (crc >>&nbsp;1) ^&nbsp;0xEDB88320;&nbsp;//CRC32 多项式的值,也可以是0x04C11DB7else              crc >>=&nbsp;1;     }       crc32_table[i] = crc;&nbsp;//生成并存储CRC32数据表  }}

unsigned&nbsp;int&nbsp;Calc_Crc32(unsigned&nbsp;int&nbsp;crc,&nbsp;char* Data,&nbsp;int&nbsp;len){    crc =&nbsp;0xFFFFFFFF;
//判断CRC32表是否生成if&nbsp;(!have_crc_table)Crc_Make_Table();
for&nbsp;(int&nbsp;i =&nbsp;0; i < len; i++)    {       crc = (crc >>&nbsp;8) ^ crc32_table[(crc ^ Data[i]) &&nbsp;0xff];   }return&nbsp;~crc;}
int&nbsp;main(){SetConsoleTitleA("Crc检测");
printf("\n\n");
printf("使用CE工具->添加地址0x402000->查找访问并尝试过掉检测!\n\n");
printf("如果修改主程序模块,将会提示 “CRC代码校验检测到您修改了代码!”:\n\n\n\n\n");
//初始内存校验值unsigned&nbsp;int&nbsp;uMainMoudleSumA =&nbsp;Calc_Crc32(0, (char*)0x400000,&nbsp;0x1D000);

//while循环开启CRC检测while&nbsp;(1)    {//CRC循环检测内存实时校验值unsigned&nbsp;int&nbsp;TmpCrcSum =&nbsp;Calc_Crc32(0, (char*)0x400000,&nbsp;0x1D000);
if&nbsp;(TmpCrcSum != uMainMoudleSumA)        {//封号处理-掉线处理MessageBoxA(NULL,&nbsp;"CRC代码校验检测到您修改了代码!",&nbsp;"Caption", MB_OK);     }Sleep(2000);   }
int&nbsp;ret =&nbsp;getchar();return&nbsp;0;}

我们先取部分关键来分析:

int&nbsp;main(){SetConsoleTitleA("Crc检测过检测"); &nbsp; &nbsp;//设置控制台printf("\n\n"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//打印printf("使用CE工具->添加地址0x502000->查找访问并尝试过掉检测!\n\n");printf("如果修改主程序模块,将会提示 “CRC代码校验检测到您修改了代码!”:\n\n\n\n\n");//初始内存校验值unsigned&nbsp;int&nbsp;uMainMoudleSumA =&nbsp;Calc_Crc32(0, (char*)0x400000,&nbsp;0x1D000);//while循环开启CRC检测while&nbsp;(1)    {//CRC循环检测内存实时校验值unsigned&nbsp;int&nbsp;TmpCrcSum =&nbsp;Calc_Crc32(0, (char*)0x400000,&nbsp;0x1D000);if&nbsp;(TmpCrcSum != uMainMoudleSumA)        {//封号处理-掉线处理MessageBoxA(NULL,&nbsp;"CRC代码校验检测到您修改了代码!",&nbsp;"Caption", MB_OK);     }Sleep(2000);   }int&nbsp;ret =&nbsp;getchar();return&nbsp;0;}

以上是关键检测代码,我们可以看到它在初始内存校验值位置调用了一个叫Calc_Crc32的函数,而它的主要关键作用就是一套算法。

unsigned&nbsp;int&nbsp;Calc_Crc32(unsigned&nbsp;int&nbsp;crc,&nbsp;char* Data,&nbsp;int&nbsp;len){    crc =&nbsp;0xFFFFFFFF;&nbsp;//将CRC初始化为-1
//判断CRC32表是否生成if&nbsp;(!have_crc_table)Crc_Make_Table();
for&nbsp;(int&nbsp;i =&nbsp;0; i < len; i++)    {       crc = (crc >>&nbsp;8) ^ crc32_table[(crc ^ Data[i]) &&nbsp;0xff];   }return&nbsp;~crc;}

根据CRC32数据表计算内存或文件CRC校验码,最后返回计算结果得到一个数。

整个执行流程就是,程序运行起来,先获得一个初始数值

之后通过while循环开启CRC检测,此时,初始的数值就会与新的数值做一个实时的校验比较,如果值不对,那就会做出封号掉线处理。

我们在Release编译程序之前先做一下属性页设置

优化选择禁用

随机基地址改成否

全程序优化改为无程序优化

编译好之后我们打开我们心爱的dbg,把程序拖入进去。

在内存布局中,我们可以看到,它的text段,也就是代码段,意思是可执行的代码,地址是 00401000 大小为 00012000

那么我们需要代码实现保护检测,那就把对应的参数传入。

CRC的校验码可以得知,Data为内存地址,它会通过一个for循环去计算长度,i为变量,len做限制,len为代码的长度,简单来说就是把代码的所有字节取出来去做一个比较。

我们看看代码段跳到00401000区域,结合代码我们可以理解,当i等于0时,它会检测第一个字节55,i等于1时,检测第二个字节8B,依次EC 51……每一个都取出来单独做一个运算。

逆向分析

那么问题来了,关于逆向分析,CRC检测会对我们造成什么困扰呢?我们先尝试在00401060处,F2打断点。

此时就会发现,00401060的地址处打入断点之后,字节便从55变成了CC,并且会弹窗出检测代码,这就是CRC检测对我们的影响。

另外我们尝试修改字节我们将0x28改为0x21

此时就会进入循环弹窗检测。

另外我们选择对字符串进行修改也是一样的情况。

CRC对逆向的影响点:

1.F2下断闪退

2.修改代码段打补丁闪退

3.资源,字符串的检测

CRC检测绕过

针对F2调试断点触发的CRC

我们可以使用硬件断点,不再使用F2,这样就不会修改代码,从而绕过CRC的一个检测。

针对任何类型触发的CRC

1.干掉外层检测逻辑:

软的碰不过那就跟它硬碰硬,我们可以尝试把它的CRC检测代码给干了,那么我们就需要知道,它的CRC检测代码是哪部分。

因为CRC会不断的读取要验证的代码,所以我们可以使用CE查看是哪些代码在读取我们的程序。

CE附加进程后,先手动添加一条代码段的地址,这里我就添加了代码段的起始地址,然后查看什么访问了该地址。

这就是我们的CRC检测点了。

0040103F&nbsp;-&nbsp;0FBE&nbsp;08&nbsp; - movsx ecx,byte ptr&nbsp;[eax]

打开dbg找到我们的检测点位置

这里就是关键的位置,函数头和函数尾都已经找到。

那么我们就要关注,到底是谁调用了这个函数,我们在函数头查找调用地址。

我们一个个点进去看,就会发现其中的一个关键的判断代码块。

很明显了,可以看到je下面就是检测到我们修改代码的一个弹窗,而je的意思就是不相等的情况下执行,符合我们前面说的,字节对比校验,所以我们只需要把je改为jmp就可以了,之后我们随便下断点都不会有CRC检测。

2.移花接木法

我们只需要自己分配一块内存A,大小相同,读取从00401000开始的每一个字节数据,写入到A里面,此时A里面的全部数据则都是为合法的内容。

3.线程法

使用PYArkClient工具,查看进程线程

观察切换次数,把对应的检测线程挂起,之后就可以绕过打断点了。

声明:本文章仅限于技术交流,切勿用于违法犯罪,切勿开发违法犯罪的相关外x挂、辅x助等工具!违者后果自负。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Web安全入门 ovouec《软件与游戏安全之CRC检测与绕过》

评论:0   参与:  1