pip install frida-tools)。⚠️ 非越狱设备无法直接注入所有系统 App,只能调试用户安装的 App。
MonkeyDev 是 iOS 动态库注入 + 开发工具链,主要用于:
Frida 是 动态 hook 工具,可远程注入 JS 脚本 hook App 函数。
两者结合的核心逻辑:
用 MonkeyDev 注入自定义动态库
在 dylib 中初始化 Frida Gadget
在 Mac 上连接设备并使用 Frida
frida-ps -U # 查看设备上运行的 Appfrida -U -n AppName -l your_script.js-U 表示连接 USB 设备-n 是 App 名称-l 是你的 Frida JS 脚本frida-ps -U 和 frida -U -n AppName 附加,执行 JS 脚本 hook 逻辑。1、日志窗口出现如下信息,没有其他后续日志以及APP不显示UI页面
Frida: Listening on 127.0.0.1 TCP port 27042原因:
Gadget 在 constructor 里启动后,会监听 127.0.0.1:27042,然后 阻塞进程继续启动,直到 Frida 客户端 attach 或显式 resume。
解决方式是加一个 FridaGadget.config,把启动行为改成不等待:
{"interaction": {"type": "listen","address": "127.0.0.1","port": 27042,"on_port_conflict": "fail","on_load": "resume" }}注意配置文件名必须和最终 dylib 名匹配:你最终包里是 Frameworks/FridaGadget.dylib,所以配置文件应叫 FridaGadget.config,放在 Frameworks 旁边或 App bundle 根目录都可以,Frida 文档也特别提到 iOS/Xcode 场景会查找这两个位置。
1. 放文件
在工程根目录建一个目录,例如:
dylib/ frida-gadget.dylib FridaGadget.config FridaGadget.config 内容建议先用不阻塞启动的版本:
{"interaction": {"type": "listen","address": "127.0.0.1","port": 27042,"on_port_conflict": "fail","on_load": "resume" }}2. 确认 dylib install name
otool -D dylib/frida-gadget.dylib 最好显示:
@executable_path/Frameworks/FridaGadget.dylib 如果不是,改一下:
install_name_tool -id @executable_path/Frameworks/FridaGadget.dylib dylib/frida-gadget.dylib 3. 在 Xcode 里链接 FridaGadget.dylib
选中 MonkeyDev 的注入 dylib target,比如 xxxDylib:
Build Phases -> Link Binary With Libraries -> + -> Add Other...
选择:
dylib/frida-gadget.dylib 然后建议在 Xcode 中把显示名改成或确认最终依赖路径是:
@executable_path/Frameworks/FridaGadget.dylib 最终效果用这个确认:
otool -L path/to/libYourMonkeyDevDylib.dylib 里面应有:
@executable_path/Frameworks/FridaGadget.dylib 4. 把 Gadget 和 config 复制进 App
选中 App target,也就是 MonkeyDev 生成的宿主 target,不是 xxxDylib target。
Build Phases -> + -> New Run Script Phase
放在 MonkeyDev 最终 codesign 脚本之前,脚本内容:
set -eGADGET_SRC="${SRCROOT}/dylib/frida-gadget.dylib"CONFIG_SRC="${SRCROOT}/dylib/FridaGadget.config"GADGET_DIR="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"GADGET_DST="${GADGET_DIR}/FridaGadget.dylib"CONFIG_DST="${GADGET_DIR}/FridaGadget.config"mkdir -p "$GADGET_DIR"cp -f "$GADGET_SRC""$GADGET_DST"cp -f "$CONFIG_SRC""$CONFIG_DST"echo"Frida Gadget copied to $GADGET_DST"echo"Frida Gadget config copied to $CONFIG_DST"5. 加初始化日志
在 MonkeyDev 注入 dylib 的 constructor 里检查 dyld image,类似:
#import <mach-o/dyld.h>#include<string.h>staticvoid LogFridaGadgetStatus(void) { uint32_t count = _dyld_image_count();for (uint32_t i = 0; i < count; i++) {constchar *name = _dyld_get_image_name(i);if (name && strstr(name, "FridaGadget.dylib")) {NSLog(@"[FridaGadget] loaded: %s", name);return; } }NSLog(@"[FridaGadget] not loaded");}然后在 CHConstructor 或 %ctor 里调用:
LogFridaGadgetStatus(); 关键点就三个:xxxDylib target 负责链接 Gadget,App target 负责复制 FridaGadget.dylib + FridaGadget.config 到 Frameworks,最终签名阶段必须在复制之后执行。
查找app相关信息
frida-ps -Uai 看到对应App 后,用显示出来的进程名或 bundle id。比如 bundle id 方式通常更稳:
frida -U -f com.demo.test -l fridajs/disable-flutter-tls.js 现在是 Xcode 已经启动 App 的场景,所以优先用 attach:
frida -U -n "进程名" -l fridajs/disable-flutter-tls.js 进入 Frida 交互后,看到类似这些日志就说明脚本跑起来了:
____ / _ | Frida 17.9.7 - A world-class dynamic instrumentation toolkit | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit