.Net 实现游戏修改器
前不久玩植物大戰僵尸,不停地玩啊玩,也通關了,準備開始享受一下IMBA的感覺?!巴嫱嫘∮螒颉蹦J街杏嘘P“誰笑到最后”,一來就有5000的陽光,隨你布置,布置完后開始攻擊,過關挺容易。但是畢竟5000的陽光可布置的植物有限,總覺得不過癮,于是找來《金山游俠》改陽光數量。好好享受了幾次imba的感覺。
不用說,我當然不甘心用別人的工具,我要自己來。我選擇.NET Framework 3.5作為該程序的實現平臺。
整個過程總結如下:
一.獲取具有窗體的進程集合
二.在所選進程的私有地址空間內查找數據
三.跟蹤所選進程的數據修改情況,獲得所要修改的數據的唯一地址
四.修改該地址中的數據內容
這就好辦了,思路有了,我們就根據思路來搜集和整理相關知識:
進程
進程只是個被動容器,其中包含了很多資源。
System.Diagnostics命名空間中的?Process類表示進程。Process類中的?GetProcesses()?方法可以獲取系統中所有進程。判斷MainWindowHandle?是否為空可以確定進程是否包含主窗體。
我當前的宿主OS是XP,在32位的?Windwos NT/2000/XP?中,進程地址空間有4GB,但卻只能訪問其地址空間底部的2GB,另外2GB留給內核模式相關的一些東西用。在這部分可訪問的2GB空間中,最低和最高的64KB不能訪問,于是可訪問的地址范圍是:0x00010000?到?0x7ffeffff。
我們還需要讀和寫進程的內存,System.Diagnostics.Process?提供的方法不能做到。還好,Windows的kernel32庫中的進程相關API可以幫到:ReadProcessMemory和?WriteProcessMemory,可以將他們表達為如下C#語句:
???? ???[DllImport("kernel32.dll", SetLastError =?true)]
????????static?extern?bool?ReadProcessMemory(
??????????IntPtr?hProcess,
??????????IntPtr?lpBaseAddress,
????????? [Out]?byte[] lpBuffer,
??????????int?dwSize,
??????????out?int?lpNumberOfBytesRead
???????? );
??????? [DllImport("kernel32.dll", SetLastError =?true)]
????????static?extern?bool?WriteProcessMemory(
????????????IntPtr?hProcess,
????????????IntPtr?lpBaseAddress,
????????????byte[] lpBuffer,
???? ???????uint?nSize,
????????????out?int?lpNumberOfBytesWritten);
名字很直觀:讀/寫進程內存。
兩個方法簽名基本相同,我這里簡單解釋一下:
hProcess?:進程句柄
lpBaseAddress:基地址,也就是起始地址(起始位置)
lpBuffer:從基地址起讀取或要寫入的內存值
nSize:讀取或寫入的數量,單位是字節
lpNumberOfBytesRead、?lpNumberOfBytesWritten:用作傳出,表示實際讀取或寫入的數量
好了!開始實戰吧!
創建一個C#的Windows forms項目
在窗體上我這樣布局:
?
為類添加如下幾個成員:
List<Process> _windowedProcesses =?new?List<Process>();//存放有窗體的進程集合
private?List<IntPtr> _addrList =?new?List<IntPtr>();//存放作為結果的地址列表
bool?isFirstSearch =?true;//是否是第一次搜索
Process?_selectedProcess;//所選進程
還要獲取有窗體的進程并列出來,讓使用者選擇需要的進程
????????private?void?RefreshProcessList()
??????? {
??????????? listBox1.Items.Clear();
??????????? _windowedProcesses.Clear();
??????????? textBox2.Enabled =?false;//在沒得到唯一的地址前不能寫入
????????????foreach?(var?p?in?System.Diagnostics.Process.GetProcesses())
??????????? {
????????????????if?(p.MainWindowHandle !=?IntPtr.Zero)//進程有窗口
??????????????? {
????????????????????if?(!string.IsNullOrEmpty(p.MainWindowTitle))//窗體名不為空。因為有些時候會有一些進程如iexplorer.exe?,它有窗口,但窗口沒名稱且沒顯示。所以應該排除一下
??????????????????? {
??????????????????? ????listBox1.Items.Add(p.MainWindowTitle);
??????????????????????? _windowedProcesses.Add(p);
??????????????????? }
??????????????? }
??????????? }
??????? }
?于是可以在我們的窗體裝載和單擊刷新按鈕時調用該方法
????????private?void?Form1_Load(object?sender,?EventArgs?e)
??????? {
??????????? RefreshProcessList();
??????? }
????????private?void?btnRefreshPList_Click(object?sender,?EventArgs?e)
??????? {
??????????? RefreshProcessList();
??????? }
為什么要區別是否是第一次搜索?因為第一次搜索是在整個進程可訪問內存范圍內查找,而之后的查找是基于第一次找到的地址。這樣做不是唯一的,但是最好的方法。
下面是搜索按鈕單擊的事件處理代碼:
????????private?void?button1_Click(object?sender,?EventArgs?e)
??????? {
????????????if?(_selectedProcess ==?null)?return;
????????????if?(isFirstSearch)
??????????? {
????????????????uint?baseAddr = 0x00010000;
????????????????uint?endAddr =?0x7ffeffff;
????????????????for?(uint?i = baseAddr; i < endAddr; i += (4 * 1024))
??????????????? {
????????????????????var?addrs = CreateAddrList(new?IntPtr(i),?int.Parse(textBox1.Text));
????????????????????if?(addrs !=null?)
??????????????????? _addrList.AddRange( addrs);
??????????????? }
??????????????? isFirstSearch =?false;
??????????? }
???????? ???else
??????????? {
??????????????? RefreshAddrList(int.Parse(textBox1.Text));
??????????? }
??????????? label2.Text =?"找到結果”+ _addrList.Count.ToString() +?"個";
????????????if?(_addrList.Count == 1)
??????????????? textBox2.Enabled =?true;
??????? }
很明顯CreateAddrList是游戲交易平臺第一次查找掉用的方法,RefreshAddrList是之后查找調用的方法。在第一次查找中,我們以4KB作一次跳躍。為什么查找的地址范圍如此本文開始已作說明,這里就不再贅述。
好了,現在來看看CreateAddrList方法:
????????private?List<IntPtr> CreateAddrList(IntPtr?baseAddr,?int?value)
??????? {
????????????int?bytesRead;
????????????byte[] buffer =?new?byte[4096];
????????????bool?ok;
????????????List<IntPtr> result =?new?List<IntPtr>();
??????????? ok = ReadProcessMemory(_selectedProcess.Handle, baseAddr, buffer, 4096,?out?bytesRead);
????????????if?(!ok)
????????????????return?null?;
????????????int?currentVal;
????????????for?(int?i = 0; i < 4096 - 3; i++)
??????????? {
?????????????? ?currentVal =?BitConverter.ToInt32(buffer, i);
????????????????if?(currentVal == value)
??????????????? {
????????????????????IntPtr?addr =?new?IntPtr(baseAddr.ToInt32() + i);
??????????????????? result.Add(addr);
??????????????????? i += 3;
???????? ???????}
??????????? }
????????????return?result;
??????? }
該方法用以創建地址列表。它接受2個參數,分別是基地址和要查找的值。
我們用ReadProcessMemory?一次讀取4KB的值,并把它存放在buffer中。由于buffer?是byte[]?,所以需要用BitConverter.ToInt32()把buffer中的一部分值轉成Int32以和要查找的值進行比對。
如果值匹配,則把對應地址添加到該方法的?result中以供方法返回。
接下來是RefreshAddrList方法:
.????????private?void?RefreshAddrList(int?value)
??????? {
????????????var?la = _addrList.ToList();
??????????? _addrList.Clear();
????????????byte[] buffer =?new?byte[4];
????????????int?bytesRead;
????????????foreach?(var?i?in?la)
??????????? {
??????????????? ReadProcessMemory(_selectedProcess.Handle, i, buffer, 4,?out?bytesRead);
????????????????if?(BitConverter.ToInt32(buffer, 0) == value)
??????????????????? _addrList.Add(i);
??????????? }
??????? }
因為要根據第一次查找的地址結果進行查找并要更新主地址列表,所以要用addrList.ToList()得到一份主地址列表的拷貝。接下來再在作為第一次搜索結果的地址表中查找新的值。如果等于之前的值的地址中的數據現在還等于新的值,那么就添加到地址列表。
?回看查找按鈕的事件處理代碼可以發現:反復多次,直到地址列表中只有一個地址時,就可以確定這就是我們要的地址,此時,我們就可以修改它了。
????????private?void?button2_Click(object?sender,?EventArgs?e)
??????? {
????????????int?value;
????????????if?(!int.TryParse(textBox2.Text,?out?value))
??????????? {
????????????????MessageBox.Show("輸入值太大!小心溢出!請重新輸入!");
????????????????return;
??????????? }
????????????var?buffer=BitConverter.GetBytes(value);
????????????int?bytesWritten;
??????????? WriteProcessMemory(_selectedProcess.Handle, _addrList[0], buffer, 4,out?bytesWritten);
??????? }
哈哈!這樣就完成了。按下F5我又IMBA了一回
該程序搜索值的數據類型是Int32 ,若我們要修改的程序的某個數據是以其他數據類型存儲的,則需要小修改下我們的修改器。
筆者水平有限,若有疑問或更好的建議,務必不吝賜教。
總結
以上是生活随笔為你收集整理的.Net 实现游戏修改器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android/安卓 点击按钮显示密码
- 下一篇: 欧拉梯形格式C语言,常微分方程数值解法