Like Share Discussion Bookmark Smile

J.J. Huang   2019-12-16   Game Cheats   瀏覽次數:

遊戲輔助 | 植物大戰殭屍 (C# 陽光修改器)

工具準備

  • Cheat Engine
  • Visual Studio
  • Plants vs. Zombies

行前準備

輔助構思

  • 透過Cheat Engine抓取遊戲陽光基址與偏移量
  • 使用C#呼叫Win32API進行記憶體讀寫

找尋基址

  • 開啟Plants vs. Zombies主程式
  • 並開啟一場新遊戲至第一關卡

  • 開啟Cheat Engine
  • 選擇Plants vs. Zombies程序

  • 新的搜尋150

  • 回到Plants vs. Zombies
  • 點擊增加陽光或是消費陽光,讓其陽光的值變動
    -這邊示範消費陽光
  • 其陽光的值變成了50

  • 回至Cheat Engine
  • 再次搜尋50
  • 僅剩下一個地址062C7578 (每次都會不一樣)

  • 對該地址點擊兩下,新增至下方的作弊表內
  • Value進行修改為500
  • 此時可見Plants vs. Zombies的陽光也變更為500

註:至此找到的地址並非所謂的基址,所以再重新開啟遊戲或是新的一輪遊戲,陽光的地址就會改變。

  • 062C7578右鍵選擇Find out what writes to this address (找出是什麼改寫了這個地址)
  • 會開啟The following opcodes write to 062C7578視窗

  • 回到Plants vs. Zombies
  • 點擊增加陽光或是消費陽光,讓其陽光的值變動
    • 這邊示範增加陽光
  • 其陽光的值變成了525
  • 回至Cheat Engine
  • 可見The following opcodes write to 062C7578視窗內多一筆資料

  • 點擊該筆資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EAX=062C2018
EBX=00000001
ECX=00000019
EDX=D57D0002
ESI=0F5CA5E8
EDI=062C2018
EBP=0018F9D8
ESP=0018F968
EIP=00430A17

Probable base pointer =062C2018

00430A0B - and ecx,32
00430A0E - mov eax,[esi+04]
00430A11 - add [eax+00005560],ecx
00430A17 - mov ecx,[eax+00005560]
00430A1D - cmp ecx,00002706
  • 可以發現指針062C2018,偏移量00005560

  • 回至Cheat Engine
  • 勾選Hex
  • 新的搜尋,062C2018
  • 搜尋出一堆地址,此時請不要關閉該Cheat Engine

  • 回到Plants vs. Zombies

  • 選擇Menu->Restart Level

  • 此時可以發現作弊表內的062C7578修改value也不管用了

  • 新開啟一個Cheat Engine

  • 選擇Plants vs. Zombies程序

  • 新的搜尋150

  • 回到Plants vs. Zombies

  • 點擊增加陽光或是消費陽光,讓其陽光的值變動

    • 這邊示範消費陽光
  • 其陽光的值變成了50

  • 回至Cheat Engine

  • 再次搜尋50

  • 僅剩下一個地址0F38A5D8 (每次都會不一樣)

  • 關閉這個Cheat Engine

  • 回到最初的Cheat Engine
  • 點擊Add Address Manually
  • 輸入0F38A5D8,新增

  • 0F38A5D8右鍵選擇Find out what writes to this address (找出是什麼改寫了這個地址)

  • 會開啟The following opcodes write to 0F38A5D8視窗

  • 回到Plants vs. Zombies

  • 點擊增加陽光或是消費陽光,讓其陽光的值變動
    -這邊示範增加陽光

  • 其陽光的值變成了75

  • 回至Cheat Engine

  • 可見The following opcodes write to 0F38A5D8視窗內多一筆資料

  • 點擊該筆資料
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EAX=0F385078
EBX=00000001
ECX=00000019
EDX=D58E0002
ESI=0F657D88
EDI=0F385078
EBP=0018F9D8
ESP=0018F968
EIP=00430A17

Probable base pointer =0F385078

00430A0B - and ecx,32
00430A0E - mov eax,[esi+04]
00430A11 - add [eax+00005560],ecx
00430A17 - mov ecx,[eax+00005560]
00430A1D - cmp ecx,00002706
  • 可以發現指針0F385078,偏移量00005560

  • 回至Cheat Engine

  • 再次搜尋,0F385078

  • 結果地址剩下兩個0018926C0029A0E0

  • 此時可以判斷0029A0E0才是該程序的地址
    • 因Windwos 是 0x004之後開始
  • 對該地址點擊兩下,新增至下方的作弊表內

  • 0029A0E0右鍵選擇Find out what accesses to this address (找出是什麼訪問了這個地址)
  • 看到一堆東西

  • 找尋前面幾筆mov的做點擊
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EAX=00452650
EBX=0018FD00
ECX=00299978
EDX=00667BA0
ESI=0F385078
EDI=00299978
EBP=00000001
ESP=0018FC60
EIP=00452679

Probable base pointer =00299978

0045266A - cmp byte ptr [edi+000004CF],00
00452671 - je 0045269B
00452673 - mov esi,[edi+00000768]
00452679 - test esi,esi
0045267B - je 0045269B
  • 可以發現指針00299978,偏移量00000768

  • 回至Cheat Engine

  • 新的搜尋,00299978

  • 找尋一下結果地址表

  • 發現四個綠色的基址006A9EC0006A9F38006A9F78006AA00C

  • 針對這四個基址右鍵選擇Find out what accesses to this address (找出是什麼訪問了這個地址)

  • 發現只有前兩個006A9EC0006A9F38有一直被持續accesses

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EAX=00299978
EBX=FFFFFFFF
ECX=00000000
EDX=00000000
ESI=00000000
EDI=00000000
EBP=00000000
ESP=00189474
EIP=004662D1

Probable base pointer =006A9EC0

004662C8 - fstp dword ptr [esp]
004662CB - push eax
004662CC - mov eax,[006A9EC0]
004662D1 - mov eax,[eax+00000824]
004662D7 - push eax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EAX=00299978
EBX=00110206
ECX=00000113
EDX=00297E98
ESI=00299978
EDI=00000113
EBP=0018FBD8
ESP=0018FA48
EIP=0054E8BF

Probable base pointer =006A9F38

0054E8B6 - jmp 0054E8BA
0054E8B8 - mov edi,ecx
0054E8BA - mov eax,[006A9F38]
0054E8BF - cmp byte ptr [eax+00000503],00
0054E8C6 - je 0054E8F0
  • 可見006A9EC0006A9F38兩個都已經沒有偏移量

  • 此時手動添加地址,透過基址加上偏移量7685560(反向)

  • 可見最後指向地址都是0F38A5D8,且值就是當前Plants vs. Zombies的陽光值

  • 對其修改ValuePlants vs. Zombies亦會一起變動

  • 至此恭喜你找到陽光的基址與偏移量

註:即便重新開啟遊戲或是新的一輪都可以透過基址加上偏移量找到陽光的地址。

註:為什麼會找到兩個基址,我的猜測是因為我們在抓的時候,一個是新增陽光、一個是消費陽光。 (以上待驗證)

編寫輔助

  • 啟動Visual Studio
  • 新增專案 Visual C\# -> Windows 傳統桌面 -> Windows Forms App (.NET Framework)
    • 名稱PZCheat

  • 新增項目

  • Class類別
    • 名稱PZHelper

  • 新增以下代碼
    • 用於Call Win32 API DLL,針對指定程序進行記憶體讀寫
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PZCheat
{
class PZHelper
{
#region API

//從指定內存中讀取字節集資料
[DllImportAttribute("kernel32.dll", EntryPoint = "ReadProcessMemory")]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, IntPtr lpNumberOfBytesRead);

//從指定內存中寫入字節集資料
[DllImportAttribute("kernel32.dll", EntryPoint = "WriteProcessMemory")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, int[] lpBuffer, int nSize, IntPtr lpNumberOfBytesWritten);

//打開一個已存在的進程對象,並返回進程的句柄
[DllImportAttribute("kernel32.dll", EntryPoint = "OpenProcess")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

//關閉一個內核對象。其中包括文件、文件映射、進程、線程、安全和同步對象等。
[DllImport("kernel32.dll")]
private static extern void CloseHandle(IntPtr hObject);

#endregion

#region 使用方法

//根據進程名獲取PID
public static int GetPidByProcessName(string processName)
{
Process[] arrayProcess = Process.GetProcessesByName(processName);
foreach (Process p in arrayProcess)
{
return p.Id;
}
return 0;
}

//讀取內存中的值
public static int ReadMemoryValue(int baseAddress, string processName)
{
try
{
byte[] buffer = new byte[4];
//獲取緩沖區地址
IntPtr byteAddress = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
//打開一個已存在的進程對象 0x1F0FFF 最高權限
IntPtr hProcess = OpenProcess(0x1F0FFF, false, GetPidByProcessName(processName));
//將制定內存中的值讀入緩沖區
ReadProcessMemory(hProcess, (IntPtr)baseAddress, byteAddress, 4, IntPtr.Zero);
//關閉操作
CloseHandle(hProcess);
//從非托管內存中讀取一個 32 位帶符號整數。
return Marshal.ReadInt32(byteAddress);
}
catch
{
return 0;
}
}

//將值寫入指定內存地址中
public static void WriteMemoryValue(int baseAddress, string processName, int value)
{
try
{
//打開一個已存在的進程對象 0x1F0FFF 最高權限
IntPtr hProcess = OpenProcess(0x1F0FFF, false, GetPidByProcessName(processName));
//從指定內存中寫入字節集資料
WriteProcessMemory(hProcess, (IntPtr)baseAddress, new int[] { value }, 4, IntPtr.Zero);
//關閉操作
CloseHandle(hProcess);
}
catch { }
}

#endregion
}
}
  • 開啟Form1.cs[設計]
  • 新增LabelTextBoxButton

  • 雙擊Button建立Click事件
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
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PZCheat
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{

}
}
}

  • 撰寫程式
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
using System;
using System.Windows.Forms;

namespace PZCheat
{
public partial class Form1 : Form
{
//遊戲陽光基址
private int baseAddress = 0x006A9EC0;
//遊戲程序名字
private string processName = "popcapgame1";
//遊戲陽光地址
private int sunAddress;
//遊戲陽光數量
private int sunNum;

public Form1()
{
InitializeComponent();
//讀取 基址
sunAddress = PZHelper.ReadMemoryValue(baseAddress, processName);
//計算 陽光的地址 = 基址中的值 + 偏移量1
sunAddress = sunAddress + 0x768;
//讀取 基址2 中存放的值
sunAddress = PZHelper.ReadMemoryValue(sunAddress, processName);
//計算 陽光的地址 = 基址2中的值 + 偏移量2
sunAddress = sunAddress + 0x5560;
//當前陽光的值
sunNum = PZHelper.ReadMemoryValue(sunAddress, processName);
//將值顯示於label1
this.label1.Text = sunNum.ToString();
}

private void button1_Click(object sender, EventArgs e)
{
if (PZHelper.GetPidByProcessName(processName) == 0)
{
MessageBox.Show("遊戲沒有運行!");
return;
}
//給陽光地址中寫入數值,TextBox裡面的值
PZHelper.WriteMemoryValue(sunAddress, processName, int.Parse(this.textBox1.Text));
}
}
}
  • 運行測試

  • 至此恭喜你已經成功做出一個簡單的單機遊戲輔助程式了

註:如要分享.exe檔案,請到專案目錄下的Debug資料夾內,將其全部複製給別人即可。注意因為使用C#撰寫,對方的電腦要有安裝.NET Framework才可執行。

其他

以上只是非常陽春的修改器,主要是有個開始即概念;以下就是一些其他的思路可以先行嘗試做做看。

  • 主動開啟Plants vs. Zombies程式

    • 使用Process.Start()並取得pid
  • 無限陽光

    • 使用Timer持續修改陽光數值

註:以上參考了
植物大战僵尸修改器DIY(一)
植物大戰殭屍 (Plants vs Zomebies) 遊戲簡介、下載與完整攻略
C# 操作地址 从内存中读取写入数据(初级)