当软件被开发出来时,为了增加软件的安全性,防止被破解,通常情况下都会对自身内存或磁盘文件进行完整性检查,以防止解密者修改程序,我们可以将exe与dll文件同时做校验,来达到相互认证的目的,解密者想要破解则比较麻烦,当我们使用的互认证越多时,解密者处理的难度也就越大。

实现磁盘文件检测,我们可以使用CRC32算法或者RC4算法来计算程序的散列值,以CRC32为例,其默认会生成一串4字节CRC32散列,我们只需要计算后将该值保存在文件或程序自身PE结构中的空缺位置即可。

具体实现:通过使用CRC32算法计算出程序的CRC字节,并将其写入到PE文件的空缺位置,这样当程序再次运行时,来检测这个标志,是否与计算出来的标志一致,来决定是否运行程序,一旦程序被打补丁,其crc32值就会发生变化,一旦发生变化程序就废了。

实现CRC32完整性检查: 生成CRC32的代码如下,其中的CRC32就是计算过程,这个过程是一个定式,我们只需要使用CreateFile打开文件,并将文件字节数全部读入到BYTE *pFile = (BYTE*)malloc(dwSize);中,然后调用crc32计算其硬盘中的hash散列值即可。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h> DWORD CRC32(BYTE* ptr, DWORD Size)
{
DWORD crcTable[256], crcTmp1; // 动态生成CRC-32表
for (int i = 0; i<256; i++)
{
crcTmp1 = i;
for (int j = 8; j>0; j--)
{
if (crcTmp1 & 1) crcTmp1 = (crcTmp1 >> 1) ^ 0xEDB88320L;
else crcTmp1 >>= 1;
}
crcTable[i] = crcTmp1;
} // 计算CRC32值
DWORD crcTmp2 = 0xFFFFFFFF;
while (Size--)
{
crcTmp2 = ((crcTmp2 >> 8) & 0x00FFFFFF) ^ crcTable[(crcTmp2 ^ (*ptr)) & 0xFF];
ptr++;
}
return (crcTmp2 ^ 0xFFFFFFFF);
} int main(int argc, char* argv[])
{
char *FileName = "c://test.exe";
// 验证文件是否存在,不存在则退出
if (GetFileAttributes(FileName) == 0xFFFFFFFF)
return 0; HANDLE hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ,
0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
DWORD dwSize = GetFileSize(hFile, NULL); // 开辟一段内存空间
BYTE *pFile = (BYTE*)malloc(dwSize); // 将数据读入文件
DWORD dwNum = 0;
ReadFile(hFile, pFile, dwSize, &dwNum, 0); // 计算CRC32
DWORD dwCrc32 = CRC32(pFile, dwSize);
if (pFile != NULL)
{
printf("CRC32 = 0x%x \n", dwCrc32);
free(pFile);
pFile = NULL;
} system("pause");
return 0;
}

1.我们将程序自身放入C://test.exe中,然后计算其hash散列值,最终得到CRC32 = 0x70122091,接着我们去找PE文件头,其结构中有很多空字节可以使用,我我们就选择PE头之前的最后4个字节作为替换位置。

2.接着就是如何定位并读出节表中是的数据了,读取数据可以这样写。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h> int main(int argc, char* argv[])
{
char szFileName[MAX_PATH] = { 0 };
char *pBuffer;
DWORD pNumberOfBytesRead;
int FileSize = 0; // 获取自身文件,并打开文件
GetModuleFileName(0, szFileName, MAX_PATH);
HANDLE hFile = CreateFile(szFileName, GENERIC_READ, 1, 0, 3, FILE_ATTRIBUTE_NORMAL, 0); // 为空则打开失败,退出
if (hFile == INVALID_HANDLE_VALUE) return FALSE; // 获取文件大小读入缓冲区
FileSize = GetFileSize(hFile, 0);
pBuffer = new char[FileSize];
ReadFile(hFile, pBuffer, FileSize, &pNumberOfBytesRead, 0);
CloseHandle(hFile); PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS32 pNtHeader = NULL; // 获取到DOS头数据
pDosHeader = (PIMAGE_DOS_HEADER)pBuffer; // 获取到NT头
pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew); // 定位到PE文件头前4字节处
DWORD OriginalCRC32 = *(DWORD *)((DWORD)pNtHeader - 4);
printf("读出节表值: %x \n", OriginalCRC32); system("pause");
return 0;
}

首先编译器生成以上代码片段,然后我们使用前面的CRC32计算工具计算出其hash散列值,CRC32 = 0x92e05c8a 将此地址,反写到程序中。

会发现,当我们尝试修改程序中的数据时,crc32散列值也会随之变化,也就是说我们动了程序crc32也就重新就算了,这好像是一个死结无法被解开,那么该如何解决这个问题呢?

我们只需要更改以下CRC32计算程序,让其跳过PE头前面的DOS头部分,不让其参与到计算中,即可解决这个冲突问题,由于DOS头没什么实际作用,跳过也无妨,将计算代码进行更改。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h> DWORD CRC32(BYTE* ptr, DWORD Size)
{
DWORD crcTable[256], crcTmp1; // 动态生成CRC-32表
for (int i = 0; i<256; i++)
{
crcTmp1 = i;
for (int j = 8; j>0; j--)
{
if (crcTmp1 & 1) crcTmp1 = (crcTmp1 >> 1) ^ 0xEDB88320L;
else crcTmp1 >>= 1;
}
crcTable[i] = crcTmp1;
}
// 计算CRC32值
DWORD crcTmp2 = 0xFFFFFFFF;
while (Size--)
{
crcTmp2 = ((crcTmp2 >> 8) & 0x00FFFFFF) ^ crcTable[(crcTmp2 ^ (*ptr)) & 0xFF];
ptr++;
}
return (crcTmp2 ^ 0xFFFFFFFF);
} BOOL CheckCRC32()
{
char szFileName[MAX_PATH] = { 0 }; char *pBuffer;
DWORD pNumberOfBytesRead;
int FileSize = 0; // 获取自身文件,并打开文件
GetModuleFileName(0, szFileName, MAX_PATH);
HANDLE hFile = CreateFile(szFileName, GENERIC_READ, 1, 0, 3, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE) return FALSE; FileSize = GetFileSize(hFile, 0);
pBuffer = new char[FileSize];
ReadFile(hFile, pBuffer, FileSize, &pNumberOfBytesRead, 0);
CloseHandle(hFile); PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS32 pNtHeader = NULL; pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
// 获取到NT头
pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew); // 定位到PE文件头前4字节处
DWORD OriginalCRC32 = *(DWORD *)((DWORD)pNtHeader - 4);
printf("读出节表值: %x \n", OriginalCRC32);
// 我们只需要计算PE结构的CRC32值,不需要计算DOS头
FileSize = FileSize - DWORD(pDosHeader->e_lfanew);
DWORD CheckCRC32 = CRC32((BYTE*)(pBuffer + pDosHeader->e_lfanew), FileSize);
printf("计算出 CRC32 = %x \n", CheckCRC32); if (CheckCRC32 == OriginalCRC32)
printf("程序没有被破解 \n");
else
printf("程序被破解 \n");
} int main(int argc, char* argv[])
{
CheckCRC32();
system("pause");
return 0;
}

编译程序,并记下 CRC32 = 86906a18 hash数值。

写入到文件中,即可实现磁盘文件的完整性检测,注意写入时应该是反写,且前面要补0.

在此次打开会提示程序没有被破解,当用户认为的修改指令时,就会提示已破解,无法继续运行下去。

如何破解: 如果目标磁盘文件进行了CRC32磁盘校验,我们该如何破解呢?思路差不多就是找到CRC32算号位置,然后观察其结果到底时与谁进行的比较,将指令取反,也可实现破解。

定位CRC32位置我们可以观察期算法特征,首先他会用到0xEDB88320L,0xFFFFFFFF,0x00FFFFFF这三个关键常数,我们可以将其作为识别条件的一部分。

其次CRC32会有一个256此的循环也可以作为识别条件,或者拦截ReadFile也可,因为计算之前必定会读取,也是一个思路。

将对比过程取反,同样可以过掉其磁盘CRC32的检测。

MapFileAndCheckSum 校验和: 通过使用系统提供的API实现反破解,该函数主要通过检测,PE可选头IMAGE_OPTIONAL_HEADER中的Checksum字段来实现的,一般的EXE默认为0而DLL中才会启用,当然你可以自己开启,让其支持这种检测.

#include <stdio.h>
#include <windows.h>
#include <Imagehlp.h>
#pragma comment(lib,"imagehlp.lib") int main(int argc,char *argv[])
{
DWORD HeadChksum = 1, Chksum = 0;
char text[512]; GetModuleFileName(GetModuleHandle(NULL), text, 512);
if (MapFileAndCheckSum(text, &HeadChksum, &Chksum) != CHECKSUM_SUCCESS)
return 0; if (HeadChksum != Chksum)
printf("文件校验和错误 \n");
else
printf("文件正常 \n"); system("pause");
return 0;
}

在编译上方代码之前,需要将编译器进行一定的设置,以确保支持校验和。

C/C++ -> 常规 -> 调试信息格式 --> 程序数据库

连接器 -> 常规 -> 启用增量链接 -> 否

连接器 -> 高级 -> 设置校验和 -> 是

启用校验和后,IMAGE_OPTIONAL_HEADER中的Checksum字段保存有该程序的hash数据。

磁盘校验还可以用于反脱壳,我们可以加壳后在壳子的PE结构中留下一些记号,当我们的程序被脱壳后程序中的判断语句将会起作用,从而让脱壳后的程序无法正常运行,也是一种思路。

C/C++ 使用CRC检测磁盘文件完整性的更多相关文章

  1. 7z常用命令行&7z检测压缩包完整性&7z压缩包错误不执行rsync同步

    7Z简介&常用命令 7Z脚本使用说明 7Z检测压缩包完整性脚本 7Z压缩包错误不执行Rsync脚本 1.7Z简介&常用命令 ⑴简介: 7z,全称7-Zip, 是一款开源软件.是目前公认 ...

  2. 反调试——7——CRC检测

    反调试--7--CRC检测 CRC32: CRC的全称是循环冗余校验,作用是为了检测数据的完整性. CRC32的检测原理: 程序被编译后,代码段是固定的,因为已经被写死了. 我们在调试程序的时候,打断 ...

  3. SHA256sum系列命令检测文件完整性

    1 sha256sum sha256sum是一个检测文件完整性的命令,一般下载的文件都会附带一个哈希值,使用sha256sum计算下载文件的哈希值再与目标哈希值比较即可确定文件是否完整,类似的命令还有 ...

  4. 反调试--CRC检测

    #include"CRC32.h" #include<Windows.h> #include<iostream> using namespace std; ...

  5. C#中Bitmap类 对图像の操作 可检测图片完整性

    try { Bitmap bm = new Bitmap(pics[ip]); BitmapToBytes(bm).Reverse().Take(2); } catch (Exception ex) ...

  6. 检测Xcode是否有问题

    之前的XCode中毒事件闹得沸沸扬扬,在网上找到检测XCode完整性的方式,有需要的小伙伴试试吧.忘记哪里转的了,愧对原创者 在终端输入 spctl 命令,并带上安装的 Xcode 的路径: spct ...

  7. OpenCV探索之路(二十七):皮肤检测技术

    好久没写博客了,因为最近都忙着赶项目和打比赛==| 好吧,今天我打算写一篇关于使用opencv做皮肤检测的技术总结.那首先列一些现在主流的皮肤检测的方法都有哪些: RGB color space Yc ...

  8. CRC检错技术原理

    一.题外话 说来惭愧,一开始是考虑写关于CRC检错技术更深层次数学原理的,然而在翻看<Basic Algebra>后,我果断放弃了这种不切实际的想法.个人觉得不是因为本人数学水平差或者能力 ...

  9. Modbus通讯错误检测方法

    标准的Modbus串行网络采用两种错误检测方法.奇偶校验对每个字符都可用,帧检测(LRC和CRC)应用于整个消息.它们都是在消息发送前由主设备产生的,从设备在接收过程中检测每个字符和整个消息帧. 用户 ...

  10. Redis-05持久化

    1 Redis持久化 RDB(Redis DataBase) AOF(Append Only File) 2 RBD 2.1 基本说明 在指定的时间间隔内将内存中的数据集快照写入磁盘文件,它恢复时将快 ...

随机推荐

  1. 电子签章Java后端与前端交互签名位置计算

    电子签章过程中存在着在网页上对签署文件进行预览.指定签署位置.文件签署等操作,由于图片在浏览器上的兼容性和友好性优于PDF文件,所以一般在网页上进行电子签章时,会先将PDF文件转换成图片,展示给用户. ...

  2. 面对科技公司的制裁,俄罗斯放出封印7年的神兽:RuTracker

    大家好,我是DD! 最近俄乌冲突引发的科技公司站队,Oracle.微软.三星等全球知名科技公司都开始对俄罗斯实施制裁与封锁.就连崇尚自由的开源社区GitHub也发文会严格限制俄罗斯获得维持其咄咄逼人的 ...

  3. 秒杀活动java怎么实现

    秒杀与其他业务最大的区别在于:秒杀的瞬间: (1)系统的并发量会非常的大 (2)并发量大的同时,网络的流量也会瞬间变大. 一个秒杀或者抢购页面,通常分为2个部分,一个是静态的HTML等内容,另一个就是 ...

  4. 你折腾一天都装不上的插件,函数计算部署 Stable Diffusion 都内置了

    在进行函数计算 Stable Diffusion 答疑的过程中,遇到很多同学在装一些插件的过程中遇到了难题,有一些需要安装一些依赖,有一些需要写一些代码,很多时候安装一个插件就能折腾几天,我们收集了很 ...

  5. 高级数据结构---B树和B+树及mysql索引分析

    mysql索引数据结构:https://www.cnblogs.com/nijunyang/p/11406688.html 几种树结构的对比: 二叉查找树:二叉搜索树,二叉排序树,优点查找快,但是在某 ...

  6. C#设计模式18——迭代器模式的写法

    是什么: 迭代器模式是一种行为型设计模式,它允许客户端通过一种统一的方式遍历集合对象中的元素,而无需暴露集合对象的内部结构. 为什么: 使用迭代器模式可以使得客户端程序与集合对象解耦,从而可以更加灵活 ...

  7. AI毕业设计生成器(基于AI大模型技术开发)

    这是一个辅助生成计算机毕业设计的工具,可以自动完成毕业设计的源码.它基于几百个github上面开源的java和python项目,运用tengsorflow技术,训练出了AI大模型.基本实现了计算机毕业 ...

  8. [转帖]深入浅出分析LSM树(日志结构合并树)

    https://zhuanlan.zhihu.com/p/415799237 ​ 目录 收起 零.前言 一.LSM树数据结构定义 二.插入操作 三.删除操作 四.修改操作 五.查询操作 六.合并操作 ...

  9. [转帖]ORACLE等待事件:enq: TX - row lock contention

    https://www.cnblogs.com/kerrycode/p/5887150.html enq: TX - row lock contention等待事件,这个是数据库里面一个比较常见的等待 ...

  10. [转帖]查看mysql分区名和各分区数据量

    – 查看mysql分区名和各分区数据量 SELECT table_name, partition_name, table_rows FROM information_schema.PARTITIONS ...