freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Frida之Java层Hook
2024-03-29 15:52:59

一、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代码。

1711638101_66058655ae36b2ee2e1ae.png!small?1711638102701

这个App的逻辑很简单,就是每隔1秒打印一次fun(40,30)函数的运行结果。运行结果如图所示:

1711638278_660587066283bffbab546.png!small?1711638281374

确认App已经能正常运行且fun()函数已成功被调用,便可以编写hook脚本。所以我们要写的hook脚本只需要调用fun()函数即可。脚本如下:

1711638395_6605877b06e5c911e019e.png!small?1711638395344

这里我们先简单分析一下这个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

1711638637_6605886d5024ffcd8b8fa.png!small?1711638637827

从运行结果可以看到参数值均已打印出来,说明fun()函数已经成功调用。

接下来我们对hook脚本进行简单修改,从而对fun(x, y)函数传入新的参数(参数可控),这里使用console.log()打印出new_sum会提示undefined,原因是我们使用了this.fun(5, 10) 来调用原始的方法,只能通过return将它返回,不能打印。

1711638738_660588d28432fe2d22ecf.png!small?1711638739083

重新保存脚本内容即时生效,不需要在重新执行注入命令。

1711638781_660588fdda5426fc362d8.png!small?1711638782652

可以使用adb logcat | grep Text来查看return的返回日志结果。

1711638860_6605894c0101382c6b1a9.png!small?1711638860545

1711638905_660589798a824b4df4291.png!small?1711638906323

三、方法重载

为App增加一些新功能,新的MainActivity.java类的代码,只做了一点改动已标注。新增代码段如下图:

1711639053_66058a0d233503a90266d.png!small?1711639053656

可以看到,fun()方法有了重载,并且在参数是两个int类型的情况下,返回两个整数之和;当参数为String类型时,返回小写字符串的形式。

1711639222_66058ab6e50b0172ac421.png!small?1711639223233

使用原脚本进行测试,发现有报错。报错如下图:

1711639280_66058af0df67e26474bfc.png!small?1711639281211

这个报错是函数的重载导致Frida不知道具体应该Hook那个函数而出现的问题。

解决方法:指定函数签名,将报错中的.overload('java.lang.String')或者.overload('int', 'int')添加到要Hook的函数名后、关键词implementation之前。当然,相应的参数和具体函数逻辑也要修改。

3.1 Int类型

这里是演示两个int类型作为参数类型的函数的Hook,这个和上面基本相同。并且成功修改了App日志。Hook脚本如下:

1711639425_66058b81a285207d88597.png!small?1711639425867

在以attach模式运行脚本,Frida页面无报错,说明已经成功。

1711639472_66058bb053a0ea6543d72.png!small?1711639472656

通过adb logcat | grep Text命令,发现成功修改App日志,并且参数可控。

1711639529_66058be9e8ebfdbcbe3ee.png!small?1711639530852

3.2 String类型

简单演示String类型的Hook,发现Frida无报错,并且fun(String x)函数参数也可控。

1711639600_66058c305966455b5dcec.png!small?1711639600599

1711639680_66058c80197f117250f67.png!small?1711639680627

四、Java层主动调用

4.1 主动调用&被动调用概念

  • 主动调用:强制调用一个函数去执行,可以直接调用关键函数。
  • 被动调用:由App按照正常逻辑去执行函数,依靠与用户交互完成程序逻辑进而间接调用到关键函数。

在Java的类中,函数可分为两种:类函数和实例方法。通俗来说,就是静态方法和非静态方法(也称动态方法)。静态方法使用static关键字修饰,在外部可以通过类直接去调用;而实例方法则没有使用static关键字修饰,在外部只能通过创建对应类的实例再通过这个实例去调用。

在Frida中如果是类函数的主动调用直接使用Java.use()函数找到类进行调用即可;如果是实例方法的主动调用,则需要使用Java.choose()函数找到对应的实例后对方法进行调用,Java.choose()函数可以在Java的堆中寻找指定类的实例。

4.2 App功能完善

这里我们继续为App添加一些新的功能。添加代码如下图:

1711639855_66058d2f5f13caf1bf1fd.png!small?1711639856966

这里可以看到又添加了两个函数,一个为secret()函数没有被static关键字修饰,另一个为static_secret()函数被static关键字修饰。

接下来,我们来完成两个隐藏函数的主动调用。脚本代码如下图:

1711639938_66058d82edd3b13d9df90.png!small?1711639939324

可以发现静态方法和一般hook方法大同小异,都是在获取类对象后通过“.”连接方法名进行调用。而动态方法则需要先通过Java.choose()函数从内存中获取相应类的实例对象,然后再通过这个实例对象去调用动态函数。

hook_fun.js脚本成功运行后,App运行日志已经被打印,所以可以证明两个函数已经执行。

1711640015_66058dcfd488db9ae7018.png!small?1711640016950

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()函数对字符进行扩展。

1711640167_66058e673ed55636fe1bf.png!small?1711640167983

来看下新的js脚本,脚本名为hook_fun_rpc.js。

1711640214_66058e962ea4418f1a9ea.png!small?1711640215083

在这个Hook脚本中定义了两个函数 CallSecretFunc()和 getTotalValue(),封装了对目标应用程序中特定类和方法的调用。并且通过 rpc.exports 暴露了这两个函数给 Frida 的 RPC 接口(这里可以理解为将这两个函数导出),使得外部可以进行调用(这里代码的意思是将CallSecretFunc()函数和getTotalValue()函数分别导出为callsecretfunc和gettotalvalue,注意:导出名不可以包含大写字母或下划线)。这里instance.total.value是获取变量total的值。

我们先测试一下hook脚本,发现并没有报错,说明脚本OK。

1711640269_66058ecd59742e38d9ea2.png!small?1711640269965

其实,在这个控制台也可以直接通过输入函数名来进行调用,此时发现total的值已经变为hellosecret_Func(每调用一次就会改变一次)。

1711640337_66058f110fbfaba608944.png!small?1711640337546

通过python脚本实现自动化,具体的python脚本如下:

1711640409_66058f59334b8c2fe9eee.png!small?1711640409736

Exit退出刚刚控制台(这里建议app重新运行),然后直接运行auto.py。结果如图:

1711640452_66058f84e22b6ecdd835a.png!small?1711640453453

其实这里和单纯执行JavaScript脚本是一致的,假如函数上千个,便可以通过循环来完成函数的自动调用。

最后,简单说下这个python脚本吧。在这个脚本中,首先通过frida.get_usb_device()获取到USB设备,然后通过device.attach(‘demo02’)进程进行注入;接着使用creat_script()函数加载编写的JavaScript代码,并使用script.on()注册了自己的消息对应的函数,通过on_message()函数处理来自Frida脚本的消息,并通过script.exports访问所有我们在JavaScript中定义的导出名,进而调用导出函数。这样就完成了RPC远程调用,达到在主机上随意调用App代码的目的。

# 安卓逆向 # 安卓工具,移动安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录