首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

游戏外挂原理剖析——以红警为例

一、前言

首先,感谢前辈巨细无遗的指南:https://www.cnblogs.com/viewll/p/4768880.html,这是本篇教程参考的重要依据。在此博客的基础之上,我额外完成了Windows动态库的编译,以及使用Python代码调用dll以实现目标功能。

看完本篇教程后,你就会明白看起来那么厉害的外挂,其实只是雕虫小技——尤其是在红警这个上古游戏上面,你可以轻而易举地修改它,不必完全弄懂那些高深的黑客知识。使用Python的原因很简单,就是要速战速决,以最少的代码量达到目的,但是会有一些小麻烦,我后面再说。

首先,我们来学习一下基础知识:

1、红警是一款C++编写的游戏,它的外挂无外乎是以下几种操作:修改内存对象的属性、直接调用游戏的某些函数、把一个写好的函数塞到程序的内存里然后调用它、篡改游戏的某些函数。其中第一种是最好理解的,不用多说。后面三种都对程序的代码段进行了hack,而且是在运行时修改的,这是如何办‍‍到‍的?请看下一条

2、Windows提供了一个dll,可以让其它程序访问以及修改另一个程序的内存,其中内置了很多函数,我们需要用的有这4个:ReadProcessMemory(读取进程内存)、WriteProcessMemory(写入进程内存)、CreateRemoteThread(创建远程线程)、VirtualAllocEx(开辟空白内存)

3、由于是在运行时修改游戏,我们在对进程的代码段进行操作的时候,都应当是编译好的机器码,但是为了方便读者理解,我不把机器码存储在脚本里,而是通过C++内联汇编编译成动态库,在要用的时候载入。

4、由于体系结构的原因,使用的Python解释器必须是32位的,dll也要编译为32位

5、本教程使用的工具有:ollydbg 2.0(查看游戏进程内存代码段)、Visual Studio 2017(编写VC++动态库)、PyCharm 2018(编写Python脚本)、Cheat Engine 6.7(查看游戏进程内存数据段)。

二、第一个功能

万事开头难,我们先做一个简单的功能:改钱,这个功能不需要写代码也能完成,因为我们只需要两步:把金钱内存地址找出来;然后把它的值改成一个你想要的数字。

打开Cheat Engine跟游戏,然后按照一个网上随处可找的教程完成附加进程,扫描金钱、修改金钱这三个操作。

看见左上角没有?

然后输入右上角的金钱数量,扫描

出来了三个地址,值都是一样的,其中只有一个修改后是有效的

改完第三个后,右上角变成了99999

好的,那么我们该怎么用代码实现这个操作?是不是也要照这个操作模拟一遍?先用ocr这种技术识别右上角的数字,然后在CE里面先扫描,再修改?

不必这么麻烦,因为这个地址,是可以用这种方法找到:

不知道读者有没有看懂这个表达式:[game.exe+635DB4]+24C,它的意思是首先找到game.exe在内存中的地址,它是程序的起始点;然后加上0x635DB4,这时你可以得到玩家对象地址的地址,然后取值,即可找到玩家对象的地址;这时候再加上0x24C,就找到了金钱的地址A;对A取值,即可以得到金钱大小,修改A这个位置的值,即可实现目标功能。也就是说,金额实际上是靠一个二级指针才找到的。

这里我们可以窥探操作系统课本里说的虚实地址映射是个什么逻辑了——这个游戏运行在我的电脑里,它很有可能占有的内存压根不是连续的,这俩数字635DB4跟24C不是实际的偏移量,你按这它们在我的内存条里按同样的方法找是不可能得到同样的结果的。

我们首先用Python定义三个函数:

GetValue跟SetValue逻辑很简单,参数是:游戏进程地址、基址、偏移量

先对游戏加上基址做一次取值,取来的值再加上偏移量才能作为要访问的地址

大家可以注意到,我的代码里有一处跟博客不同:基址要额外加上0x400000,这是我用ollydbg调试的时候发现的,我怀疑跟amd64兼容32位程序的方式有关,起始地址都要加上一个值为0x400000的偏移量,仅仅为猜测

GetProcess是使用win32api根据窗体获得进程地址的一个函数,很简单,适合于大部分的Windows程序的hack。

那么修改金钱就很简单了,三行代码搞定(我实现的是当前金钱+10000):

三、难度进阶

如果只要完成这种简单的功能,根本没必要写代码——用CE就够了,下面我们要挑战一些高难度的操作,以下功能是网上现存的某些红警外挂拥有的,比如:无视建造距离、单位直接升三星、获取任意单位控制权、获得间谍卫星。下面我们来一一实现。

首先总结一下这些功能的思路:

把内存中判断是否可以建造的函数改为return true

创建一个线程,调用单位对象上本来就有的函数,让它升级或者更改所属

创建一个线程,调用玩家对象自带的间谍卫星函数

下面说一下创建远程线程是一个什么操作:

首先大家应该明白线程模型:

仅仅在游戏外部修改程序内存的数据段,是很难有所作为的。必须有一个以程序自己人的身份,合法访问程序代码段的方法,这就是远程线程的意义:“混入内奸”。“内奸”可以任意调用程序自带的所有函数,它的运行是以创建一个线程的方法实现的,因此代码要预先编译为机器码,然后在进程内部进行一次内存申请以便写入,创建线程是最后一步。

这样,大家就能用这两个系统库函数自定义一个将机器码注入程序并创建线程的功能了:

首先我们用C++内联汇编定义了一个调用间谍卫星的函数:

地址依据可以在参考的博客里找到,我们使用VS 2017将其编译为动态库:

将得到的动态库与脚本放在一块,然后导入:

你想问我怎么调用?很简单:

就是从dll这个对象,直接用.访问函数名就行了,大家这下应该明白这到底是个什么过程了吧:VS 2017把内联汇编编译为机器码然后打包进了dll里,Python通过win32的库函数以C对象形式将dll加载进了解释器内存,然后在ra2进程内部申请了一块内存,把预先编译好的机器码写了进去,最后在此处创建了一个线程。这个线程在进程调度的某一个时机占据了处理机,执行了那5行汇编,不负所望地实现了预期功能。

下面两个功能是如法炮制:

C++内联汇编的用处第一次如此明了。

还有最后一个问题:更改建造距离是怎么实现的?如果你真的懂了远程线程的原理,即使参考博客里没有写,你也应该知道修改代码段就是这个问题的答案。这个操作非常大胆:

我要把那个函数的第一行改为return true;

事实上是行得通的,具体发生了什么,我们可以用ollydbg一探究竟:

大家请注意看0x004991D0这一行,它就是判断是否可以建造函数的首地址。忘了跟大家说了,为什么这一次倒数第二个参数要填12?意思是要在0x004991D0覆盖掉12字节的数据,也就是说我的两行汇编,编译的机器码占了12字节,一行6字节。现在是注入之前,此处不能建造。

现在是注入之后,可以随处建造,而且右边机器码的反汇编结果跟我之前写的内联汇编一模一样。

大功告成!

四、总结

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181028G1FIFO00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券