CRC算法简介
“CRC” 的全称是 “Cyclic Redundancy Checksum” 或者 "Cyclic Redundancy Check",是对数据的校验值,中文名是“循环冗余校验码”,常用于校验数据的完整性。最常用的CRC 算法是 CRC32(即数据校验值为32位)
CRC算法代码量小,容易理解,目前应用十分广泛。
CRC算法也广泛应用在游戏安全领域,用于对游戏代码的校验,防止别人通过修改代码来达到某一目的,比如对于FPS游戏,可能用户修改了减少子弹的关键代码,使自己达到无限子弹的效果
手写CRC检测算法
我们参考《加密与解密》第四版279页CRC32算法,自己手写一个CRC检测程序
#include <stdio.h>
#include <windows.h>
int crc32_table[256];
//生成具有256个元素的CRC32表
void Crc_Make_Table()
{
int crc = 0;
for (int i = 0; i < 256; i++)
{
crc = i;
for (int j = 0; j < 8; j++)
{
if (crc & 1)
crc = (crc >> 1) ^ 0XEDB88320; //CRC32多项式的值,也可以是04C11DB7
else
crc >>= 1;
}
crc32_table[i] = crc;
}
}
//根据CRC32数据表来计算字符串或者文件的CRC32值
int Calc_Crc32(char * data, int len)
{
int crc = 0XFFFFFFFF; //CRC初值是-1,即0XFFFFFFFF
for (int i = 0; i < len; i++)
{
crc = crc32_table[(crc ^ data[i]) & 0XFF] ^ (crc >> 8);
}
return ~crc;
}
//返回代码段的基址和大小
DWORD GetTextAddrAndSize(int * pTextSize)
{
HMODULE hModule = GetModuleHandle(NULL);
PIMAGE_DOS_HEADER pidh = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS32 pnh = PIMAGE_NT_HEADERS32(pidh->e_lfanew + (DWORD)pidh);
//获取节的数量
int secNum = pnh->FileHeader.NumberOfSections;
//获得节表的基址
PIMAGE_SECTION_HEADER psh = PIMAGE_SECTION_HEADER(sizeof(_IMAGE_NT_HEADERS) + (DWORD)pnh);
for (int i = 0; i < secNum; i++)
{
if (!strcmp((const char *)psh->Name, ".text"))
{
*pTextSize = psh->Misc.VirtualSize;
return psh->VirtualAddress + (DWORD)hModule;
}
psh++;
}
*pTextSize = 0;
return 0;
}
int main()
{
int textSize = 0;
DWORD dwTextAddr = GetTextAddrAndSize(&textSize);
//下面我们将CRC32校验用于自身代码,来模拟游戏中的CRC校验
printf("请尝试修改内存,并过掉检测!\n");
//先生成一个CRC32表
Crc_Make_Table();
//初始内存校验值
int sum = Calc_Crc32((char *)dwTextAddr, textSize);
while (1)
{
int tempSum = Calc_Crc32((char *)dwTextAddr, textSize);
if (tempSum != sum)
{
MessageBox(NULL, "检测到您修改了代码!", "封号!", MB_OK);
}
Sleep(2000);
}
getchar();
return 0;
}
运行程序下断被检测
我们运行程序,并用OD附加
程序正常执行,但是当我们按下F2下断点的时候(将代码数据改为CC)
这时,就会被程序检测到,如果是游戏程序的话,这个时候就会被检测进行惩罚了,,比如封号啥的,,下面我们就来尝试过掉这个CRC检测
实战过CRC检测
我们对上面401051这个地址下内存访问断点,程序断下
整体观察这段代码
就是一个死循环校验
通过堆栈观察,返回到上层函数
发现这个je就是关键位置,如果程序检测到代码改变,zf就是0,je不跳转,然后MessageBox就执行了(被检测到),所以我们将je改为jmp,使其强制跳转
这样,不论我们怎么修改代码,程序都不会被检测到了
过掉本程序检测的方式有很多,比如还可以全部将检测到之后执行的代码全部NOP掉等。大家可以发挥想象力,自行实践。
这里写的CRC例子虽然非常简单,但是麻雀虽小,五脏俱全,其他的一些大的检测也无非是加入了一些混淆技术,或者多重CRC等
参考书籍
《加密与解密第四版》
参考文章
https://www.freebuf.com/articles/system/210304.html
最后,还是那句话,技术不分对错,人心才分善恶,希望大家努力学习技术,为游戏安全贡献出自己的一份力量,也希望自己在未来的日子更上一层楼。加油~~~~~~