植物大战僵尸修改器
利用CE修改植物大战僵尸游戏逻辑,CE:cheat engine,欺骗引擎,,笔者用到的主要有以下功能
-
搜索数据+继续搜索(精确值搜索)
-
地址找到汇编代码(代码查找)
-
脚本注入代码 or 直接修改内存代码
实现CE脚本之后,可以将CE脚本做成修改器,利用图形界面实现,效果如下图所示。
- Github仓库地址
- 适用版本:Plants_Vs_Zombies_V1.0.0.1051_CN_V1
CE 部分操作说明
- First Scan:首次搜索的结果列表显示
- Next Scan:再第一次搜索的结果里面继续搜索,可以多次搜索,Scan Type决定搜索的策略是什么?直到结果搜索出来
- 点击数据,右击可以搜索是哪个地址访问了这个数据,可以搜到相关指令
- 双击可以显示寄存器等信息,右击可以显示汇编等信息,很关键,因为可以找到数据相关的汇编逻辑,从而实现脚本 或者代码注入
- 脚本代码注入:选中指令,右上角选择选择自动汇编,
- 然后选择cheat table framework code,然后选择code inject,可以修改汇编指令,从而实现脚本修改代码逻辑
1、阳光修改
看到阳光值为50,所以搜索50的数据,然后消耗阳光值,,则基本可以定位阳光的地址
- 点击first scan,然后就列出了有50数据的地址,
- 此后不继续刷新,观察到数据会一直变化,
- 观察到查看阳光的变化,此时变为40,那么该地址基本可以锁定,
- 此后再继续观察其变化就可以确定
然后接着可以修改阳值,发现修改成功,那么此时可以随意消耗阳光,种植植物。
2、阳光基址
下一关完成后,发现此时的地址不再是阳光的地址了,那么说明每次地址都会变化,需要寻找基地址,
- 重复上次搜索,然后寻找是谁访问了这个地址
- 可能搜索结果较多,继续观察count变化,如果杨光变化时,count增加,那么可以锁定
- 地址为ESI,偏移为0x5560
然后继续重复上面的测试,
- ESI 地址为0E4BDC38,继续搜索这个地址
- 查找谁访问该指针内容,发现有比较,以及赋值的,基本可以确定其为二级地址
- 观察下面的地址访问,可以看到EAX和ECX值一样,
- 所以二级地址为0x02A59E40
继续去搜索0x02A59E40,注意一二级地址都会变化,所以需要找到基地址
- 测试几次之后,发现一大堆赋值,且访问次数一直在增加,那么就是地址
- 游戏完成后,重新也开始也不会变化,那么就是基地址
- 基地址:0x6A9F38
可以看到这个基地址的数据和一级地址数据一致
3、阳光最大值
正常阳光最大值为9990,即是修改超过这个值,由于代码逻辑判断
- 之后捡起阳光值,发现退回为9990,说明其有最大值
- 通过阳光的地址,可以搜到访问这个地址的指令,
- 下图中这两个值,点击阳光之后,增加一次,说明其有访问逻辑
从这里可以看到有最大值的限制,以及一次阳光的增加多少
- 25的阳光增加,以及50阳光的增加
- 这里可以修改更大的阳光最大值,比如50000
修改后最大阳光为50000,这里注意,不能超过16位数据,高16位被去掉。
这里可以修改阳光一次增加1000
- 这里植入一个脚本,修改mov ecx,3E8,一次增加1000,原来是25
- 晚上的阳关,小蘑菇是15,大蘑菇是25,这里修改的黄色大蘑菇的值
- 也可以修改小蘑菇的逻辑
阳光最大值50000,同样可以修改那一块判断的逻辑
超过了9990的阳光值
3、初始阳光
需要利用到基地址,
- 搜索基地址的指针的访问,每个关卡结束的时候会将地址改写成50,这个时候会有一个访问值
- 这里就是初始值
脚本修改成10000,
4、无限阳光
即种植植物不需要消耗阳光值,
- 同样首先找到阳光的地址即可,不需要基地址
- 然后看谁访问了这个地址,
- 然后点击种植植物,如果访问次数加一,则说明这里是种植植物的逻辑
- 简单将递减阳光的逻辑去掉jike
下面15次,每次种植阳光都会递减,所以这里就是阳关处理的逻辑
去掉了sub的逻辑,阳光值不改变
5、植物无CD
搜索步骤如下:
- CD刚开始为0,之后一直变大,超过某个定值后为0
- 所以先搜索0,然后继续搜索变大的值,值不会太大,
- 符合条件的值不多,所以可以看到如下值比较符合
- 后面观察,之后值归0,基本可以确认这块就是相应的逻辑
打开相应的汇编代码,将跳转逻辑nop掉,则默认会清零
可以看到随时随地种植植物
6、秒杀僵尸
僵尸的血量一直会降低,
- 僵尸一出场的搜索血量值不确定的值,
- 然后搜索数值降低的值,
- 然后会搜索到如下的值,可以看到这里普通僵尸是270的血量
- 然后看汇编处理这块的逻辑
找到如下值进行,然后查看汇编逻辑如下
脚步直接将0赋值回去,则僵尸秒杀
僵尸类型很多,所以上面只是针对普通僵尸,其他僵尸类似
其他高级僵尸
7、植物无限血
同理上面找到血量的逻辑
只需要植物不掉血,则可以一直在,即血量+0,
8、向日葵无限生成阳光
向日葵生成阳光的时候,生产完阳光之后,会有间隔,这个间隔时间
- 这个间隔时间会会变大,然后清零,然后产生阳光
- 这个间隔时间会变小,然后到零,然后产生阳光,
我们可以测试两张场景,直到确认阳光 的生产地址,下图这个地址很像,每次递减到零,就会产生阳光
查看地址处的汇编代码,
9、金币无限
金币的数值并不是本身的数字,而是乘以10倍的,所以搜索的数据要除以10(笔者搜索实际数据未搜到,然后尝试除以10得知)
直接赋值,可以修改金币
汇编代码如下:
继续搜索
继续搜
两个基地址均可以正常使用
10、自动收集阳光
这些都有访问,可以挨个测试一下,哪些可以触发自动收集阳光
地址都是相同的
11、僵尸出厂倒计时
游戏刚开始时,僵尸出厂有倒计时,倒计时结束,僵尸出厂,所示搜索思路就是,
- 出厂时搜索未初始化的值
- 然后搜索数值减少
- 一直搜索到几个时,可以看到如下值,每次倒计时结束,则会有僵尸出来,
- 看到修改这里,僵尸就会出来


倒计时一直递减
倒计时为0,僵尸全部出动
全部出动僵尸
其余脚本修改都类似,大家可以自行实现
12、卡槽植物修改
13、投掷僵尸
14、土豆地雷无CD
15、食僵尸花无CD
16、寒冰菇一直冰冻
17、寒冰射手一直冰冻
利用AI进行修改器制作
直接将CE脚本的内容发给AI,并输入要求:输出MFC的框架代码,其大致给你输出一个框架,利用该框架可以实现基本的一个修改器功能,后续的修改器功能就是舔砖加瓦即可。
基础知识
- 主要用到的windows API函数如下:
- OpenProcess:打开进程,前提是找到进场ID
- ReadProcessMemeory、WriteProcessMemory:读取内存数据以及写入内存数据,这里很关键,注入代码就是靠这两个函数实现
- VirtualProtect:内存数据保护与关闭操作
进程查找与附加
- 提示词如下:这里22D3A3F0是太阳光数值的临时地址,修改其可以直接看到太阳光数值。
进程查找与附加,脱离进程的代码如下:
// 查找进程ID
BOOL CPlantsCEDlg::FindProcessId(LPCTSTR szProcessName, DWORD& dwProcessId)
{
AddLog(_T("[查找进程] 正在查找: %s"), szProcessName);
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
AddLog(_T("[查找进程] 创建进程快照失败,错误码: %d"), GetLastError());
return FALSE;
}
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
BOOL bFound = FALSE;
if (Process32First(hSnapshot, &pe))
{
do
{
if (_tcsicmp(pe.szExeFile, szProcessName) == 0)
{
dwProcessId = pe.th32ProcessID;
bFound = TRUE;
AddLog(_T("[查找进程] 找到进程: %s (PID: %d)"), pe.szExeFile, dwProcessId);
break;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
if (!bFound)
{
AddLog(_T("[查找进程] 未找到进程: %s"), szProcessName);
// 列出所有运行中的进程,帮助用户排查
AddLog(_T("[查找进程] 当前运行的进程列表:"));
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe))
{
int nCount = 0;
do
{
if (nCount < 20) // 只显示前20个
{
AddLog(_T("[查找进程] %s (PID: %d)"), pe.szExeFile, pe.th32ProcessID);
}
nCount++;
} while (Process32Next(hSnapshot, &pe));
if (nCount > 20)
{
AddLog(_T("[查找进程] ... 还有 %d 个进程"), nCount - 20);
}
}
CloseHandle(hSnapshot);
}
}
return bFound;
}
// 附加进程
BOOL CPlantsCEDlg::AttachToProcess()
{
if (m_strProcessName.IsEmpty())
{
AddLog(_T("[附加进程] 错误: 请先选择进程"));
MessageBox(_T("请先点击【选择进程】按钮选择游戏进程!"),
_T("错误"), MB_OK | MB_ICONERROR);
return FALSE;
}
if (!FindProcessId(m_strProcessName, m_dwProcessId))
{
AddLog(_T("[附加进程] 错误: 未找到进程 %s"), m_strProcessName);
AddLog(_T("[附加进程] 请确保游戏已经运行,并且进程名称正确"));
CString strMsg;
strMsg.Format(_T("未找到进程: %s\n\n请确保:\n1. 游戏已经运行\n2. 进程名称正确\n3. 以管理员权限运行此程序"),
m_strProcessName);
MessageBox(strMsg, _T("错误"), MB_OK | MB_ICONERROR);
return FALSE;
}
AddLog(_T("[附加进程] 找到进程: %s (PID: %d, 0x%08X)"),
m_strProcessName, m_dwProcessId, m_dwProcessId);
// 尝试打开进程
m_hProcess = OpenProcess(
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION |
PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION,
FALSE,
m_dwProcessId
);
if (!m_hProcess)
{
DWORD dwError = GetLastError();
AddLog(_T("[附加进程] 打开进程失败,错误码: %d"), dwError);
if (dwError == 5)
{
AddLog(_T("[附加进程] 权限不足,请以管理员权限运行"));
MessageBox(_T("无法打开进程!\n\n请以管理员身份运行此程序。"),
_T("错误"), MB_OK | MB_ICONERROR);
}
return FALSE;
}
m_bAttached = TRUE;
AddLog(_T("[附加进程] 成功附加进程 %s"), m_strProcessName);
AddLog(_T("[附加进程] 进程句柄: 0x%08X"), (DWORD_PTR)m_hProcess);
return TRUE;
}
// 分离进程
void CPlantsCEDlg::DetachFromProcess()
{
if (m_hProcess)
{
CloseHandle(m_hProcess);
m_hProcess = NULL;
}
m_bAttached = FALSE;
m_dwProcessId = 0;
m_sunlightAddr.finalAddress = 0;
// 重置地址
m_dwNoPlantCDAddress = 0;
m_dwAutoFastSunAddress = 0;
m_dwAutoCollectSunAddress = 0;
m_dwFastShootAddress = 0;
}
// 按钮点击附加进程
void CPlantsCEDlg::OnBnClickedAttachprocess()
{
// TODO: 在此添加控件通知处理程序代码
if (m_bAttached)
{
DetachFromProcess();
return;
}
if (AttachToProcess())
{
// 获取模块基址并显示初始值功能地址
DWORD_PTR dwModuleBase = GetModuleBaseAddress();
if (dwModuleBase)
{
m_dwTargetAddress = dwModuleBase + TARGET_INIT_VALUE_OFFSET;
AddLog(_T("[初始值10000] 目标地址: 0x%08X"), m_dwTargetAddress);
// 无CD功能地址
m_dwNoPlantCDAddress = dwModuleBase + NO_PLANT_CD_OFFSET;
AddLog(_T("[无CD功能] 目标地址: 0x%08X"), m_dwNoPlantCDAddress);
}
// 自动读取一次阳光值
OnBnClickedReadSunValue();
}
}
// 添加日志
void CPlantsCEDlg::AddLog(LPCTSTR szFormat, ...)
{
CString strLog;
va_list args;
va_start(args, szFormat);
strLog.FormatV(szFormat, args);
va_end(args);
CTime time = CTime::GetCurrentTime();
CString strTime = time.Format(_T("[%H:%M:%S] "));
m_listLog.AddString(strTime + strLog);
int nCount = m_listLog.GetCount();
if (nCount > 0)
m_listLog.SetCurSel(nCount - 1);
}
读取当前阳光数值:利用阳光基地址
// 读取指针链:[[baseAddress] + offset1] + offset2
DWORD_PTR CPlantsCEDlg::ReadPointerChain(DWORD_PTR baseAddress, DWORD_PTR offset1, DWORD_PTR offset2)
{
if (!m_hProcess)
return 0;
DWORD_PTR ptr1 = 0;
SIZE_T bytesRead = 0;
// 第一次读取:基址 -> 指针1
if (!ReadProcessMemory(m_hProcess, (LPCVOID)baseAddress, &ptr1, sizeof(DWORD_PTR), &bytesRead))
{
AddLog(_T("读取基址失败,错误码: %d"), GetLastError());
return 0;
}
if (bytesRead != sizeof(DWORD_PTR))
{
AddLog(_T("读取基址数据不完整"));
return 0;
}
AddLog(_T("读取基址[0x%08X] -> 指针1: 0x%08X"), baseAddress, ptr1);
// 第一次偏移计算
DWORD_PTR addr1 = ptr1 + offset1;
DWORD_PTR ptr2 = 0;
// 第二次读取:指针1+偏移1 -> 指针2
if (!ReadProcessMemory(m_hProcess, (LPCVOID)addr1, &ptr2, sizeof(DWORD_PTR), &bytesRead))
{
AddLog(_T("读取第一层偏移失败,错误码: %d"), GetLastError());
return 0;
}
if (bytesRead != sizeof(DWORD_PTR))
{
AddLog(_T("读取第一层偏移数据不完整"));
return 0;
}
AddLog(_T("读取地址[0x%08X] -> 指针2: 0x%08X"), addr1, ptr2);
// 第二次偏移计算,得到最终阳光地址
DWORD_PTR finalAddr = ptr2 + offset2;
AddLog(_T("最终阳光地址: 0x%08X"), finalAddr);
return finalAddr;
}
// 读取阳光值
DWORD CPlantsCEDlg::ReadSunlightValue()
{
if (!m_hProcess || !m_bAttached)
{
AddLog(_T("错误: 未附加进程"));
return 0;
}
// 获取最终阳光地址
m_sunlightAddr.finalAddress = ReadPointerChain(
m_sunlightAddr.baseAddress,
m_sunlightAddr.offset1,
m_sunlightAddr.offset2
);
if (m_sunlightAddr.finalAddress == 0)
{
AddLog(_T("无法获取阳光地址"));
return 0;
}
// 读取阳光值
DWORD dwSunlight = 0;
SIZE_T bytesRead = 0;
if (ReadProcessMemory(m_hProcess, (LPCVOID)m_sunlightAddr.finalAddress,
&dwSunlight, sizeof(DWORD), &bytesRead))
{
if (bytesRead == sizeof(DWORD))
{
AddLog(_T("当前阳光值: %d"), dwSunlight);
return dwSunlight;
}
}
AddLog(_T("读取阳光值失败,错误码: %d"), GetLastError());
return 0;
}
// 写入阳光值
BOOL CPlantsCEDlg::WriteSunlightValue(DWORD dwNewValue)
{
if (!m_hProcess || !m_bAttached)
{
AddLog(_T("错误: 未附加进程"));
return FALSE;
}
if (m_sunlightAddr.finalAddress == 0)
{
AddLog(_T("错误: 未获取阳光地址,请先读取阳光值"));
return FALSE;
}
// 修改内存保护属性
DWORD dwOldProtect = 0;
if (!VirtualProtectEx(m_hProcess, (LPVOID)m_sunlightAddr.finalAddress,
sizeof(DWORD), PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
AddLog(_T("修改内存保护失败,错误码: %d"), GetLastError());
// 继续尝试写入
}
// 写入新值
SIZE_T bytesWritten = 0;
BOOL bResult = WriteProcessMemory(m_hProcess, (LPVOID)m_sunlightAddr.finalAddress,
&dwNewValue, sizeof(DWORD), &bytesWritten);
// 恢复内存保护属性
VirtualProtectEx(m_hProcess, (LPVOID)m_sunlightAddr.finalAddress,
sizeof(DWORD), dwOldProtect, &dwOldProtect);
if (bResult && bytesWritten == sizeof(DWORD))
{
AddLog(_T("成功修改阳光值: %d -> %d"),
ReadSunlightValue() - dwNewValue + dwNewValue, dwNewValue);
// 验证修改
DWORD dwVerify = ReadSunlightValue();
if (dwVerify == dwNewValue)
{
AddLog(_T("验证成功: 阳光值已更新为 %d"), dwVerify);
return TRUE;
}
else
{
AddLog(_T("警告: 验证失败,当前值为 %d"), dwVerify);
return FALSE;
}
}
else
{
AddLog(_T("写入失败,错误码: %d"), GetLastError());
return FALSE;
}
}
// 按钮点击阳光数值
void CPlantsCEDlg::OnBnClickedReadSunValue()
{
// TODO: 在此添加控件通知处理程序代码
if (!m_bAttached || !m_hProcess)
{
AddLog(_T("错误: 请先附加进程"));
return;
}
AddLog(_T("开始读取阳光值..."));
DWORD dwSunlight = ReadSunlightValue();
if (dwSunlight > 0 || dwSunlight == 0) // 允许读取到0
{
CString strValue;
strValue.Format(_T("%d"), dwSunlight);
m_editSunBaseValue.SetWindowText(strValue);
CString strStatus;
strStatus.Format(_T("阳光: %d"), dwSunlight);
}
else
{
AddLog(_T("读取失败,请检查游戏是否运行正常"));
}
}
//按钮修改阳光数值
void CPlantsCEDlg::OnBnClickedModifySunValue()
{
// TODO: 在此添加控件通知处理程序代码
if (!m_bAttached || !m_hProcess)
{
AddLog(_T("错误: 请先附加进程"));
return;
}
CString strValue;
m_editSunBaseValue.GetWindowText(strValue);
if (strValue.IsEmpty())
{
AddLog(_T("错误: 请输入要修改的阳光值"));
return;
}
DWORD dwNewValue = _ttoi(strValue);
if (dwNewValue > 999999)
{
AddLog(_T("警告: 阳光值过大 (%d),可能影响游戏体验"), dwNewValue);
}
AddLog(_T("准备修改阳光值为: %d"), dwNewValue);
if (WriteSunlightValue(dwNewValue))
{
// 刷新显示
DWORD dwCurrent = ReadSunlightValue();
CString strCurrent;
strCurrent.Format(_T("%d"), dwCurrent);
m_editSunBaseValue.SetWindowText(strCurrent);
}
else
{
}
}
实现效果
效果如下:
AI进行脚本实现
- 这里将基本的逻辑跑通之后,说明主体框架已经OK,
- 剩下的就是工作量的事情,将一个个脚本都按照框架进行实现
生成代码如下:
- 注意如果代码修改的指令有错误,导致程序崩溃,可以利用CE查看修改的指令
- 比如返回地址错误,需要修正生成的指令,或者告诉AI,让其进行修改,
// PlantsVsZombiesDlg.cpp - 添加以下内容
// 在 OnInitDialog 函数中添加 CheckBox 初始化
BOOL CPlantsVsZombiesDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// ... 原有代码 ...
// 初始化自动快速生产阳光 CheckBox
m_checkAutoFastSun.SetCheck(BST_UNCHECKED);
m_bAutoFastSunEnabled = FALSE;
m_dwAutoFastSunAddress = 0;
m_pAutoFastSunMemory = NULL;
m_checkAutoFastSun.EnableWindow(FALSE); // 附加进程后才能使用
// 显示功能说明
SetDlgItemText(IDC_STATIC_AUTO_FAST_SUN_TIP, _T("功能说明: 获取阳光时自动变为100阳光,实现快速生产"));
AddLog(_T("自动快速生产阳光功能已加载"));
AddLog(_T("提示: 需要先附加进程才能使用此功能"));
return TRUE;
}
// 获取模块基址(如果之前没有)
DWORD_PTR CPlantsVsZombiesDlg::GetModuleBaseAddress()
{
if (!m_hProcess || !m_bAttached)
return 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, m_dwProcessId);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;
MODULEENTRY32 moduleEntry;
moduleEntry.dwSize = sizeof(MODULEENTRY32);
DWORD_PTR dwModuleBase = 0;
if (Module32First(hSnapshot, &moduleEntry))
{
do
{
if (_tcsicmp(moduleEntry.szModule, TARGET_PROCESS_NAME) == 0)
{
dwModuleBase = (DWORD_PTR)moduleEntry.modBaseAddr;
break;
}
} while (Module32Next(hSnapshot, &moduleEntry));
}
CloseHandle(hSnapshot);
return dwModuleBase;
}
// 启用自动快速生产阳光功能
void CPlantsVsZombiesDlg::EnableAutoFastSun()
{
if (!m_hProcess || !m_bAttached)
{
AddLog(_T("[自动快速阳光] 错误: 未附加进程"));
return;
}
if (m_bAutoFastSunEnabled)
{
AddLog(_T("[自动快速阳光] 已经启用"));
return;
}
AddLog(_T("[自动快速阳光] 正在启用..."));
// 获取模块基址和完整地址
if (m_dwAutoFastSunAddress == 0)
{
DWORD_PTR dwModuleBase = GetModuleBaseAddress();
if (dwModuleBase)
{
m_dwAutoFastSunAddress = dwModuleBase + AUTO_FAST_SUN_OFFSET;
AddLog(_T("[自动快速阳光] 目标地址: 0x%08X"), m_dwAutoFastSunAddress);
}
else
{
AddLog(_T("[自动快速阳光] 无法获取模块基址"));
return;
}
}
// 读取当前字节码
BYTE currentBytes[5] = {0}; // 原始指令5字节
SIZE_T bytesRead = 0;
if (ReadProcessMemory(m_hProcess, (LPCVOID)m_dwAutoFastSunAddress, currentBytes, 5, &bytesRead))
{
AddLog(_T("[自动快速阳光] 当前字节码: %02X %02X %02X %02X %02X"),
currentBytes[0], currentBytes[1], currentBytes[2], currentBytes[3], currentBytes[4]);
}
// 修改内存保护属性
DWORD dwOldProtect = 0;
if (!VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5,
PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
AddLog(_T("[自动快速阳光] 修改内存保护失败,错误码: %d"), GetLastError());
return;
}
// 分配内存用于存放自定义代码
m_pAutoFastSunMemory = VirtualAllocEx(m_hProcess, NULL, 2048,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (!m_pAutoFastSunMemory)
{
AddLog(_T("[自动快速阳光] 分配内存失败,错误码: %d"), GetLastError());
VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
return;
}
AddLog(_T("[自动快速阳光] 分配内存地址: 0x%08X"), (DWORD_PTR)m_pAutoFastSunMemory);
// 创建自定义代码
// newmem:
// mov [edi+58], 64 ; 将阳光设置为100 (0x64)
// mov eax, [edi] ; 读取阳光值到eax
// exit:
// jmp returnhere
BYTE customCode[] = {
0xC7, 0x47, 0x58, 0x64, 0x00, 0x00, 0x00, // mov [edi+58], 00000064
0x8B, 0x07, // mov eax, [edi]
0xE9 // jmp (偏移量需要计算)
};
// 计算返回地址的JMP偏移
// 原地址+5 是JMP指令后的返回地址
DWORD_PTR returnAddress = m_dwAutoFastSunAddress + 5;
DWORD jmpOffset = (DWORD)(returnAddress - ((DWORD_PTR)m_pAutoFastSunMemory + sizeof(customCode)));
// 创建完整的代码块(添加JMP偏移)
BYTE fullCode[sizeof(customCode) + 4];
memcpy(fullCode, customCode, sizeof(customCode));
fullCode[sizeof(customCode)] = (BYTE)(jmpOffset & 0xFF);
fullCode[sizeof(customCode) + 1] = (BYTE)((jmpOffset >> 8) & 0xFF);
fullCode[sizeof(customCode) + 2] = (BYTE)((jmpOffset >> 16) & 0xFF);
fullCode[sizeof(customCode) + 3] = (BYTE)((jmpOffset >> 24) & 0xFF);
// 写入自定义代码到分配的内存
SIZE_T bytesWritten = 0;
if (!WriteProcessMemory(m_hProcess, m_pAutoFastSunMemory, fullCode, sizeof(fullCode), &bytesWritten))
{
AddLog(_T("[自动快速阳光] 写入自定义代码失败,错误码: %d"), GetLastError());
VirtualFreeEx(m_hProcess, m_pAutoFastSunMemory, 0, MEM_RELEASE);
m_pAutoFastSunMemory = NULL;
VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
return;
}
AddLog(_T("[自动快速阳光] 自定义代码写入成功,大小: %d 字节"), bytesWritten);
// 创建JMP指令跳转到分配的内存
DWORD jmpToAllocated = (DWORD)((DWORD_PTR)m_pAutoFastSunMemory - (m_dwAutoFastSunAddress + 5));
BYTE jmpInstruction[5] = { 0xE9 };
jmpInstruction[1] = (BYTE)(jmpToAllocated & 0xFF);
jmpInstruction[2] = (BYTE)((jmpToAllocated >> 8) & 0xFF);
jmpInstruction[3] = (BYTE)((jmpToAllocated >> 16) & 0xFF);
jmpInstruction[4] = (BYTE)((jmpToAllocated >> 24) & 0xFF);
// 写入JMP指令到目标地址
if (WriteProcessMemory(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, jmpInstruction, 5, &bytesWritten))
{
AddLog(_T("[自动快速阳光] 成功启用!获取阳光时自动变为100阳光"));
AddLog(_T("[自动快速阳光] JMP从 0x%08X 跳转到 0x%08X"),
m_dwAutoFastSunAddress, (DWORD_PTR)m_pAutoFastSunMemory);
m_bAutoFastSunEnabled = TRUE;
// 验证修改
BYTE verifyBytes[5] = {0};
if (ReadProcessMemory(m_hProcess, (LPCVOID)m_dwAutoFastSunAddress, verifyBytes, 5, &bytesRead))
{
if (memcmp(jmpInstruction, verifyBytes, 5) == 0)
{
AddLog(_T("[自动快速阳光] 验证成功,内存修改正确"));
}
else
{
AddLog(_T("[自动快速阳光] 验证失败,请检查"));
}
}
}
else
{
AddLog(_T("[自动快速阳光] 写入JMP指令失败,错误码: %d"), GetLastError());
VirtualFreeEx(m_hProcess, m_pAutoFastSunMemory, 0, MEM_RELEASE);
m_pAutoFastSunMemory = NULL;
}
// 恢复内存保护属性
VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
}
// 禁用自动快速生产阳光功能(恢复原始代码)
void CPlantsVsZombiesDlg::DisableAutoFastSun()
{
if (!m_hProcess || !m_bAttached)
{
AddLog(_T("[自动快速阳光] 错误: 未附加进程"));
return;
}
if (!m_bAutoFastSunEnabled)
{
AddLog(_T("[自动快速阳光] 已经禁用"));
return;
}
AddLog(_T("[自动快速阳光] 正在禁用,恢复正常阳光获取..."));
// 获取模块基址
if (m_dwAutoFastSunAddress == 0)
{
DWORD_PTR dwModuleBase = GetModuleBaseAddress();
if (dwModuleBase)
{
m_dwAutoFastSunAddress = dwModuleBase + AUTO_FAST_SUN_OFFSET;
}
else
{
AddLog(_T("[自动快速阳光] 无法获取模块基址"));
return;
}
}
// 原始字节码: 89 47 58 8B 07
// mov [edi+58], eax
// mov eax, [edi]
BYTE originalBytes[] = { 0x89, 0x47, 0x58, 0x8B, 0x07 };
// 修改内存保护属性
DWORD dwOldProtect = 0;
if (!VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5,
PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
AddLog(_T("[自动快速阳光] 修改内存保护失败,错误码: %d"), GetLastError());
return;
}
// 恢复原始字节码
SIZE_T bytesWritten = 0;
if (WriteProcessMemory(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, originalBytes, 5, &bytesWritten))
{
AddLog(_T("[自动快速阳光] 成功禁用!已恢复正常阳光获取"));
AddLog(_T("[自动快速阳光] 恢复字节码: %02X %02X %02X %02X %02X"),
originalBytes[0], originalBytes[1], originalBytes[2], originalBytes[3], originalBytes[4]);
m_bAutoFastSunEnabled = FALSE;
// 验证恢复
BYTE verifyBytes[5] = {0};
SIZE_T bytesRead = 0;
if (ReadProcessMemory(m_hProcess, (LPCVOID)m_dwAutoFastSunAddress, verifyBytes, 5, &bytesRead))
{
if (memcmp(originalBytes, verifyBytes, 5) == 0)
{
AddLog(_T("[自动快速阳光] 验证成功,内存恢复正确"));
}
else
{
AddLog(_T("[自动快速阳光] 验证失败,请检查"));
}
}
}
else
{
AddLog(_T("[自动快速阳光] 恢复失败,错误码: %d"), GetLastError());
}
// 释放分配的内存
if (m_pAutoFastSunMemory)
{
VirtualFreeEx(m_hProcess, m_pAutoFastSunMemory, 0, MEM_RELEASE);
m_pAutoFastSunMemory = NULL;
AddLog(_T("[自动快速阳光] 已释放分配的内存"));
}
// 恢复内存保护属性
VirtualProtectEx(m_hProcess, (LPVOID)m_dwAutoFastSunAddress, 5, dwOldProtect, &dwOldProtect);
}
// CheckBox 点击响应函数
void CPlantsVsZombiesDlg::OnBnClickedCheckAutoFastSun()
{
int nCheck = m_checkAutoFastSun.GetCheck();
if (!m_bAttached || !m_hProcess)
{
AddLog(_T("[自动快速阳光] 错误: 请先附加进程"));
// 恢复之前的选中状态
m_checkAutoFastSun.SetCheck(m_bAutoFastSunEnabled ? BST_CHECKED : BST_UNCHECKED);
return;
}
if (nCheck == BST_CHECKED) // 启用
{
EnableAutoFastSun();
}
else // 禁用
{
DisableAutoFastSun();
}
}
// 修改附加进程函数,在附加成功后初始化地址
void CPlantsVsZombiesDlg::OnBnClickedBtnAttachProcess()
{
if (m_bAttached)
{
// 如果自动快速阳光功能启用,先禁用
if (m_bAutoFastSunEnabled)
{
DisableAutoFastSun();
m_checkAutoFastSun.SetCheck(BST_UNCHECKED);
}
// 如果阳光初始值功能启用,先禁用
if (m_bSunInitBigValueEnabled)
{
DisableSunInitBigValue();
m_checkSunInitBigValue.SetCheck(BST_UNCHECKED);
}
// 如果无CD功能启用,先禁用
if (m_bNoPlantCDEnabled)
{
DisableNoPlantCD();
m_checkNoPlantCD.SetCheck(BST_UNCHECKED);
}
DetachFromProcess();
m_btnAttachProcess.SetWindowText(_T("附加进程"));
m_btnReadSunlight.EnableWindow(FALSE);
m_btnModifySunlight.EnableWindow(FALSE);
m_editSunBaseValue.EnableWindow(FALSE);
m_checkSunInitBigValue.EnableWindow(FALSE);
m_checkNoPlantCD.EnableWindow(FALSE);
m_checkAutoFastSun.EnableWindow(FALSE);
UpdateStatus(_T("未附加"));
return;
}
if (AttachToProcess())
{
m_btnAttachProcess.SetWindowText(_T("分离进程"));
m_btnReadSunlight.EnableWindow(TRUE);
m_btnModifySunlight.EnableWindow(TRUE);
m_editSunBaseValue.EnableWindow(TRUE);
m_checkSunInitBigValue.EnableWindow(TRUE);
m_checkNoPlantCD.EnableWindow(TRUE);
m_checkAutoFastSun.EnableWindow(TRUE);
UpdateStatus(_T("已附加"));
// 获取模块基址并显示地址信息
DWORD_PTR dwModuleBase = GetModuleBaseAddress();
if (dwModuleBase)
{
// 阳光初始值功能地址
m_dwTargetAddress = dwModuleBase + TARGET_INIT_VALUE_OFFSET;
CString strInitAddr;
strInitAddr.Format(_T("初始值修改地址: 0x%08X"), m_dwTargetAddress);
SetDlgItemText(IDC_STATIC_INIT_ADDR, strInitAddr);
AddLog(_T("[阳光初始值] 目标地址: 0x%08X"), m_dwTargetAddress);
// 无CD功能地址
m_dwNoPlantCDAddress = dwModuleBase + NO_PLANT_CD_OFFSET;
CString strNoCDAddr;
strNoCDAddr.Format(_T("无CD修改地址: 0x%08X"), m_dwNoPlantCDAddress);
SetDlgItemText(IDC_STATIC_NO_CD_ADDR, strNoCDAddr);
AddLog(_T("[无CD功能] 目标地址: 0x%08X"), m_dwNoPlantCDAddress);
// 自动快速阳光功能地址
m_dwAutoFastSunAddress = dwModuleBase + AUTO_FAST_SUN_OFFSET;
CString strAutoFastSunAddr;
strAutoFastSunAddr.Format(_T("自动快速阳光地址: 0x%08X"), m_dwAutoFastSunAddress);
SetDlgItemText(IDC_STATIC_AUTO_FAST_SUN_ADDR, strAutoFastSunAddr);
AddLog(_T("[自动快速阳光] 目标地址: 0x%08X"), m_dwAutoFastSunAddress);
}
// 自动读取一次阳光值
OnBnClickedBtnReadSunlight();
}
}