C++ SSE2、Sunday算法,极速定位指定进程内存特征码

注:SSE2代码搜索内存不连续和映射不完整的模块会抛锚 ,有能力的自己完善!

// 调用代码
#include <iostream>
#include <windows.h>
#include <shlwapi.h>
#include <Psapi.h>
#include "SSE2PatternFind.h"
 
 
using namespace std;
 
int main()
{
    HMODULE hModule = GetModuleHandle(L"ntdll.dll");
    string pattern = "78 ?? 48 8D ?? ?? 50";
 
    clock_t begin = clock();
    vector<ULONGLONG> retList = SSE2PatternFind(hModule, pattern);
    cout << "SSE搜索用时:" << clock() - begin << " ms" << endl;
    for (int i = 0; i < retList.size(); i++)
        cout << "特征码:"+ pattern + " 第" << i + 1 << "个--->结果: " << (void*)retList[i] << endl;
 
    system("pause");
    return 0;
}
//  核心算法来自看雪论坛[url=https://bbs.kanxue.com/thread-209946.htm]https://bbs.kanxue.com/thread-209946.htm[/url];作者:liuzewei 
//  支持通配符??,支持一码多匹配,支持x86 x64
//  如使用请保留出处
 
#include <windows.h>
#include <intrin.h>
#include <psapi.h>
#include <string>
#include <vector>
#include <memory>
 
/*  SSE2PatternFind()特征码搜索
1)  module  需要搜索的模块 HMODULE hModule = GetModuleHandle(L"模块名称");
2)  pattern  搜索特征码,支持通配符?? 格式为:std::string pattern = "55 ?? 77 88 ?? AA BB";
3)  searchNum  搜索个数,0为搜索整个模块,默认为0
4)  deviation  特征码地址离目标地址的偏移距离,上负下正,默认不偏移
    return返回值,找到的地址
    调用 std::vector<ULONGLONG> retList = SSE2PatternFind(hModule, pattern, 10);
*/
std::vector<ULONGLONG> SSE2PatternFind(HMODULE module, std::string pattern, ULONGLONG searchNum = 0, ULONGLONG deviation = 0)
{
    std::vector<ULONGLONG> retList;
    //  "12 34 ?? 78 ?? BC"
    //  去除所有空格和头部?
    if (!pattern.empty())
    {
        pattern.erase(std::remove(pattern.begin(), pattern.end(), ' '), pattern.end());
        pattern.erase(0, pattern.find_first_not_of("?"));
    }
    //  特征码长度不能为单数
    if (pattern.length() % 2 != 0) return retList;
    //  特征码长度
    int len = pattern.length() / 2;
    std::string SigMask = "";
    std::string finalPattern = "";
 
    //  将特征码转换为目标格式,并将mask做相应的匹配
    for (int i = 0; i < len; i++) {
        std::string tempStr = pattern.substr(i * 2, 2);
        SigMask += (tempStr == "??") ? "?" : "x";
        finalPattern += (tempStr == "??") ? char(0xFF) : char(strtoul(tempStr.c_str(), nullptr, 16));
    }
 
    //  必须初始化,否则报错
    MODULEINFO moduleInfo = { 0 };
    if (GetModuleInformation(GetCurrentProcess(), module, &moduleInfo, sizeof(moduleInfo)) == 0)
        return retList;
    ULONGLONG VirtualAddress = (ULONGLONG)moduleInfo.lpBaseOfDll;
    ULONGLONG VirtualLength = moduleInfo.SizeOfImage;
 
   // 常规变量
    PUCHAR MaxAddress = (PUCHAR)(VirtualAddress + VirtualLength);
    PUCHAR BaseAddress;
    PUCHAR CurrAddress;
    PUCHAR CurrPattern;
    PCHAR CurrMask;
    BOOLEAN CurrEqual;
    register UCHAR CurrUChar;
 
    // SSE 加速相关变量   
    __m128i SigHead = _mm_set1_epi8((CHAR)finalPattern[0]);
    __m128i CurHead, CurComp;
    ULONG MskComp, IdxComp;
    ULONGLONG i, j, nCount = 0;
 
    //
    //   第一层遍历使用 SSE 将逐字节加速为逐 16 字节每次(最终加速 12 倍获益主要来源与此)
    //
    //   第二层子串匹配不能使用 SSE 加速,原因有四
    //     1. SSE 虽为单指令多数据,但单个指令 CPU 周期比常规指令要高
    //
    //     2. 从概率上来说,子串匹配时第一个字节命中失败与 SSE 一次性对比 16 个字节命中失败在概率上几乎相等
    //
    //     3. 根据实验采用 SSE 优化第二层子串匹配将显著降低最终查找速度
    //
    //     4. 理论上,即使 SSE 单条指令与常规指令具有同样的CPU周期,最高也只能加速 16 倍
    //
    for (i = 0; i <= VirtualLength - 16; i += 16)
    {
        CurHead = _mm_loadu_si128((__m128i*)(VirtualAddress + i));
        CurComp = _mm_cmpeq_epi8(SigHead, CurHead);
        MskComp = _mm_movemask_epi8(CurComp);
 
        BaseAddress = (PUCHAR)(VirtualAddress + i);
        j = 0;
        while (_BitScanForward(&IdxComp, MskComp))
        {
            CurrAddress = BaseAddress + j + IdxComp;
            CurrPattern = (PUCHAR)finalPattern.c_str();
            CurrMask = (PCHAR)SigMask.c_str();
            for (; CurrAddress <= MaxAddress; CurrAddress++, CurrPattern++, CurrMask++)
            {
                // 因为是暴力搜索整个系统的物理内存,而本函数自身的堆栈区当然也属于整个物理内存的一部分
                // 因此为了避免匹配到参数 SigPattern 本身,对其做了相应过滤操作,如不需要可以自行简化 2 行
                CurrUChar = *CurrPattern;
                // *CurrPattern = CurrUChar + 0x1;
                CurrEqual = (*CurrAddress == CurrUChar);
                // *CurrPattern = CurrUChar;
 
                if (!CurrEqual) { if (*CurrMask == 'x') break; }
                if (*CurrMask == 0)
                {
                    retList.push_back((ULONGLONG)(BaseAddress + j + IdxComp + deviation));
                    if (++nCount >= searchNum && searchNum != 0) return retList;
                    break;
                }
            }
 
            ++IdxComp;
            MskComp = MskComp >> IdxComp;
            j += IdxComp;
        }
    }
    return retList;
}

#include <windows.h>
#include <time.h>
#include <iostream>
using namespace std;
 
//2020.8.9代码更新,修复通配符的小BUG,该BUG会有极低几率搜索错误,现已修复
//2022.4.4代码更新,添加一句代码,修复起始地址的小BUG,该BUG会有小概率搜不到想要的内存
 
/*
findMatchingCode() 参数说明:
1) hProcess		要打开的进程句柄
2) markCode		特征码,支持通配符(**),如: 55 8b ec ** 56 83 ec 20 ** ** 08 d9 ee
3) memBeginAddr		起始搜索地址
4) memEndAddr		结束搜索地址
5) retAddr[]		记录找到的地址,传入这个参数前一定要清0,如 DWORD retAddr[32] = {0};  或者 DWORD *retAddr = new DWORD[32]();
6) deviation		特征码地址离目标地址的偏移距离,上负下正
7) isCall		是否为找CALL的跳转地址,true 则 retAddr[] 返回的是CALL跳转的地址
8) isAll		是否查找所有符合的地址,false找到第一个符合的地址后就结束搜索,true继续搜索,直到搜索地址大于结束地址(memEndAddr)
return返回值		找到的地址总数
搜不到内存可能是保护属性没有选对
*/
DWORD findMatchingCode(HANDLE hProcess, string markCode, DWORD memBeginAddr, DWORD memEndAddr, DWORD retAddr[], int deviation, bool isCall, bool isAll = false);
 
 
 
 
DWORD findMatchingCode(HANDLE hProcess, string markCode, DWORD memBeginAddr, DWORD memEndAddr, DWORD retAddr[], int deviation, bool isCall, bool isAll)
{
	//----------------------处理特征码----------------------//
	//去除所有空格和头部的?
	if (!markCode.empty())
	{
		markCode.erase(std::remove(markCode.begin(), markCode.end(), ' '), markCode.end());
        markCode.erase(0, markCode.find_first_not_of("?"));
	}
 
	//特征码长度不能为单数
	if (markCode.length() % 2 != 0) return 0;
 
	//特征码长度
	int len = markCode.length() / 2;
 
	//Sunday算法模板数组的长度
	int nSundayLen = len;
 
	//将特征码转换成byte型
	BYTE *pMarkCode = new BYTE[len];
	BYTE *pWildcard = new BYTE[len];
	for (int i = 0; i < len; i++) {
		string tempStr = markCode.substr(i * 2, 2);
		if (tempStr == "**") {
			pWildcard[i] = 0xFF;
			if (nSundayLen == len) nSundayLen = i;	//记录第一个通配符的索引,该索引越靠后,效率越高
		}
		else {
			pWildcard[i] = 0x00;
		}
		pMarkCode[i] = strtoul(tempStr.c_str(), 0, 16);
	}
	//--------------------------end-------------------------//
 
	//Sunday算法模板数组赋值,+1防止特征码出现FF时越界
	int aSunday[0xFF + 1] = { 0 };
	for (int i = 0; i < nSundayLen; i++) {
		aSunday[pMarkCode[i]] = i + 1;
	}
 
	//起始地址
	const DWORD dwBeginAddr = memBeginAddr;
	//结束地址
	const DWORD dwEndAddr = memEndAddr;
	//当前读取的内存块地址
	DWORD dwCurAddr = dwBeginAddr;
	//存放内存数据的缓冲区
	BYTE *pMemBuffer = NULL;
	//计算参数retAddr[]数组的长度,该参数传入前一定要清0
	int nArrayLength = 0;
	for (int i = 0; ; i++) {
		if (*(retAddr + i) != 0) {
			nArrayLength = i;
			break;
		}
	}
	//偏移量
	int nOffset;
	//数组下标:内存、特征码、返回地址
	int i = 0, j = 0, nCount = 0;
 
	//内存信息
	MEMORY_BASIC_INFORMATION mbi;
 
	//记录起始搜索时间
	clock_t nBeginTime = clock();
 
	//扫描内存
	while (dwCurAddr < dwEndAddr)
	{
		//查询地址空间中内存地址的信息
		memset(&mbi, 0, sizeof(MEMORY_BASIC_INFORMATION));
		if (::VirtualQueryEx(hProcess, (LPCVOID)dwCurAddr, &mbi, sizeof(mbi)) == 0) {
			goto end;
		}
 
        //添加一句代码,修改当前地址到这块内存页的起始地址,编辑于2022.4.4,没有这句会有概率搜不到想要的内存
		dwCurAddr = (DWORD_PTR)mbi.BaseAddress;
		//-----------------------------------------------------//
 
		//过滤内存空间, 根据内存的状态和保护属性进行过滤
		//一般扫描(读写及执行)即可,速度极快,扫不到的话在尝试添加(读写)这一属性
		if (MEM_COMMIT == mbi.State &&			//已分配的物理内存
			//MEM_PRIVATE == mbi.Type ||		//私有内存,不被其他进程共享
			//MEM_IMAGE == mbi.Type &&
			//PAGE_READONLY == mbi.Protect ||	//只读
			//PAGE_EXECUTE_READ == mbi.Protect ||	//读及执行
			//PAGE_READWRITE == mbi.Protect ||	//读写
			PAGE_EXECUTE_READWRITE == mbi.Protect)	//读写及执行
		{
			//申请动态内存
			if (pMemBuffer) {
				delete[] pMemBuffer;
				pMemBuffer = NULL;
			}
			pMemBuffer = new BYTE[mbi.RegionSize];
			//读取进程内存
			ReadProcessMemory(hProcess, (LPCVOID)dwCurAddr, pMemBuffer, mbi.RegionSize, 0);
			i = 0;
			j = 0;
			while (j < len)
			{
			nextAddr:
				if (pMemBuffer[i] == pMarkCode[j] || pWildcard[j] == 0xFF)
				{
					i++;
					j++;
				}
				else
				{
					nOffset = i - j + nSundayLen;
					//判断偏移量是否大于缓冲区
					if (nOffset > mbi.RegionSize - len) break;
					//判断 aSunday模板数组 里有没有 内存偏移后的值,有则回溯,否则+1
					if (aSunday[pMemBuffer[nOffset]])
					{
						i = nOffset - aSunday[pMemBuffer[nOffset]] + 1;
						j = 0;
					}
					else
					{
						i = nOffset + 1;
						j = 0;
					}
				}
			}
 
			if (j == len)
			{
				//计算找到的目标地址:
				//特征码地址 = 当前内存块基址 + i偏移 - 特征码长度
				//目标地址 = 特征码地址 + 偏移距离
				//CALL(E8)跳转的地址 = E8指令后面的4个字节地址 + 下一条指令地址(也就是目标地址 + 5)
				retAddr[nCount] = dwCurAddr + i - len + deviation;
				if (isCall) {
					DWORD temp;
					memcpy(&temp, &pMemBuffer[i - len + deviation + 1], 4);
					retAddr[nCount] += 5;
					retAddr[nCount] += temp;
				}
 
				if (++nCount >= nArrayLength)
				{
					//传入的数组下标越界就结束搜索
					goto end;
				}
 
				if (isAll) {
					i = i - len + 1;
					j = 0;
					goto nextAddr;
				}
				else {
					goto end;
				}
			}
			dwCurAddr += mbi.RegionSize; //取下一块内存地址
		}
		else
		{
			dwCurAddr += mbi.RegionSize;
		}
	}
 
 
end:
	//计算搜索用时(ms)
	clock_t nEndTime = clock();
	int nUseTime = (nEndTime - nBeginTime);
	//释放内存
	if (pMemBuffer) {
		delete[] pMemBuffer;
		pMemBuffer = NULL;
	}
	delete[] pMarkCode;
	pMarkCode = NULL;
	delete[] pWildcard;
	pWildcard = NULL;
	return nCount;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C/C++实现内存特征搜索可以通过以下步骤来实现: 1. 定义特征:根据要搜索的目标数据,定义一个特征,通常使用十六进制表示。特征可以是一个或多个字节的组合。 2. 获取内存数据:使用内存读取函数(如`memcpy`),将程序需要搜索内存数据读取到一个缓冲区中。 3. 搜索特征:在缓冲区中使用循环和条件语句,逐字节地比较缓冲区的数据和特征。如果找到完全匹配的特征,就说明目标数据被找到了。 4. 返回结果:如果找到了特征,返回找到的位置或索引;如果没有找到,返回一个指定的特殊值(如-1)表示没有找到。 下面是一个简单的示例代,用于在一个整数数组中搜索特定的整数值的内存特征: ```cpp #include <stdio.h> #include <string.h> int searchMemory(const int* data, int dataSize, int target) { const int* p = data; for (int i = 0; i < dataSize; i++) { if (*p == target) { return i; } p++; } return -1; } int main() { int data[5] = {1, 2, 3, 4, 5}; int target = 4; int result = searchMemory(data, sizeof(data)/sizeof(data[0]), target); if (result != -1) { printf("Target found at index: %d\n", result); } else { printf("Target not found\n"); } return 0; } ``` 这个示例中,`searchMemory`函数接受一个整数数组、数组大小和目标整数作为输入,然后在数组中搜索目标整数。如果找到,函数返回目标整数在数组中的索引;如果没有找到,返回-1。在`main`函数中,我们定义了一个整数数组和一个目标整数,然后调用`searchMemory`函数进行搜索。最后,根据函数的返回值输出相应结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值