给网游写一个挂吧(二) – 启动外挂上
前面的文章给网游写一个挂吧– 反反外挂驱动的驱动,我们已经可以访问游戏的内存之后,接下来需要:
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更方便的东西了!
给网游写一个挂吧(二) – 启动外挂上相关推荐
- 给网游写一个挂吧(四) – 调用游戏函数
前面的文章给网游写一个挂吧 – 启动外挂上或给网游写一个挂吧 – 启动外挂下将外挂启动后,那就可以进行读写游戏内存和调用游戏的一些操作了. 读写内存很简单,如果是内挂的话,因为是运行在进程内,甚至都可 ...
- 给网游写一个挂吧(三) – 启动外挂下
前面的文章给网游写一个挂吧 – 启动外挂上介绍了输入法注入的方法,本文解释第二种方法. 有的游戏限制比较多,可能会将输入法注入也禁用掉--这个时候就需要另想方法了.其实我们的目的很简单,就是要让不知道 ...
- 给网游写一个挂吧(一) – 反反外挂驱动的驱动
去年做了一些研究,研究做外挂的一些相关技术,打算放出来跟大家分享,分享一下我们做挂的一些思路,挂的原理,希望抛砖引玉. 外挂说白了就是用程序代替人去操纵游戏,模拟人向游戏程序发送键盘.鼠标消息.一般的 ...
- 从头写一个Cucumber测试(二) Cucumber Test
转载:https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/cucumber-test-part-2 承接上文 前一篇博 ...
- 用Java帮我写一个手机投屏到电视上的应用
好的,我可以帮助你写一个用 Java 语言开发的手机投屏应用. 首先,你需要准备一台具备投屏功能的电视,并确保手机和电视处于同一网络内. 然后,你需要在你的手机应用中添加一个用于扫描局域网内可用电视的 ...
- 通过as写一个简单的按键_自己写一个简单的二维魔方游戏
周日下午一口气刷了4集<最强大脑>,很久不看综艺,发现这个节目还挺有意思的,清北这些大佬确实有过人之处.在看视频过程中印象最深的一幕莫过于北大王心冉对阵清华张洗月,她们比的题目是盲拧一个二 ...
- 我是如何学习写一个操作系统(二):操作系统的启动之Bootloader
前言 今天本来的任务看书和把之前写的FragileOS整理一下,但是到现在还在摸鱼,书也只看一点.后来整理了一下写这个系列的思路,原本的目的是对操作系统原理性的学习和对之前写的一个玩具型操作系统的回顾 ...
- 写一个参数返回二进制中1的个数
#include<stdio.h> int main() { int num; int s=0,yus=0,count=0; printf("请输入一个数字:" ...
- 从零写一个编译器(二):语法分析之前置知识
项目的完整代码在 C2j-Compiler 前言 在之前完成了词法分析之后,得到了Token流,那么接下来就是实现语法分析器来输入Token流得到抽象语法树 (Abstract Syntax Tree ...
最新文章
- failed to load external entity file:/C:/Users/fmm/.AndroidStudio3.4/config/options/updates.xml
- mysql 选择字符集 拉丁字符集_mysql的字符集
- resultType 和resultMap 的区别?
- 【右滑返回】滑动冲突 Scroller DecorView
- 浅析Serverless
- 长的帅不是你的错,长的没特点就不应该了
- UWP通过机器学习加载ONNX进行表情识别
- STM32学习1之ADC+DMA(使用定时器触发)
- 《SQL高级应用和数据仓库基础(MySQL版)》学习笔记 ·006【事务】
- 190124每日一句
- Spring源码之bean的初始化initializeBean方法解读
- 什么是Dao层、Entity层、Service层、Servlet层、Utils层?
- 根据JAVA实体生成SQL建表语句
- javascript之dom详细笔记加练习
- 关闭IDEA提示 empty tag doesn't work in some browsers(设置inspections)
- **懒得给孩子讲故事怎么办**
- html中图片一角的卷起效果,PS里怎么做照片边角卷起效果?
- 3-33在图 3-31中,以太网交换机有6个接口,分别接到5台主机和一个路由器。在下面表中的“动作”一栏中,表示先后发送了4个帧。假定在开始时,以太网交换 机的交换表是空的。试把该表中其他的栏目都填写
- ffmpeg 实现 视频与gif互转
- 【自然语言处理】词袋模型在文本分类中的用法
热门文章
- Shiro与Springboot整合:配置依赖改造登录方法
- 由控制台输入年龄-不同类型不能直接比较
- 单例设计模式-静态内部类-基于类初始化的延迟加载解决方案及原理解析
- 对于Dubbo一些自己的答案
- mysql5.7编译安装路径_MySQL_MySQL 5.5/5.6/5.7及以上版本安装包安装时如何选择安装路径,安装环境需求:
自从昨天安 - phpStudy...
- Java 函数式编程入门
- MongoEngine MongoDB 的 ORM 库
- Heartbeat VIP/IP 与 别名/辅助IP
- springboot的登录拦截机制
- 在Visual Studio的Server Explorer中怎样修改表名