Windows 进程内存探秘:从读取到改写

3 阅读7分钟

Windows 进程内存探秘:从读取到改写

有没有想过,你的程序可以像"黑客"一样,窥探甚至修改其他进程的数据?今天我们来玩两个有趣的 Windows API —— ReadProcessMemoryWriteProcessMemory,看看如何在 Windows 平台下实现进程间的内存操作。


原理简介

ReadProcessMemoryWriteProcessMemory 是 Windows 提供的用于跨进程内存操作的 API:

  • ReadProcessMemory:读取指定进程的内存数据
  • WriteProcessMemory:向指定进程的内存写入数据

⚠️ 安全提示:这些 API 通常需要管理员权限,且可能被安全软件标记为可疑行为。请仅在合法的学习和调试场景中使用!


第一步:准备目标进程

首先,我们需要一个"小白鼠"——一个会不断输出变量地址和值的程序。

目标进程代码

#include <iostream>
#include <thread>
#include <format>
#include <Windows.h>

using namespace std;

// 这是我们想要被外部程序读取和修改的变量
int num = 1;

int main()
{
	// 获取当前进程的 PID,方便其他程序定位
	DWORD pid = GetCurrentProcessId();
	cout << format("当前进程 PID: {}", pid) << endl;
	cout << "等待外部程序连接..." << endl;
	
	// 无限循环,每 5 秒打印一次 num 的地址和值
	while (1)
	{
		// 打印 num 的内存地址和当前值
		// 使用 static_cast<void*> 将 int* 转换为 void*,以便正确显示地址
		std::cout << format("[心跳] num 的地址: {}, 当前值: {}", 
		                    static_cast<void*>(&num), num) 
		          << std::endl;
		
		// 休眠 5 秒
		std::this_thread::sleep_for(std::chrono::seconds(5));
	}
	
	return 0;
}

编译并运行

编译后运行这个程序,你会看到类似这样的输出:

当前进程 PID: 203532
等待外部程序连接...
[心跳] num 的地址: 0x7ff7028be000, 当前值: 1

记下这两个关键信息

  • PID(进程 ID):如 203532
  • num 的地址:如 0x7ff7028be000

这些信息将在下一步用到。


第二步:读取目标进程内存

现在我们来编写"窥探者"程序,读取目标进程中 num 的值。

读取内存代码

#include <iostream>
#include <Windows.h>
#include <format>

using namespace std;

int main()
{
	// ==================== 配置参数 ====================
	// 目标进程的 PID,需要替换为你实际运行的目标进程 PID
	DWORD processId = 203532;
	
	// num 变量在目标进程中的内存地址
	// 注意:每次运行目标程序,这个地址可能会变化!
	LPVOID numAddress = (LPVOID)0x7ff7028be000;
	// ==================================================
	
	// 1. 打开目标进程,获取进程句柄
	// PROCESS_ALL_ACCESS 表示请求所有访问权限
	// FALSE 表示不继承句柄
	HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
	
	// 检查是否成功打开进程
	if (!process) {
		cerr << "错误:无法打开目标进程!请检查 PID 是否正确。" << endl;
		return -1;
	}
	
	cout << "成功连接到目标进程!" << endl;
	
	// 2. 准备读取数据的缓冲区
	SIZE_T readCnt = 0;  // 实际读取的字节数
	int num = 0;         // 用于存储读取到的数据
	
	// 3. 读取目标进程的内存
	// 参数说明:
	//   - process: 目标进程句柄
	//   - numAddress: 要读取的内存地址
	//   - &num: 接收数据的缓冲区
	//   - 4: 要读取的字节数(int 类型占 4 字节)
	//   - &readCnt: 实际读取的字节数(可选,可为 NULL)
	BOOL readResult = ReadProcessMemory(
		process,           // 目标进程句柄
		numAddress,        // 目标内存地址
		&num,              // 数据缓冲区
		sizeof(int),       // 读取大小
		&readCnt           // 实际读取字节数
	);
	
	// 检查读取是否成功
	if (!readResult) {
		cerr << "错误:读取内存失败!错误代码: " << GetLastError() << endl;
		CloseHandle(process);
		return -1;
	}
	
	// 4. 输出读取结果
	cout << format("✓ 成功读取!目标进程中的 num = {}", num) << endl;
	cout << format("  读取字节数: {} bytes", readCnt) << endl;
	
	// 5. 关闭进程句柄,释放资源
	CloseHandle(process);
	
	return 0;
}

运行效果

运行程序后,你会看到:

成功连接到目标进程!
✓ 成功读取!目标进程中的 num = 1
  读取字节数: 4 bytes

太棒了!我们已经成功"偷看"到了其他进程的数据!


第三步:改写目标进程内存

光看不练假把式,接下来我们尝试修改目标进程的 num 值!

改写内存代码

在上面的代码基础上,添加写入操作:

// ...(前面的代码与读取示例相同)...

// 读取成功后,我们尝试写入新值
int newValue = 100;  // 要写入的新值

// 使用 WriteProcessMemory 写入数据
// 参数与 ReadProcessMemory 类似
BOOL writeResult = WriteProcessMemory(
	process,           // 目标进程句柄
	numAddress,        // 目标内存地址
	&newValue,         // 要写入的数据
	sizeof(int),       // 写入大小
	&readCnt           // 实际写入字节数
);

// 检查写入是否成功
if (!writeResult) {
	cerr << "错误:写入内存失败!错误代码: " << GetLastError() << endl;
	CloseHandle(process);
	return -1;
}

cout << format("✓ 成功写入!已将 num 修改为: {}", newValue) << endl;
cout << format("  写入字节数: {} bytes", readCnt) << endl;

// ...(关闭句柄等后续代码)...

完整写入示例

#include <iostream>
#include <Windows.h>
#include <format>

using namespace std;

int main()
{
	// ==================== 配置参数 ====================
	DWORD processId = 203532;                    // 目标进程 PID
	LPVOID numAddress = (LPVOID)0x7ff7028be000;  // num 变量的地址
	// ==================================================
	
	// 1. 打开目标进程
	HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
	if (!process) {
		cerr << "错误:无法打开目标进程!" << endl;
		return -1;
	}
	
	cout << "=== 进程内存操作演示 ===" << endl;
	cout << "目标 PID: " << processId << endl;
	cout << "目标地址: " << numAddress << endl << endl;
	
	// 2. 读取当前值
	int currentValue = 0;
	SIZE_T bytesProcessed = 0;
	
	BOOL readResult = ReadProcessMemory(
		process, numAddress, &currentValue, sizeof(int), &bytesProcessed
	);
	
	if (!readResult) {
		cerr << "读取失败!错误: " << GetLastError() << endl;
		CloseHandle(process);
		return -1;
	}
	
	cout << "【读取】当前值: " << currentValue << endl;
	
	// 3. 写入新值
	int newValue = 100;
	BOOL writeResult = WriteProcessMemory(
		process, numAddress, &newValue, sizeof(int), &bytesProcessed
	);
	
	if (!writeResult) {
		cerr << "写入失败!错误: " << GetLastError() << endl;
		CloseHandle(process);
		return -1;
	}
	
	cout << "【写入】已修改为: " << newValue << endl;
	
	// 4. 验证写入结果(再次读取)
	int verifyValue = 0;
	ReadProcessMemory(process, numAddress, &verifyValue, sizeof(int), &bytesProcessed);
	cout << "【验证】读取确认: " << verifyValue << endl;
	
	// 5. 清理资源
	CloseHandle(process);
	cout << "\n操作完成!" << endl;
	
	return 0;
}

运行效果

当你运行写入程序后,目标进程的输出会从:

[心跳] num 的地址: 0x7ff7028be000, 当前值: 1

变成:

[心跳] num 的地址: 0x7ff7028be000, 当前值: 100

神奇吧!我们已经成功"黑"进了另一个进程,并修改了它的数据!


API 参考速查表

API功能关键参数
OpenProcess打开目标进程,获取句柄dwDesiredAccess(权限)、dwProcessId(PID)
ReadProcessMemory读取进程内存hProcess(句柄)、lpBaseAddress(地址)、lpBuffer(缓冲区)、nSize(大小)
WriteProcessMemory写入进程内存同上
CloseHandle关闭进程句柄,释放资源hObject(句柄)

常用进程访问权限

PROCESS_VM_READ      // 读取内存权限
PROCESS_VM_WRITE     // 写入内存权限
PROCESS_VM_OPERATION // 内存操作权限
PROCESS_ALL_ACCESS   // 所有权限(最宽松)

常见问题与注意事项

❌ 问题 1:OpenProcess 返回 NULL

可能原因

  • PID 错误,目标进程未运行
  • 权限不足(尝试以管理员身份运行)
  • 目标进程受到保护(如系统进程、安全软件)

解决方案

  • 仔细检查 PID 和地址
  • 右键 → 以管理员身份运行
  • 确保目标进程是普通用户进程

❌ 问题 2:读写操作失败

可能原因

  • 地址无效或不可访问
  • 目标内存页没有读写权限
  • 地址是随机的,每次运行都会变化

解决方案

  • 确认地址正确(检查目标程序的输出)
  • 注意:开启 ASLR(地址空间布局随机化)的程序,地址每次运行都会变

⚠️ 安全警告

  1. 杀毒软件:这些 API 常被恶意软件使用,可能会被杀毒软件拦截
  2. 系统稳定性:随意修改其他进程内存可能导致崩溃
  3. 法律风险:未经授权访问他人计算机进程可能违法

进阶话题

1. 查找动态地址

实际应用中,地址不是固定的。你需要:

  • 通过模块基址 + 偏移量计算
  • 使用 CreateToolhelp32Snapshot 枚举进程模块
  • 遍历内存查找特征码

2. 注入代码

除了读写数据,还可以:

  • 使用 VirtualAllocEx 在目标进程分配内存
  • 使用 WriteProcessMemory 写入代码
  • 使用 CreateRemoteThread 执行远程线程

⚠️ 严肃警告:代码注入技术常被用于恶意目的,请务必遵守法律法规!

3. 防护方案

如果你是软件开发者,想防止被他人读写内存:

  • 使用代码混淆和加壳
  • 检测调试器和内存修改
  • 关键数据加密存储
  • 使用驱动级保护

总结

通过本文,我们学习了:

✅ 使用 OpenProcess 打开目标进程
✅ 使用 ReadProcessMemory 读取进程内存
✅ 使用 WriteProcessMemory 修改进程内存
✅ 了解了进程内存操作的基本原理和注意事项

这仅仅是 Windows 进程间操作的冰山一角。掌握这些 API 后,你可以开发游戏修改器、调试工具、进程监控软件等。但请记住:能力越大,责任越大,请在合法合规的前提下使用这些技术!


参考资料


本文仅供学习交流使用,请勿用于非法用途!