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

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

效果如下:

相关推荐
星河梦瑾1 小时前
CTF知识集-SQL注入
linux·运维·python·安全·网络安全
独行soc2 小时前
#渗透测试#SRC漏洞挖掘#红蓝攻防#黑客工具之Burp Suite介绍07-Burp IP伪造
安全·渗透测试·csrf·漏洞挖掘
从以前2 小时前
python练习之“用 Python 的 Pygame 库创建五子棋游戏”
开发语言·python·游戏·pygame
2401_854391082 小时前
SSM 架构下的垃圾分类系统,开启绿色生活
android·java·运维·安全
BBM的开源HUB5 小时前
使用FastGPT制做一个AI网站日志分析器
安全
sheng12345678rui5 小时前
mfc140.dll文件缺失的修复方法分享,全面分析mfc140.dll的几种解决方法
游戏·电脑·dll文件·dll修复工具·1024程序员节
小小小小关同学6 小时前
【并发容器】ConcurrentLinkedQueue:优雅地实现非阻塞式线程安全队列
java·开发语言·安全
Hacker_Oldv7 小时前
网络安全、Web安全、渗透测试之笔经面经总结(二)
安全·web安全
黑客老陈7 小时前
面试经验分享 | 杭州某安全大厂渗透测试岗二面
服务器·网络·经验分享·安全·oracle·面试·职场和发展