前面的那一篇文章中所使用的技术只能有效抵抗解密者直接修改硬盘文件,当我们使用动态补丁的时候,那么内存中同样不存在校验效果,也就无法抵御对方动态修改机器码了,为了防止解密者直接对内存打补丁,我们需要在硬盘校验的基础上,增加内存校验,防止动态补丁的运用。

仅对.text代码段进行校验:

通常程序中至少包括了代码段,数据段,而数据段中所存储的数据是经常会发生变动的,例如我们的全局变量,静态变量等都会默认存储在数据段,而代码段则不会发生变化,我们在检验时只需要注重.text内存段中的数据完整性即可,针对内存的校验同样可以抵御调试器的CC断点,该断点原理就是在下端处写入int3指令,同样可以检测得到。

校验思路如下

1.首先从内存得到PE的代码节的RVA和节大小

2.根据得到的RVA和节大小计算出crc32或是RC4值

3.读取自身保存的原始CRC32值,与校验结果进行比较

1.先来实现第一步,读取内存映像的起始地址与大小,我们可以这样做。

#include <stdio.h>
#include <windows.h> int main(int argc, char *argv[])
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_SECTION_HEADER pSecHeader = NULL;
DWORD ImageBase; // 获取基地址
ImageBase = (DWORD)GetModuleHandle(NULL); // 定位到PE头结构
pDosHeader = (PIMAGE_DOS_HEADER)ImageBase; // 定位到NT头
pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew); // 定位第一个区块地址,因为默认的话第一个就是.text节
pSecHeader = IMAGE_FIRST_SECTION(pNtHeader); // 取出节内偏移与节表长度
DWORD va_base = ImageBase + pSecHeader->VirtualAddress; // 定位代码节va基地址
DWORD sec_len = pSecHeader->Misc.VirtualSize; // 获取代码节长度 printf("镜像基址(.text): %x --> 镜像大小: %x \n", va_base, sec_len); system("pause");
return 0;
}

2.第二部就是计算校验和,然后计算该节的CRC32值,并存入全局变量,也就是程序打开后自动初始化计算一次内存crc32值并放入全局变量中,然后开一个线程,每三秒检测一次内存变化,如果变化则终止执行或弹窗提示,你也可以提前计算处校验和并写入PE空缺位置。

#include <stdio.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);
} // 检查内存中CRC32特征值
DWORD CheckMemory()
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_SECTION_HEADER pSecHeader = NULL;
DWORD ImageBase; // 获取基地址
ImageBase = (DWORD)GetModuleHandle(NULL); // 定位到PE头结构
pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;
pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew); pSecHeader = IMAGE_FIRST_SECTION(pNtHeader);
DWORD va_base = ImageBase + pSecHeader->VirtualAddress; // 定位代码节va基地址
DWORD sec_len = pSecHeader->Misc.VirtualSize; // 获取代码节长度 DWORD CheckCRC32 = CRC32((BYTE*)(va_base), sec_len);
// printf(".text节CRC32 = %x \n", CheckCRC32);
return CheckCRC32;
} int main(int argc,char *argv[])
{
DWORD OriginalCRC32 = 0;
OriginalCRC32 = CheckMemory(); while (1)
{
Sleep(3000);
DWORD NewCRC32 = CheckMemory();
if (OriginalCRC32 == NewCRC32)
printf("程序没有被打补丁. \n");
else
printf("程序被打补丁 \n");
} system("pause");
return 0;
}

上方代码是保护了整个程序,在实际应用中,为了提高效率,有时我们只需要保护其中一个片段代码就好,这样可以提高效率,所有我们对上面代码稍作修改即可实现针对特定片段的内存校验。

#include <stdio.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);
} // 检查内存中CRC32特征值
DWORD CheckMemory(DWORD va_base, DWORD sec_len)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeader = NULL;
PIMAGE_SECTION_HEADER pSecHeader = NULL;
DWORD ImageBase;
ImageBase = (DWORD)GetModuleHandle(NULL);
pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;
pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew); DWORD CheckCRC32 = CRC32((BYTE*)(va_base), sec_len);
return CheckCRC32;
} int main(int argc, char *argv[])
{
// 用于保存初始化时 .text 节中的CRC32值
DWORD OriginalCRC32 = 0; DWORD begin_addr, end_addr, size;
// 获取到两个位置的偏移地址
__asm mov begin_addr, offset begin;
__asm mov end_addr, offset end; // 计算出 两者内存差值
size = end_addr - begin_addr; // 校验指定内存位置
OriginalCRC32 = CheckMemory(begin_addr, size); while (1)
{
begin: // 标记为需要保护的区域
printf("hello lyshark \n");
printf("hello lyshark \n");
printf("hello lyshark \n");
end: // 保护区域声明结束 if (OriginalCRC32 == CheckMemory(begin_addr, size))
printf("此区域没有被破解 \n");
else
printf("此区域已被修改\n"); Sleep(3000);
}
system("pause");
return 0;
}

通过使用磁盘校验结合内存校验两种方式综合保护,可以极大的提高软件的安全性,绕过方式则是找到哪儿跟全局变量将其修正为正确的值即可,同样的也可以更暴力一些直接将判断条件改掉均可。

C/C++ 使用CRC检测内存映像完整性的更多相关文章

  1. Android性能优化之利用LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...

  2. 使用新版Android Studio检测内存泄露和性能

    内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁于千里之堤的蚁穴.  怎么才能检测内存泄露呢?网上教程非常多,不过很多都是使用Eclipse检测的, 其实1.3版本以后的 ...

  3. Android DDMS检测内存泄露

    Android DDMS检测内存泄露 DDMS是Android开发包中自带工具,可以测试app性能,用于发现内存问题. 1.环境搭建 参考之前发的Android测试环境搭建相关文章,这里不再复述: 2 ...

  4. 检测内存泄露:Instruments中的Leaks

    前言 如果要检测内存泄露,我们会使用Xcode7自带的Instruments中的Leaks工具来检测. 现在的开发环境是ARC,所以很少会出现内存泄漏的情况. 不过我们一定要养好码代码的规范性. 例如 ...

  5. 使用Visual Leak Detector检测内存泄漏[转]

      1.初识Visual Leak Detector 灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题.当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题 ...

  6. monkey检测内存泄漏

    monkey中检查内存泄漏,实际上是对一个操作多次操作后看内存情况,内存泄漏具体的原理可百度,现在我们梳理检测内存泄漏的方法: 测试前你需要安装: 1.MAT分析工具 2.使用工具事实监控内存指标,现 ...

  7. VS2005 检测内存泄漏的方法(转载)

    一.非MFC程序可以用以下方法检测内存泄露: 1.程序开始包含如下定义: #ifdef _DEBUG #define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __F ...

  8. Qt应用中检测内存泄露——VLD

    本文简要描述一下在Qt应用中使用VLD来检测内存泄露.本次测试环境:QtCreator2.3 + Qt4.7.4-vs2008 + VS2008 Express. 1.下载并安装:VLD-2.2: h ...

  9. Qt creator 搭配 valgrind 检测内存泄漏

    继上次重载operator new检测内存泄漏失败之后,妥协了.决定不管是否是准确指明哪一行代码出现内存泄漏,只要告诉我是否有泄漏就行了,这样就没有new替换的问题.在开发中,总是一个个小功能的开发. ...

  10. 重载operator new实现检测内存泄漏是否可行

    行与不行,就凭我这水平,说出来未免显示太过自大.不还,我还想根据自己的代码来讨论这个问题. 重载operator new来检测内存只的办法,那就是在new的时候记录指针地址及文件名.行号,在delet ...

随机推荐

  1. 【Vue】大总结

    目录 vue大回顾 模板语法处理xss攻击 Vue单页面组件 ts泛型 sass\less\css的区别 ...toRef() defineEmits 练习 根据分数显示颜色 vue大回顾 1 前端发 ...

  2. 【JAVA基础】数值处理

    #BigDecimal处理 ##保留两位小数 https://www.cnblogs.com/jpfss/p/8072379.html /** * 保留两位小数 */ @org.junit.Test ...

  3. C++实现简单的日期正则表达式

    简单的日期正则表达式 一个简单的日期解析程序,从yyyy-mm-dd格式的日期字符串中,分别获取年月日. 先设置一个简单的正则表达式,4位数字的"年",1-2位数字的"月 ...

  4. Java面试——数据库知识点

    MySQL 1.建 主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合.一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null). 超键:在关系中能唯一标识元组的属性 ...

  5. Proxifier 2023年11月时最新版 激活教程

    前言 Proxifier 是一款功能非常强大的socks5客户端,可以让不支持通过代理服务器工作的网络程序能通过HTTPS或SOCKS代理或代理链.支持64位系统支持Xp,Vista,Win7,支持s ...

  6. Blazor开发小游戏?趁热打铁上!!!

    大家好,我是沙漠尽头的狼. 网站使用Blazor重构上线一天了,用Blazor开发是真便捷,空闲时间查查gpt和github,又上线一个 正则表达式在线验证工具 和几个在线小游戏,比如 井字棋游戏.扫 ...

  7. 14-Verilog for Verification

    Verilog for Verification 1.引言 Testbench也是一个模块(module...endmodule) Testbench没有输入和输出,因为它是一个闭环,自己产生激励,灌 ...

  8. 文心一言 VS 讯飞星火 VS chatgpt (185)-- 算法导论14.1 2题

    二.用go语言,对于图 14-1中的红黑树 T 和关键字 x.key 为35的结点x,说明执行 OS-RANK(T,x) 的过程. 文心一言: 在红黑树中,OS-RANK(T, x) 是一个操作,用于 ...

  9. Go-数据类型-数字

    Go数字类型 整数 int 类 int(在32机器上为int32,在64位机器上为int64) int8 int16 int32 int64 unit类 uint(在32机器上为uint32,在64位 ...

  10. 使用requests爬虫遇到的一个奇葩的问题:UnicodeEncodeError: 'latin-1' codec can't encode character

    每一位成功的程序员,背后也许都站着无数的秃头的男人--为其提供各种开发工具&代码库,当然也包括-- 各种玄学bug-- 玄学的开端 最近在用Python做一个爬虫项目的时候遇到一个很奇怪的问题 ...