进程PID号
DWORD Pid = GetPidByName("PlantsVsZombies.exe");
printf("[*] 获取进程PID = %d \n", Pid);
// 开始搜索特征码
unsigned char ScanOpCode[3] = { 0x56, 0x57, 0x33 };
// 依次传入开始地址,结束地址,特征码,以及特征码长度
ULONG Address = ScanMemorySignatureCode(Pid, 0x401000, 0x7FFFFFFF, ScanOpCode, 3);
printf("[*] 找到内存地址 = 0x%x \n", Address);
system("pause");
return 0;
}
上述程序运行后,将枚举当前进程0x401000-0x7FFFFFFF
区域中特征码为0x56, 0x57, 0x33
的内存地址,枚举到以后则输出该内存地址的位置,输出效果图如下图所示;
有了上面的模板我们只需要在此基础之上增加KMP枚举方法即可实现,如下代码则是替换具有KMP功能的搜索模式,在代码中可看出我们仅仅只是将ScanMemorySignatureCode
函数内部的memcmp
函数替换为了KMPSearchString
函数,其他位置并没有任何变化,此处主要增加的函数有GetNextval
以及KMPSearchString
,这两个函数的核心思想是利用KMP
算法,在主字符串中寻找子字符串时,遇到匹配失败的字符时,能够跳过一些已经比较过的字符,重复利用部分匹配的结果,提高字符串匹配的效率。将子串的每个字符失配时应该跳转的位置通过GetNextval
函数计算得出,然后在KMPSearchString
函数中通过这个数组进行跳转和匹配。该算法的时间复杂度为O(m+n)
,其中m
和n
分别表示主串和模式串的长度。
#include <iostream>
#include <windows.h>
#include <tlhelp32.h>
using namespace std;
// 根据进程名得到进程PID
DWORD GetPidByName(const char* name)
{
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
DWORD pid = 0;
if (Process32First(snapshot, &pe32))
{
do
{
if (_stricmp(pe32.szExeFile, name) == 0)
{
pid = pe32.th32ProcessID;
break;
}
} while (Process32Next(snapshot, &pe32));
}
CloseHandle(snapshot);
return pid;
}
/*
* P 为模式串,下标从 0 开始。
* nextval 数组是模式串 SubString 中每个字符失配时应该回溯的位置。
*/
void GetNextval(string SubString, int nextval[])
{
int SubStringLen = SubString.size(); // 计算模式串的长度
int i = 0; // 子串的指针
int j = -1; // 前缀的指针
nextval[0] = -1; // 初始化 nextval 数组,将第一个值设为 -1
while (i < SubStringLen - 1)
{
if (j == -1 || SubString[i] == SubString[j]) // 如果子串和前缀相等,或 j==-1
{
i++; j++; // 子串指针和前缀指针分别加一
if (SubString[i] != SubString[j]) // 如果下一个字符不相等
{
nextval[i] = j; // 将前缀指针 j 的值赋给 nextval 数组中的当前位置 i
}
else // 如果下一个字符相等
{
nextval[i] = nextval[j]; // 已经有 nextval[j],所以将它赋给 nextval[i]
}
}
else // 如果子串和前缀不相等
{
j = nextval[j]; // 更新前缀指针 j 的值,指向 nextval[j]
}
}
}
/* 在 MainString 中找到 SubString 第一次出现的位置 下标从0开始*/
int KMPSearchString(string MainString, string SubString, int next[])
{
GetNextval(SubString, next);
int MainStringIndex = 0; // 存储主字符串下标
int SubStringIndex = 0; // 存储子字符串下标
int MainStringLen = MainString.size(); // 主字符串大小
int SubStringLen = SubString.size(); // 子字符串大小
// 循环遍历字符串,因为末尾 '\0' 的存在,所以不会越界
while (MainStringIndex < MainStringLen && SubStringIndex < SubStringLen)
{
// MainString 的第一个字符不匹配或 MainString[] == SubString[]
if (SubStringIndex == -1 || MainString[MainStringIndex] == SubString[SubStringIndex])
{
MainStringIndex++; SubStringIndex++;
}
else // 当字符串匹配失败则跳转
{
SubStringIndex = next[SubStringIndex];
}
}
// 最后匹配成功直接返回位置
if (SubStringIndex == SubStringLen)
{
return MainStringIndex - SubStringIndex;
}
return -1;
}
// 内存特征码搜索
ULONG ScanMemorySignatureCode(DWORD Pid, DWORD beginAddr, DWORD endAddr, char *ShellCode, DWORD ShellCodeLen)
{
char *read = new char[ShellCodeLen];
// 打开进程
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, false, Pid);
int next[100] = { 0 };
// 开始搜索内存特征
for (int x = 0; x < endAddr; x++)
{
DWORD addr = beginAddr + x;
// 每次读入ShellCodeLen字节特征
ReadProcessMemory(process, (LPVOID)addr, read, ShellCodeLen, 0);
// 在Str字符串中找Search子串,找到后返回位置
int ret = KMPSearchStrin