Hook 是一种在程序运行时动态修改或拦截函数调用、参数或返回值的技术。在 Android 安全研究、逆向分析以及自动化测试中,Hook 技术扮演着至关重要的角色。
目前 Android 生态中主要有两种主流的 Hook 框架:
本篇文章将重点介绍在安卓逆向中最常用的 Frida 框架,带你完成从环境搭建到基本命令的使用。
Frida 的工作模式是 C/S 架构(客户端/服务端),因此安装过程分为两部分:
确保电脑已安装 Python 环境,直接使用 pip 命令安装:
指定版本安装(推荐):
为了保证稳定性,建议 PC 端和手机端保持版本一致。如果需要安装特定版本(例如 16.7.14):
注:frida-tools 的版本通常会自动匹配,如非必要可不指定。
在下载服务端之前,必须知道你的 Android 设备 CPU 架构。连接手机(确保已开启 USB 调试),输入以下命令:
前往官方 GitHub 发布页:Frida Releases
下载原则:
示例场景:
下载文件:frida-server-16.7.14-android-arm64.xz

下载完成后解压得到 frida-server-16.7.14-android-arm64 文件,按照以下步骤操作:
1. 推送到手机临时目录
/data/local/tmp/ 是安卓系统中用于存放临时文件的标准目录,且允许执行二进制文件。
2. 进入手机 Shell 并提权
Frida 服务端必须以 Root 权限运行。
3. 赋予执行权限并运行
建议在命令末尾添加 & 符号,使其在后台运行,防止关闭终端窗口后服务停止。
提示:如果此时终端没有报错且光标闪烁或返回新的一行,说明启动成功。
为了让电脑端的 Frida 客户端(Python/CLI)能通过 USB 与手机端的 frida-server 通信,需要建立端口映射。
方式一:使用默认端口(推荐)
Frida 默认使用 27042 端口进行通信。如果是标准启动(即上一步中直接运行),只需执行:
方式二:使用自定义端口(非标端口)
如果默认端口被占用,或者为了规避针对默认端口的检测,可以指定非标准端口(例如 8888)。
手机端启动时指定端口:
在第三步运行 frida-server 时,需要修改启动命令,监听指定地址和端口:

电脑端设置端口转发:
客户端连接指定:
后续使用 frida 命令时,需要通过 -H 参数指定地址(或者通过 -D 指定设备),若仅通过 USB 转发,通常客户端会自动识别,或需指定 host:
为了方便输入命令,同时规避部分 App 针对 "frida-server" 文件名的简单字符串检测,建议重命名:
环境搭建完成后,可以使用以下命令来测试连接状态或执行 Hook 任务。
Frida 主要有两种注入模式:Spawn(重启/冷启动) 和 Attach(附加/热注入) 。
适用场景:App 启动阶段就有检测(如 Root 检测、模拟器检测),或者需要 Hook 启动时才执行的逻辑(如 Application.onCreate()、早期 Native 初始化)。
原理:Frida 会自动启动(重启)App,并在 App 真正启动前将脚本注入。
适用场景:App 已经在运行中,需要中途介入分析逻辑,或者为了规避针对启动注入的检测。
原理:Frida 附加到当前正在运行的进程上,不会重启 App。
在执行注入前,我们需要准确找到目标的标识符。可以使用 frida-ps 配合过滤命令查找。
1. 查找包名 (Package Name) - 用于 Spawn 模式
列出设备上安装的所有应用:
2. 查找进程名 (Name) 或 PID - 用于 Attach 模式
列出当前正在运行的进程,并进行关键词过滤:
Linux / macOS 用户 (使用 grep):
Windows 用户 (使用 findstr):
如果使用 WiFi 调试或远程调试(需在手机端启动 frida-server 时指定监听端口):
在 Frida 中,所有的 Java 层 Hook 操作都必须被包裹在 Java.perform 中执行。这是因为 Frida 的 JavaScript 脚本运行在独立的线程中,若要访问 Android 应用的 Java 虚拟机(VM)环境,必须显式地将当前线程附加到 VM 上。
Java.perform(fn) :
Frida 的入口函数。它确保当前线程已附加到 Android 的 Java VM,只有在这里面才能调用 Java.use 等 API。
注意:所有的 Hook 逻辑都应包含在回调函数 fn 中。
Java.use(className) :
动态获取一个 Java 类的引用(类似反射中的获取 Class 对象)。
implementation:
这是实现 Hook 的关键属性。通过给某个方法的 implementation 赋值一个 JavaScript 函数,从而替换掉原有的 Java 方法逻辑。
在某些特殊场景下(如脚本注入过早、类尚未加载),直接运行可能会报错。我们可以使用定时器来延迟执行。
1. 使用 setImmediate (推荐)
用于确保在当前 JS 事件循环结束后立即执行,常用于防止阻塞主线程或处理某些特定的栈问题。
不要让 setImmediate 位于代码的第一行,可能会被 Frida 的 REPL 误解析成命令而报以下错误:
2. 使用 setTimeout (延时执行)
适用于应用启动初期类还未加载(ClassNotFoundException)的情况,或者为了避开某些早期的检测逻辑。
普通方法通常指的是类中的实例方法(非静态方法)。在 Hook 这类方法时,我们需要注意保留或者合理利用 this 指针,因为它代表了当前对象的实例。
我们以 Frida-Labs 0x1 为例进行演示。
靶场地址:github.com/DERE-ad2001/Frida-Labs
查看反编译后的 Java 源代码,我们发现关键逻辑位于 MainActivity 的 check 方法中:
分析:想要触发 "Yey you guessed it right" 的分支,传入的参数必须满足 (i * 2) + 4 == i2。虽然我们可以通过计算输入正确的值(例如输入 0 和 4),但在逆向中,更直接的方法是 Hook 该方法并篡改参数,无论用户在 UI 输入什么,强制让传入逻辑的参数满足等式。
我们的策略是:拦截 check 方法,将参数强制修改为一组满足条件的固定值(例如 i=0, i2=4),然后将修改后的参数传递给原方法执行。
运行脚本后,在 App 输入框中随意输入任意数字,点击提交按钮,成功通过校验,获取到 Flag。


在 JavaScript 中调用原方法时,既可以使用 this.check(i, i2) 也可以使用 this["check"](i, i2)。
推荐使用 方括号 [] 写法,因为当混淆后的方法名包含特殊字符(如 $, -)或关键字时,点号写法可能会报错。
静态方法(static)与普通实例方法的区别在于:静态方法属于类本身,而不属于类的某个具体实例。这意味着在 Frida 中,我们无需获取类的实例对象(即不需要 this),直接通过 Java.use 获取的类引用即可进行 Hook 或调用。
我们以 Frida-Labs 0x2 为例,展示如何主动调用一个静态方法。
分析反编译后的 Java 代码:
分析:
对于静态方法,Java.use 返回的对象可以直接点出方法名进行调用。
脚本运行后,无需任何用户交互,Frida 会立即执行该函数,App 界面上的 TextView 随即更新显示 Flag。


核心总结
在 Frida 中,对字段(Field)的操作与方法(Method)不同。我们不能像拦截方法那样去 "Hook" 一个字段的读取或写入动作(虽然通过 Setter/Getter 可以间接实现),通常的操作是直接获取或修改字段的值。
我们以 Frida-Labs 0x3 为例。
反编译应用,找到校验逻辑所在的 MainActivity 和数据类 Checker:
分析:
Checker.code 默认初始化为 0。点击按钮时,逻辑直接判断它是否为 512。我们无需拦截 onClick,只需要在点击之前,将 Checker.code 的内存值修改为 512 即可。
在 Frida 中,通过 Java.use 获取到的类对象,可以直接访问其静态字段。
注意:访问字段值时,必须使用 .value 属性。如果直接打印 Checker.code,得到的是一个 Frida 的字段包装对象(Field Object),而不是具体的值。

在之前的案例中,我们处理的要么是静态方法(直接通过类调用),要么是已运行的实例方法(通过拦截获取 this)。但有时我们会遇到一种特殊情况:
目标方法是非静态的(Instance Method),且 App 当前的运行流程中并没有创建该类的实例。此时,为了执行该方法,我们需要在 Frida 脚本中手动创建一个该类的实例对象。
我们以 Frida-Labs 0x4 为例。
分析:
脚本执行后,Frida 在目标进程的内存中成功创建了一个 Check 对象,并调用了其 get_flag 方法。由于参数正确(1337),方法返回了 Flag 字符串并在控制台打印出来。

在前面的案例中,我们通过 $new() 创建了一个全新的对象。但在 Android 开发中,很多类(如 Activity, Service)是由系统管理的,如果我们自己 new 一个 Activity,它不仅无法控制当前的 UI,还可能导致应用崩溃。
场景:我们需要调用当前正在运行的某个页面(Activity)中的方法,或者获取当前内存中某个单例对象的状态。
方法:使用 Java.choose 在内存堆(Heap)中扫描并获取已存在的对象实例。
我们以 Frida-Labs 0x5 为例。
分析:
使用 Java.choose API 进行内存搜索。
注意时机:
这里建议使用 setTimeout 而非 setImmediate。
因为在 Spawn(冷启动)模式下,Frida 脚本注入极快,此时 MainActivity 可能还没来得及完成初始化(即还未进入堆内存)。延迟 500ms~1000ms 可以确保 Activity 已经创建完毕,从而避免搜索落空。
className:需要搜索的类名(字符串)。
callbacks:一个包含两个回调函数的对象:
onMatch(instance) :
onComplete() :

在实际逆向中,目标方法的参数往往不是简单的 int 或 String,而是自定义的类对象(Object)。为了调用这类方法,我们需要在脚本中手动构造一个符合要求的对象实例作为参数传入。
这是一个综合性的案例,结合了 Section 5 (对象实例化) 和 Section 6 (内存实例查找) 的技巧。我们以 Frida-Labs 0x6 为例。
分析:
脚本逻辑流程:等待 App 加载 -> 搜索 MainActivity 实例 -> 创建并配置 Checker 参数对象 -> 主动调用。

构造方法(Constructor)在 Android 逆向中是一个非常重要的 Hook 点。在 Frida 中,构造方法在 JavaScript 层面被映射为 $init。
我们以 Frida-Labs 0x7 为例。
分析:
Checker 被初始化为 (123, 321),不满足 > 512 的条件。想要通过校验,我们有两种截然不同的思路:
这种方法比较“暴力”,直接在 flag 方法执行前,创建一个满足条件的新 Checker 对象,并替换掉原本的参数 A。
这是本节的重点。我们直接 Hook Checker 类的构造方法($init)。当 App 尝试用 (123, 321) 去 new 对象时,我们强制将其修改为 (999, 999)。
关键点:Frida 中 hook 构造方法必须使用 $init 关键字。
脚本运行后,App 在初始化 Checker 时参数被修改,随后 flag 方法校验通过,Flag 显示。

进阶提示:处理重载
如果构造方法有多个(例如 Checker() 和 Checker(int a)),Frida 可能会提示模糊匹配错误。此时需要使用 overload 指明参数签名:
在 Java 中,允许存在多个同名方法,只要它们的参数列表(参数个数或类型)不同,这就是 方法重载 (Overloading) 。
当我们要 Hook 一个存在重载的方法时,如果直接使用 implementation,Frida 无法确定你到底想 Hook 哪一个版本,从而抛出 "ambiguous"(歧义)错误。此时,必须使用 .overload() 明确指定参数签名。
我们编写一个简单的 Demo Challenge4Activity,其中包含两个名为 check 的方法:
分析:
虽然方法名都是 check,但在 Smali/Bytecode 层面它们的签名是完全不同的。
使用 .overload('Type1', 'Type2', ...) 来指定目标方法的参数类型。
签名书写规则:

实用技巧:不知道签名怎么办?
如果你不确定重载的签名该怎么写(比如是一个复杂的自定义类数组),可以故意不写 .overload() 或者乱写一个 .overload()。
运行脚本时,Frida 会报错,并在错误信息中列出该方法所有可用的 overload 签名。直接把报错信息里正确的签名复制出来即可!
报错示例:

Native 层 Hook 主要针对 Android 中的 C/C++ 代码(通常编译为 .so 动态链接库)。与 Java 层不同,这里操作的是内存地址、寄存器和汇编指令。Frida 提供了强大的 Interceptor API 来实现这一层面的拦截。
Native Hook 的核心是通过 Module 找到函数的内存地址,然后利用 Interceptor.attach 挂钩该地址。
由于 Native 库(.so 文件)通常是在 App 运行时动态加载的,建议使用 setTimeout 延迟执行,或拦截 System.loadLibrary 来确保目标 so 已加载。
Interceptor.attach(target, callbacks)
这是 Native Hook 的核心函数。
回调函数详解:
onEnter: function (args)
函数执行前被调用。
args:一个数组,包含传递给函数的参数。
this.context:访问 CPU 寄存器上下文(如 this.context.x0 在 ARM64 中通常存放第一个参数或返回值)。
onLeave: function (retval)
Module 类主要用于操作加载到进程中的动态链接库(SO 文件),是定位 Hook 地址的第一步。
在 Native Hook 中,args[n] 得到的仅仅是 内存地址(指针) 。要获取指针指向的实际数据(如字符串内容、整数值、结构体数据),必须使用 Memory 类的方法进行读取;若要修改数据,则使用对应的 write 方法。
示例:综合使用内存读写
在逆向初期,我们往往不知道具体的函数名或 SO 加载情况,以下脚本非常实用。
用于查看目标 SO 是否已加载,以及获取其基址。
用于查找目标函数在内存中的偏移或确切名称(特别是在存在混淆或 C++ Name Mangling 时)。
所谓“有符号函数”,指的是在动态链接库的导出表(Export Table)中能找到名字的函数。这通常包括:
我们以 Frida-Labs 0x8 为例。
分析:
关键逻辑位于 Native 层。将 libfrida0x8.so 拖入 IDA 分析,发现 cmpstr 对应的 Native 实现是 Java_com_ad2001_frida0x8_MainActivity_cmpstr。
核心逻辑:
策略:由于最终校验调用了标准的 C 库函数 strcmp,我们可以直接 Hook libc.so 中的 strcmp。当我们的输入字符串与 Flag 进行比较时,Flag 必然会作为 strcmp 的另一个参数出现。
关键点:处理 SO 加载时机
由于我们 Hook 的是 libc.so 的函数(它是系统库,启动即加载),理论上可以直接 Hook。
但为了演示更通用的 Native Hook 流程(针对 App 自带的 so),我们采用 “监听加载” 的策略:Hook java.lang.Runtime.loadLibrary0,监控目标 SO 何时被加载。一旦检测到目标 SO 加载完毕,立即执行 Native Hook 逻辑。

在 Native 层 Hook 中,除了查看参数,最常见的需求就是修改函数的返回值。比如绕过某些布尔类型的校验函数(返回 true/false),或者修改计算结果。这需要在 onLeave 回调中进行操作。
我们以 Frida-Labs 0x9 为例。
Java 层代码:
Native 层代码 (伪代码) :
使用 IDA 打开 liba0x9.so,查看导出函数 Java_com_ad2001_a0x9_MainActivity_check_1flag:
分析:
Native 函数固定返回 1,但 Java 层要求返回 1337 才能成功。
显然,我们无法通过修改输入参数来改变结果(因为它没有参数)。我们必须拦截该函数执行完毕后的返回动作,强行将返回值从 1 修改为 1337。
使用 Interceptor.attach 的 onLeave 回调。

** 技巧提示**
在逆向分析中,我们经常会发现一些隐藏的函数。它们存在于 SO 库中,包含了关键逻辑(如解密 Flag、生成 Token),但在 App 的正常运行流程中从未被调用,或者触发条件极难满足。
此时,我们需要利用 Frida 的 NativeFunction API,将这些内存地址“包装”成 JavaScript 函数,从而实现主动调用。
我们以 Frida-Labs 0xA 为例。
1. Java 层分析
2. Native 层分析
通过 IDA 分析 stringFromJNI,发现它只是返回了一个普通的字符串,并没有 Flag。
但在导出表中,我们发现了一个可疑的函数 get_flag,虽然它在 Java 层没有被声明,也没有被调用。
分析:
由于该函数是 C 编写的,编译器会对函数名进行修饰 (Name Mangling) 以支持重载等特性。直接搜 get_flag 可能找不到,我们需要找到它在导出表中的“真实名字”。
使用 Module.enumerateExports 脚本查看:
_Z8get_flagii 就是我们要找的真实符号名(_Z 开头,8 是长度,ii 代表两个 int 参数)。
当然也可以直接在 IDA 中的反汇编界面进行查看:

使用 NativeFunction 将地址转换为函数并调用。
new NativeFunction(address, returnType, argTypes[, abi])
address:函数的内存地址 (NativePointer)。
address 参数是 pointer 类型(Module.findExportByName() 的返回值就是 pointer 类型),如果这里传的是 number 类型,需要如下转换:
returnType:返回值的类型(字符串)。
argTypes:参数类型列表(字符串数组)。
支持类型:
脚本运行后,Frida 主动执行了该函数。由于函数内部调用了 __android_log_print,我们需要去 Logcat 查看结果,这里使用的是 Android Studio 的日志查看功能。

在生产环境中,为了防止逆向分析,开发者通常会去除 SO 库中的符号表(Strip)。此时,函数名会变成类似 sub_151C0 这样的无意义名称,我们无法通过 findExportByName 直接找到它。
在这种情况下,我们需要采用 “基址 + 偏移” 的策略进行定位。
公式:目标函数绝对地址 = SO 库在内存中的基址 + 函数在文件中的偏移量
我们继续以 Frida-Labs 0xA 为例,假设 get_flag 函数的符号已被去除。
使用 IDA Pro 或 Ghidra 打开 libfrida0xa.so,跳转到目标函数。

观察左侧地址栏,可以看到该函数相对于文件头的偏移量为 0x1DD60。
在编写脚本时,我们需要先获取 SO 的基址,然后加上偏移。
关键点:Thumb 模式 (+1 问题)
在 32位 ARM 架构下,指令集分为 ARM(4字节对齐)和 Thumb(2字节对齐)。如果目标函数是 Thumb 指令集,其地址的最低位(LSB)必须为 1。
Module.findBaseAddress(name)
NativePointer.add(offset)

在某些场景下,单纯的 Hook(拦截)已经无法满足需求。例如:
此时,我们需要使用 X86Writer 或 Arm64Writer 直接修改内存中的机器码。
由于代码段(.text)通常是 只读(RX) 的,直接写入会报错。因此,必须先使用 Memory.protect 将目标内存页修改为 可读可写可执行(RWX) 。
更多 API 请查阅官方文档:
我们以 Frida-Labs 0xB 为例,演示如何通过修改汇编指令绕过逻辑判断。
1. 分析 Java 层
2. 分析 Native 层 (IDA)
Java 层直接调用了 getFlag(),但反编译该函数看似为空(Empty Body)。这通常是因为 IDA 识别错误或代码被混淆。我们直接查看汇编代码:
逻辑解读:
3. Patch 策略
我们需要阻止这个跳转,让代码“顺流而下”执行到生成 Flag 的区域。
最简单的方法是将 B.NE 指令(偏移 0x15248)替换为 NOP(什么都不做)。
4. 编写脚本
5. 运行结果


| 框架名称 |
特点 |
适用场景 |
| Xposed |
需要刷机或安装虚拟机,修改系统层级,Hook 修改重启后依然生效。 |
适用于开发模块、系统定制、用户级持久化功能增强。 |
| Frida |
基于 Python + JavaScript,跨平台,支持动态注入,无需重启设备。 |
首选工具。适用于逆向分析、安全测试、算法还原、快速验证逻辑。 |
pip install frida
pip install frida-tools
pip install frida
pip install frida-tools
pip install frida==16.7.14
pip install frida-tools==12.3.0
pip install frida==16.7.14
pip install frida-tools==12.3.0
adb shell getprop ro.product.cpu.abi
adb shell getprop ro.product.cpu.abi
adb push frida-server-16.7.14-android-arm64 /data/local/tmp/
adb push frida-server-16.7.14-android-arm64 /data/local/tmp/
adb shell
su
cd /data/local/tmp
chmod +x frida-server-16.7.14-android-arm64
./frida-server-16.7.14-android-arm64 &
cd /data/local/tmp
chmod +x frida-server-16.7.14-android-arm64
./frida-server-16.7.14-android-arm64 &
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
./frida-server-16.7.14-android-arm64 -l 0.0.0.0:8888 &
./frida-server-16.7.14-android-arm64 -l 0.0.0.0:8888 &
adb forward tcp:8888 tcp:8888
adb forward tcp:8888 tcp:8888
frida -H 127.0.0.1:8888 -l hook.js -f com.example.target
或
frida -H 127.0.0.1:8888 -l hook.js "Target App Name"
frida -H 127.0.0.1:8888 -l hook.js -f com.example.target
或
frida -H 127.0.0.1:8888 -l hook.js "Target App Name"
mv frida-server-16.7.14-android-arm64 fs
mv frida-server-16.7.14-android-arm64 fs
| 命令 |
说明 |
frida-ls-devices |
列出电脑连接的所有设备(检查 USB 连接是否正常)。 |
frida-ps -U |
USB 模式。列出当前连接的 Android 设备上正在运行的进程。 |
frida-ps -Uai |
列出设备上所有已安装的应用(包括未运行的),显示包名和应用名。 |
frida-ps -D <device_id> |
连接指定 ID 的设备(当多设备连接时使用)。 |
frida -U -l script.js -f com.example.target
frida -U -l script.js -f com.example.target
frida -U -l script.js "Target App Name"
frida -U -l script.js "Target App Name"
frida-ps -Uai
frida-ps -U | grep "com.example.target"
frida-ps -U | grep "com.example.target"
frida-ps -U | findstr "com.example.target"
frida-ps -U | findstr "com.example.target"
frida -H 192.168.1.100:8888 -l script.js -f com.example.target
或
frida -H 192.168.1.100:8888 -l script.js "Target App Name"
frida -H 192.168.1.100:8888 -l script.js -f com.example.target
或
frida -H 192.168.1.100:8888 -l script.js "Target App Name"
Java.perform(function () {
var TargetClass = Java.use("com.example.demo.MainActivity");
TargetClass.targetMethod.implementation = function (arg1, arg2) {
console.log("[*] Hook targetMethod, args: " + arg1 + ", " + arg2);
var newArg1 = "Hacked";
var result = this.targetMethod(newArg1, arg2);
console.log("[*] Original result: " + result);
return result;
};
TargetClass.field.value = new_field;
});
Java.perform(function () {
var TargetClass = Java.use("com.example.demo.MainActivity");
TargetClass.targetMethod.implementation = function (arg1, arg2) {
console.log("[*] Hook targetMethod, args: " + arg1 + ", " + arg2);
var newArg1 = "Hacked";
var result = this.targetMethod(newArg1, arg2);
console.log("[*] Original result: " + result);
return result;
};
TargetClass.field.value = new_field;
});
setImmediate(function () {
Java.perform(function () {
console.log("[*] Script Loaded immediately.");
});
});
setImmediate(function () {
Java.perform(function () {
console.log("[*] Script Loaded immediately.");
});
});
Error: could not parse 'E:\Work_Space\hook.js' line 1: expecting field name
at <anonymous> (/frida/repl-2.js:1)
Error: could not parse 'E:\Work_Space\hook.js' line 1: expecting field name
at <anonymous> (/frida/repl-2.js:1)
setTimeout(function() {
Java.perform(function() {
console.log("[*] Script Loaded after 500ms.");
});
}, 500);
setTimeout(function() {
Java.perform(function() {
console.log("[*] Script Loaded after 500ms.");
});
}, 500);
void check(int i, int i2) {
if ((i * 2) + 4 == i2) {
Toast.makeText(getApplicationContext(), "Yey you guessed it right", 1).show();
} else {
Toast.makeText(getApplicationContext(), "Try again", 1).show();
}
}
void check(int i, int i2) {
if ((i * 2) + 4 == i2) {
Toast.makeText(getApplicationContext(), "Yey you guessed it right", 1).show();
} else {
Toast.makeText(getApplicationContext(), "Try again", 1).show();
}
}
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Hook Script...");
let MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity["check"].implementation = function (i, i2) {
console.log(`[*] Original args: i=${i}, i2=${i2}`);
i = 0;
i2 = 4;
console.log(`[*] Tampered args: i=${i}, i2=${i2}`);
this["check"](i, i2);
};
});
});
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Hook Script...");
let MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity["check"].implementation = function (i, i2) {
console.log(`[*] Original args: i=${i}, i2=${i2}`);
i = 0;
i2 = 4;
console.log(`[*] Tampered args: i=${i}, i2=${i2}`);
this["check"](i, i2);
};
});
});
public class MainActivity extends AppCompatActivity {
static TextView f103t1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(C0569R.layout.activity_main);
f103t1 = (TextView) findViewById(C0569R.C0572id.textview);
}
public static void get_flag(int a) {
if (a == 4919) {
f103t1.setText("FLAG is here...");
}
}
}
public class MainActivity extends AppCompatActivity {
static TextView f103t1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(C0569R.layout.activity_main);
f103t1 = (TextView) findViewById(C0569R.C0572id.textview);
}
public static void get_flag(int a) {
if (a == 4919) {
f103t1.setText("FLAG is here...");
}
}
}
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Active Call Script...");
let MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
console.log("[*] Calling MainActivity.get_flag(4919)...");
MainActivity["get_flag"](4919);
});
});
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Active Call Script...");
let MainActivity = Java.use("com.ad2001.frida0x2.MainActivity");
console.log("[*] Calling MainActivity.get_flag(4919)...");
MainActivity["get_flag"](4919);
});
});
public void onClick(View v) {
if (Checker.code == 512) {
Toast.makeText(MainActivity.this.getApplicationContext(), "YOU WON!!!", 1).show();
} else {
Toast.makeText(MainActivity.this.getApplicationContext(), "TRY AGAIN", 1).show();
}
}
public class Checker {
static int code = 0;
}
public void onClick(View v) {
if (Checker.code == 512) {
Toast.makeText(MainActivity.this.getApplicationContext(), "YOU WON!!!", 1).show();
} else {
Toast.makeText(MainActivity.this.getApplicationContext(), "TRY AGAIN", 1).show();
}
}
public class Checker {
static int code = 0;
}
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Field Modification Script...");
let Checker = Java.use("com.ad2001.frida0x3.Checker");
console.log("[*] Original code value: " + Checker.code.value);
Checker.code.value = 512;
console.log("[*] Modified code value: " + Checker.code.value);
});
});
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Field Modification Script...");
let Checker = Java.use("com.ad2001.frida0x3.Checker");
console.log("[*] Original code value: " + Checker.code.value);
Checker.code.value = 512;
console.log("[*] Modified code value: " + Checker.code.value);
});
});
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public class Check {
public String get_flag(int a) {
if (a == 1337) {
return "FLAG{...}";
}
return "";
}
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public class Check {
public String get_flag(int a) {
if (a == 1337) {
return "FLAG{...}";
}
return "";
}
}
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Instance Creation Script...");
let CheckClass = Java.use("com.ad2001.frida0x4.Check");
let checkInstance = CheckClass.$new();
console.log("[*] Check instance created: " + checkInstance);
let flag = checkInstance.get_flag(1337);
console.log(`[*] Check.get_flag result: ${flag}`);
});
});
setImmediate(function () {
Java.perform(function () {
console.log("[*] Starting Instance Creation Script...");
let CheckClass = Java.use("com.ad2001.frida0x4.Check");
let checkInstance = CheckClass.$new();
console.log("[*] Check instance created: " + checkInstance);
let flag = checkInstance.get_flag(1337);
console.log(`[*] Check.get_flag result: ${flag}`);
});
});
public class MainActivity extends AppCompatActivity {
TextView f103t1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void flag(int code) {
if (code == 1337) {
}
}
}
public class MainActivity extends AppCompatActivity {
TextView f103t1;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void flag(int code) {
if (code == 1337) {
}
}
}
setTimeout(function () {
Java.perform(function () {
console.log("[*] Starting Memory Scan...");
Java.choose("com.ad2001.frida0x5.MainActivity", {
onMatch: function (instance) {
console.log("[*] Found instance: " + instance);
instance.flag(1337);
},
onComplete: function () {
console.log("[*] Memory Scan Complete.");
}
});
});
}, 1000);
setTimeout(function () {
Java.perform(function () {
console.log("[*] Starting Memory Scan...");
Java.choose("com.ad2001.frida0x5.MainActivity", {
onMatch: function (instance) {
console.log("[*] Found instance: " + instance);
instance.flag(1337);
},
onComplete: function () {
console.log("[*] Memory Scan Complete.");
}
});
});
}, 1000);
public class MainActivity extends AppCompatActivity {
public void get_flag(Checker A) {
if (1234 == A.num1 && 4321 == A.num2) {
}
}
}
public class Checker {
int num1;
int num2;
}
public class MainActivity extends AppCompatActivity {
public void get_flag(Checker A) {
if (1234 == A.num1 && 4321 == A.num2) {
}
}
}
public class Checker {
int num1;
int num2;
}
setTimeout(function () {
Java.perform(function () {
console.log("[*] Starting Complex Call Script...");
Java.choose("com.ad2001.frida0x6.MainActivity", {
onMatch: function (instance) {
console.log("[*] Found Host instance: " + instance);
let CheckerClass = Java.use("com.ad2001.frida0x6.Checker");
let checkerObj = CheckerClass.$new();
console.log("[*] Configuring Checker object...");
checkerObj.num1.value = 1234;
checkerObj.num2.value = 4321;
console.log("[*] Invoking get_flag...");
instance["get_flag"](checkerObj);
},
onComplete: function () {
console.log("[*] Memory Scan Complete.");
}
});
});
}, 1000);
setTimeout(function () {
Java.perform(function () {
console.log("[*] Starting Complex Call Script...");
Java.choose("com.ad2001.frida0x6.MainActivity", {
onMatch: function (instance) {
console.log("[*] Found Host instance: " + instance);
let CheckerClass = Java.use("com.ad2001.frida0x6.Checker");
let checkerObj = CheckerClass.$new();
console.log("[*] Configuring Checker object...");
checkerObj.num1.value = 1234;
checkerObj.num2.value = 4321;
console.log("[*] Invoking get_flag...");
instance["get_flag"](checkerObj);
},
onComplete: function () {
console.log("[*] Memory Scan Complete.");
}
});
});
}, 1000);
public void onCreate(Bundle savedInstanceState) {
Checker ch = new Checker(123, 321);
try {
flag(ch);
} catch (Exception e) { ... }
}
public void flag(Checker A) {
if (A.num1 > 512 && 512 < A.num2) {
}
}
public class Checker {
int num1;
int num2;
public Checker(int a, int b) {
this.num1 = a;
this.num2 = b;
}
}
public void onCreate(Bundle savedInstanceState) {
Checker ch = new Checker(123, 321);
try {
flag(ch);
} catch (Exception e) { ... }
}
public void flag(Checker A) {
if (A.num1 > 512 && 512 < A.num2) {
}
}
public class Checker {
int num1;
int num2;
public Checker(int a, int b) {
this.num1 = a;
this.num2 = b;
}
}
setImmediate(function () {
Java.perform(function () {
let MainActivity = Java.use("com.ad2001.frida0x7.MainActivity");
MainActivity["flag"].implementation = function (A) {
console.log(`[*] Hooked flag method. Original args: ${A}`);
let Checker = Java.use("com.ad2001.frida0x7.Checker");
let newChecker = Checker.$new(999, 999);
this["flag"](newChecker);
};
});
});
setImmediate(function () {
Java.perform(function () {
let MainActivity = Java.use("com.ad2001.frida0x7.MainActivity");
MainActivity["flag"].implementation = function (A) {
console.log(`[*] Hooked flag method. Original args: ${A}`);
let Checker = Java.use("com.ad2001.frida0x7.Checker");
let newChecker = Checker.$new(999, 999);
this["flag"](newChecker);
};
});
});
setImmediate(function () {
Java.perform(function () {
let Checker = Java.use("com.ad2001.frida0x7.Checker");
Checker["$init"].implementation = function (a, b) {
console.log(`[*] Checker.$init called with: a=${a}, b=${b}`);
a = 999;
b = 999;
console.log(`[*] Tampered arguments to: a=${a}, b=${b}`);
this["$init"](a, b);
};
});
});
setImmediate(function () {
Java.perform(function () {
let Checker = Java.use("com.ad2001.frida0x7.Checker");
Checker["$init"].implementation = function (a, b) {
console.log(`[*] Checker.$init called with: a=${a}, b=${b}`);
a = 999;
b = 999;
console.log(`[*] Tampered arguments to: a=${a}, b=${b}`);
this["$init"](a, b);
};
});
});
Checker["$init"].overload('int', 'int').implementation = function(a, b) { ... }
Checker["$init"].overload('int', 'int').implementation = function(a, b) { ... }
public class Challenge4Activity extends AppCompatActivity {
private void check(String str) {
Toast.makeText(this, "String版: " + str, 0).show();
}
private void check(int i) {
Toast.makeText(this, "Int版: " + i, 0).show();
}
}
public class Challenge4Activity extends AppCompatActivity {
private void check(String str) {
Toast.makeText(this, "String版: " + str, 0).show();
}
private void check(int i) {
Toast.makeText(this, "Int版: " + i, 0).show();
}
}
setImmediate(function () {
Java.perform(function () {
let Challenge4Activity = Java.use("com.xiusi.fridastudy.Challenge4Activity");
Challenge4Activity["check"].overload('java.lang.String').implementation = function (str) {
console.log(`[*] Hooked check(String), value=${str}`);
this["check"]("Hacked String");
};
Challenge4Activity["check"].overload('int').implementation = function (i) {
console.log(`[*] Hooked check(int), value=${i}`);
this["check"](99999);
};
});
});
setImmediate(function () {
Java.perform(function () {
let Challenge4Activity = Java.use("com.xiusi.fridastudy.Challenge4Activity");
Challenge4Activity["check"].overload('java.lang.String').implementation = function (str) {
console.log(`[*] Hooked check(String), value=${str}`);
this["check"]("Hacked String");
};
Challenge4Activity["check"].overload('int').implementation = function (i) {
console.log(`[*] Hooked check(int), value=${i}`);
this["check"](99999);
};
});
});
function hookNative() {
var funcPtr = Module.findExportByName("libc.so", "strcmp");
console.log("[*] Target function address: " + funcPtr);
if (funcPtr) {
Interceptor.attach(funcPtr, {
onEnter: function (args) {
console.log("[*] onEnter strcmp");
var str1 = Memory.readUtf8String(args[0]);
var str2 = Memory.readUtf8String(args[1]);
console.log(` s1: ${str1}, s2: ${str2}`);
},
onLeave: function (retval) {
console.log("[*] onLeave, retval: " + retval);
}
});
} else {
console.log("[-] Function not found!");
}
}
setImmediate(function () {
Java.perform(function () {
var Runtime = Java.use("java.lang.Runtime");
Runtime.loadLibrary0.overload('java.lang.Class', 'java.lang.String').implementation = function (loader, libname) {
console.log("[*] Loading:", libname);
var ret = this.loadLibrary0(loader, libname);
if (libname.includes("libc.so")) {
console.log("[*] 目标库已加载!", libname);
hookNative();
}
return ret;
};
});
});
function hookNative() {
var funcPtr = Module.findExportByName("libc.so", "strcmp");
console.log("[*] Target function address: " + funcPtr);
if (funcPtr) {
Interceptor.attach(funcPtr, {
onEnter: function (args) {
console.log("[*] onEnter strcmp");
var str1 = Memory.readUtf8String(args[0]);
var str2 = Memory.readUtf8String(args[1]);
console.log(` s1: ${str1}, s2: ${str2}`);
},
onLeave: function (retval) {
console.log("[*] onLeave, retval: " + retval);
}
});
} else {
console.log("[-] Function not found!");
}
}
setImmediate(function () {
Java.perform(function () {
var Runtime = Java.use("java.lang.Runtime");
Runtime.loadLibrary0.overload('java.lang.Class', 'java.lang.String').implementation = function (loader, libname) {
console.log("[*] Loading:", libname);
var ret = this.loadLibrary0(loader, libname);
if (libname.includes("libc.so")) {
console.log("[*] 目标库已加载!", libname);
hookNative();
}
return ret;
};
});
});
| API 方法 |
说明 |
适用场景 |
Module.findExportByName(name, exp) |
查找导出函数的绝对地址。找不到返回null。 |
最常用。Hook 系统函数(如libc.so的open)或 App 明确导出的 JNI 函数。 |
Module.getExportByName(name, exp) |
查找导出函数的绝对地址。找不到抛出异常。 |
确定函数一定存在时使用,用于脚本的强依赖检查。 |
Module.getBaseAddress(name) |
获取指定 SO 在内存中的基址。 |
关键。Hook未导出函数(Sub_xxx)。公式:绝对地址 = 基址 + 偏移(IDA) |
Module.enumerateExports(name, cb) |
枚举指定 SO 的所有导出符号。 |
寻找目标函数名,或模糊搜索特定的导出函数。 |
Module.enumerateImports(name, cb) |
枚举指定 SO 的所有导入符号。 |
分析该 SO 调用了哪些外部函数(如fopen,ssl_write)。 |
| API 方法 |
说明 |
典型示例 |
Memory.readUtf8String(ptr) |
读取指针处的 UTF-8 字符串。常用于读取 char* 类型参数。 |
var str = Memory.readUtf8String(args[0]); |
Memory.writeUtf8String(ptr, str) |
将字符串写入指定地址。⚠️ 慎用:必须确保目标缓冲区有足够的空间,否则会造成内存溢出崩溃。 |
Memory.writeUtf8String(args[0], "hack"); |
Memory.readInt(ptr) |
读取地址处的 4 字节整数 (int)。 |
var val = Memory.readInt(args[1]); |
Memory.writeInt(ptr, value) |
将整数写入指定地址。常用于修改标志位或计数器。 |
Memory.writeInt(args[1], 1337); |
Memory.readByteArray(ptr, len) |
读取指定长度的字节数组。常用于查看结构体、加密后的二进制流。 |
var buf = Memory.readByteArray(args[2], 16); |
hexdump(ptr, options) |
调试神器。以十六进制 + ASCII 形式打印内存块,便于观察未知数据结构。 |
console.log(hexdump(args[0], { length: 64 })); |
Interceptor.attach(targetAddr, {
onEnter: function (args) {
var strArg = Memory.readUtf8String(args[0]);
var count = Memory.readInt(args[1]);
console.log(`[*] args[0]=${strArg}, *args[1]=${count}`);
Memory.writeInt(args[1], 9999);
console.log(hexdump(args[0], {
offset: 0,
length: 32,
header: true,
ansi: true
}));
}
});
Interceptor.attach(targetAddr, {
onEnter: function (args) {
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2025-12-6 19:17
被xiusi编辑
,原因: