给网游写一个挂

来源:互联网 发布:工作报表软件 编辑:程序博客网 时间:2024/05/16 01:35

给网游写一个挂吧(一) – 反反外挂驱动的驱动

去年做了一些研究,研究做外挂的一些相关技术,打算放出来跟大家分享,分享一下我们做挂的一些思路,挂的原理,希望抛砖引玉。

 

外挂说白了就是用程序代替人去操纵游戏,模拟人向游戏程序发送键盘、鼠标消息。一般的流程就是:

1、 通过Windows系统的OpenProcess API打开目标进程 – 也就是游戏,以便能读写目标内存的数据,甚至是调用目标进程的函数,比如某些世界级的游戏里的Lua函数,或者游戏本身的C/C++函数。

2、 通过ReadVirtualMemoryWriteVirtualMemory 读取和修改一些关键信息,比如说人物的生命值,人物的坐标,游戏里地图上各个元素等等。

3、 接着

a)         要么就是调用游戏里的一些函数,绕过游戏客户端的一些约束,调用角色里的一些非常强大的功能,从而快速杀怪。但这种方法的缺点很明显,服务器会做很多的检查判定你是挂从而封杀你。

b)         要么就是直接向游戏进程发送键盘、鼠标等消息,通过判定地图上的障碍物、怪物之类的东西过地图,当然杀怪也是通过模拟按键来完成。

 

然而随着外挂对游戏的破坏性影响,反外挂和外挂的反反外挂技术都已经在内核层缠斗了,比如:

1、 为了防止外挂调用Windows系统的一系列关键API(例如OpenProcessRead/WriteVirtualMemory等),网游程序一般都会在游戏启动时在Windows操作系统上加载一个驱动程序,这个驱动程序的目的就是修改一系列关键API的代码,有时也会将游戏进程在系统中隐藏掉,从而使用户态的外挂和调试器无法访问游戏进程。

2、 为了防止外挂制作者通过内核调试技术来分析游戏对系统关键API的代码修改,反外挂驱动启动后一般都会禁用内核调试(反调试技术反调试技术二)。

3、 另外游戏程序也会屏蔽Windows系统用来向游戏进程发送键盘、鼠标等消息的函数。

4、 为了防止游戏里的关键数据被轻易修改,一般生命值这些东西游戏程序都会将其加密,使用一个加密函数去读写。

5、 另外为了防止外挂杀怪太强从而影响游戏的平衡,一般来说从游戏服务器端,一张地图里的怪物坐标、类型等数据,是一点点传到客户端的。

6、 还有很多……(我自己经验也不够,一时半会也枚举不完)。

 

那么做外挂的第一步,就是破解这个反外挂驱动,否则不仅没办法操控游戏进程,也没有办法使用调试器逆向分析游戏里的一些关键数据,于是通常的做法是:

1、 首先破解反外挂驱动的禁用内核调试的手段,一般都是通过Windbg和虚拟机用内核调试技术来完成的。

2、 然后找出反外挂驱动对系统API的更改。

3、 然后再写一个驱动程序,绕过反外挂驱动对系统API的更改。

 

但上面的方法有点累,因为破解禁用内核调试的手段就需要进行一些逆向了,而且还要对反外挂驱动逆向,比较麻烦。但是它并没有禁用本地内核调试功能和内存文件生成的功能,因此我们可以使用这种方式调试和开发驱动,简便程度比双机内核调试差一点点。

1、 使用本地内核调试:

windbg -kl

2、 生成内存(dump)文件:

修改\HKLM\SYSTEM\CurrentControlSet\Control\CrashControl里的:

AutoReboot

CrashDumpEnabled

         1 – 代表完全保存物理内存内容

         2 – ?

         3 – minidump

 

如果修改为1BSOD(蓝屏)之后,完全内存保存在%windir%\memory.dmp

如果修改为3BSOD(蓝屏)之后,minidump保存在%windir%\minidump\文件夹下面。

 

还有一个比较简单的方法,就是你在自己的破解驱动里,故意往SSDT写一些垃圾数据,然后导致系统BSOD(蓝屏),重启机器后用windbg打开dump打开memory.dmp文件,执行!analyze –v命令会自动给你显示所有被hook的函数和hook的地址、代码等等信息,这样可以直接做完第1和第2步。

 

最后剩下的编码就比较简单了,具体的代码请参看以前的博客:破解XXX游戏驱动保护过程总结、另外有个朋友的破解过程写的很详细,也建议大家看看:过 DNF TP驱动保护(一)

 

最后如果大家对调试技术感兴趣的话,可以考虑购买我的新书: 应用程序调试技术,这套视频除了讲解调试的技巧外,还尽量完整地讲解了周边用到的技术,这是因为调试技术要好的话,需要基础功和背景知识扎实才行。

 

 谢谢大家,未完待续……



给网游写一个挂吧(二) – 启动外挂上

前面的文章给网游写一个挂吧 – 反反外挂驱动的驱动,我们已经可以访问游戏的内存之后,接下来需要:

1.         找到游戏里关键元素的偏移量,比如生命值的内存的位置。一般来说,大部分大型3D游戏都是用C++编写的,游戏里面的元素都是面向对象的,比如玩家是一个对象,那么生命值、魔法值之类的东西都是这个对象的一个属性。按照C++的内存布局,一般来说,只要源代码里的结构体不发生变化,属性的偏移量一般来说都是一样的。

2.         找到游戏里一些关键函数的地址,便于外挂程序来调用。

 

查找关键元素的偏移量和关键函数地址一般来说都是苦力活,当然也是智力活,需要你的逆向工程水平不错,网上有些相关的教程,这里我就不再详述了。

 

这里假设我们已经找到游戏的偏移量了,现在的问题是如何启动外挂以操控游戏,一般来说有几种选择:

1.         要么是内挂,将挂注入到网游进程的内存空间里,这样挂就相当于网游自己的一个组件,对游戏进程拥有绝对的访问权,可以读写游戏的虚拟内存地址以及调用游戏内置的函数。这种做法的弊端是,如果游戏有非法组件检测线程的话,很有可能被发现。

2.         要么是外挂,将挂作为一个独立的进程,这样挂可以通过Read/WriteVirtualMemory来读写游戏的内存,再通过CreateRemoteThread API启动一个远程线程来调用游戏内置的函数。这种做法可以查看文章:代码注入的三种方法。

 

那本文我们讲解第一种方法 - 内挂。并针对两款游戏来说说注入内挂的方法:

 

DNF – 使用输入法注入技术

输入法注入技术的原理是,写一个输入法DLL并在系统中注册,然后向游戏发送一个切换输入法的消息 – 当然是切换到我们写的输入法,Windows会加载我们的输入法DLL,在这个DLL的DllMain函数里,我们就可以完成一些内挂加载以及初始化的工作:

1.         首先写一个输入法DLL,随便从网上下载一个示例用的输入法源码即可。

2.         在输入法DLL的DllMain函数的DLL_PROCESS_ATTACH事件中,启动外挂线程。

3.         在单独的外挂进程里 – 一般来说这个进程就是用来给外挂用户操作的一个Windows GUI程序,在合适的地方:

a)         用imm32.dll里的ImmInstallIMEw API函数在系统里注册我们的输入法。

b)         用FindWindows API查找到所有需要注入的窗口,这里就是获取DNF的窗口句柄。

c)         最后用PostMessage WM_INPUTLANGCHANGEREQUEST消息强迫Windows针对DNF窗口切换我们的输入法,从而达到加载内挂的目的。

 

关键代码如下 – 整个程序大部分代码都是用C#完成,稍后介绍选用C#的原因:

 

输入法注入代码C#部分:

 

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

public bool InjectDllToWindow(string dllPath, string windowText = "地下城与勇士",

string classText = "地下城与勇士",bool IfMonitor=true)

{

    InjectedDll = dllPath;

    WindowText = windowText;

    ClassText = classText;

 

    // 1, 注册输入法

    HKL = RegisterIME();

    if (HKL == IntPtr.Zero)

    {

        MessageBox.Show(string.Format("GetLastError: {0}", GetLastError()));

        //2,如果注册失败,检查是否已经被注册

        HKL = MImeFindByName();

    }

 

    if (HKL == IntPtr.Zero)

    {

        isRegister = false;

        return false;

    }

    isRegister = true;

 

    //3,把需要注入的dll传递给服务输入法dll

    IMESetPubString(dllPath, 0, 0, 0, 0);

 

    //4,查找所有需要注入的窗口

    List<IntPtr> windowsToInject = FindWindows(classText, windowText);

 

    //5,注入输入法到窗口

    foreach (IntPtr window in windowsToInject)

    {

        InjectToWindow(window);

    }

 

    WindowsHaveInjected = windowsToInject;

 

    if(IfMonitor)

    {

    //6,开启监视线程,监视新的窗口,一旦开启,立刻注入

        WorkThread thread = new WorkThread(MonitorDNFWindow);

        workThreadAsyncResult = thread.BeginInvoke(nullnull);

    }

    return true;

}

 

private IntPtr RegisterIME()

{

    string tempDir = Environment.CurrentDirectory;

    Environment.CurrentDirectory = Environment.SystemDirectory;//把工作目录切换到系统目录

    IntPtr hkl = ImmInstallIMEW(ImeName, ImeFriendlyName); //安装服务输入法

    Environment.CurrentDirectory=tempDir; //切换回原目录

    return hkl;

}

 

private void InjectToWindow(IntPtr hWnd)

{

    PostMessage(hWnd, WM_INPUTLANGCHANGEREQUEST, (IntPtr)0x01, HKL);

}

 

在上面第9行调用47 – 54行的函数,将外挂的工作目录切换到系统目录,因为我们将输入法放到系统目录,方便系统查找,并安装输入法。

 

第25行里,设置在输入法注入成功后,需要执行的操作,一般来说就是启动挂了。有些内挂会在注入成功后,注册一个快捷键,通过快捷键呼出一个窗口,这个窗口可以用来跟用户操作界面通信,执行操作界面来的命令。然而,在某些游戏里,呼出的窗口会马上被检查到,我们这里将介绍在游戏进程里启动.NET程序,启动一个.NET Remoting服务的方式。

 

第31 - 34行,通过FindWindows系统调用枚举系统上的窗口,找到目标窗口,执行注入操作,具体的注入操作参见56 – 59行的代码。在.NET代码里调用C/C++函数的方式,请参阅文章:使用Signature Tool自动生成P/Invoke调用Windows API的C#函数声明。

 

输入法C++部分关键代码:

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

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)

{

   switch(fdwReason)

    {

      case DLL_PROCESS_ATTACH:

    if (CilentDLL==NULL)

    {

          if (lstrlen(g_IMEDLLString)>0)

          {

              StartTheDotNetRuntime();

          }

    }

          break;

      case DLL_THREAD_ATTACH:

         break;

      case DLL_THREAD_DETACH:

         break;

      case DLL_PROCESS_DETACH:

        break;

      default:

        break;

    }

    return true;

}

 

DWORD CALLBACK StartTheDotNetRuntime(LPVOID lp)

{

        HRESULT hr = S_OK;

        ICLRMetaHost    *m_pMetaHost = NULL;

        ICLRRuntimeInfo *m_pRuntimeInfo = NULL;

        ICLRRuntimeHost    *pClrHost = NULL;

      

hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*) &m_pMetaHost);

if (hr != S_OK)

    return hr;

hr = m_pMetaHost->GetRuntime (L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*) &m_pRuntimeInfo);

if (hr != S_OK)

    return hr;

hr = m_pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*) &pClrHost );

       if (FAILED(hr)) return hr;

 

    hr = pClrHost->Start();

 

    DWORD dwRet = 0;

    hr = pClrHost->ExecuteInDefaultAppDomain(

        g_IMEDLLString,

        _T("ManagedDll.Program"), _T("Start"), _T("nothing to post"), &dwRet);

 

    hr = pClrHost->Stop();

 

    pClrHost->Release();

 

    return S_OK;

}

 

在第10行代码,输入法注入成功后在游戏进程里启动.NET虚拟机,这里启动的4.0的运行库 – 参看36行代码,虚拟机成功启动后,会返回一个ICLRRuntimeHost的COM接口,根据这个接口,外挂就可以创建托管代码运行需要的应用程序域 – 参看45 – 47行。在应用程序域里执行代码并不需要一个.exe的可执行文件,只需要是一个托管程序的DLL文件,这个DLL文件需要放在游戏的目录里,因为我们的挂是运行在游戏的进程里,工作目录也自然变成了游戏的工作目录了。

 

在47行,我们可以看到,可以指定DLL内部任意一个类型的静态函数作为入口点,下面是ManagedDll.Program.Start的源代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

namespace ManagedDll

{

     public class Program

    {

        static int Start(string argument)

        {

            RemotingServer.Start();

            while (true)

            {

                Thread.Sleep(1000);

            }

            return 0;

        }

    }

}

 

在第7行,我们启动了一个.NET Remoting服务(或者说是web服务,因为.NET Web服务本来就是基于Remoting的),等待任意一个地方的Remoting客户端链接……对于在非托管进程当中启动托管程序的方法,详情请参看:将托管dll注入到非托管进程中。

 

设计考量

 

之所以选用C#的原因是:

1.         可以快速编程,而且有丰富的类库。

2.         垃圾回收机制可以增强挂的稳定性,而且也不用考虑内存泄露的问题。

3.         有很强大的调试工具,以我经验来看,暂时还没有看到比VS更强大的调试工具。

4.         最后,在游戏进程里的内挂和外部供用户配置的GUI程序需要通信,没有比.NET Remoting更方便的东西了!

 


最后如果大家对调试技术感兴趣的话,可以考虑购买我的新书: 应用程序调试技术,这套视频除了讲解调试的技巧外,还尽量完整地讲解了周边用到的技术,这是因为调试技术要好的话,需要基础功和背景知识扎实才行。
 
未完待续……



给网游写一个挂吧(三) – 启动外挂下

前面的文章给网游写一个挂吧  启动外挂上介绍了输入法注入的方法,本文解释第二种方法。

 

有的游戏限制比较多,可能会将输入法注入也禁用掉……这个时候就需要另想方法了。其实我们的目的很简单,就是要让不知道我们挂存在的游戏,在某个时刻将挂作为游戏的一个组件加载进来。输入法注入是操作系统强制塞给游戏的,当然游戏有权利选择不要。那么我们可以用暴力解决,强制游戏加载外挂:

1.         比如利用缓冲区溢出漏洞(参考文章 如何利用缓冲区溢出的程序错误来运行黑客程序  如何利用缓冲区溢出的程序错误来运行黑客程序(续))。

2.         还有就是在游戏每次都会执行的函数上挂个钩子 ,但是一般的Windows钩子都会被游戏禁用掉……而本文的方法是Hook DirectX EndScene函数,即游戏在绘图结束后调用的函数,而且游戏会在一秒内经常调用这个函数,简直就是把它当消息队列使!

 

WOW – 使用DirectX EndScene注入技术

这个方法可以用在WOW 3.3.5.13930上,现在已经不行了,有兴趣的朋友可以自己搭一个3.3.5.13930的私服试试。据说有很多方法可以注入(DirectX EndScene函数在d3d9.dll文件中):

1、 在游戏文件夹里放一个d3d9.dll,因为Windows是先搜索游戏的工作目录再查找system32文件夹的,所以会加载到自定义的d3d9.dll

2、 直接在游戏启动前把system32文件夹中的d3d9.dll换成自己的。

3、 使用IDA直接获取EndScene的地址,并且在游戏启动后,修改这个地址的汇编码,使其先调用我们的函数,再由我们的函数将控制权交还给真实的EndScene程序。

4、 在外挂里启动游戏,启动时先将游戏进程暂停,执行一系列Hook操作:

a)         Hook LoadLibrary以便在游戏加载d3d9.dll的时候;

b)         Hook d3d9.dll里的Direct3DCreate9函数,

c)         再通过Hook过的Direct3DCreate9函数获取D3D9的指针,

d)         D3D9指针处Hook D3D9->CreateDevice函数,以获取指向设备的指针Device

e)         再从Device指针处Hook Device->EndScene函数。

5、 Windows操作系统里创建一个自己的Device

6、 或者就是使用SetWindowsHookEx API安装一个系统级别的Hook,然后我们的外挂就会被加载进每一个进程!参考文档:http://www.woodmann.com/forum/archive/index.php/t-11023.htm

 

这里我只用过第4种方法,因此本文也只介绍第4种方法,这里我们用到EasyHook这个库,这个库允许我们使用C#代码Hook系统APIEasyHook的用法很简单:

1、 在包含Hook函数的托管DLL里,创建一个类,实现了EasyHook.IEntryPoint这个接口。

2、 在类的构造函数里建立与宿主进程的连接。

3、 然后在IEntryPoint.Run函数里,注册你的Hook,下面是以CreateFile这个系统API为例:

 1、    public void Run(RemoteHooking.IContext InContext, String InChannelName)

复制代码
2、    {
3、       CreateFileHook = LocalHook.Create(
4、           LocalHook.GetProcAddress("kernel32.dll""CreateFileW"),
5、           new DCreateFile(CreateFile_Hooked),
6、           this);
7、    
8、       CreateFileHook.ThreadACL.SetExclusiveACL(new Int32[] {0});
9、    }
复制代码

 

在第5行里,那个DCreateFile就是CreateFileC#中的委托表现方式,因为是通过函数指针的方式执行的,因此会声明成一个委托。

4、 最后在外挂里,使用下面的代码注册Hook

复制代码
1、 static void Main(string[] args)
2、 {
3、     Config.Register(
4、     "A FileMon like demo application.",
5、     "FileMon.exe",
6、     "FileMonInject.dll");
7、 
8、     RemoteHooking.IpcCreateServer<FileMonInterface>(
9、         ref ChannelName, WellKnownObjectMode.SingleCall);
10、 
11、    RemoteHooking.Inject(
12、        Int32.Parse(args[0]),
13、        "FileMonInject.dll",
14、        "FileMonInject.dll",
15、        ChannelName);
复制代码

16、 } 


5、 代码里,还有一个关键的地方,就是Hook后获取的指针是一个COM接口,即拿到的是一个虚函数表,因此在Hook EndScene方法的时候,就是把这个COM接口的EndScene的虚函数指针换成我们自己的,如下表的接口定义和替换方法:

复制代码
1、    public unsafe class D3D9
2、    {
3、        [StructLayout(LayoutKind.Sequential, Pack = 4)]
4、        public struct IDirect3DDevice9
5、        {
6、            public IntPtr** VFTable;
7、        }
8、    }
9、    public IDirect3DDevice9(D3D9.IDirect3D9* InNativeIDirect3D9, 
10、    D3D9.IDirect3DDevice9* InNativeIDirect3DDevice9)
11、    {
12、    NativeIDirect3D9 = InNativeIDirect3D9;
13、    NativeIDirect3DDevice9 = InNativeIDirect3DDevice9;

14、    OverrideFunctions();
15、    }

16、    public D3D9.IDirect3D9* NativeIDirect3D9 
17、    { 
18、    getprivate set
19、    }

20、    public D3D9.IDirect3DDevice9* NativeIDirect3DDevice9 
21、    {
22、    getprivate set
23、    }

24、    private void OverrideFunctions()
25、    {
26、    OriginalEndScene = NativeIDirect3DDevice9->VFTable[0][42];

27、    RealEndScene =
28、    (DelegateEndScene)Marshal.GetDelegateForFunctionPointer(
29、    OriginalEndScene, typeof (DelegateEndScene));

30、    MyEndScene = EndScene;
31、    IntPtr PointerToMyEndScene = Marshal.GetFunctionPointerForDelegate(MyEndScene);

32、    NativeIDirect3DDevice9->VFTable[0][42] = PointerToMyEndScene;
33、    }

34、    public uint EndScene(D3D9.IDirect3DDevice9 Device)
35、    {
36、    // 防止多线程访问
37、    lock (LuaInterface.dataLock)
38、    {
39、    // 先做我们自己的事情,然后再将控制权转移给真正的EndScene函数

40、    return RealEndScene(Device);
41、    }
复制代码

42、    } 


比如在第37 – 41行,就是在EndScene调用的时候,先做我们的事情,然后再把控制权交给真正的EndScene。而第26行,EndScene函数IDirect3DDevice9的第42个函数,因为在第3行,代码已经将IDirect3DDevice9接口(实际就是一个虚函数表)当成一个普通的C/C++结构体处理 – 而且是32位机上的结构体(如果要支持64位改一下就可以了),而第6行代码,就是把这个虚函数表当作一个普通的数组处理。不过据说在DirectX10里已经把EndScene去掉了……

 

这种方式,网上已经有完整的源代码,请在此下载(这个代码跟本文讲解使用的代码是不同的,因此有兴趣的朋友可以自行研究下面的代码):

https://github.com/spazzarama/Direct3DHook

 

EasyHook的使用方式和详细原理,请参考文档:

http://www.codeproject.com/Articles/27637/EasyHook-The-reinvention-of-Windows-API-hooking 

 

最后如果大家对调试技术感兴趣的话,可以考虑购买我的新书: 应用程序调试技术,这套视频除了讲解调试的技巧外,还尽量完整地讲解了周边用到的技术,这是因为调试技术要好的话,需要基础功和背景知识扎实才行。
 

未完待续…… 



给网游写一个挂吧(四) – 调用游戏函数

前面的文章给网游写一个挂吧  启动外挂上给网游写一个挂吧  启动外挂下将外挂启动后,那就可以进行读写游戏内存和调用游戏的一些操作了。

 

读写内存很简单,如果是内挂的话,因为是运行在进程内,甚至都可以使用普通的指针操作就可以了,本文介绍调用游戏内部函数的方法,这里以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#做内挂的方法,因此用几篇文章的篇幅大概讲讲。内挂的缺点是,限制太多,因为是在游戏进程内加载东西,一不小心总是会被游戏检测出来,而调用游戏内部现有函数(比如一些变态的功能)又会被服务器端检测出封杀,所以后面会有一到两篇文章讲讲做纯外挂的方法,也就是不加载任何东西进入游戏进程。

 

如果大家对调试技术感兴趣的话,可以考虑购买我的新书: 应用程序调试技术,这套视频除了讲解调试的技巧外,还尽量完整地讲解了周边用到的技术,这是因为调试技术要好的话,需要基础功和背景知识扎实才行。


0 0