iOS method swizzling 的姿势讨论

最近看了一些关于 iOS 逆向的基础知识和一些开源代码。


根据 Cydia SubStrate 关于 MSHookMessageEx 这个 API 的介绍:

Objective-C 提供了好用的 high_level runtime API, 允许开发者使用 class_getInstanceMethod, method_setImplementation 和更强大的 method_exchangeImpletation 来 swizzle 方法实现并改变功能。

虽然这些 API 非常好用,而且 method swizzling 并不是 iOS 开发的“日常”,但是它们却不能胜任更复杂的情况。** 比方说,在继承层次上的不同 level 上去 hook 同一个 message,那么就会出现顺序的问题。 **
此外,还有一点需要注意:当使用 instrument 的时候,class 会被修改,并没有初始化("initialized")。这样做既改变了 target program 的顺序,也使得 hook 初始化时序 变得不可能。总而言之,Objective-C runtime API 的实现方式改变了。

考虑了上述所有问题,Substrate 提供了一个更好的 API :
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
不需要初始化一个 class,并且保证在继承层次上使用正确的 implementation.


根据 New Relic 的文章,假如有两个方法:

- (void) originalMethod;
- (void) swizzle_originalMethod;

如果直接使用 void method_exchangeImplementations(Method m1, Method m2) , 而且恰巧代码的实现如下(将会出现错误,这里使用了 assert):

- (void) originalMethod //m1
 {
          assert([NSStringFromSelector(_cmd) isEqualToString:@“originalMethod”]); 
          //this `assert` fails after swizzling
           //if using method_exchangedImplementations()
          //…
 }

正确的方式是去写 C 函数,并且使用 method_setImplementation() 将 C 函数强转成 IMP. 这将避免 Objective-C 传递像一个新的 selector name 这样额外的信息。
不要忘记一点:所有的 Objective-C 方法都带有 2 个隐藏的参数(这一点,当我们用 Hopper 或者 IDA 来逆向的时候很容易看到):一个指向自身的引用 id self 和一个 method selector SEL _cmd.
如果要使用一个返回 void 的 IMP, 那么就必须强转。这是因为 ARC 假设所有的 IMP 会返回 id, 并尝试 retain void 和基本类型数据。

IMP anImp; //represents objective-c function
          // such as -UIViewController viewDidLoad;
((void(*)(id,SEL))anImp)(self,_cmd); //call with a cast to prevent
                                     // ARC from retaining void.

微信读书团队曾经分享过由 Peter Steinberger 大神写的 Aspects 开源库的代码分析(文章链接)。正如之前在微博上看到的一句话:“别说写出 Aspects 了,能完全看懂 Aspects 的人也是寥寥。”


使用 Method Swizzling 是一种最后的手段,尽量应该避免。之前有人在 UITableView 上去 hook 了很多方法,并且对 UITableView 的 runloop 进行观察和做额外的工作。这些都应该谨慎对待。对于 UITableView 这样重要的基础类来说,如果不是特别特别熟悉 iOS 系统原理就开始玩“黑魔法”,很容易被“黑魔法”吞噬的。

你可能感兴趣的:(iOS method swizzling 的姿势讨论)