一、确认要hook的apk
我使用的环境是真机环境,使用USB
连接手机。安装的Objection
的版本为1.11.0
。这里注意,真机的frida-server
的版本要和你电脑的frida
版本一致,我是用的是15.2.2
的frida
。
使用frida-ps -U
找到自己要查看的包名,可能是com.x2n.xxx
的形式也可能是中文的名字。
找到自己的包,然后使用objection -g com.x2n.xxx explore
进入到REPL
模式。这里com.x2n.xxx
为中文也可,中文时我使用""
进行包裹。我使用的1.11.0
的objection
支持中文,其它版本我不确定。
二、常用的命令
进入到Objection REPL
界面[1]中,当不知道命令时通过按空格就会提示可用的命令。在出现提示后通过上下选择键及回车键便可以输入 命令。
下面以手机自带的com.android.phone
为例,开始介绍正式的使用命令。
1、help命令
当不知道当前命令的效果是什么时,在当前命令前加上help(比如help env)再回车之后就会出现当前命令的解释信息。
2、jobs命令
作业系统很好用,用于查看和管理当前所执行 Hook的任务,建议一定要掌握,可以同时运行多项Hook作业。
3、内存漫游相关指令
Objection可以快速便捷地打印出内存中的各种类的相关信息,这对App快速定位有着无可比拟的优势,下 面介绍几个常用命令。
(1)列出内存中的所有类
命令android hooking list classes
一共有22110个类。
(2)在内存中所有已加载的类中搜索包含特定关键词的类
android hooking search classes display
这里搜索一下包含display
关键词的类。
(3)从内存中搜索所有包含关键词key的方法。
android hooking search methods <key>
从上文中可以发现,内存中已加载的类高达22110个。它们的方法是类的个数的数倍,整个过程会相当耗时。
这里展示搜索包含display关键词的方法。
android hooking search methods display
(4)查看类的所有方法
android hooking list class_methods <className>
随意找到一个类com.android.internal.telephony.DisplayInfoControl
,结果如下:
三、获取四大组件相关内容
Android
基础中的基础就是Android
的四大组件,即活动 (Activity
) 、服务(Service
) 、广播接收器(Broadcast Receiver
)以及内容提供者(Content Provider
)。
Activity
可以理解为界面,一个Activity
就是一个界面;Service
相当于Windows上的一个后台进程;Broadcast Receiver
用于响应来自其他应用程序或者系统的广播消息;Content Provider
用于进程间的交互,通常通过请求从一个应用程序向其他应用程序提供数据。
第二部分介绍都是最基础的一些Java
类相关的内容。在Android
中,四大组件的相关内容是非常值得关注的,Objection
在这方面也提供了支持,下面介绍一下。
1、列出进程所有的activity
android hooking list activities
2、列出进程所有的service
android hooking list services
结果如下:
需要列出其他两个组件的信息时,只要将对应的地方更换为receivers
和providers
即可,这里不再演示。
四、Hook相关命令
作为Frida
的核心功能,Hook
总是不能绕过的。同样地,Objection
作为Frida
优秀的开发工具,Hook
相关的 命令是一定要实现的。事实上,Objection
在这方面的表现确实令人称赞。
1、对指定的方法进行Hook
android hooking watch class_method <methodName>
这里选择对Java
中File
类的构造函数进行Hook
,结果如下:
android hooking watch class_method java.io.File.$init --dump-args --dump-backtrace --dump-return
在上述命令中 , 我们加上了--dump-args
、--dumpbacktrace
、--dump-return
三个参数,分别用于打印函数的参数、 调用栈以及返回值。这三个参数对逆向分析的帮助是非常大的:有些函数的明文和密文非常有可能放在参数和返回值中,而打印调用栈可以让分析者快速进行调用链的溯源。
另外需要注意的是,此时虽然只确定了Hook
构造函数,但是默认会Hook
对应方法的所有重载
。 同时,在输出的最后一行显示Registering job 561713
,这表示这个Hook
被作为一个“作业”添加到Objection
的作业系统中了,此时运行job list
命令可以查看到这个“作业”的相关信息,如下图。可以发现这里的Job ID
对应的是561713
,同时Hooks
对应的6
正是Hook
的函数的数量。
当我在com.android.phone
对应的"电话"中进行操作时,会发现java.io.File.File(java.io.File, java.lang.String)
这一个函数被调用了。在Backtrace
之后打印的调用栈中,可以清楚地看到这个构造函数的调用来源,如下图。
注意,调用栈的顺序是从下至上的,根据Arguments
那一行会发现打开的文件路径是/data/user_de/0/com.android.phone/files
,文件名为persist_atoms.pb
。虽然Return Value
后打印的返回值为none
,表明这个函数没有返回值,但是也是真实地打印了返回值。当然,读者也可以Hook
其他函数以打印返回值进行测试。
测试结束后,可以根据"作业"的ID
来删除"作业",取消对这些函数的Hook
,最终执行结果如下:
jobs kill <id>
2、对指定类中所有函数的Hook
除了可以直接Hook
一个函数之外,Objection
还可以通过执行命令实现对指定类classname
中所有函数的Hook**(这里的所有函 数并不包括构造函数的Hook
)**。
android hooking watch class <classname>
同样以java.io.File
类为例,最终执行效果如下:
一共Hook
了56
个函数,输出结果如下:
最终Hook
的效果如下。当然,这里的调用顺序(自上而下)和之前的调用栈的打印是不同的。
五、Objection的主动调用
主动调用:android heap
相关命令。最后介绍Frida
的一大特色——主动调用在Objection
中的使用。
1、实例搜索(基于Java.choose实现)
基于最简单的Java.choose
的实现,在Frida
脚本中,对实例的搜索在Objection
中是使用以下命令实现的:
android heap search instances <classname>
这里仍以java.io.File
类为例,搜索到很多File
的实例,并且打印出对应的Handle
和toString()
的结果。
下图中显示的Hashcode
十分重要,在之后的主动调用中都是以这个值作为实例的句柄来调用和执行函数。
2、Objection中调用实例方法的两种方法
第一种:使用execute
android heap execute <Hashcode> <methodname>
这里的实例方法指的是没有参数的实例方法。下面演示一下使用Hashcode
值为31598268
所对应的实例来执行File
的getPath
方法。
使用execute
执行带参函数会报错,如下图:
第二种:使用evaluate
如果要执行带参数的函数,则需要先执行以下命令:
android heap evaluate <Hashcode>
在进入一个迷你编辑器环境后,输入想要执行的脚本内容,确认编辑完成,然后按Esc键退出编辑器,最后按回车键,即会开始执行这行脚本并输出结果。这里的脚本内容和在编辑器中直接编写的脚本内容是一样的(使用File
类的canWrite()
函数和setWritable()
函数进 行测试)。
具体实现的代码如下:
1 | console.log(clazz.canWrite()) |
在这个脚本中 , Objection
设定clazz
用于代表Hashcode
值为31598268
所对应的实例,同时函数canWrite()
用于返回这个实例所打 开的文件是否可写。setWritable()
函数用于修改对应文件是否可写的属性。脚本的编辑页面和最终的执行效果如下图。其中的True
和False
属于输出的结果。
heap evaluate
既可以执行有参函数,也可以执行无参函数,这里不再演示,留待读者自行研究。
六、Frida开发思想🌟
在介绍完Frida
和Objection
后,将在这一节中提出一个在逆向过程中常用的工作思路,通常将之称为“Frida
三板斧”。
1、定位:Objection辅助定位
经过前面的学习,我们发现Objection
在逆向过程中最强大的功能其实是从海量的代码中快速定位关键的程序逻辑。Frida
需要每次手动编写代码去Hook
从静态分析到的函数,进而观察其参数和返回值是否与需求相符,Objection
将常用的一些功能集成在一 起,使得逆向开发和分析人员在分析过程中不需要浪费精力在编写代码上。
下面以样例程序Junior.apk
为例(样本来自于《 Android Studio开发实战:从零基础到App上线(第2版)》一书中的Junior
样例,源代码在这里[2],apk
文件在这里[3]。
使用adb install -t junior.apk
命令将Junior.apk
安装并启动后,首先使用Objection
遍历一下App
的所有activity
(活动), 如下图所示。
android hooking list activities
在安装和遍历App
的所有activity
后,我们会发现整个App
总共有 17个activity
,为了方便讲解,这里选择分析的目标activity
为计算器的相关活动com.example.junior.CalculatorActivity
,并尝试使用如下命令去启动这个活动。
1 | com.example.junior on (google: 13) [usb] # android intent launch_activity com.example.junio |
观察手机页面,会发现activity
被成功启动了,最终手机显示计算器的页面。
这里选取减法作为我们的分析目标,在计算器成功启动后,从源 码地址下载对应android2
源码并直接查看这个activity
的源码:切换 到 android2
工程下 , 打开对应的junior/src/main/java/com/example/junior/CalculatorActivity.java
文件,从这个文件中的onCreate()
函数可以 看到整个活动注册了很多控件的点击事件。
1 | // onCreate函数 |
随便测试这个计算器之后会发现,每次按“等号”按钮后计算结果都会被打印出来。根据这一现象,找到对应“等号”按钮的id
为btn_equal
,并根据这个id
找到对应的点击响应函数onClick
函数中属于“等号”按钮的源码部分,最终的源码如下所示。
1 | //onClick函数 |
从onClick
函数中可以发现最终真实的点击“等号”按钮后的主要代码在caculate()
函数中。接下来就是验证我们想法的时候了:为了防止源码和真实运行代码不同,先使用以下命令验证是否存在caculate()
函数。
1 | com.example.junior on (google: 13) [usb] # android hooking list class_methods com.example.j |
上面的执行结果说明caculate()
函数确实是存在的。
接下来就很明显了,使用如下命令Hook
这个函数来确认在点击 “等号”按钮后这个函数被调用了。在Hook
上后,任意输入一个表达式并点击“等号”按钮,会发现这个函数在点击“等号”按钮后被调用,Hook
结果如下。
1 | com.example.junior on (google: 13) [usb] # android hooking watch class_method com.example |
查找代码,找到了其中的的caculate()
函数。
1 | // 开始加减乘除四则运算,计算成功则返回true,计算失败则返回false |
在这个函数中,对减法的处理是通过调用Arith
类中的sub()
函数来实现的。为了验证Arith
类在内存中是真实存在的,我们通常使用以下Objection
命令来获取一个应用在内存中的所有类。
# android hooking list classes
通常,在运行这行命令后会列出很多类,甚至会超过整个Terminal
缓存空间,这时会出现一些类被缓存冲刷掉的情况,如果只是简单地在终端窗口里查找,那么不一定能找到。其实Objection
本身有一个log
文件,用于记录objection
运行时产生的所有数据。这个日志数据存放在~/.objection
目录下的objection.log
文件中。
解决方法:在运行objection
注入App
之前,首先切换到~/.objection
目录下,将之前的objection.log
文件删除或者改名。如下所示:
在删除这个log
文件后重新注入App
,并重新遍历应用在内存中的所有类,命令如下:
1 | 注入应用 |
在遍历完成后退出Objection
注入模式以确保log
文件刷新成功, 并重新通过cat
命令查看这个objection.log
文件,由于log
文件过大,因此还需要配合grep
命令过滤文本,从而通过观察结果是否有输出来判定内存中是否存在目标类Arith
,如下图所示。
在判定内存中确实存在Arith
类后,我们进一步通过Objection
命令判断Arith
类是否存在sub()
函数。如下图所示。
在内存中确定这个函数存在后,便可以使用如下命令对这个函数进行Hook
了。
1 | android hooking watch class_method com.example.junior.util.Arith.sub --dump-args --dump-backtrace --dump-return |
最终确认这个简单计算器的减法是通过sub(java.lang.String, java.lang.String)
实现的。
这里我使用了计算器的减法时,发现没能触发hook
,这里hook
失败了。
然后我使用同样的方法尝试了其它的App
[4],成功hook
,可能是我下载的junior
来源有点问题。
1 | android hooking watch class_method com.zj.wuaipojie.Demo.a --dump-args --dump-backtrace --dump-return |
2、利用:Frida脚本修改参数
前面我们确认了Arith
类的函数sub(java.lang.String, java.lang.String)
是最终计算器减法的真实执行函数。这里尝试使用frida
的脚本去进行hook
这个sub
函数。
1 | function main(){ |
仍然是hook
失败的,进行减法操作无任何反应。
我们知道,Frida
脚本中Java
函数的主动调用(区分静态函数和实例函数)。如果是静态函数,只需要获取类对象即可直接完成函数的主动调用;如果是实例函数,只需要优先获取到类的实例对象即可完成函数的主动调用。
主动调用的代码如下:
1 | function main(){ |
成功调用。
主动调用可以,但是被动调用就不行,这里可能是App
的问题。
深入到Arith.java
代码[5]中,发现sub
函数的实现使用了BigDecimal
函数。
尝试去hook
这个代码,发现成功hook
,这里不清楚为什么。
hook
的代码如下:
1 | function main(){ |