查找阳光地址
首先打开植物大战僵尸,进入游戏,初始阳光为50.
打开CE修改器,搜索50
进入游戏,种植向日葵,阳光变成0,再次搜索
再进入游戏,收取阳光,阳光变成25,搜索25
显然地址0x144344C8保存的就是阳光,现在退出游戏重新打开,重复上面步骤
现在地址变成0x1408EDC0了,说明阳光的地址并不是固定的.对这个地址进行检测,查看是哪条指令在修改
我刚刚进行了种植和收集阳光两个步骤,显然mov是减少阳光,add是增加阳光.查看详细信息
得到EAX = 0x14089860,这个0x5560其实就是二级偏移.然而EAX也是动态变化的,我们需要在内存中搜索EAX,来查找它到底保存在哪个地方
由于这个地址保存了阳光的地址,所以它应该是不变的,否则就找不到阳光的地址了,所以可以多次重复扫描,确保把会改变的量排除.
逐一查看哪个操作码访问了上面地址,发现地址0x028CA730很有趣
列表里清一色的都是0x768,证明[EAX + 0x028CA730]保存了结构体地址,查看EAX地址
这里不管是查看EAX还是ECX,结果肯定是一样的,因为它们都指向同一个地址,且偏移也相同.EAX = 0x028C9FC8,而0x768就是一级偏移.继续搜索EAX
列表里出现绿色的基址,查找结束.
内存读写
开始写代码,C#无法直接修改内存,需要动态调用kernel32.dll
1 2 3 4 5 6 7 8 9 10 11
| [DllImport("kernel32.dll", EntryPoint = "OpenProcess")] public static extern IntPtr OpenProcess(int desiredAccess, bool heritHandle, int pocessID); [DllImport("kernel32.dll", EntryPoint = "CloseHandle")] public static extern void CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory")] public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr baseaddress, IntPtr buffer, int nsize, IntPtr bytesread); [DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory")] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr baseaddress, long[] buffer, int nSize, IntPtr byteswrite);
|
读写内存需要用到OpenProcess,官方文档里告诉我们第一个参数是访问权限,PROCESS_ALL_ACCESS指所有能获得的最高权限,但是PROCESS_ALL_ACCESS是在C里定义的,C#里却没有,注意到这个值的类型是int,我们可以在C里打印出这个值,然后直接写在C#里
所以我们只要输入0x1F0FFF就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private int ReadMemory(int pid,IntPtr toBase) { byte[] bytes = new byte[4]; IntPtr address = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0); IntPtr process = OpenProcess(0x1F0FFF, false, pid); ReadProcessMemory(process, toBase, address, 4, IntPtr.Zero); CloseHandle(process); return Marshal.ReadInt32(address); } private void WriteMemory(int pid,IntPtr toBase,int num) { IntPtr process = OpenProcess(0x1F0FFF, false, pid); WriteProcessMemory(process, toBase, new long[] { num }, 4, IntPtr.Zero); CloseHandle(process); }
|
获取进程PID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private int GetPid() { Process[] processes = Process.GetProcessesByName(ProcessName); if(processes.Length == 0) { ShowDialog("没有检测到游戏进程."); return -1; } if(processes.Length > 1) { ShowDialog("检测到多个进程,这可能是因为您开启了多个相同进程名的软件,请关闭多余软件."); return -1; } return processes[0].Id; }
|
定义全局变量
1 2 3 4 5
| private const string ProcessName = "PlantsVsZombies"; private const int sun = 9990; private int pid; private IntPtr intPtr;
|
获取进程信息
1 2 3 4 5 6 7 8
| private void GetInfo() { pid = GetPid(); if (pid == -1) return; int num1 = ReadMemory(pid, (IntPtr)0x006A9EC0); int num2 = ReadMemory(pid, (IntPtr)(num1 + 0x768)); intPtr = (IntPtr)(num2 + 0x5560); }
|
添加两个按钮,第一个按钮用来读取进程信息,第二个按钮用来修改阳光
1 2 3 4 5 6 7 8
| private void button1_Click(object sender, EventArgs e) { GetInfo(); } private void button2_Click(object sender, EventArgs e) { WriteMemory(pid, intPtr, sun); }
|
修改成功
关闭植物大战僵尸,重新打开,再次尝试.
修改成功.
优化
MemoryIO.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace PVZ_Cheater { static class MemoryIO { [DllImport("kernel32.dll", EntryPoint = "OpenProcess")] public static extern IntPtr OpenProcess(int desiredAccess, bool heritHandle, int pocessID); [DllImport("kernel32.dll", EntryPoint = "CloseHandle")] public static extern void CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory")] public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr baseaddress, IntPtr buffer, int nsize, IntPtr bytesread); [DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory")] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr baseaddress, long[] buffer, int nSize, IntPtr byteswrite); public static int GetPid(string ProcessName) { Process[] processes = Process.GetProcessesByName(ProcessName); if (processes.Length == 0) { throw new Exception("没有检测到游戏进程."); } if (processes.Length > 1) { throw new Exception("检测到多个进程,这可能是因为您开启了多个相同进程名的软件,请关闭多余软件."); } return processes[0].Id; } public static int ReadMemory(int pid, IntPtr toBase) { byte[] bytes = new byte[4]; IntPtr address = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0); IntPtr process = OpenProcess(0x1F0FFF, false, pid); ReadProcessMemory(process, toBase, address, 4, IntPtr.Zero); CloseHandle(process); return Marshal.ReadInt32(address); } public static void WriteMemory(int pid, IntPtr toBase, int num) { IntPtr process = OpenProcess(0x1F0FFF, false, pid); WriteProcessMemory(process, toBase, new long[] { num }, 4, IntPtr.Zero); CloseHandle(process); } } }
|
PVZInfo.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace PVZ_Cheater { static class PVZInfo { public const string ProcessName = "PlantsVsZombies"; public static int Pid; public static IntPtr Sun_Address; public static int Sun_Value = 9990; public static bool isReady = false; public static void GetInfo() { isReady = false; Pid = MemoryIO.GetPid(ProcessName); int num1 = MemoryIO.ReadMemory(Pid, (IntPtr)0x006A9EC0); int num2 = MemoryIO.ReadMemory(Pid, (IntPtr)(num1 + 0x768)); Sun_Address = (IntPtr)(num2 + 0x5560); isReady = true; } public static void SetSun() { if(isReady) MemoryIO.WriteMemory(Pid, Sun_Address, Sun_Value); } } }
|
增加一个Timer计时器,当选中"锁定阳光"选项时,每隔1秒将阳光赋值为9990,成功实现无限阳光