Frida 的使用

简介

Frida 是一个基于 Python 和 JS 的 Hook 与调试框架,是一款易用的跨平台 Hook 工具,无论 Java 层的逻辑,还是 Native 层(c/c++ 编写的逻辑) 的逻辑,它都可以 Hook . Frida 可以把代码插入原生 App 的内存空间,然后动态的监视和修改其行为,支持 windows ,Mac ,Linux ,Android, ios 全平台

Frida 是使用 Python  注入 JS  脚本实现的, 可以通过 JS 脚本操作手机上的 Java 代码, Python 脚本和 JS 脚本的边写和执行是在电脑上进行的,而且无需在手机上额外安装 App 和插件,所以整体实现起来更加灵活和轻量级,调试起来也更加方便。 而 Xposed 需要使用 Java 实现一个模块,然后编译并安装到手机上,灵活性相对差,但如果要做持久化的 Hook ,还是推荐 Xposed

Xposed 的优缺点

优点: 非常适合编写 java 层的 Hook 逻辑, 因为自己就是用 java 语言编写的,适合一些持久化的 Hook 操作,编写完毕后可以独立切永久的运行在手机上,适用于生产实践

缺点: 配置环境过程比较繁琐,在调试过程中需要编译和重新安装 Xposed 模块, 对 Hook Native层逻辑无能为力(目前官网已经无效,据说现在都在使用 “面具”)

Frida 的优缺点

优点: java 层和 Native 层的逻辑都能 hook ,在电脑上编写和执行脚本,修改之后无需重新编译和额外在手机上安装 App , 操作方便灵活,环境配置简单,能很好的支持跨平台

缺点: 是用 JS 操作 JAVA 逻辑,所以兼容性会差一些,更适合在开发阶段调试使用,不太适合生产实践

准备工作

在电脑上安装好 frida-tools , 并且可以成功导入

在手机上下载并运行 frida-server 文件,即在手机上启动一个服务,以便电脑上的 Frida 可客户端程序与之连接

让电脑和手机处于同一个局域网下,并且能在电脑上用 adb 命令成功连接到手机

frida的安转和配置: 写文章-CSDN创作中心

输入: frida-ps -U  输出手机上的进程,证明电脑和手机连接成功

需要下载两个示例 APP

appBasic1 :  https://appbasic1.scrape.center/

appBasic2 :  https://appbasic2.scrape.center/

Hook Java 层的逻辑

首先我们安装好第一个 App 到手机上 , 启动后如图

整个界面非常简洁,中间一个 Test 按钮,点击该按钮,会出现 Toast 提示信息, 内容为 3 。 这其中的逻辑是怎样的? 我们可以直接通过 jadx-gui 反编译一下 apk 文件, 从源码中查找入口

jadx-gui : 写文章-CSDN创作中心

可以看到源码非常简单,整体逻辑就是点击后触发 onClickTest 方法, 然后这个方法直接调用 Toast 的 makeTest 方法,显示 getMessage 方法的放回结果。这个 getMessage 方法实现的是基本的加和操作,因为在调用时传入的参数是 1 和 2, 所以显示的 Toast 内容是3

那怎么进行 Hook 呢,我们可以定义一个这样的 JS 脚本

Java.perform(() => {
    let MainActivity = Java.use('com.germey.appbasic1.MainActivity')
    console.log('start hook')
    MainActivity.getMessage.implementation = (arg1, arg2) => {
        send('Start Hook')
        return '6'
    }
})

将其保存为 hook_java.js 文件, 这里我们编写的是一个全局可用的 java 对象,通过调用其 perform 方法来实现我们的 hook 逻辑, 首先调用 java 对象的 use 方法获取指向 MainActivity 类的指针,并赋值为 MainActivity 。 然后改写 MainActivity 中的 getMessage 方法, 由于这个方法接收两个参数,因此这里也写两个参数  arg1 和 arg2 ,分别代表源码中的 i 和 i2 ,但这里我们没有对 arg1 和 arg2 做加和操作, 而是直接返回了数字 6 ,这样就完成了方法改写---不使用接收的参数,而直接放回数字 6

Hook 逻辑定义好了,怎么让它生效呢? 使用 Python 脚本调用即可,于是新建一个 hook_java.py 文件,内容如下

import frida
import sys

CODE = open('./hook_java.js', encoding='utf-8').read()
PROCESS_NAME = "com.germey.appbasic1"
PID = 2982

def on_message(message, data):
    print(message)


process = frida.get_usb_device().attach(PID)
script = process.create_script(CODE)
script.on('message', on_message)
script.load()
sys.stdin.read()

这里我们首先读出刚编写的 JS 代码,并赋值为 CODE 变量, 即把代码转换成了 Python 字符串,然后声明了一个包名,并赋值为 PROCESS_NAME 变量

接着我们使用 frida 包中的 get_usb_device 方法获取了当前连接的设备,并调用设备的 attach 方法挂载了对应的进程,该进程被赋值为 process 变量。 之后我们调用了 process 变量的 create_script 方法往脚本中注入了 Hook 脚本(就是传入 CODE变量),并件返回结果赋值为 script 变量

对于 script 变量,我们可以设置时间监听和回调方法,例如这里监听 message 事件,回调方法设置为 on_message , 这样一来, JS 代码中任何通过 send 方法发送的数据, on_message 方法都能接收到对应的内容,这就实现了 JS 到 python 的消息通信。 最后, 调用 script 变量的 load 方法注入脚本

接下来我们先启动 AppBaasic1 , 在启动编写的 Python 脚本,然后在点击 TEST 按钮

可以看到这里显示的是 6 , 正是我们在 JS 代码中的返回值,证明 Hook 成功了,同时观察一下电脑的控制台

从这里可以看到,每次点击控制台都会输出一行代码

这里的 playload 的内容就是我们在 JS 代码中使用 send 方法发送的消息内容,代表我们在 Python 脚本中成功接收到了这个消息,实现了 JS 脚本与 Python 脚本的通信

如果我们 Hook 某个方法的执行结果,然后通过 JS 代码把它保存成某个变量,再利用 send 方法把这个变量发送给 python 脚本,python 就能成功获取代码的返回结果了,之后对结果进行处理和保存,就完成数据爬取了

Hook Native 层的逻辑

现在我们尝试用 Frida 工具 Hook Native 层的代码,即 so 文件中的方法,先来看一下 AppBasic2 在 Hook 之前启动的页面

同样的使用 jadx-gui 反编译 apk 文件,查看入口逻辑

可以看到 MainActivity 类中声明了一个 native 方法,叫作 getMessage, 其参数也是 i 和 i2 ,但是这里并没有它的具体实现。紧接着的实现也很关键

 static {
        System.loadLibrary("native");
    }

这里通过 System 类的 loadLibrary 方法加载了一个 native 库,其实就是加载了一个 Native 层的 so 文件,所以源码中应该有对应的 so 文件, 在源码中仔细找一下,是可以找到的

可以看到这里有好几个 so 文件, 它们适用于不同的平台,名字都是 libnative.so 对于 so 文件, jadx-gui 就无能为力了,因为这是由 C/C++ 编译成的文件, jadx-gui 没法通过反编译得到其源码

那能用 Frida 进行 Hook 吗?能! 我们来修改一下 getMessage 方法的返回结果。 同样先实现一个 JS 脚本, 保存为 hook_native.js

Java.perform(function (){
    Interceptor.attach(Module.findExportByName('libnative.so',
        'Java_com_germey_appbasic2_MainActivity_getMessage'),{
        onEnter: function (args){
            send('hook onEnter')
            send('args[1]=' + args[2])
            send('args[2]=' + args[3])
        },
        onLeave: function (val){
            send('hook onLeave')
            val.replace(Java.vm.getEnv().newStringUtf('5'))
        }
    })
})

跟 Hook Java 层时的逻辑不同,要 Hook Native 层,需要利用 Interceptor 对象的 attach 方法,其第一个参数是指向 Native 方法的指针, 第二个参数是 Hook 逻辑的实现

对于第一个参数,这里直接调用 Module 对象的 findExportByName 方法获取了指针,该方法的第一个参数是 so 文件的名称, 这里就是 libnative.so , 第二个参数是符合一定命名规范的方法路径,开头是 Java , 然后是包名, 注意包名中间的连接字符变成了下划线,接着是被 Hook 方法所在的 Activity 的名称, 这里就是 MainActivity , 最后就是方法名称, 这些内容都是通过下划线连接

对于第二个参数,这里我们定义了两个 Hook 方法,其中 onEnter 代表被 Hook 方法执行前的逻辑, onLeave 代表被 Hook 方法执行后的逻辑。 onLeave 方法的参数是 val , 代表被 Hook 的方法,即 getMessage , getMessage 的原本返回结果是 3 ,这里我们调用 val 的 replace 方法,将其替换成了 5,实现了返回结果的修改

然后调用这个脚本, 新建一个 hook_native.py 文件

import frida
import sys

CODE = open('./hook_native.js', encoding='utf-8').read()
PROCESS_NAME = 'com.germey.appbasic2'
PID = 3502

def on_message(message, data):
    print(message)

process = frida.get_usb_device().attach(PID)
script = process.create_script(CODE)
script.on('message', on_message)
script.load()
sys.stdin.read()

# rdev = frida.get_remote_device()
# f_app = rdev.get_frontmost_application()
# print(f_app)

这里跟 Hook Java 层时不同体现在 JS 文件路径和 App 的包名上,其他完全一样。

重新启动 AppBasic2 , 同时启动该脚本

此时点击 TEST 按钮

可以看到 TEST 信息变成了 5 同时控制台输出

一样的,这里的 payload 值就是我们在JS 脚本中使用 send 发送的消息内容,我们在 onEnter 方法中调用 send 方法,发送了 arg1 和 arg2 的值, 然后 Python 脚本成功接收到了这个消息,实现了 JS 脚本与 Python 脚本的通信

官网: https://frida.re/docs/home/

关于 Frida 的一本书 陈桂林(网名:  r0ysue) 的 《安卓 Frida 逆向与抓包》

遗留问题:为什么在 python 脚本中使用包名无法实现,只能使用 PID

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值