一、前言
烧饼游戏修改器是一款元老级的游戏修改器,提供了精确搜索、模糊搜索、联合搜索、数据过滤、存储搜索与读取搜索等功能。主要实现搜索手机进程的内存数据并做相应修改。
本文中分析的版本为2.0.2(34),SDK:8,TargetSDK:17,代码经过混淆,修改器基本使用方法为输入需要修改的数据进行查询,查询结果少则可直接修改,查询结果多则需要返回游戏,使想修改的数据发生变化后再次搜索,最后根据数据变化偏移找到需要修改的地址。输入想要修改成的数据即可修改。
本程序测试机为LG Nexus 5,版本为Android 4.4,运行程序前需先root手机。
系统环境:Java 1.8.0_92 ,Android Debug Bridge version 1.0.36
使用工具:AndroidKiller、JEB、Eclipse、IDA
二、详细分析
2.1 关于smali注入代码的说明
为了便于分析,本文中涉及向工程中注入smali代码并重新打包运行,对常用的注入代码做如下说明。其中使用到的寄存器vn要根据实际代码使用的寄存器个数来做相应修改。如图2-1中的run()中“.locals 5”申明使用了5个寄存器,即v0~v4,如果我们需要插入一条日志信息,为了不影响原来代码的执行,需要自己增加一个寄存器v5,先将“.locals 5”变成“.locals 6”,再用v5来保存日志信息。
图2-1 Smali代码中对寄存器的说明
2.1.1 log信息输出
const-string vn, "提示语"
invoke-static {vn}, Lcom/android/killer/Log;->LogStr(Ljava/lang/String;)V
图2-2 Log信息输出
2.1.2 打印字符型数据
const-string vn, "string数据"
invoke-static {vn, v1}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
图2-3 打印字符数据
2.1.3 打印整型数据
const-string vn, "int数据"
invoke-static {v1}, Ljava/lang/Integer;->toString(I)Ljava/lang/String;
move-result-object v(n+1)
invoke-static {vn, v(n+1)},Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
图2-4 打印整型数据
2.1.4 打印boolean型数据
const-string vx, "boolean数据"
invoke-static {p1}, Ljava/lang/Boolean;->toString(Z)Ljava/lang/String;
move-result-object v(x+1)
invoke-static {vx, v(x+1)}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
图2-5 打印boolean型数据
2.1.5 栈追踪
new-instance vn, Ljava/lang/Exception;
const-string v(n+1),"这里是 trace"
invoke-direct{vn,v(n+1)},Ljava/lang/Exception;-><init>(Ljava/lang/String;)V
invoke-virtual{v(n+1)},Ljava/lang/Exception;->printStackTrace()V
图2-6 栈追踪
2.1.6 toast输出
const-string v0, "这里是Toast输出"
const/4 v1, 0x1
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
图2-7 Toast输出
2.1.7 加载so库
const-string v0, "这里是Toast输出"
const/4 v1, 0x1
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
图2-8 加载so库
2.2 Java层代码分析
2.2.1 加载的so文件
用AndroidKiller打开该apk,得到反编译之后的工程,如图2-9。
图2-9 反编译之后的apk工程目录
查看AndroidMainfest.xml,如图2-10,可以看到应用程序包名为“org.sbtools.gamehack”,程序主活动为“MainActivity”,还可以看到服务名为“org.sbtools.gamehack.sesrvice.FlowServ”。
图2-10 AndroidMainfest.xml
找到MainActivity.smali,其中onCreate方法如图2-11,在做一些布局工作后调用了a(),a()方法如图2-12,其主要工作为启用Instrumentation,通过Instrumentation框架,可以在主程序启动之前,创建模拟的系统对象,如Context;控制应用程序的多个生命周期;发送UI事件给应用程序;在执行期间检查程序状态等。从AndroidMainfest.xml中可以知道Instrumentation对应的方法为org.sbtools.gamehack.GameInstrumention。在Instrumentation中启用了服务,如图2-13。
图2-11 MainActivity.onCreate()
图2-12 MainActivity.a()
图2-13 启动服务
在FlowServ中,发现该方法加载了Libencode.so,而在该方法中我们可以找到decode被声明为native(图2-14),想必能用到这个decode的地方一定是关键之处,于是在工程里搜索decode,发现在/d/i.smali中用到了decode,而decode上面出现了两个可疑文件(图2-15)。在工程中注入代码打印出string v1,打开eclipse, 在DDMS中打印出如图2-16的信息,可知将路径“/data/data/org.sbtools.gamehack/files/hack”和“/data/data/
org.sbtools.gamehack/files/gamehack”作为参数传给了decode(),随后在该类的a方法中运行了hack文件(图2-17),运行之后将该文件删除(图2-15)。
图2-14 Native程序加载
图2-15 调用decode的地方
图2-16 获取的path
图2-17 给hack文件赋予root权限
这里的gamehack文件笔者未找到,IDA调试程序安装时显示Libdecode.so加载失败,因此按逻辑图2-7中的decode()执行失败,后面的hack文件不能执行,但以附加方式调试代码时候hack文件确实已经执行了,观察smali代码反应JEB翻译的java逻辑没有问题,猜测程序采用了一些反调试的手段,具体原因还待考究。
2.2.2 Socket消息传送
打开该程序,搜索界面和搜索结果界面如图2-18、图2-19,我们有多种方法来了解执行搜索时候java层的执行逻辑,比如在AndroidKiller中搜索字符串“搜索”,找到其ID,逐层分析其流程,或从搜索结果入手,搜索找到“共搜索到%ld个数值,请选择修改”出现的地方,然后利用再smali代码中添加栈回溯语句来查看栈调用顺序。
图2-18 搜索界面
图2-19 搜索结果页面
最后我们在ah类中找到程序对搜索消息的的设置(图2-20),最后发送的数据为“s [要搜索的数据] 标志位 0”,其中s表示正在进行搜索操作,当输入的搜索数据合法时,标志位为1。我们在这个方法中注入代码打印出发送的消息,发送的消息如图2-21所示。
图2-20 设置搜索消息
图2-21 发送的消息
那么现在问题来了,这些消息发送给了谁?又是谁来接收这些搜索指令呢?分析到这里,之前提到的hack文件的功能就可想而知了。
2.3 Hack文件分析
2.3.1 获取hack文件
那么这个hack文件具体做了些什么呢?为了进一步分析我们需要先提取该hack文件。打开adb shell,进入该文件所在目录,但是ls命令并未查看到,文件显然已经被删掉了。(图2-22)
图2-22 adb shell未查找到文件
此时我们有两种方法来找到该hack文件:
方法一:找到删除文件的smali代码,注释掉删除文件的代码,重新打包运行。之后用adb pull /data/data/org.sbtools.gamehack/files/hack D://命令将文件拷贝到本地计算机。注释掉的代码如图2-23、图2-24所示。
图2-23 注释掉的代码1
图2-24 注释掉的代码2
方法二:linux系统中每一个进程都有一个/proc/[pid]目录,该目录下保存着有关该进程的各类信息,其中有个文件名为exe,这个文件是一个链接指向该进程的实际可执行文件。用命令ps |grep gamehack找到该进程ID为18912(图2-25),执行cd /proc/18912 进入到该进程信息目录,用ls –al会看到我们要找的文件。linux系统删除文件是使用了unlink这个函数,这个函数只会减少某个文件的硬连接数,当某个文件被打开后,并调用unlink删除该文件时,使用ls命令是不能查看到该文件了的,但这个文件实际上还是存在在磁盘上的,当使用该文件的进程结束时或者调用close关闭该文件描述符时,这个文件才真正的从磁盘上消失。因此,只要程序还在运行,该hack文件就不会被真正删除,将该文件拷贝到sdcard,再pull到计算机上(图2-26)。
图2-25 查看进程ID
图2-26 进入进程目录并提取文件
接下来就可以对该文件进行分析了。
2.3.2 hack文件分析
IDA加载提取出来的hack文件,查看导出表,只有一个start(),双击进入start(),F5之后如图2-27所示。
图2-27 start()启动函数
在我重命名为“_HackWhat”的函数中,实现了该程序中最核心的功能。首先建立起socket连接,接收来自远端的数据,并根据第一个数据来决定后续要执行的操作(图2-28)。前面我们已经得出,当执行数据搜索的时候用字母“s”来表示,直接分析case s的代码(图2-29),可以发现在_DataMemory()中对接收到的数据做了一些处理,提取出需要搜索的数值,接着在_SearchData中读取进程内存信息进行搜索。
图2-28 接收socket数据
图2-29 搜索数据
静态分析搜索部分的代码,可以看到程序首先用ptrace来attatch进程(图2-30),接着打开了进程内存映射信息所在的文件“/proc/[PID]/maps”并读取相关信息进行保存(图2-30)。
图2-30 Ptrace进程
图2-31 读取内存映射信息
接下来程序读取进程内存信息“/proc/[PID]/mem”并保存。此处用于监测程序的内存映射信息是否发生变化。当在执行搜索操作时,程序实际上执行的为图所示的_SearchData函数,该函数在IDA中的偏移地址为0000A900。
图2-32 读取进程内存
图2-33 执行搜索
在该函数中,同样先ptrace进程,读取内存信息并和之前保存的做一个比较,校验内存是否发生变化,接着从内存信息中搜索我们要修改的数据(图2-35、图2-36),而此处的具体逻辑还待深究,此处未作进一步描述。
图2-34 读取内存信息
图2-35 搜索数据a
图2-36 搜索数据b
搜索完数据之后,执行图2-29中的_SendFindResult将搜索结果发送到远端,交给java层代码做接下来的处理。
如果搜索结果太多,需要我们返回游戏,手动使想要修改的数据发生变化,然后重新搜索(图2-37)。此时发送的消息为“f = [Data]” (图2-38)。Hack程序收到socket信息后会提取变化之后的数字,再次执行图2-33中的_SearchData函数,不过此时会读取刚才搜索数字8之后的内存,检测哪一个内存保存的数值变为了12,据此来锁定想要修改的数据所在的内存。接着执行修改操作。
图2-37 变化数据
图2-38 第二次搜索时向socket发送的数据
当点击修改之后,hack进程识别标志修改的字母“m”并执行如图2-39所示的操作流程。在对收到搜索命令做一系列提取等操作之后,ptrace注入进程并对相应内存地址的数据进行修改(图2-40),最后将修改结果发送给java层进行处理。
图2-39 修改数据a
图2-40 修改数据b
2.3.3 用IDA进行动态分析
以上分析结合了静态分析和动态分析,用IDA对进程进行动态分析的步骤如下:
1) 把IDA\dbgsrv目录下的android_server push到android中,命令:
adb push android_server /data/data/sv
2) adb shell进入该目录,为android_server赋755权限并以管理员权限运行。
cd /data/data/sv
chmod 755 android_server
su
./android_server
成功运行之后可以看到监听端口为23946。.
3) 在windows控制台下转发window到模拟器或手机端口。
adb forward tcp:23946 tcp:23946
4) 在IDA中选择android调试,在Debuggger 中的process options 的hostname 填上localhost。
5) 在Debugger中的attach上选择对应的android程序即可。
这里由于hack文件运行之后被删除了 因此需要注释掉删除文件的代码,重新编译运行之后才能attach上该进程。附加上该进程之后在IDA右侧的Modules窗格可以看到该文件基址为00008000,从刚才IDA加载的我们导出的hack文件中可以看到start偏移地址为00009010。两个地址相加即为该代码在elf文件中的实际地址,即00011010,我们在IDA中找到这个地址并下断。接着运行程序点击搜索,可以发现程序执行了如图所示的流程,在寄存器窗格中可以查看到存放接收数据的地址,Hex窗格中可以看到该地址下存放的数据。(图2-41)
图2-41 IDA动态加载hack程序——数据处理
调试到图2-42所示的过程中发现程序在读取内存信息。
图2-42 读取内存
搜索完数据之后对搜索结果进行处理,将处理之后的数据发送给远端(图2-43)。发送的信息如图2-44,其格式为“[内存地址]=[查找的数据]=[数据类型]”。
图2-43 处理搜索结果
图2-44 发送的数据
三、总结
本文中分析的烧饼游戏修改器是一款经典的游戏修改器,之后时常出现的很多修改器和外挂都在其基础上衍生,对该游戏修改器进行分析能帮助学者素数掌握修改器和外挂的思路。通过这段时间的学习,收获如下:
1) 掌握了逆向分析的大致流程和相关工具的使用;
2) 对java语言有进一步了解;
3) 熟悉了android框架及一些基础控件的使用;
4) 熟悉了smali代码语法,能读懂smali代码,通过注入samli代码来调试程序;
5) 掌握了IDA动态调试进程的方法;
6) 对Native程序有了初步的认识。
该apk是笔者分析的第一个android程序,由于没有相关分析经验,以及对工具和相关编程语言不熟悉,导致前期花费了大量时间在android编程学习和java层代码的分析上,没有抓住程序的关键点。针对本文中的游戏修改器的分析,尚有许多待完善之处:
1) 程序读取完内存之后执行搜索操作的时候用的什么方法或函数来搜索尚不确定;
2) 本文只对综合搜索进行了分析,对于修改器中提供的其他功能,如模糊搜索、深度搜索、联合搜索、反加密等还未做进一步分析。
3) 对JNI还需深入学习。
初次分析,难免有错误、不当和疏漏的地方,如有发现,还请指正,谢谢!