好久没写博客了 今天好累 休息一下 想起来写个博客 (未加壳文件)
最近在做PE文件的特征码扫描 刚开始的时候一头雾水 因为对PE文件的格式不是很了解 之前虽然看过一些PE文件的帖子 但是都是看不下去 现在针对这几天的努力 贴上我对PE文件特征码扫描的一些见解 方法和代码 本文不考虑后面的节点以及节点格式和内容
1、PE文件特征码扫描
a). 读文件 判断是否是PE格式的文件
读文件,文件的开始是DOS头 IMAGE_DOS_HEADER 这个结构体很长 想详细了解就去百度 (http://blog.csdn.net/pxm2525/article/details/39895487#) 有用的就两个字段。该结构体中,有两个字段需要注意,分别是第一个字段 e_magic,和最后一个字段 e_lfanew字段。第一个字段e_magic就是Dos 头,两个字节 0x5a4d, 第二个字段e_lfanew是跳转到 IMAGE_NT_HEADERS 结构体的偏移地址。IMAGE_NT_HEADERS 包括三部分,(1)PE头,四个字节0x00004550 ; (2)Image_File_Header(0x14h大小);(3)Image_Optinoal_Header(0xe0h)
判断是否是标准的PE文件就是判断DOS头中的e_magic 是否为0x5a4d 以及 e_magic偏移的字节在读取四个字节是否为0x00004550 还有一些是否为.exe或者是否为.dll的一些判断 这个判断在IMAGE_FILE_HEADER 中的Characteristics字节 如果是 IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 如果是IMAGE_FILE_DLL (0x2000)就是.dll
b). 获取到代码段的偏移以及大小 定位到文件的代码段
获取代码段的偏移地址和大小是在Image_Optinoal_Header中的baseofcode 字段,大小在baseofcode字段。
下面是我写的一些片段代码
// lpdata 是文件的内容buffer
lpmImageDosHeader = (IMAGE_DOS_HEADER*)lpData;
lpmImageNtHeaders = (IMAGE_NT_HEADERS*)((SIZE_T)lpData + lpmImageDosHeader->e_lfanew);
dwBaseOfCode = lpmImageNtHeaders->OptionalHeader.BaseOfCode;
dwSizeOfCode = lpmImageNtHeaders->OptionalHeader.SizeOfCode;
// 获取到代码段的大小以后,偏移到代码段
BYTE* bByte = NULL;
// 跳转到代码段
bByte = (BYTE*)((SIZE_T)lpData + dwBaseOfCode);
c). 扫描代码段
扫描代码段的时候,需要在外部给出一些要扫描的特征码 我自己写个按照字节一个个比较的,然后这个方法经理看了以后觉得比较low 然后就建议我用这个方法:
首先读取前四个特征码的的值 (int ulog)都行 然后跟代码段的比较 一次比较四个字节 但是每次比较完之后偏移一个字节 如果前四个字节相等的话 下面我做了特征码的哈希值,如果哈希值也相等的话,就说明在代码段匹配到特征码
说是这个方法简单一些 ,下面是获取哈希值的一些代码 (水平有限,高手略过)
// 获取一段字节的hash值
BOOL GetHashValue(BYTE* lpByte, ULONG uSize, WCHAR* wszHashValue)
{
if ((lpByte == NULL) || (uSize == 0))
{
return FALSE;
}
HCRYPTPROV hProv = NULL;
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) // 获得CSP中一个密钥容器的句柄
{
return FALSE;
}
HCRYPTPROV hHash = NULL;
// 初始化对数据流的hash,创建并返回一个与CSP的hash对象相关的句柄。这个句柄接下来将被CryptHashData调用。
if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash) == FALSE)
{
CryptReleaseContext(hProv, 0);
return FALSE;
}
if (CryptHashData(hHash, lpByte, uSize, 0) == FALSE) // hash文件
{
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
return FALSE;
}
DWORD dwHashLen = sizeof(DWORD);
if (!CryptGetHashParam(hHash, HP_HASHVAL, NULL, &dwHashLen, 0)) //参照msdn
{
CryptReleaseContext(hProv, 0);
CryptDestroyHash(hHash);
return FALSE;
}
BYTE *pbHash = NULL;
pbHash = (byte*)malloc(dwHashLen);
if (CryptGetHashParam(hHash, HP_HASHVAL, pbHash, &dwHashLen, 0))//获得SHA1值
{
DWORD i = 0;
for (i = 0; i < dwHashLen; i++) // 输出SHA1值
{
swprintf_s(wszHashValue + 2 * i, 4, L"%02x", pbHash[i]);
}
wszHashValue[2 * i] = '\0';
}
free(pbHash);
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return TRUE;
}
一些字节比较的代码我就不贴了 简单的一逼
二、 进程的PE特征码扫描
进程这个特征码扫描跟文件的差不多,只不过是其他进程的一些地址的获取或者偏移等。在这里今天犯了一个特别蠢的问题,在自己的进程获取到要扫描的进程的加载地址的时候, 我在VS上用alt+6 调出来内存窗口 添加地址 回车 发现全是 cc cc cc cc.... 我就无语了 难道我取错了吗 还是怎么搞得 。最后同事说进程在不同的空间 我才恍然大悟 真是醉了....
a) . 遍历所有进程 拿到pid 注意提权
CreateToolhelp32Snapshot Process32First
while (Process32Next(hProcessSnap, &ProcessInfoFirst))
{
if (FALSE == ScanSignalProcess(ProcessInfoFirst.th32ProcessID, stThreadParasPro.lpScanBytes, stThreadParasPro.uScanSize))
{
CloseHandle(hProcessSnap);
return FALSE;
}
}
b). 根据Pid,获取进程的加载的首地址
CreateToolhelp32Snapshot 代码正在调试 做个参考吧
PVOID GetModulBaseAddr(DWORD dwProcessID, PVOID pvModuleRemote)
{
if (pvModuleRemote == NULL)
{
return NULL;
}
PVOID pvBaseAddr = NULL;
IMAGE_DOS_HEADER dosHdr;
IMAGE_NT_HEADERS ntHdr;
Toolhelp32ReadProcessMemory(dwProcessID, pvModuleRemote, &dosHdr, sizeof(dosHdr), NULL);
if (dosHdr.e_magic == IMAGE_DOS_SIGNATURE)
{
Toolhelp32ReadProcessMemory(dwProcessID, (PBYTE)pvModuleRemote + dosHdr.e_lfanew, &ntHdr, sizeof(ntHdr), NULL);
if (ntHdr.Signature == IMAGE_NT_SIGNATURE)
{
pvBaseAddr = (PVOID)ntHdr.OptionalHeader.ImageBase;
}
}
return pvBaseAddr;
}
BOOL GetSignalProcessStarAddr(IN ULONG Pid, IN OUT DWORD& dwStarAddr)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, Pid);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
CloseHandle(hSnapshot);
return -1;
}
MODULEENTRY32 me;
ZeroMemory(&me, sizeof(MODULEENTRY32));
me.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(hSnapshot,&me))
{
return FALSE;
}
// 获取基址
PVOID pAddr = GetModulBaseAddr(Pid, me.modBaseAddr);
if (pAddr == NULL)
{
return FALSE;
}
if (pAddr == me.modBaseAddr)
{
printf("%08x ", (DWORD)pAddr);
dwStarAddr = (DWORD)pAddr;
}
else
{
printf("%08x ", (DWORD)me.modBaseAddr);
dwStarAddr = (DWORD)me.modBaseAddr;
}
CloseHandle(hSnapshot);
return TRUE;
}
PVOID GetModulBaseAddr(DWORD dwProcessID, PVOID pvModuleRemote)
{
if (pvModuleRemote == NULL)
{
return NULL;
}
PVOID pvBaseAddr = NULL;
IMAGE_DOS_HEADER dosHdr;
IMAGE_NT_HEADERS ntHdr;
Toolhelp32ReadProcessMemory(dwProcessID, pvModuleRemote, &dosHdr, sizeof(dosHdr), NULL);
if (dosHdr.e_magic == IMAGE_DOS_SIGNATURE)
{
Toolhelp32ReadProcessMemory(dwProcessID, (PBYTE)pvModuleRemote + dosHdr.e_lfanew, &ntHdr, sizeof(ntHdr), NULL);
if (ntHdr.Signature == IMAGE_NT_SIGNATURE)
{
pvBaseAddr = (PVOID)ntHdr.OptionalHeader.ImageBase;
}
}
return pvBaseAddr;
}
c). readprocessmemory或者Toolhelp32ReadProcessMemory 读取指定进程的PE结构信息 (IMAGE_DOS_HEADER IMAGE_NT_HEADERS 等 )
d). 获取到指定进程的代码段数据到本地
这个就不说了 直接读取指定进程内容到本地
e). 和文件扫描一样进行特征码的匹配扫描
这个就更不说了
附件:PE格式表