植物大战僵尸辅助【控制台版本】

前面介绍了使用CE和OD的简单使用:CE和OD介绍和使用CE查找阳光的教学:阳光基地址和偏移地址,下面先使用最简单的控制台程序来实现修改阳光的功能。

项目地址

1.分析程序

我们的控制台程序想要修改植物大战僵尸游戏内的数据,它们不是同一个进程,因此我们就需要先找到植物大战僵尸的进程,然后跨进程访问阳光的地址,然后跨进程写入修改后的数据。

这些步骤都需要使用WindowsAPI,因此,对于复杂的函数会单独出一期介绍。

2.查找游戏窗口的句柄

可以使用FindWindows函数来查找进程的句柄,函数定义如下:

cpp 复制代码
WINUSERAPI
HWND
WINAPI
FindWindowA(
    _In_opt_ LPCSTR lpClassName,
    _In_opt_ LPCSTR lpWindowName);
WINUSERAPI
HWND
WINAPI
FindWindowW(
    _In_opt_ LPCWSTR lpClassName,
    _In_opt_ LPCWSTR lpWindowName);
#ifdef UNICODE
#define FindWindow  FindWindowW
#else
#define FindWindow  FindWindowA

首先WINUSERAPIWINAPI 参考链接:Windows 编程之 WINUSERAPI 和 WINAPI 区别

然后HWND 的H是handle句柄的意思,WND是Window窗口的意思

可以看到根据是否定义了UNICODE宏 来使用不同的函数,由于默认定义了UNICODE宏,所以自动选用FindWindowW函数。

关于**In_opt ** ,in表示这是输入参数,opt表示可以不设值。这个只是给编译器看的,不会参与编译。
LPCWSTR :LP长指针,C是const,WSTR是指w_char宽字符。
lpClassName :进程窗口的类名
lpWindowName:进程窗口的窗口名

要知道这两个参数,需要使用VS自带的Spy++工具:


Spy++同样也返回了窗口的句柄,但是窗口句柄是操作系统临时分配的,下次启动会改变。不可能每次使用这个工具采取获取,然后修改源代码。但是程序编译完成后,标题名和类名是固定的。

通过工具我们就可以这样调用函数:

cpp 复制代码
HWND gameHandle = FindWindow(L"MainWindow", L"植物大战僵尸中文版");
if (gameHandle == NULL) {
	cout << "游戏没有打开,获取窗口句柄失败" << endl;
	return 0;
}

3.根据窗口句柄获取进程ID

相关的Windows API函数为:

cpp 复制代码
WINUSERAPI
DWORD
WINAPI
GetWindowThreadProcessId(
    _In_ HWND hWnd,
    _Out_opt_ LPDWORD lpdwProcessId);

DWORD :定义为typedef unsigned long DWORD
In :表示这个参数是输入参数
Out_opt :out表示是输出参数,opt表示可选。

因为有传出参数了,就不需要返回值了。
函数官方链接

因此可以这样写:

cpp 复制代码
DWORD dwPID= 0;
GetWindowThreadProcessId(gameHandle, &dwPID);
if (dwPID == 0) {
	cout << "获取游戏窗口PID失败" << endl;
	return 0;
}

4.根据进程ID获取进程句柄

相关API:

cpp 复制代码
WINBASEAPI
HANDLE
WINAPI
OpenProcess(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ DWORD dwProcessId
    );

函数官网链接
dwDesiredAccess官网参考链接选用值PROCESS_ALL_ACCESS ,表示进程对象的所有可能的访问权限

bInheritHandle :如果此值为 TRUE,则此进程创建的进程将继承句柄。 否则,进程不会继承此句柄。
dwProcessId :要打开的本地进程的标识符。
返回值:如果函数成功,则返回值是指定进程的打开句柄。如果函数失败,则返回值为 NULL。

因此代码如下:

cpp 复制代码
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPID);
if (hProcess == nullptr) {
	cout << "获取游戏进程句柄失败" << endl;
	return 0;
}

5.读取游戏进程内存

相关API:

cpp 复制代码
WINBASEAPI
_Success_(return != FALSE)
BOOL
WINAPI
ReadProcessMemory(
    _In_ HANDLE hProcess,
    _In_ LPCVOID lpBaseAddress,
    _Out_writes_bytes_to_(nSize,*lpNumberOfBytesRead) LPVOID lpBuffer,
    _In_ SIZE_T nSize,
    _Out_opt_ SIZE_T* lpNumberOfBytesRead
    );

hProcess :想要读取的进程句柄。
lpBaseAddress : 要读取进程的基地址
lpBuffer :参出参数,表示从指定进程读取的值存放地址
nSize :要从指定进程读取的字节数。
lpNumberOfBytesRead :参出参数,表示实际读取字节数
返回值 :如果该函数成功,则返回值为非零值。如果函数失败,则返回值为 0(零)。 要获得更多的错误信息,请调用 GetLastError。
函数官网链接

我们现在就要通过这个函数访问到阳光的基地址了,从之前的博客CE找阳光基地址得到阳光的基地址为:006A9EC0 ,一级偏移量0x768,二级偏移量为0x5560。

因此可以通过以下代码访问到动态的阳光地址:

cpp 复制代码
//读取游戏进程内存
DWORD SunBaseAddress = 0x006A9EC0;     //要读取的地址
DWORD SunBaseAddressValue = 0;         //读取存放的值
DWORD Size = 0;                       //实际读取到的字节大小
if (ReadProcessMemory(hProcess, (void*)SunBaseAddress, &SunBaseAddressValue, sizeof(DWORD), (SIZE_T*)&Size) == 0) {
	cout << "读取基地址失败" << GetLastError() << endl;
	return 0;
}
DWORD SunOffsetFirst = 0x768;     //一级偏移量
DWORD SunOffsetFirstValue = 0;         //一级偏移量中存放的值
if (ReadProcessMemory(hProcess, (void*)(SunBaseAddressValue + SunOffsetFirst), &SunOffsetFirstValue, sizeof(DWORD), (SIZE_T*)&Size) == 0) {
	cout << "读取一级偏移地址失败" << GetLastError() << endl;
	return 0;
}
DWORD SunOffsetSecond = 0x5560;     //一级偏移量
DWORD SunValue = 0;         //一级偏移量中存放的值:阳光值
if (ReadProcessMemory(hProcess, (void*)(SunOffsetFirstValue + SunOffsetSecond), &SunValue, sizeof(DWORD), (SIZE_T*)&Size) == 0) {
	cout << "读取阳光地址失败" << GetLastError() << endl;
	return 0;
}
cout << "现在的阳光是:" << SunValue << endl;

运行发现抱错:

解决方法:可以在" 项目 ---- 属性 ---- 配置属性 ---- C/C++ ---- 代码生成 ---- 基本运行时检查:"设置为默认值,点击应用,确定后即可。参考链接

运行结果如下,成功获取当前阳光值:

6.修改游戏进程内存

相关API:

cpp 复制代码
WINBASEAPI
_Success_(return != FALSE)
BOOL
WINAPI
WriteProcessMemory(
    _In_ HANDLE hProcess,
    _In_ LPVOID lpBaseAddress,
    _In_reads_bytes_(nSize) LPCVOID lpBuffer,
    _In_ SIZE_T nSize,
    _Out_opt_ SIZE_T* lpNumberOfBytesWritten
    );

hProcess :要修改的进程内存的句柄。
lpBaseAddress :要写入进程的内存地址
lpBuffer :要写入的数据地址
nSize :要写入的字节大小
lpNumberOfBytesWritten :实际写入的字节数。
返回值 :如果该函数成功,则返回值为非零值。如果函数失败,则返回值为 0(零)。 要获得更多的错误信息,请调用 GetLastError。
官网链接

代码如下:

cpp 复制代码
cout << "想要修改的阳光值:" << endl;
cin >> SunValue;
if (WriteProcessMemory(hProcess, (void*)(SunOffsetFirstValue + SunOffsetSecond), &SunValue, sizeof(DWORD), (SIZE_T*)&Size) == 0) {
	cout << "写入地址失败" << endl;
}

效果如下:

相关推荐
蚰蜒螟15 分钟前
jvm安全点(二)openjdk17 c++源码垃圾回收安全点信号函数处理线程阻塞
jvm·安全
moonsims1 小时前
低空态势感知:基于AI的DAA技术是低空飞行的重要安全保障-机载端&地面端
人工智能·安全
神经毒素8 小时前
WEB安全--Java安全--shiro721反序列化漏洞
安全·web安全
独行soc16 小时前
2025年渗透测试面试题总结-阿里云[实习]阿里云安全-安全工程师(题目+回答)
linux·经验分享·安全·阿里云·面试·职场和发展·云计算
CertiK19 小时前
Crowdfund Insider聚焦:CertiK联创顾荣辉解析Web3.0创新与安全平衡之术
安全·web3
IP管家19 小时前
物联网设备远程管理:基于代理IP的安全固件更新通道方案
服务器·网络·物联网·网络协议·tcp/ip·安全·ip
shenyan~19 小时前
关于 Web安全:1. Web 安全基础知识
安全·web安全
Unity官方开发者社区19 小时前
《Cryptical Path》开发诀窍:像玩游戏一样开发一款类Rogue游戏
java·游戏·玩游戏
DonciSacer20 小时前
网络流量分析 | NetworkMiner
安全
Mast Sail20 小时前
windows下authas调试tomcat
java·windows·tomcat·authas