前面的那一篇文章中所使用的技術只能有效抵抗解密者直接修改硬盤文件,當我們使用動態補丁的時候,那么內存中同樣不存在校驗效果,也就無法抵御對方動態修改機器碼了,為了防止解密者直接對內存打補丁,我們需要在硬盤校驗的基礎上,增加內存校驗,防止動態補丁的運用。
僅對.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;
}
通過使用磁盤校驗結合內存校驗兩種方式綜合保護,可以極大的提高軟件的安全性,繞過方式則是找到哪兒跟全局變量將其修正為正確的值即可,同樣的也可以更暴力一些直接將判斷條件改掉均可。