逆向利器:Frida Hook

本文系统讲解 Frida 框架在 Android 逆向中的应用,涵盖环境搭建、Java 层 Hook、Native 层 Hook 以及内存 Patch,配合 Frida-Labs 靶场实战案例,手把手带你从零上手。


一、什么是 Hook?

Hook 是一种在程序运行时动态修改或拦截函数调用、参数或返回值的技术。在 Android 安全研究、逆向分析以及自动化测试中,Hook 技术扮演着至关重要的角色。

目前 Android 生态中主流的 Hook 框架主要有两种,而本文将重点介绍逆向中最常用的 Frida 框架


二、Frida 环境搭建

Frida 采用 C/S 架构(客户端/服务端),安装分为 PC 端(客户端)和手机端(服务端)两部分。

2.1 安装 PC 端(客户端)

确保已安装 Python 环境,使用 pip 安装:

bash 复制代码
pip install frida frida-tools

推荐指定版本安装(PC 端和手机端版本保持一致,以保证稳定性):

bash 复制代码
pip install frida==16.7.14 frida-tools

2.2 安装手机端(服务端)

第一步:确认设备 CPU 架构

bash 复制代码
adb shell getprop ro.product.cpu.abi

第二步:下载对应版本的 frida-server

前往 Frida Releases 下载。

📌 下载原则:版本号与 PC 端一致,架构与手机匹配。

例如:手机为 arm64-v8a,PC 端 Frida 版本为 16.7.14,则下载 frida-server-16.7.14-android-arm64.xz

第三步:部署到手机

bash 复制代码
# 1. 推送到手机临时目录
adb push frida-server-16.7.14-android-arm64 /data/local/tmp/

# 2. 进入手机 Shell 并提权
adb shell
su

# 3. 赋予执行权限并后台运行
chmod +x /data/local/tmp/frida-server-16.7.14-android-arm64
/data/local/tmp/frida-server-16.7.14-android-arm64 &

💡 建议重命名 frida-server 文件(如 fs),既方便输入,也能规避部分 App 对 "frida-server" 文件名的字符串检测。

2.3 建立端口转发

方式一:默认端口(推荐)

Frida 默认通信端口为 27042

bash 复制代码
adb forward tcp:27042 tcp:27042

方式二:自定义端口(规避检测)

手机端启动时指定端口:

bash 复制代码
/data/local/tmp/fs -l 0.0.0.0:8888 &

电脑端转发:

bash 复制代码
adb forward tcp:8888 tcp:8888

客户端连接时通过 -H 参数指定地址:

bash 复制代码
frida -H 127.0.0.1:8888 -f com.example.app

三、Frida 基本命令与注入模式

3.1 两种注入模式

模式 关键词 原理 适用场景
Spawn -f 重启 App,启动前注入脚本 Root 检测、启动阶段逻辑(如 onCreate
Attach -n / -p 附加到已运行的进程 运行中分析、规避启动注入检测

3.2 查找目标应用

bash 复制代码
# 列出所有已安装的应用(获取包名,用于 Spawn)
frida-ps -Uai

# 列出正在运行的进程并过滤(用于 Attach)
frida-ps -U | grep "关键词"       # Linux / macOS
frida-ps -U | findstr "关键词"    # Windows

四、Java 层 Hook 核心 API

4.1 入门三件套

javascript 复制代码
Java.perform(function () {
    // 1. Java.use:获取目标类的引用
    var MainActivity = Java.use("com.example.app.MainActivity");

    // 2. implementation:替换目标方法的实现
    MainActivity.check.implementation = function (i, i2) {
        console.log("原始参数:i=" + i + ", i2=" + i2);
        // 3. 调用原方法(推荐方括号写法,兼容混淆名)
        return this["check"](0, 4);
    };
});
  • Java.perform(fn):入口函数,确保当前线程已附加到 Java VM。
  • Java.use(className):动态获取类引用(类似反射)。
  • implementation:替换方法的实际实现。

4.2 延迟执行技巧

某些场景下类尚未加载,直接运行会报 ClassNotFoundException,可使用延迟执行:

javascript 复制代码
// 方式一:setImmediate(推荐,注意不要放在第一行)
setImmediate(function () {
    Java.perform(function () { /* Hook 逻辑 */ });
});

// 方式二:setTimeout(适用于启动初期类未加载的情况)
setTimeout(function () {
    Java.perform(function () { /* Hook 逻辑 */ });
}, 1000);

五、Java 层实战案例

5.1 Hook 普通方法 --- 篡改参数(Frida-Labs 0x1)

目标MainActivity.check(int i, int i2),满足 (i * 2) + 4 == i2 即可通过。

策略:拦截方法,强制修改参数为满足条件的值。

javascript 复制代码
Java.perform(function () {
    var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
    MainActivity.check.implementation = function (i, i2) {
        console.log("[*] 原始参数: i=" + i + ", i2=" + i2);
        // 强制修改为 i=0, i2=4,满足 (0*2)+4==4
        return this["check"](0, 4);
    };
});

5.2 主动调用静态方法(Frida-Labs 0x2)

静态方法属于类本身,无需实例即可调用:

javascript 复制代码
Java.perform(function () {
    var TargetClass = Java.use("com.ad2001.frida0x2.TargetClass");
    // 直接通过类引用调用静态方法
    var result = TargetClass.staticMethod(参数);
    console.log("[*] 返回值: " + result);
});

5.3 修改静态字段值(Frida-Labs 0x3)

目标Checker.code 默认值为 0,需要修改为 512。

javascript 复制代码
Java.perform(function () {
    var Checker = Java.use("com.ad2001.frida0x3.Checker");
    // 注意:必须通过 .value 读写字段值
    console.log("[*] 修改前: " + Checker.code.value);
    Checker.code.value = 512;
    console.log("[*] 修改后: " + Checker.code.value);
});

⚠️ 直接打印 Checker.code 得到的是 Frida 的字段包装对象,而非具体的值,必须使用 .value

5.4 手动创建实例并调用方法(Frida-Labs 0x4)

当目标方法是非静态的,且 App 当前未创建该类实例时:

javascript 复制代码
Java.perform(function () {
    var Check = Java.use("com.ad2001.frida0x4.Check");
    // 使用 $new() 创建新实例
    var instance = Check.$new();
    var flag = instance.get_flag(1337);
    console.log("[*] Flag: " + flag);
});

5.5 内存堆搜索已有实例(Frida-Labs 0x5)

对于系统管理的类(如 Activity),不能自行 $new(),需要用 Java.choose 从堆中搜索:

javascript 复制代码
setTimeout(function () {
    Java.perform(function () {
        Java.choose("com.ad2001.frida0x5.MainActivity", {
            onMatch: function (instance) {
                console.log("[*] 找到实例: " + instance);
                // 调用实例方法
                instance.targetMethod();
            },
            onComplete: function () {
                console.log("[*] 搜索完成");
            }
        });
    });
}, 1000);  // 延迟确保 Activity 已创建

5.6 构造复杂对象作为参数(Frida-Labs 0x6)

综合运用内存实例查找 + 对象实例化

javascript 复制代码
setTimeout(function () {
    Java.perform(function () {
        // 1. 搜索 MainActivity 实例
        Java.choose("com.ad2001.frida0x6.MainActivity", {
            onMatch: function (instance) {
                // 2. 创建并配置参数对象
                var Checker = Java.use("com.ad2001.frida0x6.Checker");
                var checker = Checker.$new();
                // 配置属性...
                // 3. 主动调用
                instance.targetMethod(checker);
            },
            onComplete: function () {}
        });
    });
}, 1000);

5.7 Hook 构造方法(Frida-Labs 0x7)

Frida 中构造方法映射为 $init

javascript 复制代码
Java.perform(function () {
    var Checker = Java.use("com.ad2001.frida0x7.Checker");
    // Hook 构造方法
    Checker.$init.implementation = function (a, b) {
        console.log("[*] 原始构造参数: a=" + a + ", b=" + b);
        // 强制修改为满足条件的值
        this.$init(999, 999);
    };
});

📌 如果存在多个重载构造方法,需要使用 .overload() 指定签名:

javascript 复制代码
Checker.$init.overload('int', 'int').implementation = function (a, b) { ... };

5.8 处理方法重载(Frida-Labs Demo)

Java 中同名方法参数不同即为重载,Hook 时必须用 .overload() 明确签名:

javascript 复制代码
Java.perform(function () {
    var cls = Java.use("com.example.Challenge4Activity");

    // Hook 接收两个 int 参数的版本
    cls.check.overload('int', 'int').implementation = function (a, b) {
        console.log("[*] check(int, int) called");
        return this.check(a, b);
    };

    // Hook 接收一个 String 参数的版本
    cls.check.overload('java.lang.String').implementation = function (s) {
        console.log("[*] check(String) called");
        return this.check(s);
    };
});

签名书写规则 :基本类型用小写(intboolean),引用类型用全限定名(java.lang.String)。

💡 实用技巧 :不确定签名?故意写错 .overload(),Frida 报错时会列出所有可用签名,直接复制即可!


六、Native 层 Hook

Native 层 Hook 针对 C/C++ 代码(.so 动态链接库),操作的是内存地址和寄存器。

6.1 核心 API ------ Interceptor.attach

javascript 复制代码
Interceptor.attach(targetAddress, {
    onEnter: function (args) {
        // 函数执行前:读取/修改参数
        console.log("arg0: " + args[0]);
        console.log("arg1 字符串: " + args[1].readUtf8String());
    },
    onLeave: function (retval) {
        // 函数执行后:读取/修改返回值
        console.log("返回值: " + retval);
        retval.replace(1337);  // 修改返回值
    }
});

6.2 辅助侦察脚本

javascript 复制代码
// 查看目标 SO 是否已加载及其基址
var module = Process.findModuleByName("libtarget.so");
if (module) {
    console.log("基址: " + module.base);
    console.log("大小: " + module.size);
}

// 枚举导出函数
Module.enumerateExports("libtarget.so", {
    onMatch: function (exp) {
        console.log(exp.type + " | " + exp.name + " | " + exp.address);
    },
    onComplete: function () {}
});

6.3 Hook 有符号 Native 函数(Frida-Labs 0x8)

场景 :Hook libc.so 中的 strcmp 来截获 Flag。

javascript 复制代码
// 监听目标 SO 加载时机
Java.perform(function () {
    var Runtime = Java.use("java.lang.Runtime");
    Runtime.loadLibrary0.implementation = function (classLoader, libName) {
        var result = this.loadLibrary0(classLoader, libName);
        if (libName === "frida0x8") {
            console.log("[*] 目标 SO 已加载,开始 Hook strcmp");
            hookStrcmp();
        }
        return result;
    };
});

function hookStrcmp() {
    var strcmpAddr = Module.findExportByName("libc.so", "strcmp");
    Interceptor.attach(strcmpAddr, {
        onEnter: function (args) {
            var arg0 = args[0].readUtf8String();
            var arg1 = args[1].readUtf8String();
            if (arg0 && arg1) {
                console.log("[strcmp] " + arg0 + " vs " + arg1);
            }
        }
    });
}

6.4 修改 Native 函数返回值(Frida-Labs 0x9)

场景:Native 函数固定返回 1,但 Java 层要求返回 1337。

javascript 复制代码
var checkFlagAddr = Module.findExportByName("liba0x9.so",
    "Java_com_ad2001_a0x9_MainActivity_check_1flag");

Interceptor.attach(checkFlagAddr, {
    onLeave: function (retval) {
        console.log("[*] 原始返回值: " + retval);
        retval.replace(1337);
        console.log("[*] 修改后返回值: 1337");
    }
});

6.5 主动调用 Native 函数 ------ NativeFunction(Frida-Labs 0xA)

场景 :SO 中存在隐藏函数 get_flag,App 从未调用。

javascript 复制代码
// 方式一:通过符号名(有符号)
var getFlagAddr = Module.findExportByName("libfrida0xa.so", "_Z8get_flagii");
var getFlag = new NativeFunction(getFlagAddr, 'void', ['int', 'int']);
getFlag(参数1, 参数2);

// 方式二:通过基址 + 偏移(无符号/符号被 Strip)
var base = Module.findBaseAddress("libfrida0xa.so");
var offset = 0x1DD60;
var targetAddr = base.add(offset);
// 32位 ARM Thumb 模式需要 +1
// var targetAddr = base.add(offset + 1);
var getFlag = new NativeFunction(targetAddr, 'void', ['int', 'int']);
getFlag(参数1, 参数2);

NativeFunction 支持的类型voidintuintlongfloatdoublepointerbool 等。

⚠️ Thumb 模式注意 :在 32 位 ARM 下,如果目标函数是 Thumb 指令集,地址最低位必须为 1,即需要 base.add(offset + 1)


七、内存 Patch --- 修改机器码(Frida-Labs 0xB)

当 Hook 无法满足需求时(如需要绕过复杂的跳转逻辑),可以直接修改内存中的汇编指令

场景 :Native 函数中存在 B.NE(条件跳转)指令,跳过了 Flag 生成逻辑。

策略 :将 B.NE 替换为 NOP(空操作指令),让代码"直落"执行。

javascript 复制代码
var base = Module.findBaseAddress("libtarget.so");
var patchAddr = base.add(0x15248);  // B.NE 指令的偏移

// 1. 修改内存权限为 RWX(代码段默认只读)
Memory.protect(patchAddr, 4, 'rwx');

// 2. 使用 Arm64Writer 写入 NOP 指令
var writer = new Arm64Writer(patchAddr);
writer.putNop();
writer.flush();

console.log("[*] Patch 完成:B.NE -> NOP");

📌 代码段(.text)默认是只读(RX)的,必须先用 Memory.protect 改为可读可写可执行(RWX)才能写入。


八、速查总结表

操作 核心 API 关键点
Hook Java 方法 Java.use + .implementation 使用方括号写法兼容混淆名
修改静态字段 Java.use + .value 必须用 .value 读写
创建新实例 $new() 不适用于 Activity 等系统类
搜索堆中实例 Java.choose 注意执行时机,建议延迟
Hook 构造方法 $init 重载时用 .overload()
处理重载 .overload('type1', 'type2') 写错签名可获取所有签名提示
Hook Native 函数 Interceptor.attach args[n] 是指针,需 Memory 读写
修改返回值 retval.replace(value) onLeave 中操作
主动调用 Native NativeFunction 注意 Thumb +1 问题
内存 Patch Arm64Writer / X86Writer 需先 Memory.protect 改权限

写在最后

Frida 作为最灵活的动态 Hook 框架,几乎能覆盖 Android 逆向分析中的所有场景。本文从环境搭建到 Java 层 Hook、Native 层 Hook、再到内存 Patch,系统地梳理了 Frida 的核心用法。

相关推荐
Emotional。1 小时前
AI Agent 开发实战:用 LangChain 构建智能邮件助手
linux·服务器·网络·人工智能·python·langchain
春和景明3601 小时前
费曼学习法
java
追风少年ii2 小时前
第12篇HD文章--射血分数保留的心力衰竭患者左心室心肌免疫细胞丰度正常
python·分类·数据分析·空间·单细胞
组合缺一2 小时前
赋予 AI 灵魂:如何在 Java AI 生态实现一个会“自我反思”的长期记忆系统
java·人工智能·ai·llm·agent·solon·mcp
Lupino2 小时前
贪小便宜买的 10 元“三无”传感器,看我用 OpenClaw 强行逆袭!
python·ai编程
wangbing11252 小时前
开发指南142-类和字符串转换
java·开发语言
A懿轩A2 小时前
【Java 基础编程】Java 集合框架详解:List/Set/Map 选型 + ArrayList/HashMap 原理与使用
java·windows·list
毕设源码-赖学姐2 小时前
【开题答辩全过程】以 基于SpringBoot 的个人健康分析指导系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
Java.慈祥2 小时前
My First AI智能体!!!
人工智能·python·ai编程·智能体·coze·coze工作流·agent开发