如果已经处理到缓冲区的末尾
if ((i + SignatureCodeLength) >= size)
{
return;
}
int num = Next[MemoryData[i + SignatureCodeLength]];
// 如果特征码中有问号,从问号处开始匹配
if (num == -1)
{
// 如果特征码有问号,就从问号处开始匹配,如果没有就 i += -1
i += (SignatureCodeLength - Next[256]);
}
else
{
// 否则从匹配失败的位置开始
i += (SignatureCodeLength - num);
}
}
}
6.2.3 搜索整块内存区域
SearchMemory函数,该函数用于在指定进程的内存空间中搜索给定特征码的内存块,并把搜索到的内存地址存入结果数组中。函数为一层循环枚举给定的内存块,内部则调用SearchMemoryBlock
函数进行内存块搜索。其中,参数hProcess
为指向要搜索内存块所在进程的句柄,SignatureCode
为给定特征码的字符串指针,StartAddress
为搜索的起始地址,EndAddress
为搜索的结束地址,InitSize
为搜索结果数组初始空间大小,ResultArray
为存储搜索结果的数组引用。
该函数首先通过调用VirtualQueryEx
函数获取可读可写和可读可写可执行的内存块信息,并遍历每个内存块,对内存块进行搜索。之所以不直接搜索整个内存区域,是因为那样可以减少非必要的搜索,提高效率。
内存块的搜索通过调用SearchMemoryBlock
函数实现。搜索采用了KMP
算法,先通过GetNextArray
函数和GetSignatureCodeArray
函数将特征码转换为对应的变量,再对每个内存块逐个匹配,在匹配过程中若找到与特征码中的字节码不匹配的字节,就根据Next数组记录的回溯位置从失配的位置开始重新匹配,以降低匹配的时间复杂度。在内存块搜索过程中,若匹配成功,则将特征码匹配的起始地址存入结果数组中,最终函数返回结果数组大小。
// 实现搜索整个程序
int SearchMemory(HANDLE hProcess, char* SignatureCode, unsigned __int64 StartAddress, unsigned __int64 EndAddress, int InitSize, vector<unsigned __int64>& ResultArray)
{
int i = 0;
unsigned long BlockSize;
MEMORY_BASIC_INFORMATION mbi;
WORD SignatureCodeLength = strlen(SignatureCode) / 3 + 1;
WORD* SignatureCodeArray = new WORD[SignatureCodeLength];
// 实现特征码字符串与数组转换
GetSignatureCodeArray(SignatureCode, SignatureCodeArray);
GetNextArray(Next, SignatureCodeArray, SignatureCodeLength);
// 初始化结果数组
ResultArray.clear();
ResultArray.reserve(InitSize);
// 查询内存属性并循环
while (VirtualQueryEx(hProcess, (LPCVOID)StartAddress, &mbi, sizeof(mbi)) != 0)
{
// 判断并获取具有PAGE_READWRITE读写,或者PAGE_EXECUTE_READWRITE读写执行权限的内存
if (mbi.Protect == PAGE_READWRITE || mbi.Protect == PAGE_EXECUTE_READWRITE)
{
i = 0;
// 得到当前块长度
BlockSize = mbi.RegionSize;
// 搜索这块内存
while (BlockSize >= BLOCKMAXSIZE)
{
// 调用内存块搜索功能依次搜索内存
SearchMemoryBlock(hProcess, SignatureCodeArray, SignatureCodeLength, StartAddress + (BLOCKMAXSIZE * i), BLOCKMAXSIZE, ResultArray);
BlockSize -= BLOCKMAXSIZE;
i++;
}
SearchMemoryBlock(hProcess, SignatureCodeArray, SignatureCodeLength, StartAddress + (BLOCKMAXSIZE * i), BlockSize, ResultArray);
}
// 开始地址增加下一块长度继续搜索
StartAddress += mbi.RegionSize;
if (EndAddress != 0 && StartAddress > EndAddress)
{
return ResultArray.size();
}
}
// 释放特征码数组并返回搜索计数器
free(SignatureCodeArray);
return ResultArray.size();
}
将上述代码理解后读者可以自行使用
int main(int argc, char *argv[])
{
// 通过进程名获取进程PID号
DWORD Pid = GetPidByName("PlantsVsZombies.exe");
printf("[*] 获取进程PID = %d \n", Pid);
// 初始化MemoryData大小
MemoryData = new BYTE[BLOCKMAXSIZE];
// 存储搜索返回值
vector<unsigned __int64> ResultArray;
// 通过进程ID获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
// 开始搜索
// 搜索特征码 FF 25 ?? 从0x0000000到0xFFFFFFF 初始长度为3 返回值放入ResultArray
SearchMemory(hProcess, "FF 25 ??", 0x0000000, 0xFFFFFFF, 3, ResultArray);
// 输出结果
for (vector<unsigned __int64>::iterator it = ResultArray.begin(); it != ResultArray.end(); it++)
{
printf("0x%08X \n", *it);
}
system("pause");
return 0;
}
编译并运行上述程序片段,则会枚举hProcess
进程内特征码时FF 25 ??
的片段,枚举位置为0x0000000-0xFFFFFFF
枚举长度为3个特征,最终将枚举结果输出到ResultArray
数组内,输出效果图如下所示;
本文作