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

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

效果如下:

相关推荐
用户962377954481 天前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机1 天前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机1 天前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954481 天前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star1 天前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户962377954482 天前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
阿白的白日梦2 天前
winget基础管理---更新/修改源为国内源
windows
xiezhr3 天前
米哈游36岁程序员被曝复工当晚猝死出租屋内
游戏·程序员·游戏开发
cipher3 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
埃博拉酱5 天前
VS Code Remote SSH 连接 Windows 服务器卡在"下载 VS Code 服务器":prcdn DNS 解析失败的诊断与 BITS 断点续传
windows·ssh·visual studio code