首页
社区
课程
招聘
[原创]Frida-Hook-Java层操作大全
发表于: 2024-3-4 01:38 29547

[原创]Frida-Hook-Java层操作大全

2024-3-4 01:38
29547

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

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

什么是钩子?

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

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

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

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> 表示传递给函数的参数。

通过Jadx分析Frida-labs 0x1

可以发现,在onCreate方法中,有一个监听事件,监听了button的点击,当按钮点击下去之后,程序首先判断输入是不是数字,是数字的话,就将其从string转化为int,再进入check中与i比较,因此我们需要检查check方法。

本方法显而易见就是检查输入是否能够满足i*2 + 4 == i2,如果满足则将flag输出到f103t1所绑定的textView控件上,其中用于判断的i则来自get_random。

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

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

代码解释如下:

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

接着定义了一个名为main的JavaScript函数,其中包含了Frida的Java.perform()方法,用于执行指定的hook逻辑。

最后,通过setImmediate()函数调用main函数,确保在Frida脚本启动后立即执行。

如果我们检查check函数的参数,第一个参数i表示随机数,而第二个参数i2对应于用户输入的数字。让我们使用Frida来捕获并转储这两个参数。

在处理具有参数的方法时,重要的是使用overload(arg_type)关键字指定预期的参数类型。此外,在钩入方法时确保包括这些指定的参数在你的实现中。在这里,我们的check()函数接受两个整数参数,所以我们可以这样指定:

我们可以使用console.log查看传入的a与b是什么
file

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

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

本用例程序就一个MainActivity类,类中存在一个未被使用的静态方法get_flag,在get_flag中比较了传入的参数,如果传入的参数为4919则解密flag,设置给txtView控件,那么根据之前给出的调用模板,我们hook代码如下:

但是我们发现如果使用的是setIMMediate(main)的话我们使用
frida -U -f com.ad2001.frida0x2 -l .\Hook.js
可能会导致hook不上的情况。
file

我们事先启动Frida 0x2应用程序。然后使用如下命令注入我们的脚本
frida -U 'Frida 0x2' -l .\Hook.js
file
本方法与之前的方法不同之处是该方法是直接hook入我们后台正在启动的程序,而之前的方法是根据包名再启动一个程序。

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

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

MainActivity类
file
标记处我们可以发现,当Checker.code为512的时候点击按钮,程序则会解密并且将textView控件设置为Flag。

在JAVA代码中,如果创建了一个非静态的类,当我们需要使用这个类的时候需要new一个类的对象出来我们才能使用这个类的功能。类似代码如下:

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

file
MainActivity中没有任何东西。

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

file

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

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

让我展示一个模板给你。

这里有两个回调函数:

file
可以发现其中flag方法是未被调用的方法,并且是解密密文将Flag输出到TextView控件上。

现在我们知道如何使用Java.choose API,让我们开始编写我们的frida脚本。

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

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

file

file
我们之前已经解决过类似的问题了。在这种情况下,我们有一个get_flag()方法,在应用程序中没有被调用。如果调用此方法,它将使用AES解密标志,并将标志设置在Textview中。如果我们检查get_flag方法,它只接受一个参数,这个参数是Checker类的一个实例。参数被命名为A,其类型是Checker

在方法内部,它检查A.num1是否等于1234,以及A.num2是否等于4321。如果条件成立,该方法将继续使用AES解密加密字符串,并将解密后的结果设置在TextView中。因此,让我们检查一下Checker类。

file

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

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

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

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

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

设置num1num2的值。

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

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

现在唯一要做的是通过传递Checker类的实例来调用get_flag方法。

让我们启动frida并运行我们的脚本。

file

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

如果在ARM64 设备上不工作请看issue:https://github.com/frida/frida/issues/1575

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

我们可以看到,为了挂钩构造函数,我们可以使用$init关键字。

file
可以看到程序在使用flag方法判断之前,首先使用 Checker ch = new Checker(123, 321); 创建了一个Checker实例,则123 , 321 分别对应A.num1与 A.num2。
那么我们只需要钩住构造函数即可。

file

Java.perform(function() {
 
  var <class_reference> = Java.use("<package_name>.<class>");
  <class_reference>.<method_to_hook>.implementation = function(<args>) {
 
    /*
      我们自己的方法实现
    */
 
  }
 
})
Java.perform(function() {
 
  var <class_reference> = Java.use("<package_name>.<class>");
  <class_reference>.<method_to_hook>.implementation = function(<args>) {
 
    /*
      我们自己的方法实现
    */
 
  }
 
})
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();
            }
        }
    });
}
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();
            }
        }
    });
}
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();
   }
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();
   }
int get_random() {
    return new Random().nextInt(100);
}
int get_random() {
    return new Random().nextInt(100);
}
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);
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);
a.check.overload(int, int).implementation = function(a, b) {
 
  ...
 
}
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);
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);
Java.perform(function (){
    var <class_reference> = Java.use("<package_name>.<class>");
    a.function(val);
})
Java.perform(function (){
    var <class_reference> = Java.use("<package_name>.<class>");
    a.function(val);
})
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();
            }
        }
    }
}
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();
            }
        }
    }
}
function hook(){
    var MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
    MainActivity.get_flag(4919);
}
 
function main(){
    Java.perform(function (){
        hook();
    })
}
 
setImmediate(main);
function hook(){
    var MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
    MainActivity.get_flag(4919);
}
 
function main(){
    Java.perform(function (){
        hook();
    })
}
 
setImmediate(main);
Java.perform(function (){
 
    var <class_reference> = Java.use("<package_name>.<class>");
    <class_reference>.<variable>.value = <value>;
 
})
Java.perform(function (){
 
    var <class_reference> = Java.use("<package_name>.<class>");
    <class_reference>.<variable>.value = <value>;
 
})
function hook(){
    var a = Java.use("com.ad2001.frida0x3.Checker");
    a.code.value = 512;
}
 
function main(){
    Java.perform(function (){
        hook();
    })
}
setImmediate(main);
function hook(){
    var a = Java.use("com.ad2001.frida0x3.Checker");

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-3-4 01:43 被Shangwendada编辑 ,原因:
收藏
免费 24
支持
分享
最新回复 (14)
雪    币: 3535
活跃值: (31011)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-3-4 10:53
1
雪    币: 4583
活跃值: (6836)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
native层的操作大全也来一篇,还有最重要的检查frida的各自姿势在罗列下就更顶了
2024-3-4 11:50
0
雪    币: 6245
活跃值: (666)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
通俗易懂
2024-3-4 16:14
0
雪    币: 931
活跃值: (1658)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
大佬 Failure [INSTALL_FAILED_OLDER_SDK: Failed parse during installPackageLI: /data/app/vmdl1704171809.tmp/base.apk (at Binary XML file line #7): Requires newer sdk version #28 (current version is #27)] 这种只能换手机重新root吗
2024-3-4 18:58
0
雪    币: 1457
活跃值: (2053)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
6

晚点会总结Native层的

最后于 2024-3-4 19:39 被Shangwendada编辑 ,原因:
2024-3-4 19:38
1
雪    币: 1457
活跃值: (2053)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
7
tian_chen 大佬 Failure [INSTALL_FAILED_OLDER_SDK: Failed parse during installPackageLI: /data/app/vmdl1704171809 ...
安卓系统低了,换个高点的
2024-3-4 19:39
0
雪    币: 265
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
感谢大爹
2024-3-6 21:12
0
雪    币: 1046
活跃值: (78)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
大神能帮忙解决风控的问题吗
2024-3-16 04:12
0
雪    币: 3222
活跃值: (3202)
能力值: ( LV7,RANK:111 )
在线值:
发帖
回帖
粉丝
10
楼主, Android 11(低版本正常)上获取大部分系统方法如android.hardware.camera.stopPreview的方法和android.telephony.TelephonyManager.getNeighboringCellInfo获取的都是同一个地址, 但是frida能够正常hook上面任意一个方法却不会互相影响(hook命中打印日志已知).一直没搞明白咋回事, 有什么建议吗? 或者说在11上有没有好的方法能获取到像低版本一样的正确地址?
2024-3-21 11:33
0
雪    币: 3222
活跃值: (3202)
能力值: ( LV7,RANK:111 )
在线值:
发帖
回帖
粉丝
11

11上获取的都是这个地址

2024-3-21 11:35
0
雪    币: 1457
活跃值: (2053)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
12
ArmVMP 11上获取的都是这个地址
没太了解过这个
2024-3-21 21:06
0
雪    币: 1030
活跃值: (202)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
13
devil_1 感谢大爹
?好好好被我抓到了吧
2024-4-1 17:39
0
雪    币: 1426
活跃值: (3105)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
感谢分享
2024-7-12 18:05
0
雪    币: 346
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
佬,我想请问一下protected这个修饰符咋hook啊
2024-12-7 22:53
0
游客
登录 | 注册 方可回帖
返回
//