抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

查找阳光地址

首先打开植物大战僵尸,进入游戏,初始阳光为50.
DearXuan
打开CE修改器,搜索50
DearXuan
进入游戏,种植向日葵,阳光变成0,再次搜索
DearXuan
再进入游戏,收取阳光,阳光变成25,搜索25
DearXuan
显然地址0x144344C8保存的就是阳光,现在退出游戏重新打开,重复上面步骤
DearXuan
现在地址变成0x1408EDC0了,说明阳光的地址并不是固定的。对这个地址进行检测,查看是哪条指令在修改
DearXuan
我刚刚进行了种植和收集阳光两个步骤,显然mov是减少阳光,add是增加阳光。查看详细信息
DearXuan
得到EAX = 0x14089860,这个0x5560其实就是二级偏移.然而EAX也是动态变化的,我们需要在内存中搜索EAX,来查找它到底保存在哪个地方
DearXuan
由于这个地址保存了阳光的地址,所以它应该是不变的,否则就找不到阳光的地址了,所以可以多次重复扫描,确保把会改变的量排除。


逐一查看哪个操作码访问了上面地址,发现地址0x028CA730很有趣
DearXuan
列表里清一色的都是0x768,证明[EAX + 0x028CA730]保存了结构体地址,查看EAX地址
DearXuan
这里不管是查看EAX还是ECX,结果肯定是一样的,因为它们都指向同一个地址,且偏移也相同。EAX = 0x028C9FC8,而0x768就是一级偏移。继续搜索EAX
DearXuan
列表里出现绿色的基址,查找结束。

内存读写

开始写代码,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#里
DearXuan
所以我们只要输入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;//进程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);
}

修改成功
DearXuan
关闭植物大战僵尸,重新打开,再次尝试。
DearXuan
修改成功.

优化

DearXuan
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,成功实现无限阳光
DearXuan

评论