目录
- Cydia Substrate 简介
- Cydia Substrate 主要 API 讲解
- Cydia Substrate 其他注意点
Cydia Substrate 简介
-
相关链接
Cydia Substrate 官网地址
Cydia Substrate 维基百科(iphonedev)
Cydia Substrate 开发者:Jay Freeman(Saurik)
-
什么是 Cydia Substrate?
Cydia Substrate(原名:Mobile Substrate)实际上是一个框架,它允许第三方开发者为系统功能提供运行时补丁(Cydia Substrate Extension),类似于 macOS 上的应用程序增强器(Application Enhancer)
Cydia Substrate 是 iOS 越狱后 Cydia 插件、越狱软件运行的一个基础依赖包,提供 Cydia 插件、越狱软件运行的公共库,可以用来动态替换内存中的代码、数据等。因此,iOS 越狱环境下绝大部分 Cydia 插件、越狱软件的安装,首先必须安装 Cydia Substrate
Theos 的 Logos 语法底层就是通过 Cydia Substrate 实现的
Cydia Substrate 由 3 个主要部分组成:MobileHooker、MobileLoader、SafeMode
-
MobileHooker
MobileHooker 用于替换系统功能,这个过程被称为 hook。开发者可以使用以下两个 API:
// 主要作用于 C/C++ 函数 void MSHookFunction(void* symbol, void* hook, void** old);// 主要作用于 Objective-C 方法 void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP* old);
-
MobileLoader
MobileLoader 用于将第三方补丁代码(即 Cydia 插件)加载到正在运行的应用程序中
Cydia Substrate 的插件会被编译成动态库(
.dylib
)的形式,并被放置到 Cydia Substrate 专门用于存储动态库的目录/Library/MobileSubstrate/DynamicLibraries
中在 App 启动时,MobileLoader 首先会使用 dyld 的环境变量
DYLD_INSERT_LIBRARIES
将自己加载到正在运行的 App 进程中,然后它将查找目录/Library/MobileSubstrate/DynamicLibraries/
中的所有动态库,根据与.dylib
同名的.plist
文件所指定的作用范围(即过滤条件),有选择地在 App 进程中通过dlopen
函数加载符合条件的动态库(动态库里面是插件代码)应该使用构造函数的代码来作为执行插件功能的入口,例如:
... // 此编译器属性强制在加载时调用此函数 __attribute__((constructor))static void initialize() {NSLog(@"MyExt: Loaded");MSHookFunction(CFShow, replaced_CFShow, &original_CFShow); }
由上所述,可知:
- Cydia Substrate 的代码注入方式是通过 dyld 加载流程的:
④ 加载插入的动态库
来将第三方开发者的破解代码注入到目标 App 的进程中 - Logos 语法的
%ctor
可能是对 C++ 的构造函数__attribute__((constructor))
的包装。也就是说,Cydia Substrate 执行破解代码的入口可能是 C++ 的构造函数__attribute__((constructor))
- Cydia Substrate 的代码注入方式是通过 dyld 加载流程的:
-
MobileLoader 之 Filter(过滤器)
Cydia Substrate 的大多数用途可以归结为需要修改特定库或类集合的代码,过滤器提供了一种可以指定这种注入目标范围的方法。开发者可以通过添加过滤器来限制 MobileLoader 是否应该加载插件。过滤器被实现为位于
.dylib
旁边的同名.plist
,如果动态库被命名为foo.dylib
,则过滤器应该被命名为foo.plist
过滤器是一个带有
Filter
键的字典,Filter
键对应的值也是一个字典,该字典包含以下键:①
CoreFoundationVersion
键(对应的值是一个数组)CoreFoundationVersion
键对应的值是一个包含 1 个或者 2 个 double 类型数字的数组,数组中的数字用于表示CoreFoundation.framework
的版本,数组的第 0 个元素表示的是下限,数组的第 1 个元素表示的是上限,并且上限是可选的。有时候,开发者希望通过 Cydia Substrate 编写的插件只在特定的 iOS 版本上运行,此时就可以使用CoreFoundationVersion
键CoreFoundation.framework
的版本号在#import <CoreFoundation/CFBase.h>
中以double kCFCoreFoundationVersionNumber
进行描述。因为 OpenStep 格式的 plist 不支持存储数字,所以如果要使用此键,则必须使用 XML 格式或者二进制格式的 plist<key>CoreFoundationVersion</key> <array><real>478.47</real><real>550.32</real> </array>
②
Bundles
键(对应的值是一个数组)Bundles
键对应的值是一个用于存储 BundleID 的字符串数组,只有正在运行的 App 的 BundleID 存在于数组中时,MobileLoader 才会加载插件到当前 App 的进程中。苹果经常使用 Bundle 来组织他们的项目(BundleID 通常是域名反写的形式),这些 BundleID 是引用特定库或者特定进程的最稳定的方式,建议尽可能使用③
Classes
键(对应的值是一个数组)Classes
键对应的值是一个用于存储 Objective-C 类名的数组,只有正在运行的 App 中实现了一个或者多个数组中存在的 Objective-C 类时,MobileLoader 才会加载插件到当前 App 的进程中。有时候,开发者希望 hook 一个存在于许多项目中的、总是静态链接的 Objective-C 类。由于苹果对动态代码部署的限制,这在 iOS 上的广告框架中很常见④
Executables
键(对应的值是一个数组)Executables
键对应的值是一个用于存储可执行文件名称的数组,只有正在运行的 App 的名称存在于数组中时,MobileLoader 才会加载插件到当前 App 的进程中。该键经常用于 hook 那些没有其他可识别特征的东西,例如:很多的守护进程没有相关的 BundleID,相反地,它们是相当简单的 Unix 进程,可能不使用苹果的任何高级 API,可以通过其可执行镜像路径的最后一个部分匹配此类进程⑤
Mode
键(对应的值是Any
)当 Cydia Substrate 一开始在设计具有多种过滤类型的过滤器时,过滤器的实现方式要求当前每个过滤类型至少匹配一次,以便将插件作为一个整体进行匹配。事后看来,这是一个严重的错误,而且已经造成了很多的问题。为此,在过滤器的配置中添加了一个名为
Mode
的新键,可以将该键的值设置为字符串Any
来修复此行为。使用单一过滤类型的简单过滤器将不会受此影响// 使用单一过滤类型的过滤器,不必添加 Mode 键 Filter = {Bundles = ("com.apple.springboard", "com.apple.UIKit"); }// 使用多种过滤类型的过滤器,需要添加 Mode 键 Filter = {Mode = "Any";Bundles = ("com.apple.springboard", "com.apple.UIKit");Classes = ("UIViewController", "UIView");Executables = ("lsd", "mediaserverd"); };
⑥ 注意:
- 从 iOS 9.0 开始过滤器必须存在,没有相应过滤器的插件将不会被加载。如果要复现以前没有过滤器的效果(用于将插件添加到所有的进程中),则请将过滤器的
Bundles
键设置为com.apple.Security
- 对于 setuid 的 App,因为几乎所有的环境变量都被废弃,所以插件的开发者必须在
main()
函数中显式地执行dlopen("/Library/MobileSubstrate/MobileSubstrate.dylib", RTLD_LAZY)
,才能让 MobileLoader 正常运行。不建议这样做,因为大多数的插件不希望在根进程中运行,并且可能会出现意想不到的行为(例如:访问了一个只有根进程才能访问的文件)。这将是一个更好的选择:将 App 运行在 mobile 模式,并且用一个帮助进程执行 root 操作 - 此外,MobileLoader 还 hook 了
nlist()
函数来提高其性能,并为安全模式定义了若干个信号处理程序
- 从 iOS 9.0 开始过滤器必须存在,没有相应过滤器的插件将不会被加载。如果要复现以前没有过滤器的效果(用于将插件添加到所有的进程中),则请将过滤器的
-
SafeMode
当 Cydia Substrate 的插件导致 SpringBoard 奔溃时,MobileLoader 会捕捉到异常信息,并使设备进入安全模式。在安全模式下,所有的 Cydia Substrate 第三方插件都将被禁用。以下异常信号将调起安全模式(
SIG
是信号名的通用前缀):SIGABRT
(程序中止命令中止信号),由调用abort
函数所生成SIGILL
(程序非法指令信号),由程序执行了非法的指令所生成(可执行文件本身出现错误、试图执行数据段、堆栈溢出)SIGBUS
(程序内存字节未对齐中止信号),比如访问一个 4 Byte 长的整数,但其地址却不是 4 的倍数。该信号与SIGSEGV
信号的区别在于该信号是由于对合法存储地址的非法访问而触发的(如访问不属于自己存储空间或只读存储空间)SIGSEGV
(程序无效内存中止信号),试图访问未分配给自己的内存,或者试图往没有写权限的内存地址写数据SIGSYS
(非法的系统调用)
当设备进入安全模式时,可以
ssh
到 iPhone,执行dpkg -r 插件包名(例如:com.app.wechat)
命令
命令执行后其实是删除/Library/MobileSubstrate/DynamicLibraries/
目录下该 App 的对应的 tweak进入安全模式下,所有的第三方 tweak 应用都是运行在 Cydia Substrate 下,且会禁用 troubleshooting。但是安全模式也不能够保证一定安全。如果 tweak 存在的问题太多,引发 iOS 系统其它进程瘫痪,导致 iPhone 无法正常启动,则此时可以通过在手动强制重启 iPhone 时按住
音量+
键来完全禁用 Cydia Substrate。如果开发者在 iPhone 重启时将音量+
键用于其他目的,则需要在 iPhone 重启时等到设备卡住后,再按住音量+
键即可如果 Cydia Substrate 本身存在 bug,一个更复杂的选择是:使用 ramdisk 或者 recovery image 访问文件系统,然后删除加载 Cydia Substrate 的文件(
/usr/bin/cynject
)。因为 Cydia Substrate 在数以万计的设备上使用,如果出现 bug,则很快就会被修复,所以这类问题应该是非常罕见的
Cydia Substrate 主要 API 讲解
-
MSGetImageByName
/* 获取已加载的二进制镜像的句柄为了允许 Substrate 的其他 API(如 MSFindSymbol)来指定特定的镜像(用于限制对符号的搜索范围),需要有一个能返回镜像句柄的 API MSGetImageByName 与 MSFindSymbol 的关系,类似于 dlopen 和 dlsym 的关系(一个用于获取指向镜像的句柄,一个用于查找镜像中的符号)因为 Substrate 需要对 MSGetImageByName 所返回的句柄的存储具有复杂的知识 所以 MSGetImageByName 所返回的句柄是一个不透明的结构(MSImageRef) 因此 MSGetImageByName 所返回的句柄不能简单地等于 dlopen 所返回的句柄 此外,一些平台(如 Android)未能实现关键功能,例如 RTLD_NOLOAD(dlopen 的选项之一,用于查找现有库,而不是加载新库)因此,Substrate 提供了一个用于返回它自己的不透明类型的镜像句柄的 API 需要注意的是,不要将 MSGetImageByName 所返回的镜像句柄与 Unix 上其他用于引用镜像的 API(如 dlsym)混合使用 即使 Unix 上其他用于引用镜像的 API 被定义为可以接受任何 void* 类型的参数除了上述的注意点之外,MSGetImageByName 的调用结果与 dlopen(RTLD_NOLOAD) 的调用结果非常相似: 如果指定的库已经被加载到内存中,则进行查找并且返回对应的句柄 如果指定的库没有被加载到内存中,则不执行任何操作并且返回 NULL@param.file 要从已加载的镜像中查询的共享对象或动态库的绝对标准路径 @return 如果给定的镜像已加载,则返回该镜像的句柄(能与 Substrate 的其他 API 一起使用的指向该镜像的引用)如果给定的镜像未加载,则返回 NULL */ MSImageRef MSGetImageByName(const char * file);
使用示例:
MSImageRef image; image = MSGetImageByName("/system/lib/libc.so"); if (image != NULL) {/* image is loaded */ }
-
MSFindSymbol
/* 查找私有符号的地址当使用像 MSHookFunction 这样的 API 时,能够触及到另一个程序逻辑的核心并改变其公共 API 之外的行为,通常是很重要的 不幸的是,标准的动态加载器(也叫动态链接器)提供的与符号相关的函数(如 dlsym 和 dladdr)只能访问镜像中那些导出给外部使用的符号有些平台提供了获取私有符号的替代方案(如 Darwin 上的 nlist) 然而,这些替代方案都是高度依赖于平台的,有时甚至会在特定的情况下被破坏(例如,64 位的 macOS 或者最新版本的 iOS) 这些问题都极大地限制了这些替代方案的实用性因此,Substrate 提供了一个替代的标准化 API,可以在其支持的所有平台上使用,来执行私有名称和私有符号的低级查找 它与 MSGetImageByName 一起使用,允许从给定的库中查找给定的符号因为开发者可能不知道他正在查找的符号位于哪个库中,所以此 API 支持开发者将 NULL 作为 image 参数的值来表示搜索所有的镜像 因为不同的库中可能存在名称相同的符号(并且开发者需要的只是其中某个库中的该符号),所以在实际的开发过程中不应该过度依赖于将 NULL 作为 image 参数的值来搜索所有的镜像注意: 在 iOS 上,Substrate 本身会 hook nlist 并升级其功能,以便跟 ASLR 和 dyld 共享缓存结合使用 但是,这只是为了向后兼容在 iOS3.1 之前编写的插件,开发者应该使用更可移植的 MSFindSymbol 来代替@param.image 有效的镜像引用(由调用 MSGetImageByName 返回)或者传 NULL 以表示任何镜像 @param.name 要搜索的符号的原始名称因为这不是 dlopen 所使用的高级符号,所以该符号名称可能需要加前缀下划线或者其他特定于平台的 name-mangling @return 如果找到符号,则返回该符号的地址(按照 ARM/Thumb 指令集进行特定的调整) 如果找不到符号,则返回 NULL */ void* MSFindSymbol(MSImageRef image, const char * name);
使用示例:
MSImageRef image; image = MSGetImageByName("/usr/lib/libSystem.B.dylib");void* (*palloc)(size_t); palloc = (void* (*)(size_t))MSFindSymbol(image, "_malloc");void* data = (*palloc)(1024); free(data);
-
MSHookFunction
/* 劫持原生代码到开发者自己的实现虽然修改高级运行时的行为(如:Java 或者 Objective-C)通常可以使用受支持的高级特性(如:自定义 class loader 或者调用 runtime API)来完成 但是修改原生代码可能会使代码工具化的挑战变得令人生畏虽然在某些有限的情况下(如:处理在动态库之间链接的公共导出符号)可以使用动态加载器的特性(如:interpose 或者 preload)来交换整个符号 但是并不总是有那么简单的东西供我们使用如果开发者需要能够修改私有符号的行为,通常甚至是从同一个库或者转换单元调用的公共符号,则开发者需要更强大的原语来完成这件事 如果开发者需要更改函数内部的代码,则肯定会遇到这种情况为了达成此目的,Substrate 提供了一个 API,该 API 不仅允许开发者能够用他们自己的行为替换原生代码中的大多数任意点,而且还可以调用到原生代码 这可能需要程序计数器(program counter)或者指令指示器(instruction pointer)等复杂的汇编(disassembly)和反汇编(re-assembly)的相对逻辑hcg 注: MSHookFunction 对代码进行的是汇编级别的替换 从概念上讲,MSHookFunction 将编写跳转到替换函数的汇编指令,并在自定义的内存位置上分配一些字节 该自定义的内存位置用于存储从原始实现中裁剪出来的汇编指令和跳转到替换函数的其余部分 通俗来说,MSHookFunction 要完成对 C/C++ 函数的 hook,要做的事情有两件: 1.修改原始函数汇编代码的前 N 个字节,跳转到替换函数的入口 2.备份原始函数汇编代码的前 N 个字节,跳转回原始函数 因为在默认情况下,iOS 的内存页面不能同时具有可写和可执行的权限 所以 MSHookFunction 必须应用内核补丁才能工作(任何公开的越狱都会有一个此种内核补丁)@param.symbol 要替换实现的代码的地址。该代码通常是一个函数,但不一定是一个函数 @param.hook 与原始实现 ABI-兼容 的替换实现 @param.old 一个指向函数指针的指针(二级指针),其中将填充一个可用于调用原始实现的桩(stub)如果不进行原始实现的调用,则此参数可以传 NULL */ void MSHookFunction(void* symbol, void* hook, void** old);
使用示例:
void* (*oldConnect)(int, const sockaddr *, socklen_t);void* newConnect(int socket, const sockaddr * address, socklen_t length) {if (address->sa_family == AF_INET) {sockaddr_in* address_in = address;if (address_in->sin_port == htons(6667)) {sockaddr_in copy = *address_in;address_in->sin_port = htons(7001);return oldConnect(socket, ©, length);}}return oldConnect(socket, address, length); }MSHookFunction(&connect, &newConnect, &oldConnect);
-
MSHookMemory
/* 修改可执行内存,并更新代码签名许多设备现在都有一种被称为"代码签名"的机制,旨在防止对软件进行"未经授权"的修改 虽然"代码签名"以安全的名义呈现,但是更常用于实现数字版权管理和其他对用户有敌意的功能虽然通常可以直接更改越狱系统上的内存,但这样做可能会使进程处于"无效"的状态 在此种状态下,操作系统已经检测到对进程内存的修改并标记该进程不受其他进程的信任 这可能会导致显式或者隐式的 bugSubstrate 提供了一个 API -- MSHookFunction 函数,它可以直接 hook 目标函数并将目标函数重定向到替换逻辑 替换逻辑旨在通过在运行时生成具有有效代码签名的可执行页面,小心地将进程保持在一个仍然被操作系统认为是"有效"的状态然而,有时插件的开发人员需要更低级别的修改,例如:编辑一个常量,编辑一个文件的头部,编辑一条单独的指令 对于这些情况,Substrate 现在提供了一个 API -- MSHookMemory,它允许将任意修改和补丁写入内存此 API 在几乎所有版本的 iOS 和 macOS 上都是原子性的(在其他平台上还不是如此),这允许它在(可能与调用此 API 的代码同时运行的代码)上使用 请注意,此 API 有点慢:它可能涉及 IPC(进程间通信)、将文件写入磁盘、漏洞利用@param.target 要修改的内存的地址。该地址应该位于可执行页面的内部 @param.data 要写入目标内存地址的替换数据块的地址 @param.size 要写入的替换数据的大小即,从内存地址 target 开始,要修改的内存的大小 */ void MSHookMemory(void* target, const void * data, size_t size);
使用示例:
bool code() {return true; }const uint8_t hack[] = {0x00, 0x00, 0x80, 0x52, // mov w0, #00xc0, 0x03, 0x5f, 0xd6, // ret };MSHookMemory(&code, hack, sizeof(hack));
-
MSHookMessageEx
/* 替换 Objective-C 消息的实现Substrate 最初是面向 iOS 平台的,它被用来在 SummerBoard 中进行替换(SummerBoard 是一个在苹果构思应用商店之前就流行的主题引擎) 在执行替换、使用 WinterBoard 时,都需要更改各种 Objective-C 消息的实现SummerBoard 的工作方式是使用分类("Category"),一种 Objective-C 的原语,分类会对宿主类产生覆盖,分类用和宿主类相互冲突的方法命名以替换宿主类中的方法 然而,使用分类的同名方法替换宿主类的同名方法,有着一些严重的缺点,使它不能普遍地使用首先,宿主类同名方法的原始实现会被分类同名方法的替换实现完全取代: 与对宿主类进行子类化不同,开发者在分类同名方法的替换实现中更改方法参数后,无法调用到宿主类同名方法的原始实现 相反地,开发者必须重新实现宿主类同名方法的整个功能,而宿主类同名方法的实现可能会在苹果进行代码版本修订时发生更改此外,使用 Objective-C 2.0 ABI 的分类需要能够与宿主类进行链接: 如果宿主类在可执行文件内部,就像在 SpringBoard(iOS 的启动器)内部更改行为时一样,则不可能使用链接器 换句话说,如果无法拿到对宿主类的引用(例如:头文件),就无法为宿主类添加分类幸运的是,Objective-C 提供了一套相当高级的 runtime API 允许开发者使用 class_getInstanceMethod、method_setImplementation、method_exchangeImplementations 等功能强大的函数来交换方法的实现,改变方法的行为然而,因为这些 runtime API 不能满足更复杂的使用场景,所以只有当少数人调用这些 runtime API 进行修改时,这些 runtime API 才能工作良好 特别是,如果有多个人试图使用这些 runtime API 在继承层级结构中的不同节点上 hook 同一个消息,则会出现顺序问题最后,重要的是,正在被 hook 的类在被修改时不会被"初始化" 如果正在被 hook 的类在被修改时被"初始化"了,这既会改变目标程序的顺序,也会使其不能 hook 初始化序列) 随着时间的推移,Objective-C runtim API 实现这一点的方式发生了改变Substrate 通过提供一个考虑到所有这些问题的替换 API 来解决所有这些问题 MSHookMessageEx 始终确保被 hook 的类没有被初始化,并确保在备份继承层级结构时使用正确的"下一个实现(next implementation)"注意: 此 API 替换了旧的 API -- MSHookMessage(旧的 API -- MSHookMessage 没有考虑到上述的那些问题) 在任何情况下,新代码都不应该使用旧的 API,旧的 API 已经被弃用,并且在 Substrate 未来支持的平台上将不可用hcg 注: 1.旧的 API -- MSHookMessage 不是线程安全的 2.MSHookMessage(Ex) 底层是调用 Objective-C runtime 的 API:method_setImplementation 3.在最新版本的 Substrate 中,MSHookMessageEx 还需要一个针对 supercall 闭包的内核补丁来正确地 hook 住所有方法@param._class 要替换实现的消息所属的 Objective-C 类这个类可以是一个元类,以便 hook 一个 "非对象" 或者 "类" 的消息(直接调用 objc_getMetaClass 或者通过调用类上的 object_getClass 来获取一个元类) @param.message 要替换实现的消息对应的方法名称方法名称可以使用 @selector 关键字或者 runtime 的函数 sel_registerName 来生成 @param.hook 与消息原始实现 ABI-兼容 的替换实现的地址 @param.old 一个指向函数指针的指针(二级指针),其中将填充一个可用于调用原始实现的桩(stub)如果不进行原始实现的调用,则此参数可以传 NULL */ void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP* old);
使用示例:
NSString* (*oldDescription)(id self, SEL _cmd);// implicit self and _cmd are explicit with IMP ABI NSString* newDescription(id self, SEL _cmd) {NSString* description = (*oldDescription)(self, _cmd);description = [description stringByAppendingString:@"!"];return description; }MSHookMessageEx([NSObject class], @selector(description), &newDescription, &oldDescription);
-
MSHookInterface
/* 用于简单地调用 MSHookClassPair 的自动化模板此 API 实际上是一个 C 语言的宏,它定义了多个 Objective-C 类的 @Interface,并自动调用 MSHookClassPair 此 API 允许开发者专注于他们 hook 的实现,而无需关心任何与激活 hook 或声明 @Interface 相关的操作调用此宏需要传递 3 个参数。前两个参数是被 hook 的类,以及由开发者提供的新类 前两个参数对应于传递给 MSHookClassPair 的参数(此宏是从开发者对 MSHookClassPair 的调用模板中提取的)第三个参数是被 hook 的类的最终基类 理想情况下,为了在编译时尽可能有效地检查类型,此参数将与被 hook 的类相同 但是,这并不总是可能的:开发者不能对一个无法链接到的类进行子类化(例如,此类在可执行文件中,开发者无法拿到该类的引用或者头文件)在开发者不能使用被 hook 的类作为第三个参数的情况下,NSObject 是一个合理的替代方案 Substrate 提供了一个宏 MSSelf,它可以用于代替具有正确类型的 self。(使调用其他方法更容易)在某些情况下,被 hook 的类的超类也是一个很好的备选方案: 如果可执行文件中定义了开发者正在 hook 的 UITableViewCell 的子类,那么传递 UITableViewCell 将允许开发者使用 self 以类型安全的方式调用大多数方法注意: 因为此 API 实际上是对 MSHookClassPair 的包装,所以鼓励开发人员阅读此 API 的文档@param._class 被 hook 的 Objective-C 类 @param.hook 由开发者提供的新的 Objective-C 类,用于定义替换消息的实现 @param.base 被 hook 的 Objective-C 类的最终基类如果可能,则传递被 hook 的类或者被 hook 的类的超类。否则,可以传递 NSObject */ #define MSHookInterface(_class, hook, base)
使用示例:
MSHookInterface(NSObject, MyHook, NSObject)@implementation MyHook -(NSString *) description {NSString* description = [super description];description = [description stringByAppendingString:@"!"];return description; } @end
-
MSHookClassPair
/* 使用 @implementation-@end 关键字 hook 多个 Objective-C 消息为了理解此 API,我鼓励开发者首先阅读 MSHookMessageEx 的文档 此 API 的目标是允许开发者使用高级的声明性 Objective-C 语法一次 hook 给定类中的多个方法 此高级的声明性 Objective-C 语法与现有的 Objective-C 语法高亮兼容与其他 Substrate 的 API 类似,此函数需要一个目标类、一个替换实现、一个用于存储指向原始实现的函数指针 此 API 不是 hook 目标类中单一的方法或者函数,而是同时 hook 目标类中的多个方法和函数在替换的 hook 类上声明的每个方法都会用于被 hook 的目标类上的对应方法 然后将(为调用原始实现而生成的存根函数 stub function)作为第三个类上的方法添加,用作旧 @Interface 的存储此 API 是围绕一个用例设计的:一个"旧的"类,一个存储调用原始实现的桩(stub),一个被 hook 的类的超类,一个新的替换实现 此 API 允许使用 Objective-C 的 super 关键字调用原始实现注意: 通常情况下,此 API 不会被直接调用。相反地,应该调用宏 MSHookInterface 去设置所有所需的类@param._class 被 hook 的类(与 MSHookMessageEx 不同,该类不能是一个元类) @param.hook 用于 hook 其他类的类,它直接实现了对目标类上定义的消息的替换(有可能是实例消息,也有可能是类消息) @param.old 用于保存被 hook 的类的原始实现的类。该类将填充可用于调用被 hook 的类上的消息的原始实现的桩(stub)如果不进行原始实现的调用,则此参数可以传 NULL此类通常会是被 hook 的类的一个超类 */ void MSHookClassPair(Class _class, Class hook, Class old);
使用示例:
@interface HookOld : NSObject @end@implementation HookOld @end@interface HookNew : NSObject @end@implementation HookNew -(NSString *)description {IMP old = class_getMethodImplementation([HookOld class], _cmd);NSString* description;description = ((NSString* (*)(id, SEL)) old)(self, _cmd);description = [description stringByAppendingString:@"!"];return description; } @endMSHookClassPair([NSObject class], [HookNew class], [HookOld class]);
Cydia Substrate 其他注意点
-
Cydia Substrate 原名为 Mobile Substrate,为了保持向后兼容,Cydia Substrate 中的 API 都以
MS
开头 -
Cydia Substrate 不仅可以用于 iOS 的 hook,还可以用于 Android 的 hook
-
Cydia Substrate 工作流程:
- 用
cynject
将SubstrateLauncher.dylib
注入到launchd
中 - 在
SubstrateLauncher.dylib
中 hookposix_spawn
- 在
posix_spawn
中加载SubstrateBootstrap.dylib
- 在
SubstrateBootstrap.dylib
中加载SubstrateLoader.dylib
- 通过
SubstrateLoader.dylib
检查和加载指定目录(/Library/MobileSubstrate/DynamicLibraries
)下的自定义动态库(.dylib
+.plist
)
- 用
-
iOS 之
SpringBoard
、SummerBoard
、WinterBoard
SpringBoard
是一个标准的 iOS 应用程序,用来管理 iOS 的主屏幕。除此之外,像启动WindowSever
(窗口服务器)、bootstrapping
(引导应用程序)以及在启动时系统的一些初始化设置都是由SpringBoard
这个特定的应用程序负责的。SpringBoard
是 iOS 中事件的第一个接收者,它只能接受少数的事件,比如:按键(锁屏、静音等),触摸,加速,接近传感器等,随后使用macport
转发给需要的 App 进程SummerBoard
和WinterBoard
是 iOS 越狱后主界面的主题美化插件