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

相关推荐
安东尼肉店7 小时前
Android compose屏幕适配终极解决方案
android
2501_916007477 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun8 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户20187928316712 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子12 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822712 小时前
安卓接入Max广告源
android
齊家治國平天下12 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO12 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel12 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢12 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱