Frida存在两种操作模式:第一,通过命令行直接将JavaScript脚本注入进程中,对进程进行操作,这种模式称为CLI(命令行)模式;第二,使用Python脚本间接完成JavaScript脚本的注入工作,这种模式称为RPC (Remote Procedure Call,远程过程调用)模式,这种模式虽然加入了Python的包装,但实际对进程进行操作的还是JavaScript脚本。因此本章将重点以CLI模式讲解Frida的使用。
Frida具体操作App的方式有两种:
一种是spawn(调用)模式,简而言之就是将启动App的权利交由Frida来控制。当使用spawn模式时,即使目标App已经启动,在使用Frida对程序进行注入时,还是会由Frida将App重新启动并注入。在命令行模式中,frida命令加上-f参数就会以spawn模式操作目标App。
另一种是attach(附加)模式,这种模式是建立在目标App已经启动的情况下,Frida直接利用ptrace原理注入程序进而完成Hook操作。在CLI模式中,如果不添加-f参数,则默认通过attach模式注入App。
这里需要注意的是,正是由于Frida在以attach模式注入应用时使用ptrace原理完成,因此无法在IDA正在调试目标应用程序时以attach模式注入进程中,但是如果先用Frida注入程序后再使用IDA进行调试,则完全没有任何问题,其中详细原理读者可自行研究。
由于Hook方案是一种在函数真实运行前对函数执行流程进行动态二进制插桩的方式,因此其时机非常重要:一定要在函数执行前对函数进行Hook,否则如果在Hook之前函数已经执行结束并且不再执行,这样的Hook就没有意义了。由于App中某些函数在启动时默认只执行一次,因此也就出现了spawn和attach两种注入方式。对于只有在App启动早些时候执行或者只执行一次的方法,通常只有通过spawn方式在App尚未执行之前就对函数进行Hook,比如SO库的.init_array函数、.init_proc函数等;而对于频繁执行的函数或者需要对App进行特定操作才执行的函数,则可以在触发函数执行流程之前以attach模式对App进行注入。图2-1中左侧和右侧分别是使用两种模式Hook某App RegisterNaives函数的打印结果,可以发现当使用attach模式对RegisterNatives函数进行Hook时,没有任何信息打印出来。
图2-1 spawn模式与attach模式Hook结果对比
另外,Frida通常支持使用两种模式连接手机:USB数据线模式和网络模式,当使用USB数据线模式连接手机时,手机一定要通过ADB协议与计算机相连接,此时在注入App时只要加上-U参数即可。图2-1就是使用USB数据线模式注入应用和Hook。
当使用网络模式连接手机时,无须保证手机和计算机通过ADB协议连接。相应地,frida-server在运行时必须使用-l参数指定监听IP和端口,主机上的Frida则需要通过-H参数指定手机的IP和端口与手机建立连接。图2-2所示为网络模式下Frida注入远程手机的“设置”应用展示,观察图2-2可以发现frida-server在运行时通过-l参数指定监听来自任意IP 8888端口的连接,而Frida则通过-H参数指定连接IP为192.168.50.185、端口号为8888的设备,最终完成对“设置”应用的注入与Hook工作。
图2-2 在网络模式下使用Frida
相比于Xposed使用Java代码完成Hook模块的编写后需要重启才能使得Hook代码生效,Frida更加机动灵活。在每次对目标App进行注入时,只需要frida-server在测试手机上运行起来,然后使用和frida-server版本相同的Frida将事先编写好的JavaScript脚本注入进程即可即时完成对应用的注入和Hook工作。除此之外,在注入成功后,哪怕注入脚本被即时修改,对应的Hook效果也能即时生效。那么Frida的Hook核心主角——JavaScript脚本该如何编写呢?本小节将简要介绍关于Frida Java层Hook脚本的语法。
如图2-3所示,以“设置”中“显示”这个界面为例,Android版本号为8.1.0_r1的系统中其对应的类名为com.android.settings.DisplaySettings。
图2-3 “显示”页面
在编写Hook脚本之前还要介绍的是,当用户在“显示”页面中每次点击“主动显示”按钮时,其对应的函数int getMetricsCategory()都会被调用。因此,如果想要针对这个函数进行Hook,其对应的Frida脚本如代码清单2-1所示。在Frida脚本成功注入设置应用后,再多次点击“主动显示”按钮,会发现Frida的REPL界面出现Hook日志数据的打印,其效果如图2-4所示。
代码清单2-1 hello.js
图2-4 Hook结果
此时再次观察代码清单2-1,会发现图2-4中打印的日志和脚本中console.log()函数执行的结果相同,说明函数成功被Hook了。
这里还要介绍代码清单2-1中几个比较重要的知识点。
第一,所有针对Java层函数的Hook脚本必须处于Java.perform()的包装中,Java.perform()函数的包装表示将其中的函数注入Java运行时中,那么如果没有Java.perform()函数的包裹,会发生什么呢?如图2-5所示,这里笔者将属于Java.perform()的部分在代码中进行注释后,再次保存会发现提示错误:Current thread is not attached to the Java VM; please move this code inside a Java.perform()callback。
图2-5 没有Java.perform()函数报错
第二,在使用Java.use() API获取指定类的handle后,这里以类似于Java中调用类静态方法的方式获取对应的函数。与Java中不同的是,Frida脚本中直接以在“.”连接符后接函数名的方式得到的函数并不一定是我们想要Hook的函数。如果函数存在多个重载,此时还需要在函数名后添加.overload(<signature>)获取指定函数,比如这里针对int getMetricsCategory()这个无参函数,其对应的<signature>也为空,因此要获取这个函数,只需要在函数名后接.overload()即可。而如果函数存在多个重载,比如String字符串类的subString(),这个用于获取子字符串的函数就存在两个重载:String substring(int)和String substring(int, int),此时如果只想Hook substring(int)函数,在Frida中获取对应函数的handle,其具体Hook代码就必须如代码清单2-2中所示的在函数名substring之后添加.overload('int')关键字以获取特定函数的handle。
代码清单2-2 hookSubString.js
function hookSubString(){ Java.perform(function(){ var String = Java.use('java.lang.String') var subString_int_func = String.substring.overload('int') // 获取 substring(int)函数的handle subString_int_func.implementation = function(index){ var result = this.substring(index) console.log("substring called",'index =>',index,',result =>',result) return result } }) }
如果在获取subString函数时不添加.overload('int'),那么Frida在注入后就会报错:substring(): has more than one overload, use.overload(<signature>),报错结果如图2-6所示。
图2-6 无overload重载报错
而当按照上述步骤获取到函数的handle后,此时还没有最终完成对函数的Hook效果,真实去完成Hook工作的部分实际上是代码清单2-1和代码清单2-2中的.implementation以及在implementation后的JS函数。在这个JS函数中,我们可以执行任意用户自定义的操作,这里仅仅是打印一行日志,并且调用原函数获取了函数的返回值。
由于本书的定位以及篇幅,本小节只简单介绍了Frida函数Hook的基础,且并未展开描述。更多详细内容读者可参阅笔者的《安卓Frida逆向与抓包实战》一书中关于Frida基础的介绍。
如果说Frida工具提供了各种API供用户自定义使用,在此基础之上可以实现无数的具体功能,那么Objection可以认为是一个将各种常用的功能整合进工具中并可直接供用户在命令行中使用的利器,甚至通过Objection工具可以在不写一行代码的前提下完成App的逆向分析。
Objection集成的功能主要支持Android和iOS两大移动平台,在对Android的支持中,Objection可以快速地完成诸如内存搜索、类和模块搜索、方法Hook以及打印参数、返回值、调用栈等常用功能,是一个非常方便甚至可以说逆向必备的神器。
Objection的安装十分简单,只需要通过pip进行安装即可,默认安装Objection时会自动安装新版的Frida和frida-tools。但需要注意的是,如果读者在安装特定版本的Frida后安装Objection,则需要指定Objection的版本进行安装,比如这里使用Frida 12.8.0版本,其对应的Objection版本为1.8.4,因此此时Objection的安装命令如下:
# pip install objection==1.8.4
当然,如果读者在未安装Frida的前提下直接使用pip命令安装Objection,并未下载Frida,则会自动下载新版的Frida和frida-tools。由于Frida不同版本的API可能会有一些差异,因此在进行特定版本的Objection安装时还需要注意Frida、frida-tools和Objection的先后顺序(先安装Frida和frida-tools,再安装对应版本的Objection)。
在成功安装Objection后,让我们来一起了解一下Objection的基本使用方式。Objection的基本命令如图2-7所示。
图2-7 Objection命令提示
以“移动TV”样本为例,在通过adb命令安装App后,首先通过Jadx等反编译工具查看AndroidManifest.xml文件的内容,获取到包名为com.cz.babySister。
在保障对应版本的frida-server已经在手机端启动后,根据图2-7中的命令提示与上面获取到的App包名,最终得到Objection注入“移动TV”进程命令如下:
# objection -g com.cz.babySister explore
在成功运行注入命令后,如图2-8所示是Objection在成功注入进程后的REPL界面。在这个界面中,我们可以通过Objection相关命令对进程进行Hook等操作。
图2-8 Objection REPL界面
接下来正式介绍Objection REPL界面中支持的命令。
(1)内存枚举相关命令
Objection可以快速便捷地打印出内存中的各种已加载类的相关信息,这对快速定位App中的关键类有着关键性作用,这里先介绍几个常用命令。
①枚举进程内存中已加载的类,其命令格式如下:
# android hooking list classes
这里需要注意的是,这里列出的类都是进程已经加载过的类,如果进程还未加载目标类,相应类名是无法被列出的。如图2-9所示是“移动TV”这个样本在登录页面时内存中已经加载的类。
图2-9 枚举进程已经加载的类
②枚举内存已经加载的类中包含特定字符串的类并列出,其命令格式如下:
# android hooking search classes <pattern>
这里的pattern可以是任意字符串,如图2-10所示是样本中包含com.cz.babySister字符串的类名。
图2-10 搜索包含特定字符串的类
③获取指定类中所有非构造函数的方法签名,其命令格式如下:
# android hooking list class_methods <class_name>
注意,上述命令中,class_name是包含包名的完整类名,比如这里要打印Loading类的所有函数,则必须在Loading类名前加上其完整的包名com.cz.babySister.view,并通过“.”连接符连接。最终打印Loading类所包含函数的效果如图2-11所示。
图2-11 打印类中所有方法
(2)Hook相关命令
作为Frida的核心功能,Hook功能总是绕不过的。同样,Objection作为Frida优秀的第三方工具,也提供了很多激动人心的Hook命令。事实上,Objection在这方面表现得确实令人惊艳。
① Objection支持Hook类中全部非构造函数的方法,其命令格式如下:
android hooking watch class <class_name>
与打印特定类中所有方法的命令相同,这里class_name必须是完整的类名,同时需要注意Objection默认Hook类中的全部函数并不包括类的构造函数。这里依旧Hook Loading类的全部方法,其效果如图2-12所示。
图2-12 Hook类中所有方法
② Objection同样支持Hook指定函数,其命令格式如下:
android hooking watch class_method <classMethod> <overload> <option>
这里的classMethod与Hook类中全部函数的命令相同,classMethod必须是完整的类名加上函数名,并以“.”连接符连接。而option格式支持3个参数,其中--dump-args参数在被Hook函数执行时会打印其参数内容,若加上--dump-return参数,则会打印函数返回值,加上--dump-backtrace参数打印函数调用栈,同时这3个参数可以组合使用。
与Frida不同的是,在Hook函数时如果不指定其参数,即这里的overload格式的参数,那么默认Hook所有同名的函数,比如这里Hook Loading类的setForegroundColor方法,观察图2-12会发现该函数存在两个重载,此时若不指定参数类型,则其Hook效果如图2-13所示。
图2-13 Hook setForegroundColor方法的效果(不指定参数)
如果想要Hook指定参数的特定方法,还需要加上函数的参数类型并以双引号包含,比如这里想要Hook参数类型为int的数组的setForegroundColor()方法,那么最终Hook的命令如下:
# android hooking watch class_method com.cz.babySister.view.Loading. setForegroundColor "[I" --dump-args --dump-return --dump-backtrace
最终Hook效果如图2-14所示。
图2-14 Hook setForegroundColor([I])方法的效果(指定参数)
Objection还有很多这里未介绍的命令,比如jobs命令用于Hook任务管理,android heap命令用于操作内存中类的实例等。限于篇幅,这里仅介绍笔者认为重要的Objection命令,如果读者想要了解更多关于Objection的命令与使用方式,可以参照笔者的另一本书《安卓Frida逆向与抓包实战》,或者直接在Objection REPL界面中简单地通过按空格键查看其支持的命令列表。