給網游寫一個掛吧(四) – 調用游戲函數


前面的文章給網游寫一個掛吧 啟動外掛上給網游寫一個掛吧 啟動外掛下將外掛啟動后,那就可以進行讀寫游戲內存和調用游戲的一些操作了。

 

讀寫內存很簡單,如果是內掛的話,因為是運行在進程內,甚至都可以使用普通的指針操作就可以了,本文介紹調用游戲內部函數的方法,這里以WOW為例解釋調用過程,方法可以在WOW 3.x上使用,大家可以建個私服試試。

 

我們知道WOW里有很多的宏,玩家可以寫一些宏做一些輔助的操作,這些宏內部是使用Lua實現的,而Lua呢又可以調用C/C++函數來訪問WOW的內部數據。WOW里,玩家只能用公開的宏,還有些宏是WOW程序自用的,也就是說,WOW的程序員用C/C++實現了游戲的內核,然后再用Lua實現周邊的輔助功能,這些宏在游戲界面上是不允許被調用的。但那里有很多我們需要的功能……

 

C調用Lua函數

LuaC是可以相互調用的,當然了,看完本系列文章以后,你甚至可以讓LuaC#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代碼的字符串並執行,演示的代碼也是調用luadostring函數的方法,通過lua dostring的方法可以繞過WOW對受限宏的調用控制,不過是在3.x上試的了,后面興趣點不在這里了,在4.x上就沒有看了。

 

文章寫到這里,只是把以前做內掛的技術講了一下,在國內的網站上基本上搜不到使用C#做內掛的方法,因此用幾篇文章的篇幅大概講講。內掛的缺點是,限制太多,因為是在游戲進程內加載東西,一不小心總是會被游戲檢測出來,而調用游戲內部現有函數(比如一些變態的功能)又會被服務器端檢測出封殺,所以后面會有一到兩篇文章講講做純外掛的方法,也就是不加載任何東西進入游戲進程。

 

如果大家對調試技術感興趣的話,可以考慮購買我的新書: 應用程序調試技術,這套視頻除了講解調試的技巧外,還盡量完整地講解了周邊用到的技術,這是因為調試技術要好的話,需要基礎功和背景知識扎實才行。  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM