官网 github代码
Frida是什么? 它是原生应用程序的 Greasemonkey,或者,用更专业的术语来说,它是一个动态代码检测工具包。它允许您将 JavaScript 片段或您自己的库注入到 Windows、macOS、GNU/Linux、iOS、Android 和 QNX 上的本机应用程序中。Frida 还为您提供了一些基于 Frida API 构建的简单工具。这些可以按原样使用,根据您的需要进行调整,或者作为如何使用 API 的示例。
环境依赖 推荐 python 3.xbrew install python
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
-j
跟踪某个java函数
-J
排除某个java函数
-h
其他参考帮助文档
开发中常用的调试方式 跟踪调用栈打印 :
1 console .log ('\tBacktrace:\n\t' + Thread .backtrace (this .context , Backtracer .ACCURATE ).map (DebugSymbol .fromAddress ).join ('\n\t' ));
获取目标类
1 var targetClass = ObjC .classes .PDNetworkAccessManager ;
获取目标方法
1 2 3 4 var targetMethod = targetClass[ "- dataAccessFromNetWithFunctionId:param:finishBlock:cancelBlock:" ];
监听目标方法
1 2 3 4 5 6 7 8 9 10 11 12 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对象:
1 var param = ObjC .Object (args[2 ]);
接受Int类型参数:
1 2 3 4 5 6 var value32 = args[3 ].toInt32 ();console .log ("32位整数值: " + value32);var value64 = args[3 ].toInt64 ();console .log ("64位整数值: " + value64);
打印对象类型:
1 console .log ('Param类型:' + param.$className );
接受Block参数:
1 var finishBlock = new ObjC .Block (args[4 ]);
打印Block结构签名:
1 console .log ('blockTypes:' + finishBlock.types );
创建Block:
1 2 3 4 5 6 7 var onCompleteBlock = new ObjC .Block ({ retType : 'void' , argTypes : ['object' , 'object' ], implementation : function (dict, error ) { console .log ('Block 被调用' ); } });
拦截某个网络请求的Block回调数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 ; successBlock.implementation = function (result ) { console .log ('网络请求结果:' , result); blockFunc (result); blockList.delete (successBlock); } }, });
打印某个进程的基址,基址可以用来推算实际偏址:
1 Module .findBaseAddress ('企业微信' );
通过内存地址找到某个对象:
1 ObjC .Object (prt (0xaabbccdd ))
修改基本类型参数:
nil赋值:
获取某个类的所有方法:
1 2 var TestClass = ObjC .classes .TestClass ;console .log ('所有方法:' + TestClass .$methods );
调用某个类的类方法:
1 2 3 4 5 6 var TestClass = ObjC .classes .TestClass ;var topViewControllerMethod = TestClass ['+ topViewController' ];var topViewController = topViewControllerMethod.call (TestClass );
调用某个类的实例方法:
1 2 3 4 5 6 7 8 9 var TestClass = ObjC .classes .TestClass ;var printMethod = TestClass ['- print:' ];var object = TestClass .alloc ().init ();var urlString = ObjC .classes .NSString .stringWithString_ ("打印的字符串" );printMethod.call (object, urlString);
打印某个方法的imp地址:
1 2 3 var method = ObjC .classes .MMPreviewWindowController ["- successToParseQRCodeResult:" ];console .log (method.implementation );
将Js对象转为Object对象(字典或数组):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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)); const nsStr = NSString .stringWithUTF8String_ (cStr); const data = nsStr.dataUsingEncoding_ (4 ); const errPtr = Memory .alloc (Process .pointerSize ); Memory .writePointer (errPtr, ptr (0 )); const objPtr = NSJSONSerialization .JSON ObjectWithData_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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 运行时不可用' ); } } rpc.exports = { shortLinkOpenMiniProgram : shortLinkOpenMiniProgram, };
在Python中调用:
1 2 3 4 5 6 7 8 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' )
Demo Javascript Frida拦截微信撤回Demo
Python 直接见LinXunFeng大牛写好的代码:Github
更多接口文档 https://frida.re/docs/javascript-api/
注意 Mac测试需要关闭安全模式 iPhone使用Frida一般需要越狱,如果不越狱使用,可以参考文档
END