重新编译 FridaGadget 使其更好地用于非越狱机的代码调试和自动化测试

2,921 阅读5分钟

背景

Frida 在设计之初主要是用于应用的逆向工程,使用方法一般是在越狱环境下动态注入,在非越狱环境下使用 FridaGadget 时,由于其写死了 LC_ID_DYLIBInstall Name@rpath/Framework/FridaGadget.dylib,导致直接使用时必须在工程中创建一个名为 Framework 的实体目录。对于正向开发而言,需要对这类动态库进行环境控制,即仅在 Debug 模式下引入,一般方法为将动态库打成 Pod,然后基于 CocoaPods 的语法去做环境控制。

对于常规的 Cocoa Touch Dynamic Framework,其 Install Name 一般为 @rpath/SomeLib.framework/SomeLib 的形式,显然 FridaGadget 的 Install Name 与其不符,为了将 FridaGadget 打成 Pod,我们不得不将其封装成 Framework,以享受 CocoaPods 的自动签名功能,这就要求修改 FridaGadget 的 Install Name 为正确格式,并将其包装成标准的 Framework 格式。

其次,FridaGadget 为了保证对启动流程的控制,默认阻塞了进程,这会导致应用必须在 Frida Console Attach 后才会继续运行,这很适用于逆向工程,但对于正向开发者和测试者十分不利,更为麻烦的是,FridaGadget.config 似乎在非越狱环境下不生效,从而导致我们无法修改 FridaGadget 的默认行为,这个问题似乎只有修改源码并重新编译才能解决。

封装 Framework

修改 Install Name

对于包装成 Framework 的问题,其实不必重新编译 FridaGadget,只需要用 MachOView 打开动态库文件,找到每个架构的 LC_ID_DYLIB 并修改 Name 字段:

这里的注意点是,Name 的存储方法比较巧妙,是将其直接存储在 LC_ID_DYLIB 这一条 Load Command 的尾部,通过第三个字段 String Offset 指定基于当前 Load Command 的偏移量,这一条指令的总长度为 72,字符串从第 24 个字节开始存储,因此字符串的总长度不能超过 47 (需要额外一位来存储\0),MachOView 提供了直接修改字符串值的方法,双击 Name 的 Data 行即可修改,图中所示的就是修改后的值,字符串和二进制互转可以使用这个网站

创建 Framework

一个 Framework 需要包含 dylib、Info.plist 和 modulemap 等信息,否则 Xcode 在编译时会报错(特别是缺少 Info.plist),这里有个取巧的方案是创建一个名为 FridaGadget 的 Cocoa Touch Framework 工程,Build 后将二进制替换为真正的 FridaGadget,即完成了 FridaGadget.framework 的包装。

这里注意一个签名问题,FridaGadget.dylib需要先用自己的开发者账号重新签名一次,否则

打成 Pod 库

如果直接使用动态库,需要在 Xcode 中手动配置一个 Copy File 的 Build Phase 来实现对动态库的签名和拷贝,为了将这个过程自动化,我们可以将 FridaGadget.framework 打成 Pod 库,一方面 Pod 会为我们自动生成工程配置,另一方面也可以基于 Podfile 的 configurations 实现环境控制。

CocoaPods 包含动态库的方式很简单,只需要声明一个 vendored_frameworks

s.vendored_frameworks = "FridaGadget.framework"

对于声明了 vendored_frameworks 的 Pod 库,CocoaPods 会自动为其创建 Pods-ProjectName-frameworks.sh 的脚本,并添加一个 Embed Pods Framework 的 Build Phase 来完成动态库的签名,这里有个坑我们需要先手动重签名依次 FridaGadget.dylib,否则可能会导致 Pods 脚本中签名失败,重签名的方法可参考 Frida Doc

随后,将 Pod 库部署到企业私有环境或是公库下,即可通过 Podfile 直接引入 FridaGadget:

# 注意我还没有将其上传到公库,因此你无法直接这么引入
pod 'FridaGadget', :configurations => ['Debug']

问题

这种方式完美解决了 FridaGadget 的环境控制问题,但是启动时自动阻塞进程不是我们想要的(毕竟是正向开发环境,只是偶尔使用 Frida 来调试和自动化测试),这个问题在于 Frida 默认采用了阻塞方式,要修改可以通过在工程中创建一个 FridaGadget.config 来修改 on_load 策略,但非越狱环境 FridaGadget 下似乎读不到 Main Bundle 中的 config 文件,因此我们只剩下一条路:修改源码。

修改源码

修改默认行为

首先下载 Frida 源码,具体方式请参考 Build Frida,先完成 clone 即可,随后我们需要定位到 on_load 相关逻辑的代码,全工程搜索 on_load 首先会找到一堆看起来不像是人写的 C 代码,这些代码是由 Vala Language 自动生成的,因此核心代码都在 Vala 里,随后我们找到 gadget.vala,从中搜索 on_load 可找到默认等待的逻辑,将 WAIT 修改为 RESUME 即可:

public LoadBehavior on_load {
	get;
	set;
	default = LoadBehavior.RESUME;
}

public enum LoadBehavior {
	RESUME,
	WAIT
}

很显然,将 default 改为 LoadBehavior.RESUME 即可。

修改 Install Name

既然要重新编译,我们可以直接指定 Install Name 为 Framework 需要的形式,这个配置位于 Makefile.macos.mk 的第 364、371 行,直接修改即可:

build/.core-macos-stamp-%: build/%/lib/pkgconfig/frida-core-1.0.pc
	@if [ -z "$$MAC_CERTID" ]; then echo "MAC_CERTID not set, see https://github.com/frida/frida#macos-and-ios"; exit 1; fi
	. build/frida-meson-env-macos-$(build_arch).rc \
		&& $$CODESIGN -f -s "$$MAC_CERTID" -i "re.frida.Server" build/$*/bin/frida-server \
		## Install Name
		&& $$INSTALL_NAME_TOOL -id @rpath/../FridaGadget.framework/FridaGadget build/$*/lib/frida-gadget.dylib \
		&& $$CODESIGN -f -s "$$MAC_CERTID" build/$*/lib/frida-gadget.dylib
	@touch $@
build/.core-ios-stamp-%: build/%/lib/pkgconfig/frida-core-1.0.pc
	@if [ -z "$$IOS_CERTID" ]; then echo "IOS_CERTID not set, see https://github.com/frida/frida#macos-and-ios"; exit 1; fi
	. build/frida-meson-env-macos-$(build_arch).rc \
		&& $$CODESIGN -f -s "$$IOS_CERTID" --entitlements frida-core/server/frida-server.xcent build/$*/bin/frida-server \
		## Install Name
		&& $$INSTALL_NAME_TOOL -id @rpath/FridaGadget.framework/FridaGadget build/$*/lib/frida-gadget.dylib \
		&& $$CODESIGN -f -s "$$IOS_CERTID" build/$*/lib/frida-gadget.dylib
	@touch $@

重新编译

Frida 的编译相对比较简单,参考 官方文档 即可,这里说几个编译过程中遇到的坑。

  1. 建议使用 Node 8.0 及以上的环境;
  2. 先执行 make core-ios 再执行 make gadget-ios
  3. Build 过程需要创建一个证书来签名,具体方式参考 官方文档
  4. 在 Node 高版本执行失败时,先切换到低版本(例如 Node 5.9.1),随后可能会在 npm install 过程中遇到错误,再切换到高版本即可;

随后再按照相同的步骤,将 FridaGadget 封装成 Framework,再打包成 Pod 库,再结合环境控制,即可完美的将 FridaGadget 用于正向开发过程中,环境控制也强有力的保证了 FridaGadget 不会被打包到正式环境。