经过近几年游戏市场的变迁,手游市场也在飞速发展。同时手游本身的安全风险也逐渐暴露出来。无恒实验室也在承担着手游安全评审的相关工作,上期我们分享了游戏安全评审的技术进阶历程。2020年市场上重度手游的不断推出,游戏外挂的风险更是与日俱增,无恒实验室也加入到反外挂的战场。外挂分析作为反外挂的第一步,分析的深度、质量和时效,又往往对外挂打击起着决定性的作用。
本文从外挂分类讲起,给大家一个初步感性认知,之后对占比高达90%以上的内存修改挂的快速分析技巧进行详细介绍。
一、外挂分类
2020年伊始,外挂情报同学收集了不同游戏大量的外挂样本,从技术实现上大概分为以下几类
-
定制挂:针对特定游戏逻辑或数据特征,通过直接修改客户端逻辑、数据或读取游戏核心数据并展示,以实现游戏作弊功能,常见的有下几类
-
root、越狱类注入型外挂
-
基于应用多开形式的外挂
-
基于vmos、光速虚拟机等虚拟机挂
-
基于windows+模拟器类型的外挂
-
-
通用修改器:具备内存查找修改功能的通用或者自定义作弊工具,比如gameguardian、igg、ce、葫芦侠等
-
脚本辅助类:通过录制玩家操作反复重放,或通过取色点识图等方式进行自动操作的辅助程序。比如按键精灵、叉叉助手等
-
破解版:修改游戏客户端逻辑、数据、资源,重打包形成具备一定作弊功能的非法客户端,常见于单机休闲类游戏。
尽管技术表现形式多种多样,但从原理上无外乎内存修改、函数调用、模拟点击、协议模拟,其中尤以内存修改类外挂占比居多,不完全统计内存修改类可占到90%以上的比例。
二、内存修改挂分析思路
内存修改主要包括代码、数据、资源、显存修改外挂,分析主要有三步骤
-
确定被修改内存的类型、修改前后的数据,可能存在多处修改。如果直接命中修改代码段则大概率即是外挂功能与此代码实现有关,可省略以下步骤。
-
过滤筛选有效内存修改:通过还原内存修改位置,逐步排除无效的内存修改点。
-
确认外挂原理:根据不同的游戏引擎不同的实现方式,实现方法不同,不过思想是一致的,即通过监控游戏内存对象的分配释放,搜索第二步得到的内存地址来精确匹配修改的内存对象即可。
高质量的外挂分析,既需要知道外挂做了什么,同时也应该分析清楚外挂为什么这么做,搞清楚外挂功能的内在原理,对游戏引擎、opengl、脚本等的理解提出了比较高的要求。限于篇幅,本文仅针对内存修改挂第一步提出了不同情景下的快速分析方法。
2.1 场景1 跨进程修改手游内存
此类场景相信大家并不陌生,主要是通用修改器和定制挂,定位方法也较为简单
2.1.1通用修改器
先来看一段GG外挂脚本,如下所示,清楚写明了外挂搜索替换流程,想象下如果分析外挂时能够获取到GG脚本,那么外挂分析定位将极大的简化。
然而,现实是残酷的,实际上外挂制作者为了防止外挂脚本外泄,一般都会自定义lua解释器并对lua脚本进行加密处理,如下图所示,反编译难度和时间成本大大增加。
其实没必要硬碰硬,试想如果我们能够对GG api挂钩子,然后将API调用序列和参数都打印出来,不就变相的实现了脚本反编译,在此仅提示思路,具体实现有兴趣的同学可以动手尝试。
2.1.2通用的跨进程监控分析
顺着刚才的思路继续思考,既然是跨进程的内存读写,必然要调用系统api,如果我们在系统api上做文章,不就可以得到通用的内存修改挂的分析定位方法吗?经过实践,大致总结以下四种跨进程读写方式,感兴趣的同学可以动手实战锻炼下,细节不再赘述。
-
process_vm_readv、process_vm_writev
-
/proc/pid/mem
-
/dev/mem(涉及整个物理内存的读写,外挂用的比较少)
-
Ptrace PTRACE_PEEKDATA/PTRACE_PEEKTEXT、PTRACE_POKETEXT/POKEDATA
2.2 场景2 类似注入修改类(虚拟机、多开、Window+模拟器类)
如果说场景1是定点API突破,那场景2就比较复杂了,常规思路只能通过定位外挂模块,脱壳反编译分析+动态调试定位,对于未加固的外挂程序还相对可接受,但如果外挂模块保护比较强,在短短的一天左右时间内分析清楚外挂原理,堪称地狱难度,对人的精力、技巧考验极大,这也是本文重点要讲述的问题。
不止一次的问自己,有没有更好更有效的方法,好在懒人有懒福,经过一段时间摸索思考,终于总结出一套较为实际可行的方案。内存蜜罐分析方案作为通用的分析方案,可有效解决注入类外挂的内存修改定位难题,对跨进程修改内存也有效,可以说统一内存修改类外挂的分析方法。
三、内存蜜罐原理简介
讲原理之前,我们先回顾下内存修改挂的第一步搜索定位指定数据,可能涉及偏移和多级指针,第二步才是修改。而我们的目标是定位修改的位置和长度,如果我们直接dump外挂修改前后的进程内存进行对比,则修改的位置必然在其中。但是面对茫茫多的修改位置,如何确定外挂究竟修改的哪一处呢?因此问题转换为修改后的内存精确定位问题,这也是内存蜜罐名称的由来。
内存蜜罐方案的核心就是监控对比外挂功能修改后和修改前的内存变化,精心构造具有指定关系的内存布局,模拟修改前的内存状态,诱导外挂功能关闭开启后再次修改蜜罐内存,通过蜜罐前后的内存对比,即可定位外挂被修改的所有内存位置和修改前后数据,解决分析思路第一步的问题。
针对第二步的问题,通过逐步还原外挂修改的内存并进行测试,即可定位有效内存位置及修改前后数据。
3.1概念介绍
3.1.1结构体范围
针对每一处内存修改,外挂一般通过特征搜索定位内存地址+偏移,中间可能涉及多级指针问题。因此每一步内存修改需要确定结构体范围。假设地址0x1000中的数据被修改,则构造0x900中-0x1100中的数据,其中,0x100为结构范围配置项,可考虑4字节对齐。
3.1.2指针级别
默认1-3级指针,最多支持5级指针,指针级别越高,所需内存越大。
针对结构体中的地址地址范围,进行全局搜索。
3.2蜜罐实现步骤
3.2.1 DUMP
枚举游戏进程所有内存模块,将关注的内存dump到磁盘中,作为原始内存。由于进程运行中,各种内存时刻变化,为了缩小蜜罐监控范围,可以考虑冻结部分线程,并根据游戏类型情况可有选择的去除部分内存
-
非游戏逻辑相关的内存,比如安卓中/dev、apk、dex、jar、dalvik、zygote进程空间内存的其他内存
-
可以考虑去除系统模块内存
-
只监控游戏引擎核心模块内存及其分配的内存
3.2.2蜜罐构造
做完第一步,即可开启开挂功能,待外挂修改内存完毕,即可构造蜜罐。蜜罐构造期间、可尝试冻结游戏进程,减少无效修改项的干扰。根据构造方式的不同,又分为内存安全型蜜罐和内存破坏型蜜罐。
内存安全蜜罐
原理
以指针级别2,结构体范围为举例
实现流程
以指针级别2,结构体范围为举例
-
外挂功能开启前,dump maps文件中所有内存镜像imag0
-
根据级别筛选需要监控的内存范围列表
-
外挂功能开启后,对比监控的内存哪些位置发生改变,形成modify1(地址、原始值、修改后的值)列表,若修改代码段则仅报告修改内容,不存放到modify1中
-
指针级别1,申请内存,直接存放modify1列表相关的结构体内存范围
-
指针级别2,在imag0镜像中,搜索modify1结构体范围的指针,形成modify2(地址、原始值)列表,申请内存,直接存放modify2列表相关的结构体内存范围,并修正指针
-
指针级别3,在imag2镜像中,搜索modify1结构体范围的指针,形成modify3(地址、原始值)列表,申请内存,直接存放modify3列表相关的结构体内存范围,并修正指针
-
将以上自己构造的多个内存蜜罐保存为image1,释放modify1、modify2、modify3
-
关闭外挂功能并重新开启,对比监控的内存蜜罐中哪些位置发生改变,此处即为外挂实际修改的内存。
内存破坏性蜜罐
原理
该方式不存在多级指针问题,直接将所有指向一级指针的数据,改为构造的内存蜜罐中的地址劣势:可能会造成游戏crash或者功能异常。
实现流程
以指针级别2,结构体范围为举例,相比内存安全蜜罐,流程大大简化
-
外挂功能开启前,dump maps文件中所有内存镜像imag0
-
根据级别筛选需要监控的内存范围列表
-
外挂功能开启后,对比监控的内存哪些位置发生改变,形成modify1(地址、原始值、修改后的值)列表
-
指针级别1,申请内存,直接存放modify1列表相关的结构体内存范围
-
在进程内存空间中搜索modify1结构体地址范围,只要命中,则替换为内存蜜罐中的地址。
-
将以上自己构造的多个内存蜜罐保存为image1
-
关闭外挂功能并重新开启,对比监控的内存蜜罐中哪些位置发生改变,此处即为外挂实际修改的内存。
3.2.3计算差异
待内存蜜罐构造完成,重新关闭、打开外挂功能。由于上一步内存蜜罐已经按照外挂功能开启前后的内存变化构造了所有被新修改内存的多级内存镜像,因此重新打开外挂功能时内存蜜罐也会一并被搜索到进而修改。
通过dump的镜像内存和内存蜜罐现有内存的比对,即可定位出所有被外挂修改的蜜罐内存位置,进而映射出原始游戏进程中被蜜罐修改的内存起始位置,修改前后的数据。
3.2.4筛选有效内存
将第三步中定位出的所有原始内存修改位置,逐项还原测试外挂功能是否生效,即可精准定位有效内存的修改位置。
四、结束语
整个蜜罐原理和实现并不复杂,难点在于控制蜜罐内存占用量,实际使用中需要控制好结构体范围、多级指针深度和性能优化,由于时间仓促和保密问题,难以将整个方案详尽的展示给大家,未尽之处望大家体谅,欢迎大家拍砖讨论。