Frida-Hook-Java层操作大全

附件下载

GitHub - DERE-ad2001/Frida-Labs: The repo contains a series of challenges for learning Frida for Android Exploitation.

前期准备

  1. 使用 jadx 进行逆向工程的基础知识。
  2. 应具备理解 Java 代码的能力。
  3. 具备编写小型 JavaScript 代码片段的能力。
  4. 熟悉 adb。
  5. 设备已 root。
  6. Frida环境配置

Hook(Hooking)简介

让我们从非常基础的知识开始。

什么是钩子?

Hook是指拦截和修改应用程序或Android系统中函数或方法行为的过程。例如,我们可以钩取我们应用程序中的一个方法,并通过插入我们自己的实现来改变其功能。

现在,让我们尝试在一个应用程序中钩取一个方法。我们将使用JavaScript API 来完成这个任务,但值得注意的是,Frida也支持Python。

1、使用Hook修改被调用的方法的逻辑,返回值,传入参数

基本模板

首先让我提供给你一个模板,然后我们一步步来解释。

1

2

3

4

5

6

7

8

9

10

11

12

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方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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方法

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

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

1

2

3

int get_random() {

    return new Random().nextInt(100);

}

显而易见,本方法就只是普通的返回一个随机数。

Hook begin!

对于本样例程序,我们有两种方法去解决,首先我们可以直接hook程序逻辑。更改随机产生的值为一个固定值。或者hook check方法更改check方法传入的参数

Hook get_random方法
实现代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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()函数接受两个整数参数,所以我们可以这样指定:

1

2

3

4

5

a.check.overload(int, int).implementation = function(a, b) {

  ...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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是什么

 

2、Hook调用静态的未被调用的方法

在之前讲到的Java.use Api中,如果我们指定的类中包含了静态的方法,则我们可以直接调用该方法。模板如下:

1

2

3

4

Java.perform(function (){

    var <class_reference> = Java.use("<package_name>.<class>");

    a.function(val);

})

例题Frida-labs 0x2

MainActivity类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

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代码:

1

2

3

4

5

6

7

8

9

10

11

12

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

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

解决方法2

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

 

3、更改类中的静态变量

类似于如下写法static int code = 0;
使用static 修饰的变量则为静态变量。我们可以用如下方法更改静态变量。

1

2

3

4

5

6

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代码

1

2

3

4

5

6

7

8

9

10

11

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一个类的对象出来我们才能使用这个类的功能。类似代码如下:

1

2

Check ch = new Check();

String flag = ch.get_flag(1337);

那么在Java源码中需要new出来的实例,我们怎么使用Frida来实现呢?
模板如下:

1

2

3

4

5

6

7

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:

 

Checker中出现了get_flag方法,返回了flag。则我们使用之前的模板来Hook

Hook代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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会发生什么呢?

1

2

3

4

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类(作为第一个参数提供)的实例。

让我展示一个模板给你。

1

2

3

4

5

6

7

8

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

1

2

3

4

5

6

7

8

Java.performNow(function() {

  Java.choose('com.ad2001.frida0x5.MainActivity', {

    onMatch: function(instance) {

      // 待办事项

    },

    onComplete: function() {}

  });

});

让我们在成功找到MainActivity实例时包含一个console.log语句以打印一条消息。由于在枚举完成后我们没有任何特定的操作要执行,我们可以将onComplete块留空。

1

2

3

4

5

6

7

8

Java.performNow(function() {

  Java.choose('com.ad2001.frida0x5.MainActivity', {

    onMatch: function(instance) {

      console.log("找到实例");

    },

    onComplete: function() {}

  });

});

让我们启动Frida并注入我们的脚本。

 

Hook代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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

1

2

3

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应该等于1234num2应该等于4321,以满足if条件执行解密并设置标志的代码块。请记住,这个类也没有实例。

解决方案

这个问题很容易解决,因为我们之前已经在上一篇帖子中做过了,唯一的区别是get_flag方法的参数是Checker类的一个对象。我将总结解决这个问题的步骤如下:

  • 创建一个Checker类的实例。
  • num1设置为1234,num2设置为4321。
  • 获取MainActivity的实例。
  • 使用实例作为参数调用get_flag方法。

让我们开始编写我们的frida脚本。

首先让我们创建Checker类的实例。

1

2

var checker = Java.use("com.ad2001.frida0x6.Checker");

var checker_obj = checker.$new(); // 类对象

设置num1num2的值。

1

2

checker_obj.num1.value = 1234;

checker_obj.num2.value = 4321;

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

1

2

3

4

5

6

7

8

9

Java.performNow(function() {

  Java.choose('com.ad2001.frida0x6.MainActivity', {

    onMatch: function(instance) {

      console.log("找到实例");

    },

    onComplete: function() {}

  });

})

让我们更新脚本,加入Checker类的实例。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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并运行我们的脚本。

1

PS C:\Users\ajind> frida --f com.ad2001.frida0x6

 

 

当我们检查我们的手机时,TextView将显示标志。

7、Hook构造函数

如果在ARM64 设备上不工作请看issue:Android (arm64) app ignores $init hook in code executed in HandlerThread. · Issue #1575 · frida/frida · GitHub

挂钩构造函数十分简单,与挂钩方法类似。让我为您提供一个模板。

1

2

3

4

5

6

7

8

9

10

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代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值