injection III 注入任意 Swift 代码的原理
简介
Injection III 是一个 Swift 和 Objective-C 的代码注入工具,可以实现在运行时注入修改代码的功能。这个工具可以帮助开发者快速测试和迭代应用,提高开发效率。但是我们都知道 Swift 是一个静态语言, 在之前我们的认知是只有加了@objc
修饰的代码才能通过 Objective-c 的动态特性实现注入。那么injection III是如何实现注入任意 Swift 代码的呢?
dyld_dynamic_interpose
dyld_dynamic_interpose 是 macOS 和 iOS 上的动态链接器技术,可以在运行时替换函数,实现动态代码注入的功能。这个技术是通过修改动态链接库的符号表来实现的。dyld 会在程序启动时加载动态链接库,并建立动态链接库的符号表,这个符号表包含了动态链接库中的函数和变量等符号。dyld_dynamic_interpose 通过修改这个符号表来实现函数替换的功能。
具体来说,dyld_dynamic_interpose 允许开发者使用 interpose 功能来定义要替换的函数。interpose 定义了两个函数,一个是要替换的函数,另一个是替换后的函数。dyld 在加载动态链接库时,会根据 interpose 定义的要替换的函数来查找这个函数,并将其替换成替换后的函数。这个过程是在程序启动时完成的,因此可以实现在运行时注入修改代码的功能。
原理
- 在 dyld 中使用 dyld_dynamic_interpose 特性需要使用一个特殊的 __interpose 修饰符。该修饰符需要应用在一个数组的元素上,其中数组的元素为两个函数指针,第一个指针是需要替换的原始函数,第二个指针是需要替换成的新函数。比如,下面的代码定义了一个包含两个元素的数组,其中第一个元素是要替换的函数指针,第二个元素是要替换成的新函数指针:
__attribute__((used)) static const struct {
void *old_func;
void *new_func;
} interposers[] = {
{ (void*)old_func, (void*)new_func },
};
-
dyld_dynamic_interpose 只能用于动态链接库中的函数替换,而不能用于替换可执行文件中的函数。这是因为在可执行文件中使用 __interpose 修饰符会导致链接错误。
-
在 dyld 加载一个动态链接库时,它会扫描这个库中的 __DATA,__interpose 节,找到所有使用 __interpose 修饰符定义的函数替换关系,并将其添加到一个替换表中。当程序调用一个被替换的函数时,dyld 会在替换表中查找对应的替换函数,并执行替换操作。
-
如果需要在运行时动态添加一个函数替换关系,可以使用 dyld_dynamic_interpose 函数。该函数的原型如下:
int dyld_dynamic_interpose(const char *dylib_path, const struct dyld_interpose_tuple interposers[], size_t count);
其中,dylib_path 参数是需要添加替换关系的动态链接库路径,interposers 参数是一个包含替换关系的结构体数组,count 参数是结构体数组的长度。使用 dyld_dynamic_interpose 函数添加替换关系后,新的替换关系将被添加到替换表中,之后程序调用对应的被替换函数时,会被动态地替换成新的函数实现。
限制
从这个原理中,显而易见,我们不能修改类的属性的类型,也不能增添属性,因为这都是修改了 class 的 meta 布局。并且,对于非 final 的class,也不要添加方法或者修改方法的顺序。
参考
Swift Code Injection (johnholdsworth.com)
iOS进阶 - App启动 & dyld详解(二) - 掘金 (juejin.cn)
如果这篇文章帮到你,请点个 赞