[Frida]Frida操作方法总结

附件下载

https://github.com/DERE-ad2001/Frida-Labs

Java层

前期准备

  1. 使用 jadx 进行逆向工程的基础知识。
  2. 应具备理解 Java 代码的能力。
  3. 具备编写小型 JavaScript 代码片段的能力。
  4. 熟悉 adb。
  5. 设备已 root。
  6. 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>

    file

  • <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);

代码解释如下:

  1. 首先定义了一个名为hook的JavaScript函数,其中包含了对目标应用特定方法的hook逻辑。

    • hook函数通过Frida的Java API来获取目标应用中的MainActivity类。
    • 然后,它通过Java.use()方法获取了MainActivity类的引用,使得我们可以访问该类的方法。
    • 最后,hook函数将MainActivity类中的get_random方法进行了修改。它用自定义的实现替换了原有方法的实现,使得每次调用get_random方法时都返回固定值0。
  2. 接着定义了一个名为main的JavaScript函数,其中包含了Frida的Java.perform()方法,用于执行指定的hook逻辑。

  3. 最后,通过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是什么

file

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

file

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不上的情况。

file

解决方法1

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

file

本方法与之前的方法不同之处是该方法是直接hook入我们后台正在启动的程序,而之前的方法是根据包名再启动一个程序。

解决方法2

当我们发现使用解决方法1能够成功hook的时候,就可以推断出,是由于我们启动main函数使用的是setImmediate(main),是立即启动可能会导致脚本注入的速度比程序启动的速度快。因此我们可以改用setTimeout(main,1000),也就是延迟1秒钟启动程序。
详情可见https://www.cnblogs.com/fsjohnhuang/p/4151595.html

file

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类

file

标记处我们可以发现,当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:

file

MainActivity中没有任何东西。

Checker

file

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);

file

5、调用MainActivity中的非静态方法

前面有提到过如果不是MainActivity中的方法我们使用.$new()可以创建一个实例。那么如果我们将这个使用到MainActivity会发生什么呢?

function hook(){
    var MainActivity = Java.use("com.ad2001.frida0x5");
    var MainActivity_obj = MainActivity.$new();
}

file

好吧,它崩溃了。那么这是什么原因呢?

直接使用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

file

可以发现其中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并注入我们的脚本。

file

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);

file

6、MainActivity中非静态并且参数为非静态变量方法调用

例题Frida-labs 0x6

file

我们之前已经解决过类似的问题了。在这种情况下,我们有一个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类。

file

在Checker类中,我们有两个变量。

  • num1
  • num2

num1应该等于1234num2应该等于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(); // 类对象

设置num1num2的值。

checker_obj.num1.value = 1234;
checker_obj.num2.value = 4321;

现在让我们获取MainActivity的实例。我们可以使用Java.performNowJava.chooseAPI。我们在之前的挑战中已经做过了。

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

file

当我们检查我们的手机时,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

file

可以看到程序在使用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);

file

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

    1. Module.enumerateExports()
      通过调用 Module.enumerateExports(),我们可以获取到导出函数的名称、地址以及其他相关信息。这些信息对于进行函数挂钩、函数跟踪或者调用其他函数都非常有用。
    2. Module.getExportByName()
      当我们知道要查找的导出项的名称但不知道其地址时,可以使用 Module.getExportByName()。通过提供导出项的名称作为参数,这个函数会返回与该名称对应的导出项的地址。
    3. Module.findExportByName()
      这与 Module.getExportByName() 是一样的。唯一的区别在于,如果未找到导出项,Module.getExportByName() 会引发异常,而 Module.findExportByName() 如果未找到导出项则返回 null。让我们看一个示例。
    4. Module.getBaseAddress()
      通过调用 Module.getBaseAddress() 函数,我们可以获取指定模块的基址地址,然后可以基于这个基址地址进行偏移计算,以定位模块内部的特定函数、变量或者数据结构
    5. Module.enumerateImports()
      通过调用 Module.enumerateImports() 函数,我们可以获取到指定模块导入的外部函数或变量的名称、地址以及其他相关信息。

      例题 Frida-Labs 0x8

      MainActivity

      file

      可以发现,程序从EditText控件中获取到了用户的输入,然后调用了native层中的cmpstr函数进行比较。

Navtive层逻辑

file

程序在cmpstr中使用了strcmp函数,那么我们只需要拿到strcmp函数的传入参数就可以知道程序的正确输入了

Hook begin

首先我们使用Module.enumerateImports("libfrida0x8.so")查看导入表

file

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

获取了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可能会一直循环输出

file

因此我们可以使用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);

file

2、Hook修改native层程序返回值

首先还是给出hook的模板如下:

Interceptor.attach(functionaddr, {
    onEnter: function (args) {

    },
    onLeave: function (retval) {

    }
});

可以看到在onLeave中有一个参数retval,这个retval,就是我们hook上的程序的返回值,我们可以使用retval.replace(val)来修改返回值。

例题 Frida-labs 0x9

MainActivity

file

可以发现程序根据native层的check_flag 方法的返回值

check_flag

file

只是简简单单的返回了一个1

hookbegin

首先使用Module.enumerateExports("liba0x9.so"),查看导出表,看看check_flag方法的偏移地址

file

然后就可以使用模板一把梭了

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);

file

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

file

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

native

file

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

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)

file

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

file

对与arm64系统而言,我们使用如下api
https://frida.re/docs/javascript-api/#arm64writer

file

接下来让我用一个用例程序来讲一下这个指令的用法,我们示范的内容为arm64架构

例题Frida-labs 0xB

MainActivity

首先我们看到MainActivity函数内容

file

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

Native层内容

file

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

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

可以发现就是基地址增加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);

file

评论

  1. 咸鱼芬
    2 年前
    2024-3-13 15:34:13

    太帅拉

  2. yyy
    1 年前
    2025-3-09 20:14:50

    SWDD

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇