Frida Hook Android App 点击事件实战指南:从进程识别到成功注入

一、背景与目标

在逆向分析和自动化测试中,Hook Android 的点击事件是调试 UI 交互逻辑的重要手段之一。本文将以实际案例讲解如何通过 Frida Hook public void onClick(View view) 方法,并解决常见的 Hook 失败问题,最终实现对登录按钮的监听与响应。

步骤一

通过Jadx去反编译APK,然后我们全局搜索app页面上显示的关键字,比如"立即登录",通常可以搜索到res/layout/login.xml文件。我们双击打开布局文件。

进入可以看到对应的按钮定义

java 复制代码
 <Button
        android:textSize="15sp"
        android:textColor="@color/white"
        android:gravity="center"
        android:id="@+id/btn_login"
        android:background="@drawable/btn_login"
        android:layout_width="290dp"
        android:layout_height="50dp"
        android:layout_marginTop="80dp"
        android:layout_marginBottom="15dp"
        android:text="立即登录"/>

根据按钮ID我们去找对应的Activity逻辑实现,全局搜索btn_login可以定位到具体的实现逻辑。

并且可以通过resources.arsc去拿到btn_login对应的ID,可以根据ID去hook特定的按钮事件

java 复制代码
    <public type="layout" name="im_login" id="0x7f0a00c8" />

步骤二

编写hook代码

1.Py脚本

python 复制代码
import frida
import sys


def load_js_file(js_file):
    try:
        with open(js_file, 'r', encoding='utf-8') as f:
            return f.read()
    except FileNotFoundError:
        print(f"[-] 错误: 找不到 {js_file} 文件")
        sys.exit(1)
    except Exception as e:
        print(f"[-] 读取 {js_file} 文件时出错: {str(e)}")
        sys.exit(1)


def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)


if __name__ == '__main__':
    # Frida JS Hook 脚本内容
    js_code = load_js_file('fridaCode.js')

    print('[*] 正在连接设备...')
    device = frida.get_remote_device()

    target_pid = 6472  # ← 替换为你刚刚用 adb shell pidof 得到的 PID
    print(f'[*] 正在附加到 PID: {target_pid}')
    session = device.attach(target_pid)

    print('[*] 创建 Frida 脚本')
    script = session.create_script(js_code)
    script.on('message', on_message)

    print('[*] 加载脚本...')
    script.load()

    print('[*] 成功注入 Frida 脚本,开始监听点击事件...')

    sys.stdin.read()

注意:

1.这里我使用的是PID的方式去附加进程,因为我的App通过frida-ps -U指令查出来的只有子进程,但是UI类逻辑一般是在主进程的,所以直接通过PID附加

2.PID查看命令
adb shell pidof 包名

输出:6472 这个就是进程ID

3.子进程一半命名是: 主进程包名:子进程 如:com.android.flysilkworm:filedownloader

4.如果进程注入不正确是无法hook的

这里也贴出包名注入的方法:

python 复制代码
import frida
import sys


def load_js_file(js_file):
    try:
        with open(js_file, 'r', encoding='utf-8') as f:
            return f.read()
    except FileNotFoundError:
        print(f"[-] 错误: 找不到 {js_file} 文件")
        sys.exit(1)
    except Exception as e:
        print(f"[-] 读取 {js_file} 文件时出错: {str(e)}")
        sys.exit(1)


def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)


if __name__ == '__main__':
    jscode = load_js_file("fridaCode.js")

    # 获取远程设备并 attach 到主进程
    device = frida.get_remote_device()
    process_name = "包名"  # 替换为你应用的主进程名
    try:
        session = device.attach(process_name)
    except Exception as e:
        print(f"[-] 无法 attach 到进程 {process_name}: {e}")
        sys.exit(1)

    script = session.create_script(jscode)
    script.on('message', on_message)
    print('[*] Hook Start Running')
    script.load()

    sys.stdin.read()

JS脚本:

python 复制代码
Java.perform(function () {
    console.log("[*] 正在 Hook IMNewLoginActivity.onClick...");

    var IMNewLoginActivity = Java.use("robot.app.acys.ims.IMNewLoginActivity");

    IMNewLoginActivity.onClick.implementation = function (view) {
        console.log("[*] IMNewLoginActivity.onClick() 被调用");

        // 获取 View ID
        var viewId = view.getId();
        console.log("[+] 点击的 View ID: 0x" + viewId.toString(16));

        // 尝试获取资源名称
        try {
            var resources = this.getResources();
            var resourceName = resources.getResourceEntryName(viewId);
            console.log("[+] 对应资源名称: " + resourceName);
        } catch (e) {
            console.log("[-] 获取资源名称失败");
        }

        // 如果是登录按钮(替换为你的 btn_login ID)
        if (viewId === 0x7f080084) {
            console.log("[!] 登录按钮被点击!");
        }

        return this.onClick(view);
    };
});

🧩 Frida JS 注入通用模板

✅ 基本结构:Hook 某个类的某个方法

python 复制代码
Java.perform(function () {
    // 替换为你想 Hook 的完整类名
    var targetClass = Java.use("com.example.target.ClassName");

    // 替换为你想 Hook 的方法名
    targetClass.methodName.overload('参数类型1', '参数类型2').implementation = function (param1, param2) {
        console.log("[*] 方法被调用!");
        
        // 打印参数
        console.log("[+] 参数 1: " + param1);
        console.log("[+] 参数 2: " + param2);

        // 调用原始方法(可选)
        var result = this.methodName(param1, param2);

        // 打印返回值(如果有的话)
        console.log("[+] 返回值: " + result);

        // 可以修改返回值或参数
        return result;
    };
});

🎯 示例 1:Hook void onClick(View view)

python 复制代码
Java.perform(function () {
    var LoginActivity = Java.use("robot.app.acys.im.LoginActivity");

    LoginActivity.onClick.implementation = function (view) {
        console.log("[*] onClick 被点击");
        console.log("[+] View ID: 0x" + view.getId().toString(16));
        return this.onClick(view);
    };
});

🎯 示例 2:Hook String login(String username, String password)

python 复制代码
Java.perform(function () {
    var LoginManager = Java.use("com.example.LoginManager");

    LoginManager.login.overload('java.lang.String', 'java.lang.String').implementation = function (user, pass) {
        console.log("[*] 登录方法被调用");
        console.log("[+] 用户名: " + user);
        console.log("[+] 密码: " + pass);

        // 修改密码为 test123
        pass = "test123";

        var result = this.login(user, pass);
        console.log("[+] 登录结果: " + result);

        return result;
    };
});

🎯 示例 3:Hook 构造函数 new Person(String name, int age)

python 复制代码
Java.perform(function () {
    var Person = Java.use("com.example.Person");

    Person.$init.overload('java.lang.String', 'int').implementation = function (name, age) {
        console.log("[*] 构造函数被调用");
        console.log("[+] 创建对象 - 名字: " + name + ", 年龄: " + age);

        // 调用原构造函数
        return this.$init(name, age);
    };
});

🎯 示例 4:Hook 静态方法 static void initConfig()

python 复制代码
Java.perform(function () {
    var Config = Java.use("com.example.Config");

    Config.initConfig.overload().implementation = function () {
        console.log("[*] 静态方法 initConfig 被调用");
        return this.initConfig();
    };
});

🎯 示例 5:Hook 多个重载方法(overload)

python 复制代码
Java.perform(function () {
    var Utils = Java.use("com.example.Utils");

    // Hook Utils.load(int)
    Utils.load.overload('int').implementation = function (id) {
        console.log("[*] load(int) 被调用,ID: " + id);
        return this.load(id);
    };

    // Hook Utils.load(String)
    Utils.load.overload('java.lang.String').implementation = function (name) {
        console.log("[*] load(String) 被调用,名称: " + name);
        return this.load(name);
    };
});

📌 注意事项

类名 必须是完整的类路径,如:java.lang.String、com.example.MyClass

方法名 直接写方法名即可,如 .login

参数类型 必须使用 Java 完整类型名,如:java.lang.String, int, boolean 等

this 在 implementation 中代表当前对象实例

$init 是构造函数的特殊标识符

overload(...) 如果方法有多个重载版本,必须指定参数类型来区分

✅ 最后:一个"万能"Hook 模板(自动打印所有参数)

python 复制代码
function hookMethod(className, methodName, paramTypes) {
    Java.perform(function () {
        var clazz = Java.use(className);
        var method = clazz[methodName];

        if (paramTypes) {
            method = method.overload.apply(this, paramTypes);
        }

        method.implementation = function () {
            console.log(`[*] Hooked: ${className}.${methodName}()`);

            for (var i = 0; i < arguments.length; i++) {
                console.log(`[+] 参数[${i}]: ${arguments[i]}`);
            }

            var ret = this[methodName].apply(this, arguments);
            console.log(`[+] 返回值: ${ret}`);

            return ret;
        };
    });
}

// 使用示例:
hookMethod("com.example.LoginManager", "login", ["java.lang.String", "java.lang.String"]);

ADB指令启动frida

其中启动命令是 ./frida-server &

端口转发adb forward tcp:27042 tcp:27042

27042

相关推荐
*才华有限公司*21 分钟前
安卓前后端连接教程
android
氦客1 小时前
Android Compose中的附带效应
android·compose·effect·jetpack·composable·附带效应·side effect
雨白1 小时前
Kotlin 协程的灵魂:结构化并发详解
android·kotlin
我命由我123451 小时前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Modu_MrLiu1 小时前
Android实战进阶 - 用户闲置超时自动退出登录功能详解
android·超时保护·实战进阶·长时间未操作超时保护·闲置超时
Jeled2 小时前
Android 网络层最佳实践:Retrofit + OkHttp 封装与实战
android·okhttp·kotlin·android studio·retrofit
信田君95272 小时前
瑞莎星瑞(Radxa Orion O6) 基于 Android OS 使用 NPU的图片模糊查找APP 开发
android·人工智能·深度学习·神经网络
tangweiguo030519872 小时前
Kotlin 实现 Android 网络状态检测工具类
android·网络·kotlin
nvvas3 小时前
Android Studio JAVA开发按钮跳转功能
android·java·android studio
怪兽20144 小时前
Android多进程通信机制
android·面试