5.2 磁盘CRC32完整性检测

CRC校验技术是用于检测数据传输或存储过程中是否出现了错误的一种方法,校验算法可以通过计算应用与数据的循环冗余校验(CRC)检验值来检测任何数据损坏。通过运用本校验技术我们可以实现对特定内存区域以及磁盘文件进行完整性检测,并以此来判定特定程序内存是否发生了变化,如果发生变化则拒绝执行,通过此种方法来保护内存或磁盘文件不会被非法篡改。总之,内存和磁盘中的校验技术都是用于确保数据和程序的完整性和安全性的重要技术。

磁盘CRC(循环冗余校验)用于检测磁盘数据的完整性,一般而言某些木马专杀工具同样会用到磁盘CRC特征校验技术,该技术的实现原理与内存验证原理完全一致,针对磁盘的验证同样很简单,但此处我们需要将计算到的CRC32值存储到PE文件自身中,通常我们可以存储到PE文件的前一个DWORD的位置上,程序运行后对比这个值,来判断程序是否被打过补丁,如果打过直接结束掉。

// 检查磁盘完整性
BOOL CalculateDiskCRC32()
{
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 TRUE;
}

// 取文件长度
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)
{
return FALSE;
}
else
{
return TRUE;
}
return TRUE;
}

int main(int argc, char* argv[])
{
BOOL ref = CalculateDiskCRC32();
if (ref == TRUE)
{
printf("[-] 程序已被修改 \n");
}
else
{
printf("[+] 程序正常 \n");
}

system("pause");
return 0;
}

首先读者运行上述程序,则程序会输出当前的CRC32值be63ac8b我们记下这个HASH值,如下图所示;

并将此值替换到如下图中的黄色位置,当程序运行后会读取该区域内的数据,并与动态计算的CRC32值进行计算,最终判断是否被修改,如下图所示;

通过CRC32数据对比并遍历磁盘文件,我们可以实现一个简单的特征定位查杀程序,用于专门定位某些特殊的程序,如下是修改后的代码片段;

// 计算文件CRC过程
BOOL CalcCRC32(char *FilePath)
{
// 打开文件
HANDLE hFile = CreateFile(FilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}

// 获取文件大小
DWORD dwSize = GetFileSize(hFile, NULL);
if (dwSize == 0xFFFFFFFF)
{
return FALSE;
}

// 分配内存
BYTE *pFile = (BYTE*)malloc(dwSize);
if (pFile == NULL)
{
return FALSE;
}

// 读取内存
DWORD dwNum = 0;
ReadFile(hFile, pFile, dwSize, &dwNum, NULL);

// 计算CRC32
DWORD dwCRC32 = CRC32(pFile, dwSize);
if (pFile != NULL)
{
free(pFile);
pFile = NULL;
}

CloseHandle(hFile);
return dwCRC32;
}

int main(int argc, char* argv[])
{
WIN32_FIND_DATA stFindFile;
HANDLE hFindFile;
char *szFilter = "*.exe"; // 筛选所有的.exe结尾的文件
char szFindFile[MAX_PATH]; // 保存欲检测程序的路径
char szSearch[MAX_PATH]; // 保存完整的筛选路径
int ret = 0; // 搜索状态返回值

lstrcpy(szFindFile, "D:\\"); // 搜索D盘目录下的所有exe结尾的文件
lstrcpy(szSearch, "D:\\");
lstrcat(szSearch, szFilter);
DWORD dwTmpCRC32;

hFindFile = FindFirstFile(szSearch, &stFindFile);
if (hFindFile != INVALID_HANDLE_VALUE)
{
do
{
lstrcat(szFindFile, stFindFile.cFileName);
dwTmpCRC32 = CalcCRC32(szFindFile);

// 比较判断
if (dwTmpCRC32 == 0xbe63ac8b)
{
printf("[*] CRC32 = %x 发现病毒 %s \n", dwTmpCRC32, szFindFile);
}
else
{
printf("[-] CRC32 = %x 正常程序 %s \n", dwTmpCRC32, szFindFile);
}
// 删除程序名称只保留"C:\"
szFindFile[3] = '\0';
ret = FindNextFile(hFindFile, &stFindFile);
} while (ret != 0);
}

FindClose(hFindFile);

system("pause");
return 0;
}

运行程序输出效果如下图所示;