如何打造一款游戲外掛
目錄
- 前言
- 1.內存數據排查
- 1.1 例-植物大戰僵屍
- 1.2 例-只狼
- 2.外掛程序編寫
- 3.掛機類自動外掛
- 4.網絡游戲外掛
前言
記得小學的時候玩頁游把家里電腦弄壞了(怎么回事,頁游還能弄壞電腦?),然后我爸拿去重新裝了系統,回來之后電腦里有了兩個新的單機游戲,植物大戰僵屍和大魚吃小魚。
那個年代周圍的小學生都是賽爾號/奧比島/洛克王國/天書奇譚/龍之刃/夢幻西游,玩摩爾庄園的小學生說出來都不是九年義務教育的。植物大戰僵屍雖然現在看來比較久遠,但他的游戲元素卻是推生出許多游戲音樂文化,比如Billie Eilish的《bad guy》,節奏簡直就是植物大戰僵屍的燈 燈 燈 燈 燈燈~
了。
當時因為生存模式被虐,在網上找了修改器,可以修改陽光點數,那時候雖然沒學計算機專業,但對修改器的運行機制還是有所猜測的,當時小學因為不知道內存這回事,所以是覺得修改的文件中的數據。
這個猜測如今看起來很搞笑,但也不失道理(要開始狡辯了),有些游戲的數據可能是先放在文件中的,在游戲啟動之后把文件中的數據讀取到游戲的內存區域,所以我們需要先修改數據所在的文件內容,然后啟動游戲,但是很多游戲的數據文件是加密的(出大問題,騎馬與砍殺就沒有加密),我們沒辦法從文件進行修改,所以需要讓游戲進程先把它讀到內存,然后從內存修改,然后再退出游戲的時候游戲進程自動把它保存下來,之后也就是這個數據了。
所以綜上可以發現有這幾類修改對象:
-
游戲數據在本地未加密文件,直接修改它來修改游戲數據
- 這一類的代表就是騎馬與砍殺,它的角色數據在文檔中,可以肆意修改玩家屬性,但是不要修改太大,因為數據會溢出。
此外它的游戲數據,如NPC對話,NPC姓名,國家名稱,城堡名稱都是明文給出的(還是用Excel保存的???),最可怕的是它的內部機制,如兵種上限/士氣/圍攻時間/酒館花費等都是明文的,怪不得騎砍會有各種版本。
- 這一類的代表就是騎馬與砍殺,它的角色數據在文檔中,可以肆意修改玩家屬性,但是不要修改太大,因為數據會溢出。
-
數據封裝,通過修改內存來修改游戲數據
-
網絡游戲,數據在對方服務器,無法直接修改,通過模擬網絡請求表單來模擬自己獲得游戲物品(看對方服務器邏輯嚴密程度了)
- 舉幾個例子(騰訊游戲全部忽略,這個不在新手教程里面),你正在玩一個網頁游戲龍之刃,打死了一個小怪,獲得了15銀幣,這時你的網頁需要給服務器發送給你銀幣數據+15的信息,你可以用瀏覽器自帶的工具開發者調試看看這個數據的格式,然后模仿一個相似的,發送的服務器,比較菜的游戲廠商當然無法完善這方面的邏輯了。(另外我有一個想法,steam在我們玩游戲的時候應該會有一個線程監控我們正在玩什么游戲,所以我們可以監控這個線程發出的網絡表單,然后我們自己不斷發出,這不是卡套刷滿?)
下面開始實戰部分,因為不想再下載騎砍,而且這種修改文件的教程在騎砍官網很多,所以我就跳過啦。
一. 內存數據排查
游戲作為進程運行在我們的電腦中,所以要修改它的數據我們可以在內存進行搜索修改。
拿植物大戰僵屍為例,我們要修改的是他的陽光數量。假如我們現在的陽光值是75,我們需要在內存中查找75的數字。
先用內存工具打開植物大戰僵屍的進程。
然后查找25這個數字,下圖只找到一個,所以陽光的數據就存儲在21BF10C8這個地址了,但很多游戲搜索出來可能不止這一個地址,我們需要將查詢的那個道具(陽光)花費掉一點,變成另一個值再次掃描一次才能精准定位。
修改這個地址的數值。
在游戲中查看陽光數量。
是不是太簡單了?! 這是因為這個游戲本身設計已經和這個時代脫軌了,如今的游戲制造商不僅要考慮內容,還需要在安全上和高玩斗智斗勇。
接下來再拿一個游戲練練手,就是最近非常火的宮崎英高老賊作品只狼。
Sekiro : Shadows Die Twice
作為一個九周目下忍,這款游戲真的讓我知道了死字怎么寫。
先來看看我當前的金幣,2323。
然后按照慣例選擇只狼的進程,搜索2323這個數據,可以發現出現了很多。
我去殺了一個小怪,把金幣加到了2360。
再次搜索發現還有5個地址存在,直接修改會發現不會改變,有一種猜測是它用了一種類似於病毒中多進程相互守護一樣的方法,會讓數據保持一致,所以我們可以直接全部選中同事修改,會發現第三個沒有修改成功,因為這個值不是背包中金幣數量,而是戰斗界面的數量,你可以自己試試。
金幣順利添加了兩萬,其它數據在本地的都是如此,可以自己嘗試,但這種方法效率總是不高,我們可以使用代碼的形式來把這個修改過程自動化。
這里多說一句,代碼的方式是把數據的地址編碼到代碼中,如果游戲的更新導致這個地址改變話,代碼里面也要同步修改,所以需要頻繁更新的游戲其實修改器很多會失效。
二. 外掛程序編寫
先來了解幾個Windows的API。
HWND FindWindow(LPCTSTR IpClassName,LPCTSTR IpWindowName); 通過類名或窗口名查找,返回窗口句柄 DWORD GetWindowThreadProcessId(HWND hWnd,LPDWORD lpdwProcessId); 得到窗口句柄后通過GetWindowThreadProcessId這個函數來獲得窗口所屬進程ID和線程ID HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId) 打開一個已存在的進程對象,並返回進程的句柄 bool WriteProcessMemory(HANDLE hProcess,LPVOID lpBaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesWritten ); 能寫入某一進程的內存區域。入口區必須可以訪問,否則操作將失敗
附上代碼:
#include <windows.h> #include <stdio.h> int main() { HWND h = ::FindWindow(NULL, "植物大戰僵屍中文版"); // 尋找並打開進程 DWORD processid; GetWindowThreadProcessId(h, &processid); HANDLE hprocess = 0; hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processid); if (hprocess == 0) { // 對應處理 printf("打開進程失敗!\n"); return 1; } else { printf("打開進程成功!\n"); DWORD hp = 3000; // 要修改的游戲數據最大值 LPCVOID addr = (LPVOID)0x21BF10C8; // 通過CE找到的游戲數據地址 DWORD res = WriteProcessMemory(hprocess, (LPVOID)addr, &hp, 4, 0); // 寫入內存修改游戲數據 return 0; } }
這樣一個代碼,可以讓我們隨時調用,把陽光修改為2000,這個程序沒有做圖形化界面,因為太簡單了,所以當做入門Demo吧。
我們修改了通過地址直接游戲的數據,那么能不能說做一個游戲助手,讀出游戲的某些數據來輔助玩家呢? CE工具雖然可以搜索讀取,但是效率太差,接下來介紹一個新的API,來讀取固定地址的數據。
HWND ReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); 根據進程句柄讀入該進程的某個內存空間lpBaseAddress的nSize字節,並寫入緩沖區lpBuffer,多次計算基址和偏移即可
嘗試讀取 :
附上代碼實現:
#include <stdio.h> #include <windows.h> int main() { HWND h = ::FindWindow(NULL, "植物大戰僵屍中文版"); // 尋找並打開進程 DWORD processid; GetWindowThreadProcessId(h, &processid); HANDLE processh = 0; processh = OpenProcess(PROCESS_ALL_ACCESS,FALSE,processid); if (processh == 0) { // 對應處理 printf("打開進程失敗!\n"); return 1; } else { printf("打開進程成功!\n"); int sun; // 用於存放陽光數據 LPCVOID mbase = (LPCVOID)0x1E0CF020; LPVOID mbuffer = (LPVOID)&sun; ::ReadProcessMemory(processh, mbase, mbuffer, 4, 0); printf("您有陽光:%d\n", sun); return 0; }
三. 掛機自動外掛
上述都是一些修改數據的外掛,此外還有一種是自動操作類的修改器,比如自動玩連連看(霧),自動下棋(霧),自動拼圖(笑)。
例如下面這樣的連連看,當你用CE觀察內存時,會發現它的數據奇妙在於這是一個二維數組,在內存中不同牌有不同數值,此處參考網上lgx大佬給出的方案。
想要自動消除連連看,肯定需要先模擬鼠標的操作,介紹一個API,它可以模擬鼠標操作,但不會移動我們的物理鼠標(虛無點擊?)。
LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam); 其中Msg為WM_LBUTTONDOWN,WM_LBUTTONUP,表示對鼠標的軟件模擬操作(雖然兼容性和安全性不如硬件模擬的mouse_event,但不改變移動鼠標指針)
除此之外,對一個二維數組尋找可以消除的兩幅牌,可以使用BFS算法實現。
void clearapair() { //找到可消去的兩個點並點擊 POINT p1, p2; int x1, y1, x2, y2; for (y1=0; y1<11; y1++) for (x1=0; x1<19; x1++) { if (!chessdata[y1][x1]) continue; for (y2=0; y2<11; y2++) for (x2=0; x2<19; x2++) if (chessdata[y2][x2] && (chessdata[y1][x1]==chessdata[y2][x2]) && (x1!=x2 || y1!=y2) ) { p1.x=x1; p1.y=y1; p2.x=x2; p2.y=y2; readchess(); if (llk_bfs(y1, x1, y2, x2) != -1) { click2p(p1, p2); return; } } } }
四. 網絡游戲修改
這個是我從小學四年級開始玩的游戲,因為時間很長沒有登錄,這里累積了很多經驗,在領取之前我們可以先觀察一下。
抓到了一條表單。
初步分析,經驗數據不是直接明文規定的,而是從了其他數值代替,並且獲取經驗這一類型也是有其他數字代替,這里初步猜測為age。
好吧,可能種類太多,抓不出來相同的Age。
但是我換了一種方式,對寵物使用了兩次經驗包,抓下來的兩次包,都是叫fcode1f3....的這個包。
其中Age類型也是一樣。
現在過了幾分鍾,我們把第三個包也使用掉。
囊達? 難道,這個Age真的是...(小聲bb)
還是不確定,讓我們用爬蟲來模擬這次表單吧,刷爆(封號)!!!
測試N次過后...
成功啦!!!
需要注意的是,這個游戲服務器以及幾年沒更新了,上一條公告還是十年前的。
如果你要使用爬蟲來模擬使用道具,非常關鍵的一個信息就是cookie,這是確認你登錄的賬號的判斷信息。
下面貼出代碼(我的cookie肯定要刪除掉啦)
import java.util.ArrayList; import java.util.List; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; public class Renren { private static String renRenLoginURL = "http://resourcemop.l.imop.com/res/fc/fcde1f39034b164a0d5fe7e455b0c32e"; private HttpResponse response; private CloseableHttpClient httpclient = HttpClients.createDefault(); private boolean login() { HttpGet httpGet = new HttpGet(renRenLoginURL); httpGet.setHeader("Accept-Ranges", "bytes"); httpGet.setHeader("Age", "74342"); httpGet.setHeader("Cache-Control", ""); httpGet.setHeader("Content-Length", "3266"); httpGet.setHeader("Content-Type", "text/plain"); httpGet.setHeader("Date", "Thu, 23 May 2019 08:41:58 GMT"); httpGet.setHeader("Expires", "Sun, 20 May 2029 08:41:58 GMT"); httpGet.setHeader("Last-Modified", "Wed, 22 May 2019 08:13:20 GMT"); httpGet.setHeader("Server", "Apache"); httpGet.setHeader("Via", "1.0 Static1.lzr.squid1:80 (squid/2.6.STABLE7)"); httpGet.setHeader("X-Cache", "HIT from Static1.lzr.squid1"); httpGet.setHeader("Referer", "http://s34.l.imop.com/"); httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"); try { response = httpclient.execute(httpGet); } catch (Exception e) { e.printStackTrace(); return false; } finally { httpGet.abort(); } return true; } public static void main(String[] args) { Renren renRen = new Renren(); renRen.login(); } }
這個其實是兩年前寫的人人網爬蟲,改了改表單數據就可以用啦。
篇幅有限,所以暫時就寫這么多。之后可能會繼續更新其他的修改教程,steam掛卡這個我是一定要寫的。
敲代碼是熱愛,敲到世界充滿愛! 撤退!