Frida常见用法

官网 github代码

Frida是什么?

它是原生应用程序的 Greasemonkey,或者,用更专业的术语来说,它是一个动态代码检测工具包。它允许您将 JavaScript 片段或您自己的库注入到 Windows、macOS、GNU/Linux、iOS、Android 和 QNX 上的本机应用程序中。Frida 还为您提供了一些基于 Frida API 构建的简单工具。这些可以按原样使用,根据您的需要进行调整,或者作为如何使用 API 的示例。

环境依赖

推荐 python 3.x brew install python

安装 frida-tools

pip install frida-tools

主语言

Frida 的核心是用 C 语言编写的,并将QuickJS 注入到目标进程中,您的 JS 在执行时可以完全访问内存、挂钩函数,甚至在进程内调用本机函数。有一个双向通信通道,用于在您的应用程序和目标进程内运行的 JS 之间进行通信。

使用 Python 和 JS 可以使用无风险的 API 进行快速开发。Frida 可以帮助您轻松捕获 JS 中的错误,并为您提供异常而不是崩溃。

支持多个语言绑定 例如 Node.js、 Python、 Swift、 .NET、 Qml等。为其他语言和环境构建额外的绑定非常容易.

JavaScript 与 TypeScript 的区别 TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。

什么是TypeSctipt? TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。

Swift版本 其他语言版本

运行模式

注释模式

大部分情况下,我们都是附加到一个已经运行到进程,或者是在程序启动到时候进行劫持,然后再在目标进程中运行我们的代码逻辑。这种方式是Frida最常用的使用方式。注入模式的大致实现思路是这样的,带有GumJS的Frida核心引擎被打包成一个动态连接库,然后把这个动态连接库注入到目标进程中,同时提供了一个双向通信通道,这样控制端就可以和注入的模块进行通信了,在不需要的时候,还可以在目标进程中把这个注入的模块给卸载掉。

嵌入模式

针对没root过的设备Frida提供了一个动态连接库组件 frida-gadget, 可以把这个动态库集成到程序里面来使用Frida的动态执行功能。一旦集成了gadget,就可以和程序使用Frida进行交互。

预加载模式

自动加载Js文件。

主要工具

查看可用的设备列表

frida-ls-devices 用于获取可用的设备列表,在多设备交互的情况下会非常有用

获取设备的进程列表

frida-ps 用于获取进程列表信息

参数 描述
-U 连接到USB设备
-D 如果当前有多台USB设备,可以使用该参数指定设备的UDID(frida-ls-devices 列出的那些 id)
-R/-H 连接到远程 frida-server,主要用于远程调试
-a 仅显示正在运行的应用
-i 显示所有已安装的应用(包括 AppStore安装的应用和系统应用)

举例: 连接到USB设备查看进程列表 frida-ps -U

连接到USB设备查看正在运行的应用 frida-ps -U -a

连接到USB设备查看所有安装的应用 frida-ps -U -a -i

连接到指定的USB设备查看正在运行的应用 frida-ps -D d007dc58edd70caad950ff01b41ebf73cfa49fbe -a

杀死进程

frida-kill 用来结束设备上的指定进程

跟踪函数/方法的调用

frida-trace

参数 描述
-i 包含的C函数
-x 排除某个C函数
-m hook匹配 OC
-M 忽略匹配的OC
-y hook匹配 Swift
-Y 忽略匹配的Swift
-j 跟踪某个java函数
-J 排除某个java函数
-h 其他参考帮助文档
开发中常用的调试方式

跟踪调用栈打印

javascript 复制代码
console.log('\tBacktrace:\n\t' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));

获取目标类

javascript 复制代码
var targetClass = ObjC.classes.PDNetworkAccessManager;

获取目标方法

javascript 复制代码
var targetMethod =
        targetClass[
        "- dataAccessFromNetWithFunctionId:param:finishBlock:cancelBlock:"
        ];

监听目标方法

javascript 复制代码
Interceptor.attach(targetMethod.implementation, {
  onEnter: function (args) {
    var functionId = ObjC.Object(args[2]);
    var param = ObjC.Object(args[3]);
    var finishBlock = new ObjC.Block(args[4]);  
    var cancelBlock = new ObjC.Block(args[5];
  },
  onLeave: function (retval) {
            // 在方法返回后处理返回值
    console.log("Return value: " + ObjC.Object(retval));
  },
});

接受方法参数的OC对象:

javascript 复制代码
var param = ObjC.Object(args[2]);

接受Int类型参数:

javascript 复制代码
var value32 = args[3].toInt32();
console.log("32位整数值: " + value32);

// 示例2:使用 toInt64
var value64 = args[3].toInt64();
console.log("64位整数值: " + value64);

打印对象类型:

javascript 复制代码
console.log('Param类型:' + param.$className);

接受Block参数:

javascript 复制代码
var finishBlock = new ObjC.Block(args[4]);

打印Block结构签名:

javascript 复制代码
console.log('blockTypes:' + finishBlock.types);

创建Block:

javascript 复制代码
var onCompleteBlock = new ObjC.Block({
  retType: 'void',
  argTypes: ['object', 'object'], // 两个对象参数
  implementation: function (dict, error) {
      console.log('Block 被调用');
    }
  });

拦截某个网络请求的Block回调数据:

js 复制代码
var blockList = new Set(); // 避免提前释放,临时存储
var C = ObjC.classes.HTTPClient;
var m = C['- sendRequest:success:fail:'];
Interceptor.attach(m.implementation, {
    onEnter: function (args) {
        var successBlock = new ObjC.Block(args[3]);
        blockList.add(successBlock);
        var blockFunc = successBlock.implementation;
        // 如果不确定有block的参数,可以通过types属性打印block签名
        successBlock.implementation = function (result) {
            console.log('网络请求结果:', result);
            blockFunc(result); // 调用原始 success block
            blockList.delete(successBlock);
        }
    },
});

打印某个进程的基址,基址可以用来推算实际偏址:

sh 复制代码
偏移 = 内存地址 - 模块基址
js 复制代码
 Module.findBaseAddress('企业微信');

通过内存地址找到某个对象:

js 复制代码
ObjC.Object(prt(0xaabbccdd))

修改基本类型参数:

javascript 复制代码
args[2] = ptr(123);

nil赋值:

javascript 复制代码
args[3] = ptr("0x0");

获取某个类的所有方法:

javascript 复制代码
var TestClass = ObjC.classes.TestClass;
console.log('所有方法:' + TestClass.$methods);

调用某个类的类方法:

javascript 复制代码
// 获取类
var TestClass = ObjC.classes.TestClass;
// 获取类的 + topViewController 类方法
var topViewControllerMethod = TestClass['+ topViewController'];
// 调用类方法(有返回值)
var topViewController = topViewControllerMethod.call(TestClass);

调用某个类的实例方法:

javascript 复制代码
// 获取类
var TestClass = ObjC.classes.TestClass;
// 获取类的 - topViewController 实例方法,且有一个参数NSString
var printMethod = TestClass['- print:'];
// 首先初始化获取实例
var object = TestClass.alloc().init();
var urlString = ObjC.classes.NSString.stringWithString_("打印的字符串");
// 调用实例方法(无返回值)
printMethod.call(object, urlString);

打印某个方法的imp地址:

js 复制代码
var method = ObjC.classes.MMPreviewWindowController["- successToParseQRCodeResult:"];
console.log(method.implementation);
// 可以通过此地址进行lldb断点调试 br set -a 0x104b746d6

将Js对象转为Object对象(字典或数组):

js 复制代码
function jsonToObject(jsValue) {
    const NSString = ObjC.classes.NSString;
    const NSJSONSerialization = ObjC.classes.NSJSONSerialization;
    let jsonString;
    if (typeof jsValue === 'string') {
        jsonString = jsValue;
    } else {
        try {
            jsonString = JSON.stringify(jsValue);
        } catch (e) {
            throw new Error('JSON.stringify 失败: ' + e);
        }
    }
    const cStr = Memory.allocUtf8String(String(jsonString)); // 分配并写入 UTF-8
    const nsStr = NSString.stringWithUTF8String_(cStr);
    const data = nsStr.dataUsingEncoding_(4); // 4 = NSUTF8StringEncoding
    const errPtr = Memory.alloc(Process.pointerSize);
    Memory.writePointer(errPtr, ptr(0));
    const objPtr = NSJSONSerialization.JSONObjectWithData_options_error_(data, 0, errPtr);
    const errObjPtr = Memory.readPointer(errPtr);
    if (objPtr.isNull()) {
        if (!errObjPtr.isNull()) {
            const errObj = new ObjC.Object(errObjPtr);
            throw new Error('NSJSONSerialization 解析失败: ' + errObj.toString());
        } else {
            throw new Error('NSJSONSerialization 返回空且没有 NSError');
        }
    }
    return new ObjC.Object(objPtr);
}

ObjC.available: 一个布尔值,指定当前进程是否加载了Objective-C运行时。ObjC除非是这种情况,否则不要调用任何其他属性或方法。

ObjC.api: 将函数名称映射到NativeFunction实例的对象,用于直接访问大部分Objective-C运行时 API。

ObjC.classesObjC.Object :为每个当前注册的类映射类名到 JavaScript 绑定的对象。您可以通过使用点表示法并用下划线替换冒号来与对象交互,即: [NSString stringWithString:@"Hello World"] 变成 const { NSString } = ObjC.classes; NSString.stringWithString_("Hello World");. 注意方法名称后面的下划线。

ObjC.protocols: 一个对象映射协议名称到ObjC.Protocol 每个当前注册协议的 JavaScript 绑定。

ObjC.mainQueue:主线程的GCD队列

ObjC.schedule(queue, work)work: 在 .指定的 GCD 队列上调度 JavaScript 函数queue。AnNSAutoreleasePool在调用之前创建work,并在返回时清理。

通过IP连接Frida调试

frida 默认的frida-service启动地址为 127.0.0.1,所以直接通过 frida -H 连接上无法访问的。 1.ssh 登录越狱设备后,重新启动一个frida-server服务,如:frida-server -l 0.0.0.0:12345,然后通过 frida -H <ip:12345>连接。 2.直接Frida启动文件,添加 -l 参数,路径在 /var/jb/Library/LaunchDaemons/Library/LaunchDaemons,启动文件名称为:re.frida.server.plist.

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>re.frida.server</string>
	<key>Program</key>
	<string>/var/jb/usr/sbin/frida-server</string>
	<key>ProgramArguments</key>
	<array>
		<string>/var/jb/usr/sbin/frida-server</string>
	</array>
	<key>UserName</key>
	<string>root</string>
	<key>POSIXSpawnType</key>
	<string>Interactive</string>
	<key>RunAtLoad</key>
	<true/>
	<key>KeepAlive</key>
	<true/>
	<key>ThrottleInterval</key>
	<integer>5</integer>
	<key>ExecuteAllowed</key>
	<true/>
</dict>
</plist>

记得使用root用户修改,切换root用户可以使用 sudo -s.

Frida JS代码给Photon开放接口

在Js代码中可以将方法报漏给Python调用:

js 复制代码
/// 通过短链接打开小程序
function shortLinkOpenMiniProgram(shortLink) {
    if (ObjC.available) {
        try {
            var pathUrl = shortLink;
            var MMMessageUiUtils = ObjC.classes.MMMessageUiUtils;
            var innerBrowser = ObjC.classes.NSString.stringWithString_(pathUrl);
            var forceWebview = 0;
            var withMessage = ptr("0x0");
            MMMessageUiUtils['+ openInnerBrowser:forceWebview:withMessage:'](innerBrowser, forceWebview, withMessage);
        } catch (e) {
            console.log('[!] 模拟调用异常:', e);
        }
    } else {
        console.log('[!] Objective-C 运行时不可用');
    }
}

// 对外开放的方法,python调用
rpc.exports = {
    shortLinkOpenMiniProgram: shortLinkOpenMiniProgram,
};

在Python中调用:

py 复制代码
    # 读取JS脚本,假设上面的路径为 JS_SCRIPT_PATH
    with open(JS_SCRIPT_PATH, 'r', encoding='utf-8') as f:
        js_code = f.read()
    pid = get_wechat_pid()
    session = frida.attach(pid)
    script = session.create_script(js_code)
    script.load()
    script.exports_sync.short_link_open_mini_program('#小程序://某小程序/VcwOqlN49ynLeRn')

Frida17版本大变革

变动描述

从 Frida 17.0.0 开始,在Python中调用Frida脚本代码时候,ObjC 对象不再作为全局变量自动可用,需要显式导入 frida-objc-bridge,否则报错ObjC is not defined,参考:github.com/frida/frida...

Frida 在命令行直接调试是不影响的,主要是Python调用的代码,需要打包。

解决方案
1. 使用 frida-compile(推荐)

这是新版本 Frida 的标准做法:

bash 复制代码
# 安装必要的依赖
npm install --save-dev frida-compile
npm install frida-objc-bridge
npm install --save-dev @types/frida-gum

##### 或者直接运行
npx frida-compile -S -c calendar_price.ts -o _agent.js
2. 在 Python 中使用
python 复制代码
import frida

#### 读取编译后的脚本
with open('_agent.js', 'r') as f:
    script_content = f.read()

#### 连接设备和应用
device = frida.get_usb_device()
session = device.attach("应用名")  # 或使用 bundle ID

#### 创建和加载脚本
script = session.create_script(script_content)
script.load()

#### 调用函数
script.exports.simulateCalendarPriceRequest(1397051404)
文件说明
  • calendar_price.ts - TypeScript 源码
  • _agent.js - 编译后的 JavaScript 文件(用于 Frida)
  • package.json - 包含编译脚本的配置

注意事项

  1. 新版本 Frida 需要显式导入所需的 bridge
  2. 编译步骤是必须的,Python里不能直接运行 TypeScript 文件
  3. 如果遇到其他 bridge(如 Swift bridge)的问题,可能需要额外处理

Demo

Javascript

Frida拦截微信撤回Demo

Python

直接见LinXunFeng大牛写好的代码:Github

更多接口文档

frida.re/docs/javasc...

注意

Mac测试需要关闭安全模式 iPhone使用Frida一般需要越狱,如果不越狱使用,可以参考文档

END

相关推荐
肥晨2 小时前
前端私有化变量还只会加前缀嘛?保姆级教程教你4种私有化变量方法
前端·javascript
小高0072 小时前
前端 Class 不是花架子!3 个大厂常用场景,告诉你它有多实用
前端·javascript·面试
Juchecar2 小时前
Pandas技巧:利用 category 类型节省内存
python
没有鸡汤吃不下饭3 小时前
前端【数据类型】 No.1 Javascript的数据类型与区别
前端·javascript·面试
码流之上4 小时前
【一看就会一写就废 指间算法】设计电子表格 —— 哈希表、字符串处理
javascript·算法
跟橙姐学代码4 小时前
Python时间处理秘籍:别再让日期时间卡住你的代码了!
前端·python·ipython
mortimer5 小时前
Python 文件上传:一个简单却易犯的错误及解决方案
人工智能·python
Juchecar6 小时前
NumPy编程:鼓励避免 for 循环
python
Asort6 小时前
JavaScript 从零开始(七):函数编程入门——从定义到可重用代码的完整指南
前端·javascript