Frida基础
虚拟环境安装
给 frida一个纯净的 Python 环境
conda 创建虚拟环境
1 | conda create -n frida python=3.8 |
进入虚拟环境
1 | conda activate frida |
退出虚拟环境
1 | conda deactivate |
删除虚拟环境
1 | conda remove --name frida --all |
Frida
github 地址: frida
官方文档 https://frida.re/docs/home/
安装
1 | pip install frida-tools # 会自动帮你下载Frida 最新版 |
安装指定版本
1 | pip install frida==版本号 |
因为 一个 frida-tools 会对应多个 frida 版,所以安装指定版本不能直接安装最新版,需查看对应版本号
访问 https://github.com/frida/frida/releases/tag/ + frida 版本号,找到 python3-frida-tools-版本号,即 frida-tools 版本号
1 | pip install frida-tools==版本号 |
查看版本号,验证是否安装成功
1 | frida --v |
frida 代码提示
1 | npm i @types/frida-gum |
frida 版本与 Android 版本与 Python 版本
frida | Android | Python |
---|---|---|
12.3.6 | 5-6 | 3.7 |
12.8.0 | 7-8 | 3.8 |
14+ | 9+ | 3.8 |
fridaserver
安装
fridaserver 与 frida 版本需要匹配,和 frida-tools 一样,访问 https://github.com/frida/frida/releases/tag/ + frida 版本号,可以找到对应的 fridaserver 版本。
文件名的格式为:frida-server-(version)-(platform)-(cpu).xz
,需要下载的安卓的也就是frida-server-15.1.14-android-arm64.xz
, 解压后将文件 push 到手机内/data/local/tmp/
下,并重命名 fsarm64
1 | adb push C:\Users\kuizuo\Desktop\frida-server-15.1.14-android-arm64 /data/local/tmp/fsarm64 |
使用
1 | # CMD 手机端 |
新版本 fridaserver 无需端口转发,旧版可能还需要新开一个 CMD 窗口执行adb forward tcp:27042 tcp:27042
Frida 命令
Hook 前提: 在 hook 时,要保证参数类型执行流程与原代码保持一致,必要的调用与结果返回不可省略,否则将有可能导致程序崩溃。
frida -help
查看帮助,常用选项如下
选项 | 功能 |
---|---|
-U,–usb | 连接 USB 设备 |
-F, –attach-frontmost | app 最前显示的应用 |
-H HOST, –host=HOST | 通过端口连接 frida-server 默认监听 局域网 ip:27042 |
-f FILE, –file=FILE spawn FILE | 以包名方式,自动启动 app 用%resume 恢复主线程 |
-l SCRIPT, –load=SCRIPT | 以 js 脚本方式注入 |
-n NAME, –attach-name=NAME | 以包名附加 |
-p PID, –attach-pid=PID | 以 PID 附加 |
-o LOGFILE, –output=LOGFILE | 将结果输出到文件上 |
–debug | 附加到 Node.js 进行调试 |
–no-pause | 启动后,自动运行主线程 可省略%resume |
简单 Hook 脚本演示
注:Frida 老版本不支持 es6 语法。
代码如下
1 | // java层的代码 都需要在perform下执行 |
运行 frida -U -F -l hook.js
,触发 hook 的函数,便可打印出参。
获取类
1 | // Java.use(类名) |
静态方法与实例方法
1 | 类.方法.implementation = function () { |
重载方法
如果方法有重载,需要使用.overload('java.lang.String')
给定参数个数与类型,如果有重载,但是不使用 overload,frida 将会报错
1 | Util.test.overload('java.lang.String').implementation = function (a) { |
hook 所有重载方法
像上述两个重载方法,就需要编写两份代码,如果重载方法过多,代码不能很好的复用,就可以使用获取类下的所有重载方法
1 | 类.方法.overloads // 返回所有重载方法,依次为每个成员实现implementation方法即可hook多个重载方法 |
构造方法
1 | 类.$init.implementation = function () { |
实例化对象
1 | 类.$new() // 等同于 new 类() |
主动调用类方法
以下的“类”,是通过Java.use()
返回的值。
静态方法
1 | 类.方法() |
实例方法 实例化对象
1 | let obj = 类.$new() |
实例方法 获取已有对象(Java.choose)
内存中遍历,找到所有符合条件的对象。
1 | Java.choose('类路径', { |
这样调用不优雅,会陷入回调地狱,所以可以封装成一个外部函数,来调用。(留个伏笔 TODO…)
修改函数参数与返回值
1 | Utils.md5.implementation = function (a) { |
获取与修改类字段(成员变量)
静态字段
1 | 类.字段.value // 获取类的属性值 |
实例字段 实例化对象
1 | let obj = 类.$new() |
实例字段 获取已有对象(Java.choose)
1 | Java.choose('类路径', { |
:::tip
注: 如果字段名与方法名一样,则需要给字段名前加下划线_,否则获取到的是方法
:::
内部类与匿名类
内部类
1 | const 外部类$内部类 = Java.use('外部类$内部类') // 变量命名随意 |
匿名类
匿名类是根据内存生成,没有具体的内部类名,通过 smali 代码来判断,获取到的可能像下面这样
1 | const $1 = Java.use('包名.MainActivity$1') |
枚举类
1 | Java.choose("枚举类" { |
获取所有类
1 | Java.enumerateLoadedClassesSync() // 同步获取已加载所有类,返回一个数组 |
加载类下所有方法,属性
使用到 Java 的反射
1 | const Utils = Java.use('com.kuizuo.app.Utils') |
函数堆栈的打印
1 | function showStack() { |
HashMap 的打印
1 | RequestUtil.paraMap.overload('java.util.Map').implementation = function (a) { |
安卓关键代码类
类名 | 方法 | 作用 |
---|---|---|
android.widget.Toast | show | 弹窗提示 |
android.widget.EditText | getText | 获取编辑框文本 |
java.lang.StringBuilder | toString | 字符串获取与拼接 |
java.lang.String | toString/getBytes | 获取字符串与字符串字节 |
写文件
写文件如果写入的不是私有空间的话,需要获取内部存储空间权限
私有空间 /data/data/包名
、/storage/emulated/0/Android/data/包名
1 | let current_application = Java.use('android.app.ActivityThread').currentApplication() |
修改类型
Java.cast
1 | utils.shufferMap2.implementation = function (map) { |
构建 Java 数组
1 | // 普通字符串数组 |
注: 第一个参数类型给的是Ljava.lang.String;
而不是 [Ljava.lang.String;
指定函数下 hook(取消 hook)
HashMap.put.implementation = null
取消对 HashMap.put 方法的 hook
1 | const HashMap = Java.use('java.util.HashMap') |
dex 加载
注入一个类 registerClass
JavaScript API | Frida • A world-class dynamic instrumentation framework
通常是加载某个类,复写某些方法,达到绕过的目的,如证书效验
但此方法相对繁琐,不如直接编写 java 代码编译成 dex 直接注入来的方便,也就有了 dex 的动态加载。
DexClassLoader
1 | Java.perform(function () { |
dx
bat: android\SDK\build-tools\sdk 版本\dx.bat
jar 包: android\SDK\build-tools\sdk 版本\lib\dx.jar
使用
1 | dx --dex --output=C:\Users\zeyu\Desktop\com\output.dex C:\Users\zeyu\Desktop\com\* |
C:\Users\zeyu\Desktop\com\*
下存放 java 代码编译后的.class 将其转为 dex 文件,也可指定.class 文件
注: C:\Users\zeyu\Desktop\com\*
绝对路径可能会报错,可使用相对路径。
baksmali 与 smali
github: JesusFreke/smali: smali/baksmali (github.com)
下载地址: JesusFreke / smali / Downloads — Bitbucket
baksmali 将 dex 编译成 smali
smali 将 smali 编译成 dex
使用
反编译 dex
1 | java -jar baksmali-2.5.2.jar d classes.dex # 将会生成out的文件夹 |
回编译 dex
1 | java -jar smali-2.5.2.jar a out # 将会生成out.dex文件 |
apktool
iBotPeaches/Apktool: A tool for reverse engineering Android apk files (github.com)
安装文档: Apktool - How to Install (ibotpeaches.github.io)
apksigner
jar 包: android\SDK\build-tools\sdk 版本\lib\apksigner.jar
1 | apksigner sign --ks xxx.jks xxx.apk |
frida 注入 dex 文件
1 | Java.openClassFile("/data/local/tmp/xxx.dex").load(); |
脱离 PC 使用 frida
Termux
使用 Termux 终端,补齐 python,node 环境,相当于手机端运行电脑端的 frida,本质上与电脑端相同。
frida-inject
同 fridaserver,下载 frida-inject 移动到手机上,
1 | adb push C:\Users\kuizuo\Desktop\frida-inject-15.1.14-android-arm64 /data/local/tmp/fiarm64 |
使用
前提,hook 的 js 脚本也移动到 fiarm64 相同路径或指定路径。
1 | ./fiarm64 -n 包名 -s 脚本.js |
可以加-e,–eternalize 使其在后台运行。
frida-gadget.so
免 root 使用 frida,但需要重打包 app,比较稳定。可通过魔改系统,让系统帮我们注入 so,免去重打包的繁琐
环境
abd、aapt、jarsigner、apksigner、apktool(这些都需要添加到环境变量中)
使用
使用到 objection patchapk 命令,选项如下
选项 | 例子 | 功能 |
---|---|---|
-s xxx.apk | -s xxx.apk | 指定 apk 文件 |
-a so 版本 | -a arm64-v8a | 指定安卓 so 版本 |
-V frida-gadget 版本号 | -V 15.1.14 | 指定 frida-gadget 版本号,默认最新 |
-d, –enable-debug | -d | 是否允许调试 |
-c, –gadget-config TEXT | -c config.txt | 加载配置方式打包 |
frida-gadget 可能会下载失败,去 github 下载frida-gadget-15.1.14-android-arm64.so.xz,解压后将 gadget 文件更名libfrida-gadget.so
为存放到C:\Users\zeyu\.objection\android\arm64-v8a
执行
1 | objection patchapk -a arm64-v8a -V 15.1.14 -s xxx.apk |
将会生成 xxx.objection.apk 文件,卸载原 app(与原 apk 签名不一样,无法覆盖安装),重新安装
重新打开将会进入白屏,正常现象,等待 frida 去连接,相当于 apk 中运行了一个 frida-server。