0x46
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

一、Frida基础知识
1.1 Frida介绍
官网对Frida的介绍是“Frida是平台原生App的Greasemonkey”,专业一点就是一种动态插装工具,可以插入一些代码到原生App的内存空间去动态地监视和修改其行为,这些原生平台可以是Windows、mac、Linux、Android或者iOS,同时Frida开源。
在安卓逆向过程中,Frida存在两种操作模式:一种是CLI命令行模式,这种模式是通过命令行直接将JavaScript脚本注入到进程中,对进程进行操作;另一种是RPC模式,这种模式是使用python进行JavaScript脚本的注入,但实际对进程操作的还是JavaScript脚本。两种模式本质相同,只是RPC模式更适用于对复杂数据处理。
1.2 Frida操作App的方式
- spwan模式:简而言之就是将启动App的权利交由Frida来控制,通俗来说就是即使目标App已经启动,在使用这种模式注入程序时还是会重新启动App。在CLI模式中,通过加上-f参数指定包名则会以spwan模式操作App。
- attach模式:是建立在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行hook的操作。在CLI模式中,不添加-f参数则默认会通过attach模式注入App。
二、Java层hook基础
为了后面能更清楚的了解App的运行内容,这里先创建一个简单的App用于练习。
这里直接给出MainActivity.java代码。
这个App的逻辑很简单,就是每隔1秒打印一次fun(40,30)函数的运行结果。运行结果如图所示:
确认App已经能正常运行且fun()函数已成功被调用,便可以编写hook脚本。所以我们要写的hook脚本只需要调用fun()函数即可。脚本如下:
这里我们先简单分析一下这个hook脚本,然后再继续。
首先定义了一个hook()函数,用于存放hook脚本,然后调用Java.perform()函数将脚本中的内容注入到Java运行库,这个函数的参数是一个匿名函数,函数内容是监控和修改Java函数逻辑的主题内容(注意:Java.perform()函数非常重要,任何对App中Java层的操作都必须包裹在这个函数中)。Java.use()函数,这个函数的参数是Hook的函数所在类的名(参数类型是一个字符串),返回值可以理解为是一个JavaScript对象,用于后续的调用和重写等操作。
获取到对应的JavaScript对象后,通过“.”连接fun这个匿名函数,然后在加上implementation关键词来实现MainActivity对象的fun()函数,最后通过“=”连接一个匿名函数,参数内容和原Java的内容保持一致。
使用Frida的CLI模式并以attach模式对App进行注入。运行:frida -U demo02 -l hook_fun.js
从运行结果可以看到参数值均已打印出来,说明fun()函数已经成功调用。
接下来我们对hook脚本进行简单修改,从而对fun(x, y)函数传入新的参数(参数可控),这里使用console.log()打印出new_sum会提示undefined,原因是我们使用了this.fun(5, 10) 来调用原始的方法,只能通过return将它返回,不能打印。
重新保存脚本内容即时生效,不需要在重新执行注入命令。
可以使用adb logcat | grep Text来查看return的返回日志结果。
三、方法重载
为App增加一些新功能,新的MainActivity.java类的代码,只做了一点改动已标注。新增代码段如下图:
可以看到,fun()方法有了重载,并且在参数是两个int类型的情况下,返回两个整数之和;当参数为String类型时,返回小写字符串的形式。
使用原脚本进行测试,发现有报错。报错如下图:
这个报错是函数的重载导致Frida不知道具体应该Hook那个函数而出现的问题。
解决方法:指定函数签名,将报错中的.overload('java.lang.String')或者.overload('int', 'int')添加到要Hook的函数名后、关键词implementation之前。当然,相应的参数和具体函数逻辑也要修改。
3.1 Int类型
这里是演示两个int类型作为参数类型的函数的Hook,这个和上面基本相同。并且成功修改了App日志。Hook脚本如下:
在以attach模式运行脚本,Frida页面无报错,说明已经成功。
通过adb logcat | grep Text命令,发现成功修改App日志,并且参数可控。
3.2 String类型
简单演示String类型的Hook,发现Frida无报错,并且fun(String x)函数参数也可控。
四、Java层主动调用
4.1 主动调用&被动调用概念
- 主动调用:强制调用一个函数去执行,可以直接调用关键函数。
- 被动调用:由App按照正常逻辑去执行函数,依靠与用户交互完成程序逻辑进而间接调用到关键函数。
在Java的类中,函数可分为两种:类函数和实例方法。通俗来说,就是静态方法和非静态方法(也称动态方法)。静态方法使用static关键字修饰,在外部可以通过类直接去调用;而实例方法则没有使用static关键字修饰,在外部只能通过创建对应类的实例再通过这个实例去调用。
在Frida中如果是类函数的主动调用直接使用Java.use()函数找到类进行调用即可;如果是实例方法的主动调用,则需要使用Java.choose()函数找到对应的实例后对方法进行调用,Java.choose()函数可以在Java的堆中寻找指定类的实例。
4.2 App功能完善
这里我们继续为App添加一些新的功能。添加代码如下图:
这里可以看到又添加了两个函数,一个为secret()函数没有被static关键字修饰,另一个为static_secret()函数被static关键字修饰。
接下来,我们来完成两个隐藏函数的主动调用。脚本代码如下图:
可以发现静态方法和一般hook方法大同小异,都是在获取类对象后通过“.”连接方法名进行调用。而动态方法则需要先通过Java.choose()函数从内存中获取相应类的实例对象,然后再通过这个实例对象去调用动态函数。
hook_fun.js脚本成功运行后,App运行日志已经被打印,所以可以证明两个函数已经执行。
4.3 补充
PS: 这里仅是个人理解
静态方法直接调用,非静态方法可以理解为hook动态函数,通过找到instance实例,从实例调用函数方法。这里主要说下Java.choose()、onMatch:function(instance){…}和onComplete:function(){}。
- Java.choose(): 通常用于在运行时监视和控制特定类的实例行为。与Java.use()不同,Java.use()是用于直接操作目标类。
- onMatch:function(instance){…}和 onComplete(){}可以理解为是事件处理或回调函数。
- onMatch:function(instance){…}:一般用于某种模式匹配成功时要执行的操作。
- onComplete(){}:通常表示某个操作或任务完成时要执行的操作。在异步编程中使用较多。
五、RPC模式及其自动化
RPC模式其实就是使用python完成JavaScript脚本对进程的注入,在文章开始已经做过简单介绍,这里就不在叙述,想详细了解的请自行百度。
在开始之前,先对App在做简单修改,增加一个字符串类型的实例变量total,同时使每次调用secret()函数对字符进行扩展。
来看下新的js脚本,脚本名为hook_fun_rpc.js。
在这个Hook脚本中定义了两个函数 CallSecretFunc()和 getTotalValue(),封装了对目标应用程序中特定类和方法的调用。并且通过 rpc.exports 暴露了这两个函数给 Frida 的 RPC 接口(这里可以理解为将这两个函数导出),使得外部可以进行调用(这里代码的意思是将CallSecretFunc()函数和getTotalValue()函数分别导出为callsecretfunc和gettotalvalue,注意:导出名不可以包含大写字母或下划线)。这里instance.total.value是获取变量total的值。
我们先测试一下hook脚本,发现并没有报错,说明脚本OK。
其实,在这个控制台也可以直接通过输入函数名来进行调用,此时发现total的值已经变为hellosecret_Func(每调用一次就会改变一次)。
通过python脚本实现自动化,具体的python脚本如下:
Exit退出刚刚控制台(这里建议app重新运行),然后直接运行auto.py。结果如图:
其实这里和单纯执行JavaScript脚本是一致的,假如函数上千个,便可以通过循环来完成函数的自动调用。
最后,简单说下这个python脚本吧。在这个脚本中,首先通过frida.get_usb_device()获取到USB设备,然后通过device.attach(‘demo02’)进程进行注入;接着使用creat_script()函数加载编写的JavaScript代码,并使用script.on()注册了自己的消息对应的函数,通过on_message()函数处理来自Frida脚本的消息,并通过script.exports访问所有我们在JavaScript中定义的导出名,进而调用导出函数。这样就完成了RPC远程调用,达到在主机上随意调用App代码的目的。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)