FCAnd
GitHub - deathmemory/FridaContainer: FridaContainer 整合了网上流行的和自己编写的常用的 frida 脚本。 frida 脚本模块化,Java & Jni Trace。
FRIDA 使用经验交流分享-看雪
使用
首先编辑index.ts,这个文件在 npm run build 后会生成编译的_fcagent.js
然后frida注入_fcagent.js就行。
# after edit index.ts
npm run watch # 自动编译
# 或者
npm run build # 手动编译
frida -U -f com.example.android --no-pause -l _fcagent.js
关于环境构建
npm
查看当前项目已安装包(项目跟目录必须有 package.json 文件)并把所有包的依赖显示出来。
$ npm ls --depth 0
@dmemory/fridacontainer@1.0.10 ...\FridaContainer-master
├── @types/base64-js@1.3.0
├── @types/frida-gum@18.3.1
├── @types/node@18.19.87
├── base64-ts@2.0.1
├── frida-compile@10.2.5
└── glob@11.0.2
如果构建成功了,会在当前项目下生成该环境,包括frida-compile等。
在配置过程中遇到以下问题:IDE对frida-gum库的类型定义文件(index.d.ts)的索引依赖于工作区是否显式打开该文件,关闭后索引失效。
解决方法:
将 @types/frida-gum 添加到 tsconfig.json 的 types 数组中:
{
"compilerOptions": {
// ... existing code ...
"types": ["frida-gum","base64-js","node"],
// ... existing code ...
},
"include": [
"utils",
"index.ts"
],
// ... existing code ...
}
ts
在原文中的评论区也有提到过,使用typescript写脚本需要被frida-compile编译过后才能使用的,需要使用npm run build编译,编译之后会生成一个新的_fcagent.js脚本。
总结:开发时使用的是ts,编译ts后生成js,注入时使用的是js。
npm run build # 编译脚本
frida -U -f com.example.android --no-pause -l _fcagent.js # 注入脚本
假如我不想每次修改完ts脚本后都重新使用npm run build,那么我们可以使用npm run watch达到实时编译的效果。
效果图如下,也就是保存一次就会自动compile一次,期间不要关闭这个终端和终止这个watch就行。

为什么使用命令行注入而不是使用python脚本注入呢?
DMemory大佬的解释是:
我是用 frida 命令行注入脚本的,python 非必要的情况下一般不用。在控制台直接调用 rpc.exports 就行。
比如,在 ts 里这样赋值
import {DMLog} from "../utils/dmlog";
import {FCAnd} from "../utils/FCAnd";
import {FCCommon} from "../utils/FCCommon";
rpc.exports = {
main() {
console.log("rpc inject");
// 接下来就可以使用FCAnd的模块了
}
}
然后直接在控制台调用:
rpc.exports.main()
这样做的好处是可以在修改完ts脚本后,js就自动编译(因为启动了npm run watch),在命令行里就能实时注入,不用说需要Ctrl+C终止frida -U -f后再重新注入js脚本。
如果使用的是python注入,就需要每次重新启动 python 脚本,才能使用在 typescript 中新写的函数。
例如说:
ts中:

自动编译的js脚本:

那么我们直接使用js脚本就可以了,里面已经包含了编译的库代码了,该打包的都封装好了,方便了我们编写ts。
agent
在index.ts中,会看到有一行导入被注释掉了:
import {DianPing} from "./agent/dp/dp";
这一行是我们的业务脚本存放的地方,在项目根目录创建 agent 目录,在这里开发业务脚本。
例如说有个app叫DianPing,我们就可以取名这个模块为DianPing,并开发自己的业务脚本。
模块介绍
对于初学者只需要学会使用几个常见的模块就行
FridaContainer/docs/android.md
可以通过vscode的大纲快速浏览文件的函数
堆栈内存模块
堆栈跟踪
// 获取当前堆栈信息
FCAnd.getStacks();
// 打印当前堆栈信息
FCAnd.showStacks();
堆栈信息显示
// 打印指定层数的堆栈信息,并显示相关模块信息
FCCommon.showStacksModInfo(context, 10);
这个函数会从当前堆栈指针开始,打印指定数量的堆栈内容,并尝试解析每个地址所属的模块信息。
获取 LR 寄存器
// 获取 LR 寄存器值(链接寄存器,存储函数返回地址)
let lr = FCCommon.getLR(context);
该函数支持 ARM 和 ARM64 架构,会根据当前架构自动获取正确的 LR 寄存器值。
模块操作
模块信息获取
// 根据内存地址获取所属模块信息
let moduleInfo = FCCommon.getModuleByAddr(address);
// 打印所有已加载模块信息
FCCommon.printModules();
// 显示所有已加载模块信息
FCAnd.showModules();
模块转储
// 转储指定模块到文件
FCCommon.dump_module("libc.so", "/data/local/tmp/");
// 将内存区域转储到文件
FCCommon.dump2file(address, size, "/data/local/tmp/memory_dump.bin");
dump_module 函数会自动计算模块大小,并将整个模块内容保存到指定目录。如果在 Android 环境下,建议保存到应用的私有目录(如 /data/data/com.package.name/)以避免权限问题。
内存操作
// 向指定内存地址写入字符串内容。
FCAnd.writeMemory(addr, "new data");
// 在指定内存范围内搜索匹配的模式,并将其替换为指定的数据。
FCAnd.replaceMemoryData()
DEX 转储
// 通用 DEX 转储方法
FCAnd.dump_dex_common();
// 使用 loadClass 方式转储 DEX
// 通过 `loadClass` 的方式实现 Dex 文件的 Dump。该方法会加载所有类,并在加载过程中拦截 `DefineClass` 方法以获取 Dex 文件的内存地址和大小,最终将 Dex 文件保存到指定路径。
FCAnd.dump_dex_loadAllClass();
// 当程序启动完成后,调用 rpc.exports.ddc() 完成转储
上下文获取
// 获取应用上下文
let context = FCAnd.getApplicationContext();
Hook Trace
URI/URL Hook
// Hook android.net.Uri.parse 方法,参数为是否显示堆栈
FCAnd.hook_uri(true);
// Hook java.net.URL 构造函数,参数为是否显示堆栈
FCAnd.hook_url(true);
JSON Hook
// Hook `org.json.JSONObject.getString` 方法,监控其调用。当该方法被调用且键值为指定的 `pKey` 时,会打印键值,并且可以打印堆栈信息。
FCAnd.hook_JSONObject_getString("auth_token");
// Hook `com.alibaba.fastjson.JSONObject` 的多个方法(如 `getString`、`getJSONArray`、`getJSONObject`、`getInteger`)。当这些方法被调用且键值为指定的 `pKey` 时,会打印键值,并且可以打印堆栈信息。
FCAnd.hook_fastJson("user_info");
Map Hook
// Hook Map.put 和 LinkedHashMap.put 方法
// 第一个参数为要监控的 key,第二个参数为是否精确匹配
FCAnd.hook_Map("password", true);
打印 Map
// 打印 HashMap 内容
FCAnd.printHashMap(hashMapObject);
日志log Hook
// Hook android.util.Log 的所有日志方法
FCAnd.hook_log();
跟踪 Native 加载
// 跟踪所有动态库加载
// 监控 `dlopen` 函数的调用,打印加载的动态库路径。用于追踪动态库的加载过程。
FCAnd.traceLoadlibrary();
// 跟踪所有 fopen 调用
FCAnd.traceFopen();
// 当指定的共享库(`.so` 文件)加载时,附加到指定的地址并执行回调函数。
FCAnd.attachWhenSoLoad();
// 监控动态库的加载过程,当加载指定的动态库时,执行回调函数。
FCAnd.whenSoLoad();
send_recv Hook
// 监控 `send` 和 `recv` 系统调用,打印发送和接收的数据内容及堆栈信息。
FCAnd.hook_send_recv();
svc Hook
// 监控指定的 SVC 地址列表,当这些地址被调用时,打印堆栈信息。
FCAnd.watch_svc_address_list();
方法追踪
Java方法 追踪
// 使用默认配置追踪 Java 方法
FCAnd.traceJavaMethods();
// 自定义追踪配置
/**
* java 方法追踪
* @param clazzes 要追踪类数组 ['M:Base64', 'E:java.lang.String']
* 类前面的 M 代表 match 模糊匹配,E 代表 equal 精确匹配
*
* @param whitelist 指定某类方法 Hook 细则,可按白名单或黑名单过滤方法。
* { '类名': {white: true, methods: ['toString', 'getBytes']} }
*
* @stackFilter 按匹配字串打印堆栈。如果要匹配 bytes 数组需要十进制无空格字串,例如:"104,113,-105"
*/
FCAnd.traceJavaMethods(
['M:Base64', 'E:java.lang.String'], // 要追踪的类
{'java.lang.String': {white: true, methods: ['substring', 'getBytes']}}, // 方法白名单
"password" // 堆栈过滤字符串
);
// traceArtMethods 是 traceJavaMethods 的别名
FCAnd.traceArtMethods(['M:retrofit2']);
通过 python/android/traceLogCleaner.py 脚本收集 trace 日志,可以按线程、格式化输出日志
格式化 trace 效果

- 注:如果 java trace 出现崩溃可以尝试调用纯净模式
FCAnd.traceJavaMethods_custom,这里没有默认 trace 的类FCAnd.tjm_default_cls和默认单类白名单FCAnd.tjm_default_white_detail,需要自己手动附加,可以减少默认 trace 的类来判断崩溃的原因。若还有崩溃,请提交 issue 。
FCAnd.traceJavaMethods_custom(['E:java.net.URI'],
{'java.net.URI': {white: true, methods: ['$init']}},
"match_str_show_stacks");
JNI 跟踪
本功能是 jnitrace 的一个简化和嵌入版。
通过 FCAnd.jni 命名空间访问:
// 简单跟踪所有 JNI 函数
FCAnd.jni.traceAllJNISimply();
// 跟踪特定 JNI 函数
FCAnd.jni.traceJNI(['CallStaticObjectMethod', 'CallObjectMethod']);
FCAnd.jni.hookJNI('NewStringUTF', {
onEnter: function (args) {
var str = args[1].readCString();
DMLog.i('NewStringUTF', 'str: ' + str);
if (null != str) {
if (str == 'mesh' || str.startsWith('6962')) {
var lr = FCAnd.getLR(this.context);
DMLog.i('NewStringUTF', '(' + Process.arch + ')lr: ' + lr
+ ', foundso:' + FCAnd.getModuleByAddr(lr) );
// FCCommon.getStacksModInfo(this.context, 100);
}
}
}
});
通过 python/android/traceLogCleaner.py 脚本收集 trace 日志,可以按线程、格式化输出日志
输出样例:

Native方法 追踪
FCAnd.jni.hook_registNatives();
Stalker 跟踪
// 使用 Frida Stalker 跟踪指定模块中的函数执行
FCCommon.stalkerTrace("libssl.so", functionAddress);
stalkerTrace 函数使用 Frida 的 Stalker API 跟踪指定函数的执行流程,记录指令执行和寄存器变化,对于分析复杂算法和加密函数非常有用。
注意:由于函数内使用了 Stalker.exclude,每次使用后建议重启进程,否则可能会出现段错误或访问错误。
寄存器变化跟踪
用于跟踪寄存器值的变化,通常与 stalkerTrace 配合使用。
// 获取上下文中变化的寄存器值
let changedRegs = FCCommon.get_diff_regs(context, previousRegs);
工具函数
字符串与数组处理
// 将 JS 对象转换为 Java 字符串
let javaStr = FCAnd.newString("Hello World");
// 将 Java 字节数组打印为十六进制字符串
let hexStr = FCAnd.printByteArray(byteArray);
//将字节数组以十六进制格式打印,类似于 `hexdump` 工具的输出格式。
FCAnd.byteshexdump();
// 将字符串转换为十六进制字符串
let hexStr = FCCommon.str2hexstr("Hello"); // 输出: "48656c6c6f"
// 将字符串转换为十进制数组(数组的保存格式为十进制)
let decArray = FCCommon.str2hexArray("ABC"); // 输出: [65, 66, 67]
// 将 ArrayBuffer 转换为十六进制字符串(带空格分隔)
let hexWithSpace = FCCommon.arrayBuffer2Hex(buffer); // 输出: "48 65 6c 6c 6f"
C++ 标准字符串处理
// 创建新的 C++ 标准字符串对象
let stdString = FCCommon.newStdString();
这个函数返回一个 StdString 对象,用于处理 C++ 标准字符串。
签名转换
// 将 C++ 的符号名称(如 `_Z4hahaii`)转换为可读的函数签名(如 `haha(int, int)`)。
FCAnd.prettyMethod_C();
// 将 JNI 方法 ID 转换为可读的函数签名。
FCAnd.prettyMethod_Jni();
复制文件
// 复制文件
FCCommon.copyFile("/data/local/tmp/source.bin", "/data/data/com.example.app/files/dest.bin");
gson.toJson
增加 gson 库,可使用 gson.toJson 等功能
将 Java 对象转化成 Json,仓库已经集成了 gson 库,即使 APP 没有内置 gson 也可以使用。
当 gson 功能遇到瓶颈崩溃时,会用自实现的方法做兜底转换。
// 将 Java 对象转换为 JSON 字符串。如果无法直接转换,会尝试通过反射解析对象的字段。
FCAnd.toJSONString(javaObject);
// 通过反射解析 Java 对象的字段,并将其转换为 JSON 字符串。
FCAnd.parseObject(javaObject);
// 注册 Gson 库,用于 JSON 序列化和反序列化。如果 Gson 未加载,会尝试加载。
FCAnd.registGson();
反调试
通过 FCAnd.anti 命名空间访问:
普通反调试
// 绕过调试检测
FCAnd.anti.anti_debug();
// 绕过 ptrace 检测
FCAnd.anti.anti_ptrace();
// 绕过 fgets 检测
FCAnd.anti.anti_fgets();
抓包反调试
// SSL 证书固定绕过,相当于 frida 版的 JustTrustMe
FCAnd.anti.anti_sslPinning("/data/local/tmp/cert-der.crt");
// SSL 证书验证绕过
FCAnd.anti.anti_ssl_unpinning();
// Cronet 库 SSL 证书验证绕过 (32位)
FCAnd.anti.anti_ssl_cronet_32();
load 自定义 ssl 证书
将证书 cert-der.crt 传到手机,然后调用下面的语句
FCAnd.anti.anti_sslLoadCert("/data/local/tmp/cert-der.crt");
类加载器
frida multi dex hook(java use)
目前支持通过 DexClassLoader | InMemoryDexClassLoader | BaseDexClassLoader | LoadClass 动态加载的 Dex
// 通过 `dalvik.system.DexClassLoader` 加载指定的类,并在加载后执行回调函数。
FCAnd.useWithDexClassLoader('com.cls.name', function (cls: Wrapper) {
DMLog.i('tag', JSON.stringify(cls));
});
// 通过 `dalvik.system.InMemoryDexClassLoader` 加载指定的类,并在加载后执行回调函数。
FCAnd.useWithInMemoryDexClassLoader('com.cls.name', function (cls: Wrapper) {
DMLog.i('tag', JSON.stringify(cls));
});
// 通过 `dalvik.system.BaseDexClassLoader` 加载指定的类,并在加载后执行回调函数。
FCAnd.useWithBaseDexClassLoader('com.cls.name', function (cls: Wrapper) {
DMLog.i('tag', JSON.stringify(cls));
});
// 监控 `ClassLoader.loadClass` 方法,当加载指定类时执行回调函数。
FCAnd.useWhenLoadClass('com.cls.name', function (cls: Wrapper) {
DMLog.i('tag', JSON.stringify(cls));
});
动态加载 dex
在利用 InMemoryDexClassLoader 加载内存 Dex 找不到类的情况下适用。
FCAnd.anti.anti_InMemoryDexClassLoader(function(){
const cls = Java.use("find/same/multi/dex/class");
// ...
});
查找指定类
// 通过多种方式(如 `ClassLoader` 和 `DexClassLoader`)查找指定的类,并打印查找结果。
FCAnd.findClass();
枚举 ClassLoader
// 枚举当前进程的所有 `ClassLoader`,查找指定的类并执行回调函数。
FCAnd.enumerateClassLoadersAndUse();
// 枚举当前进程的所有 `ClassLoader`,并为每个 `ClassLoader` 获取一个 `Java.ClassFactory`,用于后续操作。
FCAnd.enumerateClassLoadersAndGetFactory();
DMLog 模块功能使用介绍
DMLog 是一个用于 Frida 脚本中的日志记录工具类,提供了多种级别的日志输出功能,支持在 Android 和 iOS 环境下使用。
主要特性
- 支持多种日志级别(DEBUG、INFO、WARN、ERROR)
- 自动获取并显示线程信息(在 Java 环境下)
- 输出格式化的日志,包含时间戳、进程ID、线程名称、线程ID和标签
- 提供 send 方法用于向 Frida 客户端发送消息
类属性
static bDebug: boolean = true- 控制是否输出 DEBUG 级别的日志,默认为 true
日志输出方法
-
d(tag: string, str: string) - 输出 DEBUG 级别日志
// 使用示例 DMLog.d('TAG', '这是一条调试信息'); -
i(tag: string, str: string) - 输出 INFO 级别日志
// 使用示例 DMLog.i('TAG', '这是一条信息日志'); -
w(tag: string, str: string) - 输出 WARN 级别日志
// 使用示例 DMLog.w('TAG', '这是一条警告信息'); -
e(tag: string, str: string) - 输出 ERROR 级别日志
// 使用示例 DMLog.e('TAG', '这是一条错误信息');
内部方法
-
log_(logfunc, leval, tag, str) - 内部使用的日志格式化和输出方法
- 参数说明:
logfunc: 日志输出函数(如 console.log, console.error 等)leval: 日志级别字符串tag: 日志标签str: 日志内容
该方法会自动添加以下信息到日志中:
- 日志级别
[DEBUG/INFO/WARN/ERROR] - 时间戳
[yyyy/MM/dd HH:mm:ss] - 进程ID
[PID:xxx] - 线程名称(Java环境下)
[ThreadName] - 线程ID
[ThreadID] - 标签
[TAG]
- 参数说明:
消息发送方法
-
send(tag: string, content: string) - 向 Frida 客户端发送消息
// 使用示例 DMLog.send('TAG', '这是发送给客户端的消息');发送的消息格式为 JSON 字符串:
{ "tid": 线程ID, "status": "msg", "tag": "标签", "content": "内容" }

浙公网安备 33010602011771号