【Frida Android】基础篇9:Java层Hook基础——Hook构造函数

文章目录

    • [1. Hook 语法](#1. Hook 语法)
      • [1.1 构造函数与实例创建核心语法](#1.1 构造函数与实例创建核心语法)
      • [1.2 Hook 构造函数逻辑](#1.2 Hook 构造函数逻辑)
      • [1.3 主动创建类实例](#1.3 主动创建类实例)
      • [1.4 init 与 new 的关系](#1.4 init 与 new 的关系)
      • [1.5 示例:结合`init\`和\`new`的用法](#1.5 示例:结合$init$new的用法)
    • [2. 案例解析](#2. 案例解析)
      • [2.1 核心代码分析](#2.1 核心代码分析)
        • [2.1.1 Checker类](#2.1.1 Checker类)
        • [2.1.2 MainActivity类](#2.1.2 MainActivity类)
    • [3. Hook 思路](#3. Hook 思路)
      • [3.1 问题定位](#3.1 问题定位)
      • [3.2 解决方案](#3.2 解决方案)
      • [3.3 执行步骤](#3.3 执行步骤)
        • [3.3.1 步骤1:获取活跃的MainActivity实例](#3.3.1 步骤1:获取活跃的MainActivity实例)
        • [3.3.2 步骤2:创建符合条件的Checker实例](#3.3.2 步骤2:创建符合条件的Checker实例)
        • [3.3.3 步骤3:调用flag方法触发解密](#3.3.3 步骤3:调用flag方法触发解密)
    • [4. Hook 脚本](#4. Hook 脚本)
      • [4.1 JS 脚本](#4.1 JS 脚本)
      • [4.2 Python 脚本](#4.2 Python 脚本)
      • [4.3 成功显示 Flag](#4.3 成功显示 Flag)
    • [5. 技术总结](#5. 技术总结)
      • [5.1 两种构造干预方式的核心差异](#5.1 两种构造干预方式的核心差异)
      • [5.2 案例中选择`new\`而非\`init`的关键原因](#5.2 案例中选择$new而非$init的关键原因)
      • [5.3 Frida工具特性的场景适配](#5.3 Frida工具特性的场景适配)
      • [5.4 适用场景总结](#5.4 适用场景总结)

⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。

1. Hook 语法

在Frida中,操作Java类的实例化过程主要涉及两个核心方法:$init$new。其中$init对应Java中的构造函数逻辑,用于Hook或修改实例初始化过程;$new则对应Java中new关键字的操作,用于主动创建类的实例。

1.1 构造函数与实例创建核心语法

  1. 获取目标类引用 :通过Java.use("类全名")获取目标类的JavaScript包装对象。
  2. Hook构造函数($init :通过类引用.$init.implementation重写构造函数逻辑,干预实例初始化参数或过程。
  3. 主动创建实例($new :通过类引用.$new(参数)主动调用构造函数创建实例,无需依赖原程序的实例创建逻辑。

1.2 Hook 构造函数逻辑

$init是Java构造函数在Frida中的映射,用于定义实例初始化时的逻辑。通过重写$init.implementation,可以修改传入的参数或在初始化后调整实例属性。

示例 :Hook Checker构造函数并修改初始化参数

js 复制代码
Java.perform(function(){
    // 获取Checker类引用
    var Checker = Java.use("com.xxx.xxx.Checker");
    // Hook构造函数($init对应Java中的构造方法)
    Checker.$init.implementation = function(a, b) {
        // 修改参数为520, 520后调用原始构造逻辑
        this.$init(520, 520); 
    };
});

1.3 主动创建类实例

$new对应Java中new关键字的操作,用于主动创建类的实例。它在内部会调用$init方法完成初始化,因此使用时需传入构造函数所需的参数。当需要绕开原程序的实例创建逻辑,直接生成符合条件的实例时,$new是核心工具。

示例 :主动创建Checker实例

js 复制代码
Java.perform(function(){
    // 获取Checker类引用
    var Checker = Java.use("com.xxx.xxx.Checker");
    // 调用$new创建实例,传入参数600, 600(等价于Java中的new Checker(600, 600))
    var validChecker = Checker.$new(600, 600);
});

1.4 init 与 new 的关系

  • $init是构造函数的具体实现逻辑,负责初始化实例的属性和状态;
  • $new是创建实例的入口,调用$new时会自动触发$init来完成实例初始化;
  • 若已Hook$init,则通过$new创建实例时,会执行重写后的$init逻辑。

1.5 示例:结合$init$new的用法

以下示例先Hook构造函数打印参数,再主动创建符合条件的实例:

js 复制代码
Java.perform(function(){
    var Checker = Java.use("com.xxx.xxx.Checker");
    
    // Hook构造函数,打印原始参数
    Checker.$init.implementation = function(a, b) {
        this.$init(a, b); // 保留原始逻辑
    };
    
    // 主动创建实例
    var customChecker = Checker.$new(1000, 1000);
    console.log("主动创建的实例参数:num1=" + customChecker.num1.value + ", num2=" + customChecker.num2.value);
});

2. 案例解析

本章示例应用的链接:

https://pan.baidu.com/s/16EE2XE-OZS_xBRPlWUODbw?pwd=n2vb

提取码: n2vb

使用APK:Challenge 0x7.apk

2.1 核心代码分析

案例的核心逻辑围绕MainActivityChecker两个类展开,关键代码解析如下:

2.1.1 Checker类

简单的实体类,构造函数接收两个int参数ab,并赋值给实例变量num1num2。其作用是存储判断条件所需的参数,无其他复杂逻辑。

java 复制代码
public class Checker {
    int num1; // 需满足 >512
    int num2; // 需满足 >512
    // 构造函数:初始化num1和num2
    Checker(int a, int b) {
        this.num1 = a;
        this.num2 = b;
    }
}
2.1.2 MainActivity类
  • onCreate方法中创建Checker实例时使用了固定参数(123, 321),这两个值均不满足num1>512num2>512的条件,导致flag方法的解密逻辑无法触发。
  • flag方法是核心判断逻辑:仅当传入的Checker实例满足num1>512且num2>512时,才会执行AES解密并显示flag。
java 复制代码
// MainActivity中关键逻辑
public void flag(Checker A) throws ... {
    // 核心条件:只有参数满足时才解密flag
    if (A.num1 > 512 && 512 < A.num2) { 
        // AES解密并显示flag
        Cipher cipher = Cipher.getInstance("AES");
        ...
        this.t1.setText(decrypted); 
    }
}

简言之,案例的核心矛盾是:原程序创建的Checker实例参数不满足条件,导致flag无法显示,而Hook的目标就是解决这一矛盾。

3. Hook 思路

要触发flag显示,需满足flag方法的参数条件。由于原程序中Checker实例的参数固定为(123, 321),因此需要通过Hook技术绕开这一限制。

3.1 问题定位

  • 限制来源:MainActivity.onCreate中创建的Checker实例参数(123, 321)不满足num1>512且num2>512
  • 目标:使flag方法接收到符合条件的Checker实例,触发解密逻辑。

3.2 解决方案

无需修改原程序的构造函数逻辑,而是通过主动创建符合条件的Checker实例 ,并手动调用flag方法,直接绕开原程序的参数限制。

3.3 执行步骤

3.3.1 步骤1:获取活跃的MainActivity实例
  • 原因:flag方法是MainActivity的成员方法,且需要其内部的TextView t1已初始化(否则设置文本会失败)。
  • 实现:通过Java.choose("com.ad2001.frida0x7.MainActivity")枚举进程中已加载的MainActivity实例,确保获取的是onCreate执行完毕后的活跃实例(此时t1已绑定布局)。
3.3.2 步骤2:创建符合条件的Checker实例
  • 关键:直接调用Checker的构造函数,传入满足条件的参数(如600, 600)。
  • 实现:通过Java.use("com.ad2001.frida0x7.Checker").$new(600, 600)主动创建实例,无需依赖原程序的构造调用。
3.3.3 步骤3:调用flag方法触发解密
  • 操作:使用步骤1获取的MainActivity实例,调用其flag方法并传入步骤2创建的Checker实例。
  • 结果:由于参数满足条件,flag方法执行AES解密,最终通过t1.setText显示flag。

4. Hook 脚本

4.1 JS 脚本

js 复制代码
import Java from 'frida-java-bridge';

Java.perform(function () {
    Java.choose('com.ad2001.frida0x7.MainActivity', {
        // 当找到MainActivity实例时触发的回调
        onMatch: function (instance) {
            console.log("找到MainActivity实例,准备执行操作");

            // 获取Checker类的引用
            var checkerClass = Java.use("com.ad2001.frida0x7.Checker");
            // 手动创建Checker实例,传入参数600和600(满足flag方法中"num1>512且num2>512"的条件)
            var validChecker = checkerClass.$new(600, 600);
            // 调用MainActivity实例的flag方法,并传入符合条件的Checker对象,触发解密逻辑
            instance.flag(validChecker);
        },

        // 枚举完成(所有实例都已处理)时的回调,此处无需额外操作
        onComplete: function () {
        }
    });
});

4.2 Python 脚本

python 复制代码
import frida
import sys
import time

def on_message(message, data):
    if message['type'] == 'send':
        print(f"[Hook 日志] {message['payload']}")
    elif message['type'] == 'error':
        print(f"[错误] {message['stack']}")

# 目标应用包名
PACKAGE_NAME = "com.ad2001.frida0x7"

def main():
    try:
        device = frida.get_usb_device(timeout=10)
        print(f"已连接设备:{device.name}")

        print(f"启动进程 {PACKAGE_NAME}...")
        pid = device.spawn([PACKAGE_NAME])
        device.resume(pid)
        time.sleep(2)

        process = device.attach(pid)
        print(f"已附加到进程 PID: {pid}")

        with open("./js/compiled_hook.js", "r", encoding="utf-8") as f:
            js_code = f.read()

        script = process.create_script(js_code)
        script.on('message', on_message)
        script.load()
        print("JS 脚本注入成功,开始监控...(按 Ctrl+C 退出)")

        sys.stdin.read()

    except frida.TimedOutError:
        print("未找到USB设备")
    except frida.ProcessNotFoundError:
        print(f"应用 {PACKAGE_NAME} 未安装")
    except FileNotFoundError:
        print("未找到 js 脚本,请检查路径")
    except Exception as e:
        print(f"异常:{str(e)}")
    finally:
        if 'process' in locals():
            process.detach()
        print("程序退出")

if __name__ == "__main__":
    main()

4.3 成功显示 Flag

5. 技术总结

5.1 两种构造干预方式的核心差异

在Frida中,Java构造函数可通过$init方法映射进行Hook(重写$init.implementation修改参数),但这种方式强依赖Hook时机 ------必须在目标实例创建前完成Hook,否则无法拦截已创建的实例。而通过Java.use(类名).$new(参数)主动创建实例,则完全绕开了原程序的实例创建逻辑,无需依赖Hook时机,直接构造符合条件的对象。

5.2 案例中选择$new而非$init的关键原因

  • 时机问题规避 :原程序中Checker实例在MainActivity.onCreate中被快速创建(new Checker(123, 321)),若使用$init Hook,需确保注入脚本在onCreate执行前生效(如spawn模式提前注入),否则会因实例已创建而Hook失效。而$new主动创建实例无需关心原实例的创建时机,直接构造符合条件的对象(600, 600),稳定性更高。
  • 逻辑简化 :原程序的flag方法仅依赖Checker实例的参数是否满足条件,无需修改原实例的其他逻辑。通过$new直接创建"合格"实例,比Hook$init更直接,避免了因构造函数逻辑复杂(如额外初始化操作)导致的Hook风险。
  • 依赖资源就绪flag方法需操作MainActivityTextView(UI组件),必须在onCreate执行完毕后调用(否则TextView未初始化)。Java.choose枚举MainActivity实例,确保了调用flag时依赖的UI资源已就绪,而$init Hook无法直接保证这一点。

5.3 Frida工具特性的场景适配

  • Java.use不仅用于Hook类,更支持主动调用构造函数($new),这在需要"正向创建符合条件的对象"时尤为高效。
  • Java.choose的核心价值是获取"已初始化的活跃实例",对于依赖生命周期的组件(如Activity、UI控件),它能确保操作在实例就绪后执行,避免空指针等错误。

5.4 适用场景总结

当目标逻辑依赖特定对象的属性/参数,且原程序的实例创建时机早、流程简单时,主动创建符合条件的实例($new)+ 枚举活跃目标实例(Java.choose 是更可靠的方案。它规避了Hook时机的限制,直接触发目标逻辑,尤其适合UI相关场景或需要快速验证的场景。而$init Hook更适合需要修改"所有新创建实例"的通用场景,需额外注意注入时机的把控。

相关推荐
百锦再5 小时前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
会跑的兔子6 小时前
Android 16 Kotlin协程 第二部分
android·windows·kotlin
键来大师6 小时前
Android15 RK3588 修改默认不锁屏不休眠
android·java·framework·rk3588
江上清风山间明月9 小时前
Android 系统超级实用的分析调试命令
android·内存·调试·dumpsys
百锦再9 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang
用户693717500138413 小时前
Kotlin 协程基础入门系列:从概念到实战
android·后端·kotlin
SHEN_ZIYUAN13 小时前
Android 主线程性能优化实战:从 90% 降至 13%
android·cpu优化
曹绍华13 小时前
android 线程loop
android·java·开发语言
雨白13 小时前
Hilt 入门指南:从 DI 原理到核心用法
android·android jetpack
介一安全13 小时前
【Frida Android】实战篇3:基于 OkHttp 库的 Hook 抓包
android·okhttp·网络安全·frida