前面的那一篇文章中所使用的技术只能有效抵抗解密者直接修改硬盘文件,当我们使用动态补丁的时候,那么内存中同样不存在校验效果,也就无法抵御对方动态修改机器码了,为了防止解密者直接对内存打补丁,我们需要在硬盘校验的基础上,增加内存校验,防止动态补丁的运用。
仅对.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);
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; 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;
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; } DWORD crcTmp2 = 0xFFFFFFFF; while (Size--) { crcTmp2 = ((crcTmp2 >> 8) & 0x00FFFFFF) ^ crcTable[(crcTmp2 ^ (*ptr)) & 0xFF]; ptr++; } return (crcTmp2 ^ 0xFFFFFFFF); }
DWORD CheckMemory() { 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);
pSecHeader = IMAGE_FIRST_SECTION(pNtHeader); DWORD va_base = ImageBase + pSecHeader->VirtualAddress; DWORD sec_len = pSecHeader->Misc.VirtualSize;
DWORD CheckCRC32 = CRC32((BYTE*)(va_base), sec_len); 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;
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; } DWORD crcTmp2 = 0xFFFFFFFF; while (Size--) { crcTmp2 = ((crcTmp2 >> 8) & 0x00FFFFFF) ^ crcTable[(crcTmp2 ^ (*ptr)) & 0xFF]; ptr++; } return (crcTmp2 ^ 0xFFFFFFFF); }
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[]) { 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; }
|

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