frida零基础入门
It’s Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript or your own library into native apps on Windows, macOS, GNU/Linux, iOS, watchOS, tvOS, Android, FreeBSD, and QNX. Frida also provides you with some simple tools built on top of the Frida API. These can be used as-is, tweaked to your needs, or serve as examples of how to use the API.
Frida是一个动态代码插桩工具,或者说框架,它允许安全研究员,逆向工程师等在Windows、macOS、GNU/Linux、iOS、watchOS、tvOS、Android、FreeBSD和QNX上,向原生应用中注入JavaScript代码片段或自己的库。Frida还提供了一些基于其API构建的简单工具,可以直接使用、按需调整,或者作为使用API的参考示例。官网定义将其与Greasemonkey(油猴子)类比,Greasemonkey是一个Firefox插件,可以让用户编写用户脚本(UserScript)来修改网页内容或行为,在不改动网页源码的前提下,实现自定义功能。
Frida核心(Gum)使用c语言编写,通过将编写的js代码(开发者使用javascript语言编写的注入代码,gumjs包含微型的js执行引擎quickjs)注入目标进程中,达到访问目标进程的内存,hook特定操作,调用目标进程的函数的效果(目标进程与你嵌入的代码内置一个双向的通信通道)。这里使用javascript可以方便的捕获错误,而不是coredump。
0.Frida安装
避坑提醒,安装最新版本的frida工具,防止存在bug
objection工具是专为渗透测试场景而优化的Frida命令行客户端,将Frida的底层能力用简单命令进行了封装
sudo pip install -U frida-tools frida
sudo pip install -U objection
sudo npm install -g frida @types/frida-gum
1.相关概念
frida提供多种机制用于实现动态代码插桩的目的
- 注入机制,适合完全控制目标设备,开发者想控制一个已有程序、附加到一个正在运行的程序,或者在程序刚启动时劫持它,然后运行自定义的插桩逻辑,frida-server场景
- 嵌入机制,运用在某些仅拥有目标程序的源码或二进制文件,且无法直接控制设备的场景,frida-gadget场景
frida-core是frida的核心通信和注入层,负责将GumJS脚本注入目标进程
frida-server是一个运行在在目标设备上的守护进程,调用frida-core
frida-gadget是frida的轻量级注入载荷(payload),本质是一个可嵌入的共享库
stalker是frida内部的核心跟踪引擎,其提供线程跟踪,捕获函数和代码块甚至指令的执行
2.工具说明
基本工具的使用说明,官网已详尽的叙述,本节是对官方文档的实践
frida-ls-devices
用于列出当前可用的设备(包括物理设备、模拟器或远程连接的目标),方便开发者进行动态分析和调试
frida-ls-devices
Id Type Name OS
-------------- ------ --------------- ------------
local local Local System Ubuntu 22.04
36081FDH30010K usb Pixel 7 Pro Android 14
barebone remote GDB Remote Stub
socket remote Local Socket
frida-ps
展示目标设备正在运行的进程
frida-ps -U
...
10261 地图
20676 富途牛牛
12060 币安
13105 微信
4305 微信输入法
6365 支付宝
4091 欧路词典
16677 知乎
12711 翻译
11561 通讯录
...
frida-discover
frida-discover自动列出程序中所有可被frida-trace跟踪的内部函数
frida-discover -U 微信
boot.oat
Calls Function
42 sub_691800
26 sub_3059e0
22 sub_3298c0
....
libutils.so
Calls Function
26 sub_1cc90
24 systemTime
24 _ZN7android12uptimeMillisEv
...
frida-trace
frida-trace命令动态跟踪应用函数调用,自动生成跟踪的函数样例
比如下面的例子,跟踪所有模块的send开头的函数生成桩函数,除voipMain模块
frida-trace -U -i "send*" 微信 -X "libvoipMain.so"
sendEvent: Loaded handler at "/tmp/test/__handlers__/libtenpay_utils.so/sendEvent.js"
sendfile: Loaded handler at "/tmp/test/__handlers__/libc.so/sendfile.js"
sendmmsg: Loaded handler at "/tmp/test/__handlers__/libc.so/sendmmsg.js"
send: Loaded handler at "/tmp/test/__handlers__/libc.so/send.js"
sendmsg: Loaded handler at "/tmp/test/__handlers__/libc.so/sendmsg.js"
sendto: Loaded handler at "/tmp/test/__handlers__/libc.so/sendto.js"
sendSignalToProcessGroup: Loaded handler at "/tmp/test/__handlers__/libprocessgroup.so/sendSignalToProcessGroup.js"
Started tracing 7 functions. Web UI available at http://localhost:35103/
frida-kill
frida提供的命令行工具,kill指定设备的目标进程
frida-kill -U 16677
frida-cli
frdia提供一个REPL(交互式解释器)界面,旨在模拟IPython(或Cycript)的诸多优秀特性,让开发者能更贴近代码进行快速原型开发和便捷调试
3.Frida闯关
github:aprz512提供的frida_example.apk包含7个关卡,帮助初学者快速入门frida的Java层注入
分析apk时如果不知道当前界面使用的类实现,可以通过三种方法快速定位:
- 通过adb logcat过滤ActvityManager过滤
- 使用jadx针对frida_example.apk进行反编译,浏览AndroidManifest.xml的相关信息可以快速定位
- adb shell dumpsys window可以过滤出当前窗口实现类

- 登录破解,java层函数重载
分析逆向源码发现,作者使用用户名作HmacSHA256算法的消息认证码,对其自身对用户名进行消息摘要

这里只需要hook关键验证函数a返回固定字符串talent即可完成破解
function main() {
Java.perform(() => {
const Activity = Java.use('com.example.androiddemo.Activity.LoginActivity');
Activity.a.overload('[B').implementation = function (arg0) {
return "talent";
}
});
}
- 修改函数参数及返回值
源码中作者将字符串"请输入密码:“gzip压缩后,使用base64编码与固定字符串判断

function main() {
Java.perform(() => {
const Activity = Java.use('com.example.androiddemo.Activity.FridaActivity1');
Activity.a.implementation = function (arg0) {
return "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=";
}
});
}
- 修改类的静态变量与实例的私有变量
成员函数oncheck中检查类的静态变量static_bool_var和实例的私有变量bool_var

function main() {
Java.perform(() => {
const Activity = Java.use('com.example.androiddemo.Activity.FridaActivity2');
// 类的静态成员变量,在类初始化时就创建
// 1.使用类提供的静态方法
// 2.直接设置类的静态变量
Activity.setStatic_bool_var();
Activity.static_bool_var.value = true;
// 类的实例成员变量,在类实例化时创建
// 1.使用choose方法,其在java的堆内存中查找类的实例,在匹配到指定的类实例后,调用指定的onMatch回调,查找完成后调用onComplete回调
// 2.HOOK类的构造函数,在构造函数中设置相关动作,这要求在类的实例未创建,使用spawn模式可以完成相关工作
Java.choose('com.example.androiddemo.Activity.FridaActivity2', {
onMatch: function (inst) {
inst.setBool_var();
},
onComplete: () => { }
});
Activity.$init.overload().implementation = function () {
this.$init();
this.static_bool_var.value = true;
this.bool_var.value = true;
}
});
}
- 同名成员变量与成员函数的特殊处理
相关问题在2017年已解决,https://github.com/frida/frida-java-bridge/issues/20

function main() {
Java.perform(() => {
Java.choose('com.example.androiddemo.Activity.FridaActivity3', {
onMatch: function (inst) {
inst.static_bool_var.value = true;
inst.bool_var.value = true;
inst._same_name_bool_var.value = true;
},
onComplete: () => { }
});
});
}
- 嵌套类的HOOK操作
关卡4主要涉及嵌套类,针对相同前缀的函数族HOOK操作

function main() {
Java.perform(() => {
const Inner = Java.use('com.example.androiddemo.Activity.FridaActivity4$InnerClasses');
const JClass = Inner.class;
const methods = JClass.getDeclaredMethods();
methods.forEach(item => {
const name = item.getName();
if (name.startsWith("check")) {
try {
const method = Inner[name];
method.overload().implementation = () => { return true; };
} catch (err) {
console.warn(err.message);
}
}
});
});
}
- 动态类的HOOK操作
分析源码,关键函数getDynamicDexCheck调用loaddex动态创建com.example.androiddemo.Dynamic.DynamicCheck类的实例,实例的check方法返回false

破解的方法很多,比如实现一个CheckInterface类对象覆盖原有对象,又或者只是HOOK核心com.example.androiddemo.Dynamic.DynamicCheck类的实例的check函数
待解决,hook函数getDynamicDexCheck,创建新的FakeCheck实例并没有成功
方案一:
function main() {
Java.perform(() => {
const CheckInterface = Java.use('com.example.androiddemo.Dynamic.CheckInterface');
const FakeCheck = Java.registerClass({
name: 'com.example.androiddemo.Dynamic.FakeCheck',
implements: [CheckInterface],
methods: {
check: function () { return true; }
},
fields: {
description: 'java.lang.String',
limit: 'int',
}
});
Java.choose('com.example.androiddemo.Activity.FridaActivity5', {
onMatch: function (inst) {
inst.DynamicDexCheck.value = FakeCheck.$new();
},
onComplete: () => { }
});
});
}
方案二:
function main() {
Java.perform(() => {
Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
if (loader.findClass('com.example.androiddemo.Dynamic.DynamicCheck')) {
Java.classFactory.loader = loader
}
} catch (err) {
console.log(err.message);
}
},
onComplete: function () {
console.log("enumerateClassLoaders complete");
}
})
Java.use('com.example.androiddemo.Dynamic.DynamicCheck').check.implementation = function () {
return true
}
});
}
- import类的HOOK操作

// 方案一:
// 类是惰性加载的,在未执行onclick方法时,enumerateLoadedClasses并没有Frida6ClassX并没有加载
function main() {
Java.perform(() => {
Java.perform(() => {
Java.enumerateLoadedClasses({
onMatch: function (name, handle) {
try {
if (name.startsWith("com.example.androiddemo.Activity.Frida6.Frida6Class")) {
Java.use(name).check.implementation = () => { return true; };
}
} catch (err) {
console.log(err.message);
}
},
onComplete: function () {
console.log("enumerateLoadedClasses complete");
}
});
});
});
}
// 方案二:
Java.enumerateClassLoaders({
onMatch: function (loader) {
[
"com.example.androiddemo.Activity.Frida6.Frida6Class0",
"com.example.androiddemo.Activity.Frida6.Frida6Class1",
"com.example.androiddemo.Activity.Frida6.Frida6Class2",
].forEach(item => {
try {
if (loader.findClass(item)) {
Java.classFactory.loader = loader;
Java.use(item).check.implementation = () => { return true; };
}
} catch (err) {
console.log(err.message);
}
})
},
onComplete: function () {
console.log("enumerateClassLoaders complete");
}
});
至此frida在Java层的基本注入方式已经介绍完毕,无论是静态类的Java.use(),还是动态加载类时配合ClassFactory.get(classloader)的使用,Frida为Android应用分析和渗透测试提供了极其灵活的入口。但这仅仅是万里长征的第一步,真正复杂的逆向目标往往会涉及Native层函数劫持,绕过So加壳与延迟加载,动态分析JNI接口,内存结构观察与对象恢复,甚至网络流量的劫持等高级主题,这些后续有时间会陆续补充。