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

前面介绍了使用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;
}

效果如下:

相关推荐
李詹29 分钟前
Steam游戏服务器攻防全景解读——如何构建游戏级抗DDoS防御体系?
服务器·游戏·ddos
mon_star°2 小时前
搭建基于火灾风险预测与防范的消防安全科普小程序
安全·微信小程序·小程序·微信公众平台
神经毒素2 小时前
WEB安全--RCE--webshell bypass
网络·安全·web安全
进取星辰3 小时前
Windows 10 上运行 Ollama 时遇到 llama runner process has terminated: exit status 2
windows·llama
sukalot4 小时前
Windows同步技术-使用命名对象
windows
程序猿chen5 小时前
量子跃迁:Vue组件安全工程的基因重组与生态免疫(完全体终局篇)
前端·vue.js·git·安全·面试·前端框架·跳槽
ALe要立志成为web糕手5 小时前
第十六周蓝桥杯2025网络安全赛道
安全·web安全·网络安全·蓝桥杯
快乐点吧6 小时前
【Flume 】Windows安装步骤、配置环境
大数据·windows·flume
w23617346016 小时前
Apache中间件解析漏洞与安全加固
安全·中间件·apache
Java手札6 小时前
Windows下Golang与Nuxt项目宝塔部署指南
开发语言·windows·golang