回顾上上上…篇发布的文章《打造macOS下最强的微信取证工具》中使用 frida 工具从内存中获取到了关键数据,frida objc 的能力都来自于 frida-objc-bridge ,本着好奇探索心理想研究下原理,也没找到相关文章资料,倒是找到不少 frida-java-bridge 的文章。那么本文将从了解 Objective-C Runtime 开始,例如它的消息发送机制、Method Swizzling 等,再去探索 frida 中的 frida-objc-bridge 实现原理以及它最关键的 choose 方法的实现。
Objective-C Runtime 是一个运行时库,它为 Objective-C 语言的动态属性提供支持,因此所有 Objective-C 应用程序都链接到它。Objective-C 运行时库支持函数在共享库中实现,位于/usr/lib/libobjc.A.dylib
。
Objective-C 是一种动态语言,这意味着对象类型是在运行时确定的,包括查找给定的函数名称。
在 Objective-C 中,调用类的方法需要向对象发送一条消息,其中包含方法的名称和它期望的参数。在运行时,函数根据其名称查找,然后调用。这意味着编译后的代码还必须维护所有相关对象方法的名称,因为这些方法在运行时使用。
// message_send_demo.m
#import <Foundation/Foundation.h>
@interface AClass : NSObject
@end
@implementation AClass : NSObject
@end
int main() {
id a = @"this is NSString";
[a characterAtIndex:1];
id acls = [AClass new];
[acls characterAtIndex:2];
}
如上 objc
代码,即使调用一个不存在的方法也能正确编译,不过在运行时会抛出异常:
$ clang -framework Foundation message_send_demo.m -o demo
$ ./demo
2023-04-18 11:38:07.537 demo[15135:508503] -[AClass characterAtIndex:]: unrecognized selector sent to instance 0x156e0bbc0
2023-04-18 11:38:07.538 demo[15135:508503] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AClass characterAtIndex:]: unrecognized selector sent to instance 0x156e0bbc0'
*** First throw call stack:
(
0 CoreFoundation 0x00000001c4d35148 __exceptionPreprocess + 240
1 libobjc.A.dylib 0x00000001c4a7fe04 objc_exception_throw + 60
2 CoreFoundation 0x00000001c4dc8ef8 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00000001c4c94494 ___forwarding___ + 1764
4 CoreFoundation 0x00000001c4c93cf0 _CF_forwarding_prep_0 + 96
5 demo 0x0000000104797f64 main + 84
6 dyld 0x000000010482508c start + 520
)
libc++abi: terminating with uncaught exception of type NSException
[1] 15135 abort ./demo
Objective-C 中的方法调用通过使用 objc_msgSend(void /* id self, SEL op, ... */)
函数向对象发送消息,上面的代码:[a characterAtIndex:1]
在运行时转换为:objc_msgSend(id self, @selector(characterAtIndex:), 1)
。接下来继续剖析id
和SEL
数据类型,来揭开 objc
消息发送机制的神秘面纱。
id 是 objc 中指向任何(NSObject)类实例的指针(和C中的 void*
还是有所区别的 void*
指一个未知类型或未知内容的指针),id定义在 **runtime/objc.h** 头文件中**:**
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
id 是个指向 objc_object
结构体的指针,其成员 isa 指向 objc_class
结构体,objc_class
定义在 runtime.h 头文件下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
objc_class
结构它具有名称 ( name ) 、指向其超类 ( super_class ) 的指针、指向实例变量的指针 ( ivars )、方法列表 ( methodLists )、缓存 ( cache ),最后是协议列表 ( protocols )。
就把 objc_method_list
结构体看作一个数组就行了,成员类型是 objc_method
结构体:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;