Frida Hook Android手册

Frida Hook Android手册

write by ppl on 2025/4

Frida的安装与配置

​ 网上教程一大堆,略

连接 Android 设备

运行Frida服务端

$> adb shell
su
cd /data/local/tmp
./frida-server

进行端口转发

$> adb forward tcp:27042 tcp:27042

​ 27042 用于与frida-server通信的默认端口号,之后的每个端口对应每个注入的进程。

检测Frida是否成功运行

$> frida-ps -U

​ 可以列出所有进程的PID,包名。

连接模式

  1. spawn 模式:启动新进程
$> frida -U -f cn.binary.frida -l script.js# 暂停进程
$> frida -U -f cn.binary.frida -l script.js --pause
  1. attach 模式:附加到运行中的进程
# 附加到进程 pid
$> frida -U -p 1234 -l script.js
# 附加到进程名
$> frida -U -n com.example.demo -l script.js
# 附加到前台进程
$> frida -U -F -l script.js

常用参数

  • -U:连接到远程 USB 设备
  • -p:指定进程 PID
  • -n:指定进程包名
  • -l:指定注入的 js 代码文件
  • -f:启动新进程
  • -F:附加到前台进程

注入方法

1. JS 脚本注入

​ 使用-l命令参数,通过 frida 工具将写好的Js脚本注入到目标进程中。

2. Bash 命令行

​ 不使用-l命令参数,则使用Frida命令会打开Frida的Bash 命令行逐行输入Js代码交互。

3. Python代码注入

​ Frida 提供了 Python 接口,可以使用 Frida 的 Python API 可以更方便地操作和动态注入脚本。

import frida
import systarget = "<pid_or_process_name>"js_code = """
var targetFunction = Module.findExportByName(null, 'target_function_name');Interceptor.attach(targetFunction, {onEnter: function(args) {console.log('Intercepted function call!');args[0] = ptr('0x12345678');},onLeave: function(retval) {console.log('Function returned with value: ' + retval);retval.replace(0xabcdef);}
});
"""def on_message(message, data):print(message)if __name__ == '__main__':device = frida.get_usb_device()session = device.attach(2595)script = session.create_script(jscode)script.on('message', on_message)script.load()sys.stdin.read()

ApkLab与APK的重打包

​ APKLab是一个基于VS Code的Android逆向工程扩展,继承了多个强大的工具 :

  • apktool:反编译APK资源
  • dex2jar:将DEX转换为JAR
  • jadx:反编译Java字节码文件
  • keytool:管理密钥和证书

​ 集成了多个工具,可以方便的进行反编译、查看资源文件、修改代码、重新打包、签名等操作。

听说cursor可以用APKLab自动化逆向apk

Java层Hook

基本结构

​ Frida Hook Java脚本的执行是基于 Java.perform() 的,它确保 Java 环境在执行你的脚本时已经初始化。Java.use() 用来获取 Java 类的引用,这样可以对该类进行操作。implementation 是用来重写 Java 方法实现的关键字,可以在这里插入自己的代码,比如打印日志、修改参数、改变返回值等。

Java.perform(function () {var myClass = Java.use('com.example.MyClass');  // 获取类的引用// Hook doSomething 方法myClass.doSomething.overload().implementation = function () {console.log('doSomething 方法被调用');// 可以修改返回值或进行其他操作var result = this.doSomething();  // 调用原本的方法console.log('原始返回值:' + result);return result;  // 或者返回修改后的值};
});

​ 在获取类的引用要写明类的路径,即 完整的包名+类名 (即使是Java自带的类)。它会返回一个 Java 类的代理对象,你可以通过它来访问类的字段、方法等。

获取入参、重载方法

​ 可以使用 overload() 方法来钩取某个方法的特定重载版本。如果想获取函数的入参,可以通过 args 来访问。这些入参通常是一个数组,你可以根据方法签名提取出具体的值。

var TargetClass = Java.use("com.example.targetclass");  
var method = TargetClass.methodName.overload('java.lang.String', 'int');  // 指定重载方法签名method.implementation = function (str, num) {console.log("Original args: str = " + str + ", num = " + num);
};

或者

var TargetClass = Java.use("com.example.targetclass");  
TargetClass.methodName.overload('java.lang.String', 'int').implementation = function (str, num) {console.log("Original args: str = " + str + ", num = " + num);
};

​ 钩取某个方法的特定重载版本要通过参数类型的不同指明。 overload() 的参数数量应与Hook原函数的参数数量一致。参数类型:如果是基本类型直接是类型名的字符串,如果是非基本类型则为 包名+类名 的字符串。

​ 如果参数是数组:基本类型数组,用左中括号接上基本类型的缩写;对象数组,用左中括号接上完整类名再接上分号

  targetClass.methodName.overload('[I').implementation = function(arr) {// 通过 Java Array API 访问数组的每个元素let length = arr.length;for (let i = 0; i < length; i++) {console.log('元素 ' + i + ': ' + arr[i]);}};
基本类型 缩写
boolean Z
byte B
char C
double D
float F
int I
long J
short S

​ 对象数组:例如 '[java.lang.String;'

thissuper

this 代表的是当前类的实例,而 super 代表父类的实例。调用当前类的方法,通过 this 来调用当前类的实例方法或构造函数。调用父类的方法,通过 super 来调用父类的实例方法。

构造函数

$init 是构造函数的特殊表示, 是所有类构造函数的标识符。

var MyClass = Java.use('com.example.MyClass');
MyClass.$init.overload('java.lang.String').implementation = function(arg) {console.log('Constructing MyClass with argument:', arg);return this.$init(arg);  // 调用构造函数
};

​ 通常需要调用原构造函数。

获取类字段(属性)

​ 可以使用 getset 来访问 Java 类的字段。字段是通过 value 访问的。

var MyClass = Java.use('com.example.MyClass');
var fieldValue = MyClass.someField.value;  // 获取字段值
console.log('Field value: ', fieldValue);MyClass.someField.value = 42;  // 修改字段值
console.log('Field value after modification: ', MyClass.someField.value);

数组实例

​ 使用Java.array()创建一个 Java 数组实例。第一个参数为数组类型,第二个参数为数组内容。

var StringArray = Java.array('java.lang.String', ['a', 'b', 'c']);

​ 题外话:Java传参的数组通常是bytes数组,将bytes转换为十六进制并输出:

function bytesToHex(bytes){let hex = [];for(let i=0; i < bytes.length;;i++){let current = bytes[i] & 0xff;let hexValue = current.toString(16);if(hexValue.length == 1){hexValue = "0" + hexValue;}hex.push(hexValue);}return hex.join(" ");
}

静态方法

​ 静态方法是属于类本身的,因此你可以直接通过类来访问和 hook 静态方法调试。实例方法是属于对象实例的,因此在 hook 实例方法时,首先你需要通过 Java.use() 获取类的引用,当创建该类的实例时,或者在 hook 代码中通过实例方法进行调用。

​ 被动hook写法上似乎没有区别。。。。。但在主动调用时,静态方法不需要实例化对象。

主动调用

​ 以上都是被动调用,当原函数被调用时,hook函数才会发生作用。主动调用(或者说是直接调用)指的是在脚本中通过 Frida 代码主动触发目标程序的函数或方法。

​ 主动调用实例化方法:

Java.perform(function () {const MyClass = Java.use('com.example.MyClass');  // 获取目标类的引用// 创建对象实例const myObject = MyClass.$new();  // 使用 $new() 创建对象实例// 主动调用实例方法myObject.someInstanceMethod('Hello, Frida!');  // 修改实例方法的返回值MyClass.someInstanceMethod.implementation = function(arg) {console.log('Method called with argument:', arg);return 'Modified return value';  // 修改返回值};
});

​ 主动调用静态方法:

Java.perform(function () {const MyClass = Java.use('com.example.MyClass');  // 获取目标类的引用// 主动调用静态方法MyClass.staticMethod('Hello, static method!');  // 直接调用静态方法MyClass.staticMethod.implementation = function(arg) {console.log('Static method called with argument:', arg);return 'Modified static return value';  // 修改返回值};
});

遍历其他数据结构

Map

const mapField= this.stringMap.value; // 获取Map字段
if(mapField != null){const keySet = mapField.keySet();let iterator = keySet.iterator(); // 获取迭代器while(iterator.hasNext()){let key = iterator.next();let value = mapField.get(key);;console.log("  ["+ key + "=>" + value + "]");}
}

​ 之前做过的某题,使用Java.castthis转换为CollectionTraversal类型,然后就是用.value获取不同数据结构对应的字段

Java.perform(function () {var CollectionTraversal = Java.use("cn.binary.frida.CollectionTraversal");CollectionTraversal.traverseCollections.implementation = function () {console.log("[*] traverseCollections called");// 获取当前实例的字段var listField = this.stringList.value;var mapField = this.stringMap.value;var arrayField = this.stringArray.value;// 遍历 List<String>if (listField !== null) {var size = listField.size();console.log("[-] stringList:");for (var i = 0; i < size; i++) {var item = listField.get(i);console.log("   [" + i + "]: " + item);}}// 遍历 Map<String, String>if (mapField !== null) {console.log("[-] stringMap:");try {var keySet = mapField.keySet();var iterator = keySet.iterator(); // 获取迭代器while (iterator.hasNext()) {var key = iterator.next(); // 获取下一个元素var value = mapField.get(key); // 获取键对应的值console.log("   [" + key + " => " + value + "]");}} catch (e) {console.log("   Error in stringMap: " + e.message);}}// 遍历 String[]if (arrayField !== null) {var arrayLength = arrayField.length;console.log("[-] stringArray:");for (var j = 0; j < arrayLength; j++) { // 遍历数组var val = arrayField[j]; // 获取数组元素console.log("   [" + j + "]: " + val); // 打印数组元素}}// 调用原始函数return this.traverseCollections.call(this);};
});

调用栈打印

什么是调用栈?
调用栈是函数调用过程中形成的堆栈信息,记录了函数调用的顺序和位置。说人话就是看xx方法由谁调用,然后层层递进,直到最开始的调用者。在逆向工程中,了解是谁调用了某个函数非常重要,通过分析调用栈(CallStack),我们可以快速定位调用路径,从而更好地还原程序逻辑。

function print_callstack( {
const Log = Java.use("android.util.Log");
const Exception = Java.use("java.lang.Exception");
console.log(Log.getStackTraceString(Exception.$new()));

​ 它的原理是主动调用Java代码,生成一个异常对象,然后打印异常对象的堆栈信息。
​ 等价的Java代码:

android.util.Log().getStackTraceString(new java.lang.Exception())

​ 可以在任何Hook函数中调用print_callstack()来输出谁调用了它。

Native层Hook

​ 关于Native层是什么、加载方式就不细说了,这里主要讨论Frida Hook Native

Native模块遍历

​ Frida 提供了Process.enumerateModules方法列出所有模块。

Process.enumerateModules().forEach(function(module){console.log("Module "+module.name+", Base Address:"+module.base.toString());
});

​ 当so文件拥有符号时,可采用Moudle.findExportByName的方法查找地址

const func_addr = Module.findExportByName('libnative.so','target_function');
console.log(func);

​ 没有符号时,函数地址 = 模块基地址 + 偏移量 (通常在IDA中将起始地址设为0,则函数在IDA中的地址即为偏移量)

const offset = 0x1234;
const module_base = Module.findBaseAddress('libnative.so','target_function');
const func_addr = module_base.add(offest);

​ Frida对指针变量存放进行了封装,实现了多种内存操作的辅助函数(这也是为什么上面代码不用加号运算符的原因),如可以使用NativePointer新建指针

const addr = new NativePointer('0x1234');

​ 此外,使用NativeFunction类可以创建Native函数的引用

const func = new NativeFunction(addr,'int',['pointer','int']); // 地址、返回值、参数列表

​ hexdump可以打印内存。不过个人感觉用ida动调要方便些,

//读取128字节的内存内容
var data = Memory.readByteArray(addr,128);
console.log(hexdump(addr,{offset : 0,length : 128,header : true,anssi : true   
}));

基本结构

Interceptor.attach 是 Frida 提供的一个功能强大的 API,用于hook目标函数。当目标函数被调用时,onEnteronLeave 回调函数会被触发。

  • onEnter 在目标函数被调用时执行,允许你访问传入的参数。
  • onLeave 在目标函数返回时执行,允许你访问返回值,并且可以修改返回值。
Interceptor.attach(targetAddress, {onEnter: function(args) {// 在这里处理函数调用前的逻辑},onLeave: function(retval) {// 在这里处理函数返回时的逻辑}
})

​ 在 onEnter 回调中,我们可以访问函数的输入参数。这些参数通过 args 数组传递,每个参数是一个 NativePointer 对象。可以通过以下方式打印或修改这些参数:

  • args[index].toInt32():将参数转换为 32 位整数。
  • args[index].toString():将参数转换为字符串。
  • args[index].readUtf8String():如果参数是字符串指针,可以读取字符串内容。

onLeave 回调中,我们可以访问和修改函数的返回值。

  • retval.toInt32():获取返回值作为整数。
  • retval.replace(value):修改返回值,value 可以是任何合法的值或 NativePointer 对象。

​ 例如这里hook了open函数,致使当程序试图打开包含"hack"字样的文件时,打开失败

var openFunc = Module.findExportByName(null, 'open');
console.log('[*] open: ' + openFunc);
Interceptor.attach(openFunc, {onEnter: function(args) {console.log('[*] open 被调用');const filename_ptr = args[0];const filename = Memory.readUtf8String(filename_ptr);console.log('[*] filename: ' + filename);if (filename.includes('hack')) {console.log('[*] 禁止打开文件: ' + filename);this.forbid = true;} else {console.log('[*] 允许打开文件: ' + filename);this.forbid = false;}},onLeave: function(retval) {if (this.forbid) {retval.replace(-1);}}
});

​ 这里的this与hook java层的this不同。Java层的this指代Java 对象本身,而这里的this指代函数被hook时调用一次的过程,生命周期随目标函数调用的开始而开始,随调用结束而结束。因此可以利用js的动态性向this中添加属性,即this.forbid,使得forbid能够同时在两个回调函数中调用。(不嫌污染全局变量就全用var一样的)

文件重定向

var openFunc = Module.findExportByName(null, 'open');
console.log('[*] open: ' + openFunc);
Interceptor.attach(openFunc, {onEnter: function(args) {console.log('[*] open 被调用');var filename_ptr = args[0];var filename = Memory.readUtf8String(filename_ptr);console.log('[*] filename: ' + filename);if (filename.includes('/proc/self/status')) {console.log('[*] 重定向到 /data/local/tmp/status.txt');// 重新申请内存var new_filename_ptr = Memory.allocUtf8String('/data/local/tmp/status.txt');args[0] = new_filename_ptr;this.new_filename_ptr = new_filename_ptr; }},
});

​ 使用Memory.allocUtf8String申请内存写入字符串,替换arg[0]为新的内存地址

主动调用

var getLicense = Module.findExportByName("libfrida.so", '_Z10getLicenseiPKc');
console.log('[*] getLicense: ' + getLicense);var getLicenseFunc = new NativeFunction(getLicense, 'pointer', ['int', 'pointer']);for (var i = 0; i < 3; i++) {var password_ptr = Memory.allocUtf8String("password");var license = getLicenseFunc(i, password_ptr);console.log('[*] license: ' + Memory.readUtf8String(license));
}

​ 使用NativeFunction创建函数引用,方便用于主动调用。然后用 Memory.allocUtf8String分配内存,用于传递字符串参数,最后使用Memory.readUtf8String读取返回的字符串值。

NativeFunction创建函数引用

new NativeFunction(address,reyurnType,argTypes[,abi])

address函数地址,传入NativePointer类型;returnType返回值类型,传入string类型;argTypes参数类型,传入array;abi调用约定,通常默认

内存搜索

const pattern = "66 6c 61 67 7b 46 49 4e 44 5f 4d 45 5f"; // ASCII: flag{FIND_ME_Process.enumerateRanges("--rw-").forEach(range => {try {Memory.scan(range.base, range.size, pattern, {onMatch: function (address, size) {const str = Memory.readUtf8String(address);console.log("[*] Found flag:", str);},onError: function (reason) {console.error("Memory scan error:", reason);},onComplete: function () {}});} catch (error) {console.error("Memory scan error:", error);}
});

​ Process.enumerateRanges("--rw-")枚举当前进程中所有具备 读/写权限rw)的内存段,不包括可执行(x)段。 flag 通常保存在数据段或堆中,而不是代码段。遍历这些内存范围,在每个内存段内扫描是否存在该 hex 字节模式。

​ onMatch:如果匹配到,就将该地址的数据当作 UTF-8 字符串读取,并打印出来。

​ onError:如果扫描出错,输出错误原因。

​ onComplete:扫描该段内存结束后的回调(这里为空)。

​ 用try catch包裹整个扫描过程,防止因某段内存访问异常而中断整个流程。

Patch(Arm64Writer)

Frida CModule

Frida检测与对抗

Frida Stalker Trace

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/968136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

curtime与now函数在MySQL中的区别

CURTIME() 和 NOW() 是 MySQL 中两个常用的日期和时间函数,它们的主要区别在于返回的结果类型和用途。返回结果类型:CURTIME() 函数返回的是时间类型(TIME),格式为 ‘HH:MM:SS’。它只包含一天中的小时、分钟和秒…

Trick 总结

几何最小圆覆盖 \(\to\) 加点,圆半径由两或三个点决定。乱搞由先前答案剪枝,随机打乱枚举顺序大致只有 \(O(\log n)\) 次更新。

2025年最新出炉:车载电源十大品牌性能排行榜,光伏电源/氢能源车载直流转换器/新能源车载直流转换器/高功率密度电源/军用电源产品排行

行业分析:车载电源市场格局与技术创新趋势 随着新能源汽车产业的蓬勃发展,车载电源作为核心零部件的重要性日益凸显。根据最新市场调研数据显示,2025年全球车载电源市场规模预计突破千亿元,技术创新与产品质量成为…

成都恒利泰PIN-to-PIN 国产版 HT-LFCW-5500+

成都恒利泰PIN-to-PIN 国产版 HT-LFCW-5500+1、替代产品型号Mini-Circuits LFCW-5500+ 成都恒利泰出了 PIN-to-PIN 国产版 HT-LFCW-5500+, DC-5.5 GHz,LTCC-4Pin,2.01.25 mm。 抱着“小白鼠”心态申请了两片,今天把…

身份认证状态的存储与传递( Spring Boot篇 )

Spring Boot 登录信息获取机制摘要 Spring Boot 中浏览器获取登录信息的核心是通过身份凭证的存储与验证实现的,主要包括两种主流方案: Session+Cookie方案(经典方式) 服务端创建Session存储用户信息,生成唯一JSE…

国标GB28181算法算力平台EasyGBS打造园区智能化安防监控高效解决方案

一、方案背景 随着城市化进程的加速和企业规模的不断扩大,园区的安防需求变得日益复杂。传统安防监控系统在面对大规模、多区域、多设备的监控需求时,常常面临设备兼容性差、监控效率低、数据管理复杂等问题。为解决…

20232306 2025-2026-1 《网络与系统攻防技术》实验六实验报告

实验内容及要求 本实践目标是掌握metasploit的用法。 指导书参考Rapid7官网的指导教程。 https://docs.rapid7.com/metasploit/metasploitable-2-exploitability-guide/ 下载官方靶机Metasploitable2,完成下面实验内容…

数据库基础(lab5:单表查询 三)

lab5:单表查询(三) 一、对查询结果排序 (ORDER BY) ORDER BY 子句用于对 SELECT 语句返回的结果集进行排序。基本语法作用: 根据一个或多个列对结果进行升序或降序排序。 语法: ORDER BY 字段名 [ASC | DESC];ASC: …

ubuntu18解决 libc.so.6: version `GLIBC_2.28‘ not found

系统版本root@AI-server:/home/ubuntu# cat /etc/os-release NAME="Ubuntu" VERSION="18.04.6 LTS (Bionic Beaver)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 18.04.6 LTS" VERS…

current linux

当然可以!你提到的 + #引号 + current linux + #引号 + 可能是指当前的 Linux 系统,或者你正在使用 Linux 系统进行操作。为了更好地帮助你,我需要更具体地了解你的需求。请告诉我:你是在哪台 Linux 系统上操…

curl linux 命令

curl 是一个用于从远程服务器获取数据的命令行工具,常用于与 HTTP/HTTPS 服务器进行交互。它支持多种协议,包括 HTTP、FTP、SMTP、POP3 等。? 常用 curl 命令示例 1. 下载网页内容 curl https://example.com 2. 下载…

聚焦广州少儿编程机构实力榜TOP5!课程/师资/赛事成果大公开,高性价比品牌全测评

聚焦广州少儿编程机构实力榜TOP5!课程/师资/赛事成果大公开,高性价比品牌全测评随着人工智能时代的到来,编程教育已成为提升孩子核心竞争力的重要途径,广州家长对少儿编程机构的需求也日益增长。优质的编程机构不仅…

20232409 2025-2026-1 《网络与系统攻防技术》实验八实验报告

Web安全 1.实验内容1.1Web前端HTML。能安装启停Apache,编写含表单的HTML网页。1.2Web前端javascipt。能用JS验证表单并回显欢迎信息,测试XSS注入。1.3Web后端数据库。能安装配置MySQL,完成建库建用户建表操作。1.4W…

2025年最受欢迎上门家教老师排行榜,上门家教/一对一家教老师口碑推荐榜

行业背景分析 随着个性化教育需求的持续增长,上门家教市场呈现出专业化、精细化的明显趋势。据最新教育行业数据显示,2025年一对一上门家教市场规模预计较去年增长23.6%,其中优质师资的供需比达到1:8.5,显示出市场…

是时候从 MySQL 转到 PostgreSQL 18 了

是时候从 MySQL 转到 PostgreSQL 18 了2025-11-17 17:06 Zongsoft 阅读(0) 评论(0) 收藏 举报数据库技术革新的浪潮中,PostgreSQL 18 的发布标志着关系型数据库进入了新的时代,它不仅在性能上实现质的飞跃,更为…

小程序客服系统客服软件--如何接入ttkefu

小程序客服系统客服软件--如何接入ttkefu一、需要具备哪些条件呢 企业主体的微信小程序 配置业务域名(需要配置哪个外部链接,就将链接里的域名配置为业务域名、如:https://w100.ttkefu.com/***.html,就把w100.ttkef…

count函数在oracle中的使用场景有哪些

Oracle中的COUNT函数是一个非常有用的聚合函数,它可以用来计算表中行的数量或者某列非空值的数量。以下是COUNT函数在Oracle中的一些常见使用场景:统计记录数:这是COUNT函数最常见的用途。例如,如果你想统计一个表…

Photoshop下载教程(附2025最新版安装步骤与完整图文讲解)

前言: 很多人觉得 Photoshop 是设计师的专属工具,但实际情况远不止于此。只要掌握正确的安装流程和基础操作,即便是零基础用户,也能轻松使用它完成修图、调色、排版等任务。 本篇文章将为你带来一份2025版 Photosh…

makefile入门3 目标自动生成与模式规则

makefile入门3 目标自动生成与模式规则makefile强大之处是目标可以自动生成,这样面对大型项目时可以通过模式规则(pattern)来制定一类文件的通用规则。 假如现在有一个项目有10个c语言文件,我们可以在bash命令行中先…