【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 小时前
【Android】Compose绘制系统 VS 传统View绘制系统
android
介一安全5 小时前
【Frida Android】基础篇10:Native层Hook基础--普通 Hook
android·网络安全·逆向·安全性测试·frida
落日漫游6 小时前
Nginx负载均衡:高性能流量调度指南
网络安全·微服务
位步6 小时前
在linux系统中使用通用包安装 Mysql
android·linux·mysql
生莫甲鲁浪戴7 小时前
Android Studio新手开发第二十六天
android·前端·android studio
sky0Lan8 小时前
一个类似 pytest 的 html 报告
android·html·pytest
怪兽20148 小时前
Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?
android·面试
雨白9 小时前
掌握协程的边界与环境:CoroutineScope 与 CoroutineContext
android·kotlin
木易 士心9 小时前
Android 开发核心知识体系与面试指南精简版
android·面试·职场和发展