Bootstrap

PVZ逆向分析与C#内存操作(含源文件)

首先打开植物大战僵尸,进入游戏,初始阳光为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

[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就行了 

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

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;
}

定义全局变量

private const string ProcessName = "PlantsVsZombies";//进程名称
private const int sun = 9990;//每次修改的阳光数值

private int pid;//进程PID
private IntPtr intPtr;//阳光的地址

获取进程信息

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);
}

添加两个按钮,第一个按钮用来读取进程信息,第二个按钮用来修改阳光

private void button1_Click(object sender, EventArgs e)
{
    GetInfo();
}
private void button2_Click(object sender, EventArgs e)
{
    WriteMemory(pid, intPtr, sun);
}

修改成功

关闭植物大战僵尸,重新打开,再次尝试。

 修改成功,修改器制作完成。接下来是优化

MemoryIO.cs

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

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

 源文件:https://dearx.lanzoui.com/ibVLJrm95be

;