游戏逆向_Android读写游戏内容

  • Post author:
  • Post category:其他

一、背景

Android外挂的实现,需要涉及相应游戏内容的读写。读写的游戏内容包括代码和数据

针对不同的读写对象,通用的步骤就是寻找对象地址(位置)→获取相应权限→读写。下面将更详细介绍下相关实现。

二、实现方式

实现方式可以分为两大类:注入式和非注入式。

注入式:需要注入到相应游戏进程空间,常用方法是通过ptrace和zygote注入。

非注入式:不需要注入到游戏进程空间,通过Android系统机制从其它地方入手读写游戏内容。

根据需要读写游戏内容的性质,可以采用不同的方式。注入式适合读写动态数据,比如游戏过程中计算的中间变量(伤害等)、动态加载的属性(生命值等)、游戏状态(成功标志等)、动态编译代码(Unity、lua脚本等)。而非注入式比较适合修改静态代码、资源信息等(针对有反注入保护的游戏,非注入也可以用来获取动态数据)。

(一)非注入式:

1、/data/data/$PackageName私有文件夹下数据修改,比如修改lib目录下的SO(为游戏运行时实际加载SO库)。在获取ROOT权限后,可修改里面内容。比如要修改主逻辑模块,直接利用十六进制编辑器(UltraEdit等工具)修改保存。

2、直接利用APKTOOL等工具解密APK文件,可获取部分游戏资源信息。APK的常见文件目录介绍如下:

AndroidManifest.xml 每个应用都必须有的,包含包名、权限、Activity等相关信息;

META-INF 该目录下存放应用的签名信息;

Res 该目录下存放的是资源文件(图片字符串等);

Lib 该目录存放的是SO文件;

Assets 该目录存放的是配置文件(但是进程包含资源文件信息);

Classes.dex Java字节码文件(Java源码编译产出);

resources.arsc 编译后的二进制资源文件。

可以看出,这种方式可以获取到不少游戏内容。比如有些安全性差的lua引擎手游,其lua脚本会明文存放在Assets目录里面(通常命名有lua或者script相关字符)。又比如Unity游戏,如图1所示,其C#脚本编译后的DLL会存放在assets/bin/Data/Managed/Assembly-CSharp.dll里,可直接利用ILSpy等工具反编译的游戏脚本代码(关于Unity的辅助或破解版就有通过直接修改此文件内容实现功能)。
在这里插入图片描述
3、利用/proc文件系统,每个进程在该文件夹下都有相应的文件(提及进程了当然是在运行过程中才有),里面会包含一些重要文件,如图2所示。部分重要文件介绍如下:

/proc/$pid/cmdline 用于开始进程的命令 ;

/proc/$pid/cwd 当前进程工作目录的一个链接 ;

/proc/$pid/environ 可用进程环境变量的列表 ;

/proc/$pid/exe 正在进程中运行的程序链接;

/proc/$pid/fd/ 进程打开的每一个文件的链接;

/proc/$pid/mem 进程在内存中的内容;

/proc/$pid/stat 进程的状态信息;

/proc/$pid/statm 进程的内存使用信息;

/proc/$pid/maps 进程虚拟地址空间使用信息。

通过这些文件,可以获取相应的游戏信息。其中mem文件可获取到游戏的内存镜像,既是可通过修改该文件实现修改游戏进程空间内容目的。比如葫芦侠修改器既是通过这种非注入方式实现通用修改器功能(《Debug Hacks》里面有样例代码,如图3所示,直接使用read、write等函数即可读写进程内容。一般需要配合maps里面的内容获取模块基址信息。)
在这里插入图片描述
在这里插入图片描述
(二)注入式:

注入式的读写,目的性较强,既是事先已经通过静态或者动态调试确定了在特定位置可以获取到内容地址,然后需要在游戏动态运行过程中读写相应地址内容(涉及到内存的读写操作,比如memset、memcpy、mmap、strcpy等函数)。其具体实现方式如下:

1、在注入到游戏进程空间后,直接修改特定位置汇编指令,改变其对特定地址的读写逻辑。比如在游戏外挂中常用到的修改函数参数方法,就可以通过这个方式实现:分析定位到具体函数后,在其刚开始的汇编指令段找到个参数使用点,改变其赋值即可。如图4所示,某个游戏一个功能函数参数R1代表能量值,将红框地址的指令改写成FF25(MOVS R5, #0xFF)即可实现动态修改该函数参数为0xFF的目的(实现能量恒满的外挂功能)。

这种方式需要了解ARM、Thumb等汇编指令的常见读写指令。比如写操作,ARM汇编指令MOV R3, #0的Opcode为00 30 A0 E3。
在这里插入图片描述
2、上述方式其实是最基本的注入式写方式,拥有最简单的逻辑:静态分析到地址→改写页属性(mprotect函数改写属性)→直接修改内容。更进一步,遇到些读写操作需要占用大量字节的情况,无法在原有地址构造相应汇编指令实现,则需要HOOK技术。这里又分成两种:一种是利用MSHOOKFUNCTION、GOT表替换等实现函数地址替换,另一种是利用Inline Hook实现函数中间过程跳转。由于本文不是HOOK专题文章,故相对简单介绍下这些方式如下:

A、MSHOOKFUNCTION、GOT表替换等方式,可简单调用函数实现,较为方便。但是局限性较强,不能获取到函数中间变量数据,仅限于参数和返回值的读写。

B、Inline Hook方式,实现较为复杂,但是优势明显:可读写函数中间运算数据。比如游戏有个巨大的受击计算函数,里面涉及到伤害防御力等计算,伤害值仅是一个临时变量,这个时候,就需要静态分析出伤害值的存放位置(哪个位置哪个寄存器),然后利用Inline Hook读写相应数据即可。


版权声明:本文为douluo998原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。