Frida 操作方法总结
附件下载
https://github.com/DERE-ad2001/Frida-Labs
Java层
前期准备
- 使用 Jadx 进行逆向工程的基础知识。
- 应具备理解 Java 代码的能力。
- 具备编写小型 JavaScript 代码片段的能力。
- 熟悉 adb。
- 设备已 root。
- Frida 环境配置。
Hook(Hooking)简介
让我们从非常基础的知识开始。
什么是钩子?
Hook 是指拦截和修改应用程序或 Android 系统中函数或方法行为的过程。例如,我们可以钩取我们应用程序中的一个方法,并通过插入我们自己的实现来改变其功能。
现在,让我们尝试在一个应用程序中钩取一个方法。我们将使用 JavaScript API 来完成这个任务,但值得注意的是,Frida 也支持 Python。
1、使用 Hook 修改被调用的方法的逻辑,返回值,传入参数
基本模板
首先提供一个模板,然后一步步来解释。
Java.perform(function() {
var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<method_to_hook>.implementation = function(<args>) {
/*
我们自己的方法实现
*/
}
})
Java.perform
是 Frida 中用于创建一个特殊上下文的函数,让你的脚本能够与 Android 应用程序中的 Java 代码进行交互。它就像是打开了一扇门,让你能够访问并操纵应用程序内部运行的 Java 代码。一旦进入这个上下文,你就可以执行诸如钩取方法或访问 Java 类等操作来控制或观察应用程序的行为。var <class_reference> = Java.use("<package_name>.<class>");
在这里,你声明一个变量<class_reference>
来表示目标 Android 应用程序中的一个 Java 类。你使用Java.use
函数指定要使用的类,该函数接受类名作为参数。<package_name>
表示 Android 应用程序的包名,<class>
表示你想要与之交互的类。<package_name> <class_reference>.<method_to_hook>.implementation = function(<args>) {}
在所选的类内部,通过<class_reference>.<method_to_hook>
符号访问你想要钩取的方法。这是你可以定义自己的逻辑以在钩取的方法被调用时执行的地方。<args>
表示传递给函数的参数。
例题Frida-Labs 0x1
通过 Jadx 分析 Frida-labs 0x1
onCreate 方法
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(C0570R.layout.activity_main);
final EditText editText = (EditText) findViewById(C0570R.C0573id.editTextTextPassword);
this.f103t1 = (TextView) findViewById(C0570R.C0573id.textview1);
final int i = get_random();
((Button) findViewById(C0570R.C0573id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.ad2001.frida0x1.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
String obj = editText.getText().toString();
if (TextUtils.isDigitsOnly(obj)) {
MainActivity.this.check(i, Integer.parseInt(obj));
} else {
Toast.makeText(MainActivity.this.getApplicationContext(), "Enter a valid number !!", 1).show();
}
}
});
}
可以发现,在 onCreate
方法中,有一个监听事件,监听了 button
的点击,当按钮点击下去之后,程序首先判断输入是不是数字,是数字的话,就将其从 string 转化为 int,再进入 check
中与 i
比较,因此我们需要检查 check
方法。
check 方法
void check(int i, int i2) {
if ((i * 2) + 4 == i2) {
Toast.makeText(getApplicationContext(), "Yey you guessed it right", 1).show();
StringBuilder sb = new StringBuilder();
for (int i3 = 0; i3 < 20; i3++) {
char charAt = "AMDYV{WVWT_CJJF_0s1}".charAt(i3);
if (charAt < 'a' || charAt > 'z') {
if (charAt >= 'A') {
if (charAt <= 'Z') {
charAt = (char) (charAt - 21);
if (charAt >= 'A') {
}
charAt = (char) (charAt + 26);
}
}
sb.append(charAt);
} else {
charAt = (char) (charAt - 21);
if (charAt >= 'a') {
sb.append(charAt);
}
charAt = (char) (charAt + 26);
sb.append(charAt);
}
}
this.f103t1.setText(sb.toString());
return;
}
Toast.makeText(getApplicationContext(), "Try again", 1).show();
}
本方法显而易见就是检查输入是否能够满足 i*2 + 4 == i2
,如果满足则将 flag 输出到 f103t1
所绑定的 textView
控件上,其中用于判断的 i
则来自 get_random
。
get_random
int get_random() {
return new Random().nextInt(100);
}
显而易见,本方法就只是普通的返回一个随机数。
Hook begin!
对于本样例程序,我们有两种方法去解决,首先我们可以直接 hook 程序逻辑。更改随机产生的值为一个固定值。或者 hook check 方法更改 check 方法传入的参数
Hook get_random 方法
实现代码
function hook(){
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.get_random.implementation = function (){
return 0;
}
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);
代码解释如下:
首先定义了一个名为
hook
的 JavaScript 函数,其中包含了对目标应用特定方法的 hook 逻辑。hook
函数通过 Frida 的 Java API 来获取目标应用中的MainActivity
类。- 然后,它通过
Java.use()
方法获取了MainActivity
类的引用,使得我们可以访问该类的方法。 - 最后,
hook
函数将MainActivity
类中的get_random
方法进行了修改。它用自定义的实现替换了原有方法的实现,使得每次调用get_random
方法时都返回固定值 0。
接着定义了一个名为
main
的 JavaScript 函数,其中包含了 Frida 的Java.perform()
方法,用于执行指定的 hook 逻辑。最后,通过
setImmediate()
函数调用main
函数,确保在 Frida 脚本启动后立即执行。
hook check 方法
如果我们检查 check 函数的参数,第一个参数 i
表示随机数,而第二个参数 i2
对应于用户输入的数字。让我们使用 Frida 来捕获并转储这两个参数。
在处理具有参数的方法时,重要的是使用 overload(arg_type)
关键字指定预期的参数类型。此外,在钩入方法时确保包括这些指定的参数在你的实现中。在这里,我们的 check()
函数接受两个整数参数,所以我们可以这样指定:
a.check.overload(int, int).implementation = function(a, b) {
...
}
function hook2(){
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.check.overload('int','int').implementation = function (a,b){
console.log("Origin i and i2 = ",a,b);
return this.check(a,b);
}
}
function main(){
Java.perform(function (){
hook2();
})
}
setImmediate(main);
我们可以使用 console.log
查看传入的 a
与 b
是什么

在 this.check(a,b);
中的 a
,b
改为自己设定的值就可以了。

2、Hook 调用静态的未被调用的方法
在之前讲到的 Java.use Api中,如果我们指定的类中包含了静态的方法,则我们可以直接调用该方法。模板如下:
Java.perform(function (){
var <class_reference> = Java.use("<package_name>.<class>");
a.function(val);
})
例题 Frida-labs 0x2
MainActivity 类
package com.ad2001.frida0x2;
import android.os.Bundle;
import android.util.Base64;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
/* renamed from: t1 */
static TextView f103t1;
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(C0569R.layout.activity_main);
f103t1 = (TextView) findViewById(C0569R.C0572id.textview);
}
public static void get_flag(int a) {
if (a == 4919) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec("HILLBILLWILLBINN".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
cipher.init(2, secretKeySpec, iv);
byte[] decryptedBytes = cipher.doFinal(Base64.decode("q7mBQegjhpfIAr0OgfLvH0t/D0Xi0ieG0vd+8ZVW+b4=", 0));
String decryptedText = new String(decryptedBytes);
f103t1.setText(decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
本用例程序就一个 MainActivity
类,类中存在一个未被使用的静态方法 get_flag
,在 get_flag
中比较了传入的参数,如果传入的参数为 4919
则解密 flag,设置给 txtView 控件,那么根据之前给出的调用模板,我们 hook 代码如下:
Hook 代码:
function hook(){
var MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
MainActivity.get_flag(4919);
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);
但是我们发现如果使用的是 setIMMediate(main)
的话我们使用 frida -U -f com.ad2001.frida0x2 -l .\Hook.js
可能会导致 hook不上的情况。

解决方法 1
我们事先启动 Frida 0x2 应用程序。然后使用如下命令注入我们的脚本
frida -U 'Frida 0x2' -l .\Hook.js

本方法与之前的方法不同之处是该方法是直接 hook 入我们后台正在启动的程序,而之前的方法是根据包名再启动一个程序。
解决方法 2
当我们发现使用 解决方法1
能够成功hook的时候,就可以推断出,是由于我们启动main函数使用的是 setImmediate(main)
,是立即启动可能会导致脚本注入的速度比程序启动的速度快。因此我们可以改用 setTimeout(main,1000)
,也就是延迟 1 秒钟启动程序。
详情可见 https://www.cnblogs.com/fsjohnhuang/p/4151595.html

3、更改类中的静态变量
类似于如下写法 static int code = 0;
使用 static
修饰的变量则为静态变量。我们可以用如下方法更改静态变量。
Java.perform(function (){
var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<variable>.value = <value>;
})
例题 Frida-labs 0x3
MainActivity 类

标记处我们可以发现,当 Checker.code
为 512 的时候点击按钮,程序则会解密并且将 textView 控件设置为 Flag。
Hook 代码
function hook(){
var a = Java.use("com.ad2001.frida0x3.Checker");
a.code.value = 512;
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);
4、调用非 MainActivity,非静态方法
在 Java 代码中,如果创建了一个非静态的类,当我们需要使用这个类的时候需要 new 一个类的对象出来我们才能使用这个类的功能。类似代码如下:
Check ch = new Check();
String flag = ch.get_flag(1337);
那么在 Java 源码中需要 new 出来的实例,我们怎么使用 Frida 来实现呢?
模板如下:
Java.perform(function() {
var <class_reference> = Java.use("<package_name>.<class>");
var <class_instance> = <class_reference>.$new(); // Class Object
<class_instance>.<method>(); // 调用方法
})
例题 Frida-labs 0x4
MainActivity:
MainActivity 中没有任何东西。
Checker
Checker
中出现了 get_flag
方法,返回了flag。则我们使用之前的模板来 Hook
Hook 代码:
function hook(){
console.log("Hook Success!");
var Check = Java.use("com.ad2001.frida0x4.Check");
var Check_obj = Check.$new();
var String = Check_obj.get_flag(1337);
console.log(String);
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);

5、调用 MainActivity 中的非静态方法
前面有提到过如果不是 MainActivity 中的方法我们使用 .$new()
可以创建一个实例。那么如果我们将这个使用到 MainActivity 会发生什么呢?
function hook(){
var MainActivity = Java.use("com.ad2001.frida0x5");
var MainActivity_obj = MainActivity.$new();
}
好吧,它崩溃了。那么这是什么原因呢?
直接使用 Frida 创建 MainActivity
或任何 Android 组件可能会很棘手,因为 Android 的生命周期和线程规则。Android 组件,如 Activity
子类,依赖于应用程序上下文进行正确运行。在 Frida 中,您可能缺少必要的上下文。Android UI 组件通常需要具有关联 Looper
的特定线程。如果涉及UI任务,请确保在具有活动 Looper
的主线程上执行。活动是较大的 Android 应用程序生命周期的一部分。创建 MainActivity
的实例可能需要应用处于特定状态,并且通过 Frida 管理整个生命周期可能并不直接。总之,为 MainActivity
创建实例并不是一个好主意。
那么这里的解决方案是什么呢?
当 Android 应用程序启动时,系统会创建 MainActivity
的一个实例(或 AndroidManifest.xml 文件中指定的启动器活动)。创建 MainActivity
实例是 Android 应用程序生命周期的一部分。因此,我们可以使用 frida 获取 MainActivity
的实例,然后调用 flag()
方法来获取我们的标志。
在现有实例上调用方法
在现有实例上调用方法可以很容易地通过 Frida 完成。为此,我们将使用两个 API。
Java.performNow
:用于在 Java 运行时环境中执行代码的函数。Java.choose
:在运行时枚举指定 Java 类(作为第一个参数提供)的实例。
让我展示一个模板给你。
Java.performNow(function() {
Java.choose('<包名>.<类名>', {
onMatch: function(instance) {
// 待办事项
},
onComplete: function() {}
});
});
这里有两个回调函数:
- onMatch
onMatch
回调函数在Java.choose
操作期间找到指定类的每个实例时执行。- 这个回调函数接收当前实例作为它的参数。
- 您可以在
onMatch
回调中定义自定义操作,以在每个实例上执行。 function(instance) {}
,instance
参数表示目标类的每个匹配实例。您可以使用任何其他名称。
- onComplete
onComplete
回调在Java.choose
操作完成后执行操作或清理任务。此块是可选的,如果您在搜索完成后不需要执行任何特定操作,则可以选择将其留空。
例题 Frida-labs 0x5
MainActivity
可以发现其中 flag 方法是未被调用的方法,并且是解密密文将 Flag 输出到 TextView 控件上。
BeginHook!
现在我们知道如何使用 Java.choose
API,让我们开始编写我们的 Frida 脚本。
- 包名:
com.ad2001.frida0x5
- 类名:
MainActivity
- 函数名:
flag
Java.performNow(function() {
Java.choose('com.ad2001.frida0x5.MainActivity', {
onMatch: function(instance) {
// 待办事项
},
onComplete: function() {}
});
});
让我们在成功找到 MainActivity
实例时包含一个 console.log
语句以打印一条消息。由于在枚举完成后我们没有任何特定的操作要执行,我们可以将 onComplete
块留空。
Java.performNow(function() {
Java.choose('com.ad2001.frida0x5.MainActivity', {
onMatch: function(instance) {
console.log("找到实例");
},
onComplete: function() {}
});
});
让我们启动 Frida 并注入我们的脚本。

Hook 代码
function hook(){
Java.choose('com.ad2001.frida0x5.MainActivity',{
onMatch:function (MainActivity){
MainActivity.flag(1337);
console.log("Hook Success!");
},onComplete:function (){
}
})
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);

6、MainActivity 中非静态并且参数为非静态变量方法调用
例题 Frida-labs 0x6
我们之前已经解决过类似的问题了。在这种情况下,我们有一个 get_flag()
方法,在应用程序中没有被调用。如果调用此方法,它将使用 AES 解密标志,并将标志设置在 Textview 中。如果我们检查 get_flag
方法,它只接受一个参数,这个参数是 Checker
类的一个实例。参数被命名为 A
,其类型是 Checker
。
public void get_flag(Checker A) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
// 方法体
}
在方法内部,它检查 A.num1
是否等于 1234
,以及 A.num2
是否等于 4321
。如果条件成立,该方法将继续使用 AES 解密加密字符串,并将解密后的结果设置在 TextView 中。因此,让我们检查一下 Checker
类。

在Checker类中,我们有两个变量。
num1
num2
num1
应该等于 1234
,num2
应该等于 4321
,以满足 if
条件执行解密并设置标志的代码块。请记住,这个类也没有实例。
解决方案
这个问题很容易解决,因为我们之前已经在上一篇帖子中做过了,唯一的区别是 get_flag
方法的参数是 Checker
类的一个对象。我将总结解决这个问题的步骤如下:
- 创建一个
Checker
类的实例。 - 将
num1
设置为 1234,num2
设置为 4321。 - 获取
MainActivity
的实例。 - 使用实例作为参数调用
get_flag
方法。
让我们开始编写我们的frida脚本。
首先让我们创建 Checker
类的实例。
var checker = Java.use("com.ad2001.frida0x6.Checker");
var checker_obj = checker.$new(); // 类对象
设置 num1
和 num2
的值。
checker_obj.num1.value = 1234;
checker_obj.num2.value = 4321;
现在让我们获取 MainActivity
的实例。我们可以使用 Java.performNow
和 Java.choose
API。我们在之前的挑战中已经做过了。
Java.performNow(function() {
Java.choose('com.ad2001.frida0x6.MainActivity', {
onMatch: function(instance) {
console.log("找到实例");
},
onComplete: function() {}
});
})
让我们更新脚本,加入 Checker
类的实例。
Java.performNow(function() {
Java.choose('com.ad2001.frida0x6.MainActivity', {
onMatch: function(instance) {
console.log("找到实例");
var checker = Java.use("com.ad2001.frida0x6.Checker");
var checker_obj = checker.$new(); // 类对象
checker_obj.num1.value = 1234;
checker_obj.num2.value = 4321;
},
onComplete: function() {}
});
});
现在唯一要做的是通过传递 Checker
类的实例来调用 get_flag
方法。
Java.performNow(function() {
Java.choose('com.ad2001.frida0x6.MainActivity', {
onMatch: function(instance) {
console.log("找到实例");
var checker = Java.use("com.ad2001.frida0x6.Checker");
var checker_obj = checker.$new(); // 类对象
checker_obj.num1.value = 1234; // num1
checker_obj.num2.value = 4321; // num2
instance.get_flag(checker_obj); // 调用get_flag方法
},
onComplete: function() {}
});
});
让我们启动 Frida 并运行我们的脚本。
PS C:\Users\ajind> frida -U -f com.ad2001.frida0x6

当我们检查我们的手机时,TextView 将显示标志。
7、Hook 构造函数
如果在 ARM64 设备上不工作请看 issue:https://github.com/frida/frida/issues/1575
挂钩构造函数十分简单,与挂钩方法类似。让我为您提供一个模板。
Java.perform(function() {
var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.$init.implementation = function(<args>){
/*
*/
}
});
我们可以看到,为了挂钩构造函数,我们可以使用 $init
关键字。
例题 Frida-labs 0x7
MainActivity
可以看到程序在使用 flag
方法判断之前,首先使用 Checker ch = new Checker(123, 321);
创建了一个 Checker
实例,则 123,321 分别对应 A.num1
与 A.num2
。
那么我们只需要钩住构造函数即可。
Hook 代码
function hook(){
var Checker = Java.use("com.ad2001.frida0x7.Checker");
Checker.$init.implementation = function (a,b){
console.log("Origin num",a,b);
this.$init(600,600);
console.log("Hook Success");
}
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);

Native 层
前期准备
- 使用 Jadx 进行逆向工程的基础知识。
- 能够理解 Java 代码。
- 能够编写简短的 JavaScript 代码片段。
- 熟悉 adb。
- 已 root 的设备。
- 对 x86/ARM64 汇编和逆向工程有基础了解。
1、Hook Native 层中调用的函数并且读取传入的参数
对于 Native 层的函数 Hook,我们使用如下模板。
Interceptor.attach(targetAddress, {
onEnter: function (args) {
console.log('Entering ' + functionName);
// Modify or log arguments if needed
},
onLeave: function (retval) {
console.log('Leaving ' + functionName);
// Modify or log return value if needed
}
});
Interceptor.attach
:将回调函数附加到指定的函数地址。targetAddress
应该是我们想要挂钩的本地函数的地址。onEnter
:当挂钩的函数被调用时,调用此回调。它提供对函数参数 (args
) 的访问。onLeave
:当挂钩的函数即将退出时,调用此回调。它提供对返回值 (retval
) 的访问。需要获取 targetAddress 我们可以方便的使用如下 API
Module.enumerateExports()
通过调用Module.enumerateExports()
,我们可以获取到导出函数的名称、地址以及其他相关信息。这些信息对于进行函数挂钩、函数跟踪或者调用其他函数都非常有用。Module.getExportByName()
当我们知道要查找的导出项的名称但不知道其地址时,可以使用Module.getExportByName()
。通过提供导出项的名称作为参数,这个函数会返回与该名称对应的导出项的地址。Module.findExportByName()
这与Module.getExportByName()
是一样的。唯一的区别在于,如果未找到导出项,Module.getExportByName()
会引发异常,而Module.findExportByName()
如果未找到导出项则返回null
。让我们看一个示例。Module.getBaseAddress()
通过调用Module.getBaseAddress()
函数,我们可以获取指定模块的基址地址,然后可以基于这个基址地址进行偏移计算,以定位模块内部的特定函数、变量或者数据结构Module.enumerateImports()
通过调用Module.enumerateImports()
函数,我们可以获取到指定模块导入的外部函数或变量的名称、地址以及其他相关信息。
例题 Frida-Labs 0x8
MainActivity
可以发现,程序从 EditText 控件中获取到了用户的输入,然后调用了 native 层中的 cmpstr
函数进行比较。
Navtive 层逻辑
程序在 cmpstr
中使用了 strcmp
函数,那么我们只需要拿到 strcmp
函数的传入参数就可以知道程序的正确输入了
Hook begin
首先我们使用 Module.enumerateImports("libfrida0x8.so")
查看导入表

可以发现 strcmp
来自于 libc.so
,那么我们就可以使用 Module.findExportByName("libc.so","strcmp");
来获取 strcmp
的地址了

获取了 strcmp
的地址就可以使用之前给的模板进行 Hook 了
function hook(){
var targetAddress = Module.findExportByName("libc.so","strcmp");
console.log("Strcmp Address: ",targetAddress.toString(16));
Interceptor.attach(targetAddress,{
onEnter:function (args){
},onLeave:function(retval){
}
})
console.log("success!");
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);
但是我们需要注意的是 strcmp
可能不止调用一次,因此我们需要判断 strcmp
的第一个参数是否为0我们才进行操作,不然 hook 可能会一直循环输出

因此我们可以使用 Memory.readUtf8String(args[0]);
来获取我们的输入字符串,并且使用 if (input.includes("111"))
来判断
hook 代码
function hook(){
var targetAddress = Module.findExportByName("libc.so","strcmp");
console.log("Strcmp Address: ",targetAddress.toString(16));
Interceptor.attach(targetAddress,{
onEnter:function (args){
var input = Memory.readUtf8String(args[0]);
if (input.includes("111")){
console.log(Memory.readUtf8String(args[1]));
}
},onLeave:function(retval){
}
})
console.log("success!");
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main);

2、Hook 修改 native 层程序返回值
首先还是给出 hook 的模板如下:
Interceptor.attach(functionaddr, {
onEnter: function (args) {
},
onLeave: function (retval) {
}
});
可以看到在 onLeave
中有一个参数 retval
,这个就是我们 hook 上的程序的返回值,我们可以使用 retval.replace(val)
来修改返回值。
例题 Frida-labs 0x9
MainActivity

可以发现程序根据 native 层的 check_flag
方法的返回值
check_flag
只是简简单单的返回了一个 1
hookbegin
首先使用 Module.enumerateExports("liba0x9.so")
,查看导出表,看看 check_flag
方法的偏移地址
然后就可以使用模板一把梭了
hook 代码
function hook(){
var check_flag = Module.enumerateExports("liba0x9.so")[0]["address"];
console.log("Func address = ",check_flag);
Interceptor.attach(check_flag,{
onEnter:function (args){
},onLeave:function (retval){
console.log("Origin retval : ",retval);
retval.replace(1337);
}
})
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(hook);

3、调用 native 层中未被调用的方法
提供一个模板。
var native_adr = new NativePointer(<address_of_the_native_function>);
const native_function = new NativeFunction(native_adr, '<return type>', ['argument_data_type']);
native_function(<arguments>);
逐行解释。
var native_adr = new NativePointer(<address_of_the_native_function>);
要在 Frida 中调用一个本地函数,我们需要一个 NativePointer
对象。我们应该将要调用的本地函数的地址传递给 NativePointer
构造函数。接下来,我们将创建 NativeFunction
对象,它表示我们想要调用的实际本地函数。它在本地函数周围创建一个 JavaScript 包装器,允许我们从 Frida 调用该本地函数。
const native_function = new NativeFunction(native_adr, '<return type>', ['argument_data_type']);
第一个参数应该是 NativePointer
对象,第二个参数是本地函数的返回类型,第三个参数是要传递给本地函数的参数的数据类型列表。现在我们可以像在 Java 空间中那样调用该方法了。
native_function(<arguments>);
好的,我们明白了。让我们来看看例题。
例题 Frida-labs 0xA
MainActivity

发现就是在主函数中加载了stringFromJNI
。
native

没有关于 flag 的信息,但是有未被调用的 flag 函数,我们直接使用 hook 调用它输出 log。

hook 代码
function hook(){
var a = Module.findBaseAddress("libfrida0xa.so");
var b = Module.enumerateExports("libfrida0xa.so");
var get_flagaddress = null;
var mvaddress = null;
for(var i = 0 ; b[i]!= null ; i ++ ){
// console.log(b[i]["name"])
if(b[i]["name"] == "_Z8get_flagii"){
console.log("function get_flag : ",b[i]["address"]);
console.log((b[i]["address"] - a).toString(16));
// mvaddress = b[i]["address"] - a;
get_flagaddress = b[i]["address"];
}
}
console.log(ptr.toString(16));
var get_flag_ptr = new NativePointer(get_flagaddress);
const get_flag = new NativeFunction(get_flag_ptr,'char',['int','int']);
var flag = get_flag(1,2);
console.log(flag)
//console.log(b);
}
function main(){
Java.perform(function (){
hook();
})
}
setImmediate(main)

4、更改 Native 层方法的汇编指令
首先我们先看来自 x86 指令集的 frida 使用模板
var writer = new X86Writer(opcodeaddr);
Memory.protect(opcodeaddr, 0x1000, "rwx");
try {
writer.flush();
} finally {
writer.dispose();
}
X86Writer
的实例化:
var writer = new X86Writer(<指令的地址>);
- 这将创建一个
X86Writer
类的实例,并指定我们要修改的指令的地址。这设置了写入器以操作指定的内存位置。
插入指令:
try { /* 在此处插入指令 */ }
- 在
try
块内,我们可以插入要修改/添加的 x86 指令。X86Writer
实例提供了各种方法来插入各种 x86 指令。我们可以查阅文档以了解详情。
刷新更改:
writer.flush();
- 插入指令后,调用
flush
方法将更改应用到内存中。这确保修改后的指令被写入内存位置。
清理:
finally { /* 释放X86Writer以释放资源 */ writer.dispose(); }
finally
块用于确保X86Writer
资源得到适当清理。调用dispose
方法释放与X86Writer
实例关联的资源。
解除段只读权限:Memory.protect
。我们可以使用这个函数来修改内存区域的保护属性。Memory.protect
函数的语法如下:
Memory.protect(地址, 大小, 保护属性);
地址
:要更改保护的内存区域的起始地址。大小
:内存区域的大小,以字节为单位。保护属性
:内存区域的保护属性。
那么如何使用进行覆写呢?
对于 x86 系统而言我们首先需要查看官方文档中的使用方法
https://frida.re/docs/javascript-api/#x86writer

对于 arm64 系统而言,我们使用如下 api
https://frida.re/docs/javascript-api/#arm64writer
接下来让我用一个用例程序来讲一下这个指令的用法,我们示范的内容为 arm64 架构
例题 Frida-labs 0xB
MainActivity
首先我们看到 MainActivity
函数内容

发现 MainActivity
就是在用户点击按钮后调用了 getflag
方法,但是正常点击 getflag
方法并不会返回 flag 值。
Native 层内容

惊讶的发现 MainActivity
中什么都没有,显然这是不存在的。接下来我们到控制流窗口中查看。

查看控制流发现程序出现了永假条件跳转。导致 ida 识别不到输出 flag 的功能。那么我们可以把这个 B.NE
给 Nop 掉即可
首先我们需要计算 B.NE
的偏移地址

可以发现就是基地址增加 15248,然后我们覆写为 Nop 就可以了
Hook 代码
function hook(){
var Base = Module.getBaseAddress("libfrida0xb.so");
console.log("Base address : ",Base);
var BNE = Base.add(0x15248);
Memory.protect(Base,0x1000,"rwx");
var writer = new Arm64Writer(BNE);
try{
writer.putNop();
writer.flush();
console.log("Success!!");
}finally {
writer.dispose();
}
}
function main(){
Java.perform(function (){
hook();
})
}
setTimeout(main,1000);
