给网游写一个挂吧(一) – 反反外挂驱动的驱动
去年做了一些研究,研究做外挂的一些相关技术,打算放出来跟大家分享,分享一下我们做挂的一些思路,挂的原理,希望抛砖引玉。
外挂说白了就是用程序代替人去操纵游戏,模拟人向游戏程序发送键盘、鼠标消息。一般的流程就是:
1、 通过Windows系统的OpenProcess API打开目标进程 – 也就是游戏,以便能读写目标内存的数据,甚至是调用目标进程的函数,比如某些世界级的游戏里的Lua函数,或者游戏本身的C/C++函数。
2、 通过ReadVirtualMemory和WriteVirtualMemory来 读取和修改一些关键信息,比如说人物的生命值,人物的坐标,游戏里地图上各个元素等等。
3、 接着
a) 要么就是调用游戏里的一些函数,绕过游戏客户端的一些约束,调用角色里的一些非常强大的功能,从而快速杀怪。但这种方法的缺点很明显,服务器会做很多的检查判定你是挂从而封杀你。
b) 要么就是直接向游戏进程发送键盘、鼠标等消息,通过判定地图上的障碍物、怪物之类的东西过地图,当然杀怪也是通过模拟按键来完成。
然而随着外挂对游戏的破坏性影响,反外挂和外挂的反反外挂技术都已经在内核层缠斗了,比如:
1、 为了防止外挂调用Windows系统的一系列关键API(例如OpenProcess、Read/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
如果修改为1,BSOD(蓝屏)之后,完全内存保存在%windir%\memory.dmp
如果修改为3,BSOD(蓝屏)之后,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(null, null); } 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系统API。EasyHook的用法很简单:
1、 在包含Hook函数的托管DLL里,创建一个类,实现了EasyHook.IEntryPoint这个接口。
2、 在类的构造函数里建立与宿主进程的连接。
3、 然后在IEntryPoint.Run函数里,注册你的Hook,下面是以CreateFile这个系统API为例:
1、 public void Run(RemoteHooking.IContext InContext, String InChannelName)
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就是CreateFile在C#中的委托表现方式,因为是通过函数指针的方式执行的,因此会声明成一个委托。
4、 最后在外挂里,使用下面的代码注册Hook:
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的虚函数指针换成我们自己的,如下表的接口定义和替换方法:
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、 get; private set;
19、 }
20、 public D3D9.IDirect3DDevice9* NativeIDirect3DDevice9
21、 {
22、 get; private 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函数
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#做内挂的方法,因此用几篇文章的篇幅大概讲讲。内挂的缺点是,限制太多,因为是在游戏进程内加载东西,一不小心总是会被游戏检测出来,而调用游戏内部现有函数(比如一些变态的功能)又会被服务器端检测出封杀,所以后面会有一到两篇文章讲讲做纯外挂的方法,也就是不加载任何东西进入游戏进程。
如果大家对调试技术感兴趣的话,可以考虑购买我的新书: 应用程序调试技术,这套视频除了讲解调试的技巧外,还尽量完整地讲解了周边用到的技术,这是因为调试技术要好的话,需要基础功和背景知识扎实才行。