Frida笔记
虚拟环境安装
由于 Python 版本兼容性问题,建议是安装虚拟环境
安装 virtualenvwrapper
pip install virtualenvwrapper-win -i https://pypi.tuna.tsinghua.edu.cn/simple
添加虚拟环境变量
添加一个 WORKON_HOME
为 E:\Envs
(虚拟环境的路径)
创建虚拟环境
mkvirtualenv --python=python版本路径 环境名
mkvirtualenv --python=E:\Python37\python.exe py37
mkvirtualenv --python=E:\Python38\python.exe frida
默认是用户路径下 C:\Users\{username}\Envs\
进入虚拟环境
workon #列出所有虚拟环境
workon 环境名 #进入对应名字下的虚拟环境
退出虚拟环境
deactivate
删除虚拟环境(必须先退出虚拟环境内部才能删除当前虚拟环境)
rmvirtualenv 虚拟环境名称
pip 相关指令
查看虚拟环境中安装的包:
pip freeze
pip list
收集当前环境中安装的包及其版本:
pip freeze > requirements.txt
在部署项目的服务器中安装项目使用的模块:
pip install -r requirements.txt
Frida
github 下载地址: frida/frida
文档: Welcome | Frida • A world-class dynamic instrumentation framework
安装
pip install frida-tools # 会自动帮你下载Frida 最新版
安装指定版本
pip install frida==版本号
查看 frida-tools 版本
因为 一个 frida-tools 会对应多个 frida 版,所以安装指定版本不能直接安装最新版,需查看对应版本号
访问 https://github.com/frida/frida/releases/tag/ + frida 版本号,找到 python3-frida-tools-版本号,即 frida-tools 版本号
pip install frida-tools==【frida-tools版本号】 # 无【】
查看 版本号,验证是否安装成功
frida --v
上述安装,有可能无法下载,建议科学上网,或使用 whl 离线安装
pip install frida-15.1.14-cp38-cp38-win_amd64.whl
pip install frida_tools-10.4.1-py3-none-any.whl
frida 代码提示
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
adb push C:\Users\kuizuo\Desktop\frida-server-15.1.14-android-arm64 /data/local/tmp/fsarm64
adb shell
su
cd data/local/tmp
chmod 777 fsarm64
./fsarm64
使用
# CMD 手机端
adb shell
su
./data/local/tmp/fsarm64 # 启动fs服务
# 可添加参数 -l 0.0.0.0:9000 指定端口为9000(默认27042),用于frida -H连接多个设备
# CMD 电脑端
workon frida #进入frida环境
frida -H -U -l hook.js
新版本 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 语法。
代码如下
// java层的代码 都需要在perform下执行
Java.perform(function () {
// Java.use() // 选择对应的类名 返回实例化的对象 可直接调用类下方法(反编译后查看)
var Util = Java.use('com.dodonew.online.util.Utils')
// 调用类下的md5方法 同时实现方法改为新函数
Util.md5.implementation = function (a) {
console.log('a: ', a)
var ret = this.md5(a)
console.log('ret: ', ret)
return ret
}
})
运行 frida -U -F -l hook.js
,触发 hook 的函数,便可打印出参。
获取类
// Java.use(类名)
let J_String = Java.use('java.lang.String')
let HashMap = Java.use('java.util.HashMap')
let Utils = Java.use('com.kuizuo.app.Utils')
静态方法与实例方法
类.方法.implementation = function () {
this.方法()
}
// 如果有返回值则需要将返回值返回
Util.md5.implementation = function (a) {
console.log('a: ', a)
let ret = this.md5(a)
console.log('ret: ', ret)
return ret
}
const HashMap = Java.use('java.util.HashMap')
HashMap.put.implementation = function (key, value) {
console.log(JSON.stringify({ key: key.toString(), value: value.toString() }))
let ret = this.put(key, value) // 如果不修改的话,则需要原封不动的传入。
return ret
}
重载方法
如果方法有重载,需要使用.overload('java.lang.String')
给定参数个数与类型,如果有重载,但是不使用 overload,frida 将会报错
Util.test.overload('java.lang.String').implementation = function (a) {
let ret = this.test(a)
return ret
}
Util.test.overload('int').implementation = function (a) {
let ret = this.test(a)
return ret
}
hook 所有重载方法
像上述两个重载方法,就需要编写两份代码,如果重载方法过多,代码不能很好的复用,就可以使用获取类下的所有重载方法
类.方法.overloads // 返回所有重载方法,依次为每个成员实现implementation方法即可hook多个重载方法
let overloads = RequestUtil.encodeDesMap.overloads
for (const overload of overloads) {
overload.implementation = function () {
// console.log(Array.from(arguments));
console.log([...arguments])
// 两者都是打印参数,将类数组转真实数组
return this.encodeDesMap(...arguments)
}
}
构造方法
类.$init.implementation = function () {
this.$init()
}
实例化对象
类.$new() // 等同于 new 类()
主动调用类方法
以下的“类”,是通过Java.use()
返回的值。
静态方法
类.方法()
实例方法 实例化对象
let obj = 类.$new()
obj.方法()
实例方法 获取已有对象(Java.choose)
内存中遍历,找到所有符合条件的对象。
Java.choose('类路径', {
onMatch: function (obj) {
obj.方法()
},
onComplete: function () {
console.log('内存中的对象搜索完毕')
},
})
这样调用不优雅,会陷入回调地狱,所以可以封装成一个外部函数,来调用。(留个伏笔 TODO…)
修改函数参数与返回值
Utils.md5.implementation = function (a) {
let b = '随便设置的参数值'
let result = this.md5(b) // 直接修改成b
return '随便设置的返回值' // frida会将字符串包装成java的String对象。
// return J_String.$new("随便设置的返回值");
}
获取与修改类字段(成员变量)
静态字段
类.字段.value // 获取类的属性值
类.字段.value = '新的值' // 修改类的值
实例字段 实例化对象
let obj = 类.$new()
obj.字段.value
实例字段 获取已有对象(Java.choose)
Java.choose('类路径', {
onMatch: function (obj) {
console.log(obj.字段.value)
},
onComplete: function () {},
})
注: 如果字段名与方法名一样,则需要给字段名前加下划线_,否则获取到的是方法
内部类与匿名类
内部类
const 外部类$内部类 = Java.use('外部类$内部类') // 变量命名随意
const 外部类$1 = Java.use('外部类$1') // 获取第一个内部类
匿名类
匿名类是根据内存生成,没有具体的内部类名,通过 smali 代码来判断,获取到的可能像下面这样
const $1 = Java.use('包名.MainActivity$1')