[Win32] 实现内存修改器

0. 实现原理


  1. 在游戏运行中启动修改器, 输入游戏当前想要修改的金币/矿石/木材/…的数值
  2. 找到程序运行时的进程ID, Windows为每个进程都分配4GB的虚拟地址空间, 我们只需要在虚拟地址空间去遍历保存数值与游戏中数值相同的地址, 去修改它即可, 但需要注意仅搜索一次得到的保存此数值的地址极可能不唯一, 故在得到多个地址时就再在游戏中花费一点金币/…的值, 再到刚刚保存的多个地址中搜索. 直到获得唯一的地址.
  3. 修改此地址上的数值, 完成目标 !

直接使用CUI操作, 没有GUI美丽的外衣.(加上GUI不就成了金山某侠了吗?)

1. 找到目标进程


首先将游戏运行起来, Windows为每个进程分配4GB的地址空间, 前2GB归应用程序所有, 后2GB属于整个操作系统共用. 我们要向在指定的进程地址空间中修改数据就先要得到此进程的操作句柄.

实现原理: 输出当前所有运行中的进程及进程ID, 主动输入指定的进程ID, 返回目标进程的操作句柄. 通过句柄就能在找到指定的空间修改数据了.

实现输出和查找进程ID的demo

#include 
#include 
#include 

using namespace std;

int main()
{
     
	PROCESSENTRY32 pe32;
	pe32.dwSize = sizeof(pe32);
	PROCESSENTRY32 _pe32;
	_pe32.dwSize = sizeof(_pe32);

	HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

	if (hProcessSnap == INVALID_HANDLE_VALUE)
	{
     
		cout << "调用失败" << endl;
		return -1;
	}

	BOOL bMore = Process32First(hProcessSnap, &pe32);

	while (bMore)
	{
     
		cout << "进程名称: " << pe32.szExeFile << endl;
		cout << "进程ID: " << pe32.th32ProcessID << endl;

		bMore = Process32Next(hProcessSnap, &pe32);
	}

	bMore = Process32First(hProcessSnap, &_pe32);

	DWORD ProcessID;
	cout << "Input ProcessID: ";
	cin >> ProcessID;
	while (bMore)
	{
     
		if (_pe32.th32ProcessID == ProcessID)
		{
     
			cout << "find ID: " << ProcessID << " 进程名称: " << _pe32.szExeFile << endl;
			break;
		}

		bMore = Process32Next(hProcessSnap, &_pe32);
	}
	CloseHandle(hProcessSnap);

	system("pause");
	return 0;
}

这样就能将所有正在运行中的进程名与进程ID打印出来, 得到ID即可使用函数得到句柄
[Win32] 实现内存修改器_第1张图片

2. 找到指定数据所在的地址


Windows采用分页机制来管理数据, 以4KB为单位搜索可以提高搜索速率. 下面的CompareAPage函数的功能就是比较目标进程内存1页大小的内存. 得到的地址可能不止一个, 故将符合的所有地址保存在一个set中, 接下来会在这些地址中再筛选.

BOOL CompareAPage(DWORD dwBaseAddr, DWORD dwValue)
{
     
	// 读取1页内存
	BYTE arBytes[4096];
	if (!ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddr, arBytes, 4096, NULL))
	{
     
		return FALSE;
	}

	// 在这1页中查找
	DWORD* pdw;
	for (int i = 0; i < 4 * 1024 - 3; i++)
	{
     
		pdw = (DWORD*)&arBytes[i];
		if (pdw[0] == dwValue)
		{
     
			if (g_HashMap.size() >= 1024)
			{
     
				return FALSE;
			}
			else
			{
     
				g_HashMap.insert(dwBaseAddr + i);
			}
		}
	}
	return TRUE;
}

使用FindFirst函数来在2GB的内存中搜索

BOOL FindFirst(DWORD dwValue)
{
     
	const DWORD dwOneGB = 1024 * 1024 * 1024;
	const DWORD dwOnewPage = 4 * 1024;

	if (g_hProcess == NULL)
	{
     
		return FALSE;
	}

	// 在用户地址空间进行搜索, Win98为应用地址预留地址为4MB至2GB
	// Win2000预留地址为64KB至2GB
	// 故需判断操作系统类型, 用以决定开始地址
	DWORD dwBase;
	OSVERSIONINFO vi = {
      sizeof(vi) };
	GetVersionEx(&vi);
	if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
	{
     
		dwBase = 4 * 1024 * 1024; // Win98系列, 4MB起
	}
	else
	{
     
		dwBase = 64 * 1024;
	}

	// 从开始地址到2GB的地址空间进行查找
	for (; dwBase < 2 * dwOneGB; dwBase += dwOnewPage)
	{
     
		CompareAPage(dwBase, dwValue);
	}
	return TRUE;
}

3. 在所得到的地址中筛选得到正确的地址


数值改变后再到之前保存的地址中查找数据变化相同的数据, 直到满足结果

BOOL FindNext(DWORD dwValue)
{
     
	// 在g_HashMap中查找
	BOOL bRet = FALSE;
	DWORD dwReadValue;
	unordered_set<DWORD> newSet;
	for (auto& e : g_HashMap)
	{
     
		if (ReadProcessMemory(g_hProcess, (LPVOID)e, &dwReadValue, sizeof(DWORD), NULL))
		{
     
			if (dwReadValue == dwValue)
			{
     
				newSet.insert(e);
				bRet = TRUE;
			}
		}
	}
	g_HashMap.swap(newSet);
	return bRet;
}

4. 修改数值


BOOL WriteMemory(DWORD dwAddr, DWORD dwValue)
{
     
	return WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL);
}

5. 主函数


#include "MemRepair.h"

HANDLE g_hProcess; // 保存游戏进程句柄
unordered_set<DWORD> g_HashMap; // 保存数据地址

int main()
{
     
	ShowProcess();

	DWORD ProcessID;
	cout << "Input ProcessID: ";
	cin >> ProcessID;
	g_hProcess = FindProcess(ProcessID);
	cout << endl << "-----------------------------" << endl;
	int val;
	cout << "Input val = ";
	cin >> val;
	FindFirst(val);
	ShowHash();
	cout << endl << "----------------" << endl;
	size_t countAddr = 0;
	// 若地址数量不再减少, 则将所有的数据都修改了!
	while (g_HashMap.size() > 1 && countAddr != g_HashMap.size())
	{
     
		countAddr = g_HashMap.size();
		cout << "Input val = ";
		cin >> val;

		FindNext(val);
		if (FindNext == false)
		{
     
			cout << "找不到此数据! " << endl;
			return -1;
		}
		ShowHash();
		cout << "----------------" << endl;

	}

	cout << "Input new val = ";
	cin >> val;
	auto it = g_HashMap.begin();
	while (it != g_HashMap.end())
	{
     
		if (WriteMemory(*it, val))
		{
     
			cout << "Write data success!" << endl;
		}
		else
		{
     
			cout << "Write data fail!" << endl;
		}
		it++;
	}

	return 0;
}

6. 测试


本来想使用红警2测试, 由于游戏版本过低原因我无法切回桌面, 故使用红警3

  1. 打开游戏, 开始游戏, 这个时候只有15000很少.

  2. 打开修改器程序. 输入指定游戏进程ID
    [Win32] 实现内存修改器_第2张图片
    输入5692, 等待输入金额
    在这里插入图片描述
    输入15000的金额, 程序进行搜索, 将保存此数值的地址打印并保存起来
    [Win32] 实现内存修改器_第3张图片
    发现多个地址, 我们在游戏中建一个电厂花费点钱, 金钱变为14200, 再在窗口输入这个数

    [Win32] 实现内存修改器_第4张图片
    这时在刚刚保存的地址中仅有两个地址存储的数据为14200, 再重复上一步花点钱. 再输入进去
    [Win32] 实现内存修改器_第5张图片
    最后, 输入你想要的数字, 回车, 返回游戏查看

  3. ohhhhhhhhhhhhhhhhhhhhhhhh, 让我们使用先锋轰炸机轰平Tokyo吧 !


需要原码可以联系作者哦

参考文献:
Windows程序设计(第三版) 张铮 孙宝山 周天立

你可能感兴趣的:(Win32,c++)