前言

回顾上上上…篇发布的文章《打造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 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) 。接下来继续剖析idSEL数据类型,来揭开 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;
Powered by Kali-Team