前言
上次我们已经简单介绍过了病毒特征码提取的基本方法,那么这次我们就通过编程来实现对于病毒的特征码查杀。
定义特征码存储结构
为了简单起见,这次我们使用的是setup.exe以及unpacked.exe这两个病毒样本。经过上次的分析,我们对setup.exe样本的特征码提取如下:
x2ax2ax2axcexe4x2axbaxbax2axc4xd0x2axc9xfax2axb8
xd0x2axc8xbex2axcfxc2x2axd4xd8x2axd5xdfx2ax2ax2a
为了方便起见,这里同时也将该特征码的文件偏移保存下来,即0x0c040。然后是unpacked.exe的特征码:
x13x8bx45xf0xe8x00x00x00x00x81x04x24xd7x86x00x00
xffxd0xebx11x6ax10x68x30x80x40x00xffx75xfcx53xff
它的文件偏移为0x1921。
有了以上的信息,就可以开始进行编程了。首先需要定义一个数据结构,用于保存特征码和文件偏移。该结构如下:
#define NAMELEN 20
#define SIGNLEN 32
typedef struct SIGN
{
char szVirusName[NAMELEN];
LONG lFileOffset;
BYTE bVirusSign[SIGNLEN + 1];
}_SIGN, *PSIGN;
利用该数据结构定义一个全局变量,该全局变量保存有上述两个病毒的特征码,定义如下:SIGN Sign[2] =
{
{
// setup.exe
“setup.exe”,
0x0c040,
“x2ax2ax2axcexe4x2axbaxbax2axc4xd0x2axc9xfax2axb8”
“xd0x2axc8xbex2axcfxc2x2axd4xd8x2axd5xdfx2ax2ax2a”
},
{
// unpacked.exe
“unpacked.exe”,
0x1920,
“x13x8bx45xf0xe8x00x00x00x00x81x04x24xd7x86x00x00”
“xffxd0xebx11x6ax10x68x30x80x40x00xffx75xfcx53xff”
}
};
至此,病毒特征码的基础定义部分就到这里。上述程序中,我是将病毒特征码保存在一个全局变量中,大家也可以另外创建一个文件,类似于PEiD的userdb.txt文件,从而专门保存病毒的特征码。现实中的杀毒软件的特征库也是以专门的文件的形式,保存在本地计算机中的。这里我为了简单起见,选择以全局变量的形式进行保存。
主体程序的编写
首先需要编写一个函数用于对目标程序指定位置处的十六进制代码进行检测:BOOL CheckSig(char* FilePath)
{
DWORD dwSigNum = 0;
DWORD dwNum = 0;
BYTE buffer[SIGNLEN+1];
int i;
HANDLE hFile = NULL;
hFile = CreateFile(FilePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
for(i=0; i <= 1; i++)
{
// 将待检测程序的文件指针指向特征码的偏移位置
SetFilePointer(hFile, Sign[i].lFileOffset, NULL, FILE_BEGIN);
// 读取目标程序指定偏移位置的特征码
ReadFile(hFile, buffer, sizeof(buffer), &dwNum, NULL);
// 特征码的比对
if(memcmp(Sign[i].bVirusSign, buffer, SIGNLEN) == 0)
{
printf("发现病毒程序:%s
", FilePath);
CloseHandle(hFile);
return TRUE;
}
}
CloseHandle(hFile);
return FALSE;
}
然后就是main函数的编写:int main()
{
WIN32_FIND_DATA stFindFile;
HANDLE hFindFile;
char *szFilter = "*.exe"; // 保存搜索的筛选条件(所有exe文件)
char szFindFile[MAX_PATH]; // 保存欲检测的程序的路径
char szSearch[MAX_PATH]; // 保存完整筛选路径
int ret = 0; // 搜索的返回值
lstrcpy(szFindFile, "E:\");
lstrcpy(szSearch, "E:\");
lstrcat(szSearch, szFilter);
hFindFile = FindFirstFile(szSearch, &stFindFile);
if(hFindFile != INVALID_HANDLE_VALUE)
{
do
{
// 组成完整的待检测程序的路径
lstrcat(szFindFile, stFindFile.cFileName);
// 利用特征码检测目标程序是不是病毒程序
if(!CheckSig(szFindFile))
{
printf("%s不是病毒程序
",szFindFile);
}
// 删除程序名称,只保留“E:”
szFindFile[3] = ' ';
ret = FindNextFile(hFindFile, &stFindFile);
}while( ret != 0 );
}
FindClose(hFindFile);
return 0;
}
上述程序仅仅是检测E盘根目录下所有后缀为exe的程序是否为病毒程序,其实还可以进行修改,使其可以全盘搜索,大家可以参考“熊猫烧香专杀工具”的相关代码部分。另外为了慎重起见,仅仅通过后缀进行exe程序的检测是不严谨的,常用的检测一个程序是不是exe程序的方法,就是解析目标程序中的相应位置是否为“MZ”以及“PE”。我这里为了简单起见,就不采用该方法,有兴趣的朋友可以自行编程实现。
程序的测试
这里我使用的是Code::Blocks13.12这款开源并且免费的开发环境,因为这款软件可以自动计算程序的执行时间,便于我们之后的对比操作。为了进行测试,我已经在E盘的根目录下放置了10个程序,其中4个程序是我们之前讲过的病毒样本,还有6个程序是我们以前也曾使用过的一些工具软件:
图1
上图中前方带有小方块的就是病毒样本。然后我们在Code::Blocks中编译运行程序:
图2
可见程序已经很成功地识别出了setup.exe以及unpacked.exe这两个病毒样本。事实上,cf.exe以及OSO.exe也是病毒程序,但由于我并没有把这两个样本的特征码加入我们程序的特征库,因此并没能识别出来。最后,Code::Blocks还显示出了本次程序的运行时间,当然每次的运行时间可能都不一样,包括在不同的计算机上运行的结果应该也是不同的。但是通过多次运行进行观察,基本上是0.016秒,也就是16毫秒。
与CRC32病毒识别方式的对比
我们以前的程序使用的CRC32算法来识别病毒,那么我们这里可以对比一下,看看这次我们所讲的方法和CRC32算法在程序运算时间上的优劣。CRC32病毒特征识别的程序如下:#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类型的CRC32值
//
DWORD CalcCRC32(char* FilePath)
{
HANDLE hFile = CreateFile(FilePath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Create Error");
return FALSE;
}
DWORD dwSize = GetFileSize(hFile,NULL);
if (dwSize == 0xFFFFFFFF)
{
printf("GetFileSize Error");
return FALSE;
}
BYTE *pFile = (BYTE*)malloc(dwSize);
if (pFile == NULL)
{
printf("malloc Error");
return FALSE;
}
DWORD dwNum = 0;
ReadFile(hFile,pFile,dwSize,&dwNum,NULL);
DWORD dwCrc32 = CRC32(pFile,dwSize);
if (pFile != NULL)
{
free(pFile);
pFile = NULL;
}
CloseHandle(hFile);
return dwCrc32;
}
int main()
{
WIN32_FIND_DATA stFindFile;
HANDLE hFindFile;
char *szFilter = "*.exe"; // 保存搜索的筛选条件(所有exe文件)
char szFindFile[MAX_PATH]; // 保存欲检测的程序的路径
char szSearch[MAX_PATH]; // 保存完整筛选路径
int ret = 0; // 搜索的返回值
lstrcpy(szFindFile, "E:\");
lstrcpy(szSearch, "E:\");
lstrcat(szSearch, szFilter);
DWORD dwTmpCRC32;
hFindFile = FindFirstFile(szSearch, &stFindFile);
if(hFindFile != INVALID_HANDLE_VALUE)
{
do
{
// 组成完整的待检测程序的路径
lstrcat(szFindFile, stFindFile.cFileName);
// 利用CRC32算法检测目标程序是不是病毒程序
dwTmpCRC32 = CalcCRC32(szFindFile);
// 匹配setup.exe的CRC32值
if(dwTmpCRC32 == 0x89240FCD)
{
printf("发现病毒程序:%s
",szFindFile);
}
// 匹配unpacked.exe的CRC32值
else if(dwTmpCRC32 == 0xC427A090)
{
printf("发现病毒程序:%s
",szFindFile);
}
else
{
printf("%s不是病毒程序
",szFindFile);
}
// 删除程序名称,只保留“C:”
szFindFile[3] = ' ';
ret = FindNextFile(hFindFile, &stFindFile);
}while( ret != 0 );
}
FindClose(hFindFile);
return 0;
}
相比而言,程序的主体部分还是基本一致的,只不过是对于病毒特征的验证方式稍有不同。因为关于CRC32算法我们已经在前几次的课程中运用过,所以这里不再赘述。看一下运行结果:
图3
可见,利用CRC32算法提取出来的病毒特征码的检测方式,在结果上与上一个程序是一样的,同样是发现了两个病毒,没有特征码的病毒就没能识别。然后再看一下用时,我的测试结果是0.063秒,也就是63毫秒,是之前的时间的3.9375倍,那么也就说明了,CRC32算法在效率上是不如传统的特征码查杀方式的。