发布于 

Frida Hook Java 的高级用法

Hook 构造函数

构造函数在 Java 中用 <init> 表示,是对象创建时的入口。通过 Hook 构造函数,我们可以在对象初始化之前或之后拦截和修改数据。

基本构造函数 Hook

Java.perform(function () {
    var User = Java.use("com.example.app.model.User");

    // Hook 构造函数 $init
    User.$init.overload('java.lang.String', 'int').implementation = function (name, age) {
        console.log("[构造函数] 创建 User 对象");
        console.log("    原始参数: name=" + name + ", age=" + age);

        // 修改参数后调用原构造函数
        this.$init(name, age);

        // 修改构造后的字段值
        this._vipLevel.value = 999;
        console.log("    已将 VIP 等级修改为: " + this._vipLevel.value);
    };
});

Hook 多参数重载构造函数

当一个类有多个构造函数时,需要使用 overload() 指定参数类型。

Java.perform(function () {
    var Config = Java.use("com.example.app.Config");

    // Hook 无参构造
    Config.$init.overload().implementation = function () {
        console.log("[Config] 无参构造被调用");
        this.$init();
    };

    // Hook 带 String 参数的构造
    Config.$init.overload('java.lang.String').implementation = function (path) {
        console.log("[Config] 带参构造: " + path);
        this.$init(path);
        console.log("[Config] 初始化后配置项数: " + this._items.value.size());
    };
});

Hook 重载方法(Overload)

Java 方法重载(Overload)允许同名方法拥有不同参数列表。Frida 中必须通过 overload() 指定参数类型来精确匹配目标方法。

精确匹配重载方法

Java.perform(function () {
    var Crypto = Java.use("com.example.app.Crypto");

    // 方法签名为: String encrypt(String data)
    Crypto.encrypt.overload('java.lang.String').implementation = function (data) {
        console.log("[encrypt] String 版本被调用");
        console.log("    输入: " + data);
        var result = this.encrypt(data);
        console.log("    输出: " + result);
        return result;
    };

    // 方法签名为: String encrypt(String data, String key)
    Crypto.encrypt.overload('java.lang.String', 'java.lang.String')
        .implementation = function (data, key) {
        console.log("[encrypt] 双参数版本被调用");
        console.log("    data: " + data);
        console.log("    key: " + key);
        var result = this.encrypt(data, key);
        console.log("    输出: " + result);
        return result;
    };

    // 方法签名为: byte[] encrypt(byte[] data, byte[] key, int mode)
    Crypto.encrypt.overload('[B', '[B', 'int')
        .implementation = function (data, key, mode) {
        console.log("[encrypt] 字节数组版本被调用");
        console.log("    mode: " + mode);

        // 将 byte[] 转为可读的 hex 字符串
        var dataBytes = Java.array('byte', data);
        var hex = "";
        for (var i = 0; i < dataBytes.length; i++) {
            hex += ("0" + (dataBytes[i] & 0xFF).toString(16)).slice(-2);
        }
        console.log("    data(hex): " + hex);

        var result = this.encrypt(data, key, mode);
        return result;
    };
});

使用 Java.available() 检查方法签名

当不确定方法有哪些重载时,可以通过反射获取方法的参数类型信息。

Java.perform(function () {
    var Target = Java.use("com.example.app.Target");

    // 获取类的所有方法
    var methods = Target.class.getDeclaredMethods();
    for (var i = 0; i < methods.length; i++) {
        var method = methods[i];
        console.log("方法: " + method.getName());

        var paramTypes = method.getParameterTypes();
        for (var j = 0; j < paramTypes.length; j++) {
            console.log("  参数" + j + ": " + paramTypes[j].getName());
        }
        console.log("  返回值: " + method.getReturnType().getName());
        console.log("---");
    }
});

枚举所有已加载的类和方法

在逆向分析时,我们经常需要浏览某个包下有哪些类和方法,找到潜在的 Hook 目标。

枚举特定包下的所有类

Java.perform(function () {
    Java.enumerateLoadedClasses({
        onMatch: function (className) {
            // 过滤特定包名
            if (className.indexOf("com.example.app.security") !== -1) {
                console.log("[类] " + className);
            }
        },
        onComplete: function () {
            console.log("[*] 类枚举完成");
        }
    });
});

枚举类的所有方法

Java.perform(function () {
    var Target = Java.use("com.example.app.security.Signature");

    // 获取所有已声明的方法
    var methods = Target.class.getDeclaredMethods();
    console.log("[*] " + Target.class.getName() + " 的方法列表:");

    for (var i = 0; i < methods.length; i++) {
        var m = methods[i];
        var modifiers = Java.use("java.lang.reflect.Modifier");
        var isPrivate = modifiers.isPrivate(m.getModifiers());

        console.log("  " + (isPrivate ? "[private] " : "") + m.getName() +
                    "(" + m.getParameterTypes().length + "个参数) -> " +
                    m.getReturnType().getName());
    }
});

Hook 动态加载的类

许多 APP 使用 DexClassLoaderPathClassLoader 动态加载 DEX 文件,这些类在 APP 启动时并不存在。Hook 动态加载的类需要解决时机问题。

解决时机问题

// 方法一:延迟执行 Hook
setTimeout(function () {
    Java.perform(function () {
        try {
            var DynamicClass = Java.use("com.dynamic.loaded.PluginClass");
            DynamicClass.execute.implementation = function () {
                console.log("[动态类] execute 被调用");
                return this.execute();
            };
            console.log("[+] 动态类 Hook 成功");
        } catch (e) {
            console.log("[-] 动态类尚未加载: " + e);
        }
    });
}, 5000); // 延迟 5 秒,等待动态 DEX 加载

// 方法二:Hook ClassLoader 监控新类的加载
Java.perform(function () {
    var ClassLoader = Java.use("java.lang.ClassLoader");

    ClassLoader.loadClass.overload('java.lang.String').implementation = function (name) {
        var clazz = this.loadClass(name);
        if (name.indexOf("com.dynamic") !== -1) {
            console.log("[ClassLoader] 加载类: " + name);

            // 类加载后立即 Hook
            try {
                var LoadedClass = Java.use(name);
                if (LoadedClass.process) {
                    LoadedClass.process.implementation = function (data) {
                        console.log("[动态类 Hook] process: " + data);
                        return this.process(data);
                    };
                }
            } catch (e) {
                // ignore
            }
        }
        return clazz;
    };
});

Hook 匿名内部类

匿名内部类在编译后以 $数字 的形式命名,常见于回调接口的实现(如 OnClickListener、Runnable 等)。

Java.perform(function () {
    // 先枚举找到目标匿名类
    var targetClasses = [];
    Java.enumerateLoadedClasses({
        onMatch: function (name) {
            // 匹配特定外部类的匿名内部类
            if (name.indexOf("com.example.app.LoginActivity$") !== -1 &&
                name.indexOf("$Adapter") === -1) {
                targetClasses.push(name);
            }
        },
        onComplete: function () {}
    });

    // 逐个尝试 Hook
    targetClasses.forEach(function (className) {
        try {
            var AnonClass = Java.use(className);
            // 检查是否包含目标方法
            if (AnonClass.onResponse) {
                AnonClass.onResponse.implementation = function (response) {
                    console.log("[匿名类] " + className + " onResponse");
                    console.log("    响应: " + response);
                    return this.onResponse(response);
                };
                console.log("[+] 已 Hook: " + className);
            }
        } catch (e) {
            // 忽略不匹配的类
        }
    });
});

Hook 系统类(Activity/Service)

Hook Android 框架层的系统类是分析 APP 行为的重要手段。

Hook Activity 生命周期

Java.perform(function () {
    var Activity = Java.use("android.app.Activity");

    // Hook onCreate
    Activity.onCreate.implementation = function (savedInstanceState) {
        console.log("[Activity] onCreate: " + this.getClass().getName());
        this.onCreate(savedInstanceState);
    };

    // Hook onResume(每次页面可见都会调用)
    Activity.onResume.implementation = function () {
        var activityName = this.getClass().getName();
        console.log("[Activity] onResume: " + activityName);
        this.onResume();
    };

    // Hook startActivityForResult(追踪页面跳转)
    Activity.startActivityForResult.overload(
        'android.content.Intent', 'int'
    ).implementation = function (intent, requestCode) {
        console.log("[Activity] startActivityForResult");
        console.log("    目标: " + intent.getComponent());
        console.log("    requestCode: " + requestCode);
        this.startActivityForResult(intent, requestCode);
    };
});

Hook Service

Java.perform(function () {
    var Service = Java.use("android.app.Service");

    Service.onCreate.implementation = function () {
        console.log("[Service] onCreate: " + this.getClass().getName());
        this.onCreate();
    };

    Service.onStartCommand.implementation = function (intent, flags, startId) {
        console.log("[Service] onStartCommand: " + this.getClass().getName());
        if (intent !== null) {
            console.log("    Action: " + intent.getAction());
        }
        return this.onStartCommand(intent, flags, startId);
    };
});

Hook 第三方 SDK

Hook 微信 SDK 示例

Java.perform(function () {
    // Hook 微信支付回调
    try {
        var WXPayEntryActivity = Java.use(
            "com.tencent.mm.opensdk.pay.WXPayEntryActivity"
        );
        console.log("[+] 找到微信支付入口 Activity");

        // Hook onActivityResult 捕获支付结果
        WXPayEntryActivity.onActivityResult.implementation = function (
            requestCode, resultCode, data) {
            console.log("[微信支付] requestCode=" + requestCode +
                        " resultCode=" + resultCode);
            if (data) {
                console.log("[微信支付] data: " + data.getExtras());
            }
            this.onActivityResult(requestCode, resultCode, data);
        };
    } catch (e) {
        console.log("[-] 未找到微信 SDK: " + e);
    }
});

Hook 支付宝 SDK 示例

Java.perform(function () {
    try {
        var PayTask = Java.use("com.alipay.sdk.app.PayTask");

        // Hook 支付方法,截获订单信息
        PayTask.payV2.overload('java.lang.String', 'boolean')
            .implementation = function (orderInfo, showLoading) {
            console.log("[支付宝] payV2 被调用");
            console.log("[支付宝] 订单信息: " + orderInfo);
            console.log("[支付宝] 显示加载: " + showLoading);

            var result = this.payV2(orderInfo, showLoading);
            console.log("[支付宝] 支付结果: " + result);
            return result;
        };
    } catch (e) {
        console.log("[-] 未找到支付宝 SDK: " + e);
    }
});

线程安全的 Hook 回调

在多线程环境中,Hook 回调可能在不同线程上被触发,需要确保线程安全。

线程安全模式

Java.perform(function () {
    var NetworkUtil = Java.use("com.example.app.util.NetworkUtil");

    NetworkUtil.sendRequest.implementation = function (url, params) {
        // 获取当前线程信息
        var Thread = Java.use("java.lang.Thread");
        var threadName = Thread.currentThread().getName();
        console.log("[线程: " + threadName + "] sendRequest");
        console.log("    URL: " + url);

        // 使用 Java 方法在主线程执行敏感操作
        var mainHandler = Java.use("android.os.Handler");
        var Looper = Java.use("android.os.Looper");

        Java.scheduleOnMainThread(function () {
            // 主线程中安全地执行 Java 操作
            var result = NetworkUtil.parseResponse("{\"status\":\"ok\"}");
            console.log("[主线程] 解析结果: " + result);
        });

        // 当前线程继续正常执行
        return this.sendRequest(url, params);
    };
});

防止重复 Hook

多次执行 Frida 脚本可能导致重复 Hook,需要做防护。

var hookedMethods = {};

function safeHook(className, methodName, callback) {
    var key = className + "." + methodName;
    if (hookedMethods[key]) {
        console.log("[跳过] 已经 Hook 过: " + key);
        return;
    }

    Java.perform(function () {
        try {
            var Clazz = Java.use(className);
            Clazz[methodName].implementation = callback;
            hookedMethods[key] = true;
            console.log("[+] Hook 成功: " + key);
        } catch (e) {
            console.log("[-] Hook 失败: " + key + " - " + e);
        }
    });
}

// 使用安全 Hook
safeHook("com.example.app.Login", "verify",
    function (token) {
        console.log("[verify] token=" + token);
        return this.verify(token);
    }
);

总结

本文深入介绍了 Frida Hook Java 的高级用法,从构造函数和重载方法的精确匹配,到动态类加载的时机处理,再到系统类和第三方 SDK 的实战 Hook。掌握这些技术后,你可以在逆向分析中灵活地拦截和修改任何 Java 层的逻辑。下一篇文章将探讨如何利用 Frida 辅助分析 OLLVM 混淆的 Native 代码。