前面的文章給網游寫一個掛吧 – 啟動外掛上或給網游寫一個掛吧 – 啟動外掛下將外掛啟動后,那就可以進行讀寫游戲內存和調用游戲的一些操作了。
讀寫內存很簡單,如果是內掛的話,因為是運行在進程內,甚至都可以使用普通的指針操作就可以了,本文介紹調用游戲內部函數的方法,這里以WOW為例解釋調用過程,方法可以在WOW 3.x上使用,大家可以建個私服試試。
我們知道WOW里有很多的宏,玩家可以寫一些宏做一些輔助的操作,這些宏內部是使用Lua實現的,而Lua呢又可以調用C/C++函數來訪問WOW的內部數據。WOW里,玩家只能用公開的宏,還有些宏是WOW程序自用的,也就是說,WOW的程序員用C/C++實現了游戲的內核,然后再用Lua實現周邊的輔助功能,這些宏在游戲界面上是不允許被調用的。但那里有很多我們需要的功能……
C調用Lua函數
Lua和C是可以相互調用的,當然了,看完本系列文章以后,你甚至可以讓Lua、C#和C相互調用。Lua好像是基於堆棧虛擬機實現的,就是說操作數(oprand)都在堆棧上,Lua解釋器根據操作符的要求,在操作數堆棧上取相應數目的參數。比如說,要在C里調用下面這個Lua函數:
function f(x, y) return (x ^ 2 * math.sin(y)) / (1 – x) end |
那么在C里,調用的方法是:
1 2 3 4 5 |
lua_getglobal(L, "f"); lua_pushnumber(L, x); lua_pushnumber(L, y);
lua_pcall(L, 2, 1, 0); |
具體詳情和原理請參見:Lua文檔。當然要在C#里調用Lua函數的話,說白了就是將P/Invoke技術和上面的方法結合起來就可以了。
在C#里調用任意函數
在C/C++里是可以跳轉到任意地址執行代碼,只要將一個地址顯式轉換成函數指針再調用即可,那么在C#里,方法也是類似的:
1. 定義一個委托,即跟在P/Invoke里調用函數指針的方式是一樣。
2. 使用函數Marshal.GetDelegateForFunctionPointer將給定的地址(也就是一個數字)轉換成委托的實例。
3. 傳入參數調用即可。
如果要調用的不是現有函數的話,那么可以在進程里分配一段內存,使用Marshal類里的AllocHGlobal函數來分配內存,將我們要執行的代碼以機器碼的形式寫進去,然后通過上面講的方式調用。這里介紹一個庫,可以把上面的流程簡化,BlackMagic(如果找不到的話,可以聯系我要也行,不過我不一定總是回郵件的):
源碼下載:http://www.shynd.com/public/BlackMagic.1.1.source.rar
文檔下載:http://www.shynd.com/public/BlackMagic.1.0.Doc.zip
庫下載:http://www.gamedeception.net/threads/14468-BlackMagic-Managed-Memory-Manipulation
用BlackMagic,在進程里動態注入代碼並執行的方式如下面的代碼所示:
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 |
private void Synchronize() { while (_magic.ReadInt((uint)Offsets.LuaThreadLock) != 0) { Thread.Sleep(0); } ThreadSusspender.SuspendThread(MainThread); }
private void AsmUpdateCurrentManager() { _magic.Asm.AddLine("mov EDX, {0}", CurrentManager); _magic.Asm.AddLine("FS mov EAX, [0x2C]"); _magic.Asm.AddLine("mov EAX, [EAX]"); _magic.Asm.AddLine("add EAX, 8"); _magic.Asm.AddLine("mov [EAX], edx"); }
private void ResumeMainThread() { ThreadSusspender.ResumeThread(MainThread); }
public void DoString(string lua) { Synchronize(); uint codeCave = _magic.AllocateMemory(0x2048);
_magic.WriteASCIIString(codeCave + 0x1024, lua);
_magic.Asm.Clear(); AsmUpdateCurrentManager();
_magic.Asm.AddLine("push {0}", 0); _magic.Asm.AddLine("mov eax, {0}", codeCave + 0x1024); _magic.Asm.AddLine("push eax"); _magic.Asm.AddLine("push eax"); _magic.Asm.AddLine("call {0}", (uint) Offsets.LuaDoString); _magic.Asm.AddLine("add esp, 0xC"); _magic.Asm.AddLine("retn");
_magic.Asm.InjectAndExecute(codeCave); _magic.FreeMemory(codeCave); ResumeMainThread(); }
private uint CurrentManager { get { return _magic.ReadUInt(_magic.ReadUInt((uint)Offsets.ClientConnection) + (uint)Offsets.ClientManager); } } |
第26行代碼將主線程暫停,以便我們的線程修改進程的對象時,不會影響到主線程顯式的畫面。第27行在游戲進程里分配了2K字節的內存,29行將lua代碼拷貝到內存的后半段,31 – 32行做一些初始化的操作,比如清除上一次動態注入的匯編碼,32行是跟游戲相關的代碼,后面會提到。第34 – 40行插入要動態執行的代碼,而代碼的一些參數 – 主要就是數字。第42行注入和執行剛生成的代碼,第43行代碼在代碼執行完畢之后釋放內存,並在第44行恢復線程的執行。
而第47 – 53行代碼是獲取游戲(這里是WOW)里全局對象,因為WOW里可以從這個全局對象抓取到所有的數據 – 例如游戲的人物、障礙物之類的信息。
上面的代碼也演示了WOW里的一個技巧,Lua里有一個函數dostring,可以接受一段lua代碼的字符串並執行,演示的代碼也是調用lua里dostring函數的方法,通過lua dostring的方法可以繞過WOW對受限宏的調用控制,不過是在3.x上試的了,后面興趣點不在這里了,在4.x上就沒有看了。
文章寫到這里,只是把以前做內掛的技術講了一下,在國內的網站上基本上搜不到使用C#做內掛的方法,因此用幾篇文章的篇幅大概講講。內掛的缺點是,限制太多,因為是在游戲進程內加載東西,一不小心總是會被游戲檢測出來,而調用游戲內部現有函數(比如一些變態的功能)又會被服務器端檢測出封殺,所以后面會有一到兩篇文章講講做純外掛的方法,也就是不加載任何東西進入游戲進程。
如果大家對調試技術感興趣的話,可以考慮購買我的新書: 應用程序調試技術,這套視頻除了講解調試的技巧外,還盡量完整地講解了周邊用到的技術,這是因為調試技術要好的話,需要基礎功和背景知識扎實才行。