最近看了一些关于 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 系统原理就开始玩“黑魔法”,很容易被“黑魔法”吞噬的。