前言
? ? ? ?上次我們已經簡介過了病毒特征碼提取的基本方法,那么這次我們就通過編程來實現對于病毒的特征碼查殺。
?
定義特征碼存儲結構
? ? ? ?為了簡單起見。這次我們使用的是setup.exe以及unpacked.exe這兩個病毒樣本。經過上次的分析,我們對setup.exe樣本的特征碼提取例如以下:
\x2a\x2a\x2a\xce\xe4\x2a\xba\xba\x2a\xc4\xd0\x2a\xc9\xfa\x2a\xb8
\xd0\x2a\xc8\xbe\x2a\xcf\xc2\x2a\xd4\xd8\x2a\xd5\xdf\x2a\x2a\x2a
? ? ? ?為了方便起見,這里同一時候也將該特征碼的文件偏移保存下來。即0x0c040。
然后是unpacked.exe的特征碼:
\x13\x8b\x45\xf0\xe8\x00\x00\x00\x00\x81\x04\x24\xd7\x86\x00\x00
\xff\xd0\xeb\x11\x6a\x10\x68\x30\x80\x40\x00\xff\x75\xfc\x53\xff
? ? ? ?它的文件偏移為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,“\x2a\x2a\x2a\xce\xe4\x2a\xba\xba\x2a\xc4\xd0\x2a\xc9\xfa\x2a\xb8” \“\xd0\x2a\xc8\xbe\x2a\xcf\xc2\x2a\xd4\xd8\x2a\xd5\xdf\x2a\x2a\x2a”
},
{// unpacked.exe“unpacked.exe”,0x1920,“\x13\x8b\x45\xf0\xe8\x00\x00\x00\x00\x81\x04\x24\xd7\x86\x00\x00” \“\xff\xd0\xeb\x11\x6a\x10\x68\x30\x80\x40\x00\xff\x75\xfc\x53\xff”
}
};
? ? ? ?至此。病毒特征碼的基礎定義部分就到這里。
上述程序中,我是將病毒特征碼保存在一個全局變量中,大家也能夠另外創建一個文件。相似于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\n", 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不是病毒程序\n",szFindFile);}// 刪除程序名稱,僅僅保留“E:\”szFindFile[3] = '\0';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\n",szFindFile);}// 匹配unpacked.exe的CRC32值else if(dwTmpCRC32 == 0xC427A090){printf("發現病毒程序:%s\n",szFindFile);}else{printf("%s不是病毒程序\n",szFindFile);}// 刪除程序名稱,僅僅保留“C:\”szFindFile[3] = '\0';ret = FindNextFile(hFindFile, &stFindFile);}while( ret != 0 );}FindClose(hFindFile);return 0;
}
? ? ? ?相比而言,程序的主體部分還是基本一致的,僅僅只是是對于病毒特征的驗證方式稍有不同。由于關于CRC32算法我們已經在前幾次的課程中運用過,所以這里不再贅述。看一下執行結果:
圖3
? ? ? ?可見,利用CRC32算法提取出來的病毒特征碼的檢測方式,在結果上與上一個程序是一樣的,相同是發現了兩個病毒,沒有特征碼的病毒就沒能識別。
然后再看一下用時,我的測試結果是0.063秒,也就是63毫秒,是之前的時間的3.9375倍,那么也就說明了,CRC32算法在效率上是不如傳統的特征碼查殺方式的。
?
小結
? ? ? ?本文討論了病毒特征碼查殺的編程實現。并與CRC32算法在效率上進行了對照。由于我們僅僅有兩個特征碼,為了便于課程的解說。我採用的是直接利用if…else語句進行特征碼的對照。
假設病毒的特征碼的數量非常龐大,那么有多少特征碼就須要使用多少個if語句,那么這顯然是非常沒有效率的。能否夠利用一定的算法來優化大量的特征碼的比對工作,不是我們討論的重點。有興趣的朋友能夠研究一下。