逆向分析isKindOfClass 内部实现

前段时间,同事给看了一段有趣的代码。对于这段代码执行的结果的出乎意外,我们产生了各种的猜测。但猜测毕竟只是猜测,难免会有误会。对于isKindOfClass的实现很好奇,于是决定探究一下isKindOfClass的内部实现!


一、代码

int main(int argc, const char * argv[]) {
    //测试1
    if ([[NSString class] isKindOfClass:[NSString class]]) {
        NSLog(@"YES");
    }else{
        NSLog(@"NO");
    }
    //测试2
    if ([[NSObject class] isKindOfClass:[NSObject class]]) {
        NSLog(@"YES");
    }else{
        NSLog(@"NO");
    }
    return 0;
}
2016-07-21 08:40:48.020 TestIsKindOfClassDemo[13712:616567] NO
2016-07-21 08:40:48.021 TestIsKindOfClassDemo[13712:616567] YES
Program ended with exit code: 0

是不是很好奇测试1的结果是NO,而测试2得结果是YES?

二、分析

很好奇isKindOfClass的内部实现是如何实现的,但苦于上搜索索没有结果(其实之后找到了源码,链接在文章结尾)于是决定反汇编isKindOfClass 函数所在的库–libobjc.dylib

如何知道是在这个动态库中?

在Xcdoe中,进入isKindOfClass的头文件,通过实例方法-isKindOfClass所在的头文件路径,不难可以猜测:类方法+isKindOfClass也是在objc这个库中!!!
逆向分析isKindOfClass 内部实现_第1张图片

三、如何得到库文件?

通常我们在使用第三方库或者自己编写的库时,头文件和库文件都是放在一起的。但是在浏览iOS文件系统时,我们很快就会发现一个问题:/System/Library/Frameworks/System/Library/PrivateFrameworks等目录下,怎么没有库文件?翻了一下书本,发现

从iOS3.1开始,包括frameworks在内的许多库文件被存进了一个大cache里,这个cache位于/System/Library/Caches/com.apple.dyld/可以使用KenyTm开发的dyld_decache将其中的二进制文件提取出来

摘自小黄书–《iOS应用逆向工程》

四、取出缓存中的库文件

工具:

  • iFunBox
  • dyld_decache

导出缓存文件

打开iFunBox导出对应Frameworks的缓存文件(不能用scp)。选择dyld_shared_cache_armv7s 右键–>复制到Mac。(之所以选择armv7s是因为发现下载下来工具dyld_decache不支持64位。)
逆向分析isKindOfClass 内部实现_第2张图片

提取出库文件

dyld_decache -o ./ /Users/fenglihai/Desktop/IOS逆向/导出系统framework/dyld_shared_cache_armv7s

逆向分析isKindOfClass 内部实现_第3张图片

使用file命令确认是一个动态库文件!

fenglihaideMacBook-Pro:~ fenglihai$ file /Users/fenglihai/Desktop/IOS逆向/导出系统framework/导出后文件/usr/lib/libobjc.dylib 
/Users/fenglihai/Desktop/IOS逆向/导出系统framework/导出后文件/usr/lib/libobjc.dylib: Mach-O dynamically linked shared library arm
fenglihaideMacBook-Pro:~ fenglihai$ 

五、反汇编

工具

  • Hopper Disassembler

libobjc.dylib拖拽到Hopper Disassembler中,并搜索isKindOfClass

逆向分析isKindOfClass 内部实现_第4张图片

汇编代码分析

2fec5594         push       {r4, r7, lr}                                        
2fec5596         add        r7, sp, #0x4
2fec5598         mov        r4, r2
2fec559a         bl         _object_getClass
2fec559e         b          0x2fec55a2

2fec55a0         ldr        r0, [r0, #0x4]                                      

2fec55a2         cbz        r0, 0x2fec55ac                                      

2fec55a4         cmp        r0, r4
2fec55a6         bne        0x2fec55a0

2fec55a8         movs       r0, #0x1
2fec55aa         pop        {r4, r7, pc}

2fec55ac         movs       r0, #0x0                                            
2fec55ae         pop        {r4, r7, pc}
  1. push {r4, r7, lr}

    序言套路,从右到左入栈,保存调用者的下一条要执行的指令(lr)、栈桢(r7)以及其他特殊寄存器(r4)以便恢复。
    lr,链接寄存器(link register )存放调用者的下一条指令。
    r7,栈帧指针(Frame Pointer)存放调用者的栈桢(也就是栈底)。
    r4,通用寄存器。
    逆向分析isKindOfClass 内部实现_第5张图片

  2. add r7, sp, #0x4

    即r7 = sp + 4 ,初始化新的栈桢为:sp+0x4。因为r4也被入栈,所以sp+4刚好指向栈中存放r7的位置。
    逆向分析isKindOfClass 内部实现_第6张图片

  3. mov r4, r2

    将isKindOfClass的参数aClass存放到r4中。oc中的函数会隐含两个参数:self和_cmd,r0存放参数self,r1存放参数_cmd。

  4. bl _object_getClass

    调用object_getClass方法,并设置lr寄存器,返回结果存放在r0中。从object_getClass的汇编代码可以看出,r0作为object_getClass的参数,r0也就是self。

    _object_getClass:
    2fea9a04         cmp        r0, #0x0                                            
    2fea9a06         ite        ne
    2fea9a08         ldrne      r0, [r0]
    2fea9a0a         moveq      r0, #0x0
    2fea9a0c         bx         lr
    2fea9a0e         nop 
  5. b 0x2fec55a2

    跳转到0x2fec55a2地址。

  6. ldr r0, [r0, #0x4]

    将r0 + 4 的内容存放到寄存器r0中。由第4条指令可以知道r0 = object_getClass(self) 的首地址; 那么r0+4 即偏移4个字节: object_getClass(self)->super_class。此时r0 = [object_getClass(self) superclass]

        id 以及 Class 的定义:
    
        typedef struct objc_class *Class;  
        typedef struct objc_object {  
            Class isa;  
        } *id;  
    
        struct objc_class {  
            struct objc_class * isa;  
            struct objc_class * super_class;  /*父类*/  
            const charchar *name;                 /*类名字*/  
            long version;                   /*版本信息*/  
            long info;                        /*类信息*/  
            long instance_size;               /*实例大小*/  
            struct objc_ivar_list *ivars;     /*实例参数链表*/  
            struct objc_method_list **methodLists;  /*方法链表*/  
            struct objc_cache *cache;               /*方法缓存*/  
            struct objc_protocol_list *protocols;   /*协议链表*/  
    
        };  
    
  7. cbz r0, 0x2fec55ac

    判断r0是否等于0,即if([object_getClass(self) superclass] == 0)。是则执行地址0x2fec55ac所在的指令。否则执行第8条指令

  8. cmp r0, r4

    比较r0 和 r4 。r0此时为:[object_getClass(self) superclass],r4即为isKindOfClass函数的参数aClass

  9. bne 0x2fec55a0

    如果(r0 != r4)跳转到 0x2fec55a0地址所在指令,如果相等继续执行第10条指令

  10. movs r0, #0x1

    将立即数1赋值给r0。r0用作函数返回。

  11. pop {r4, r7, pc}

    结语套路,恢复r4,和r7,并把lr中的值赋给程序计数器pc.。

  12. movs r0, #0x0

    将立即数0赋值给r0。r0用作函数返回。

  13. pop {r4, r7, pc}

    结语套路,恢复r4,和r7,并把lr中的值赋给程序计数器pc.

整体逻辑如下:

char +[NSObject isKindOfClass:](void * self, void * _cmd, void * arg2) {
    r4 = arg2;
    r0 = _object_getClass();

loc_2fec55a2:
    if (r0 == 0x0) goto loc_2fec55ac;
    goto loc_2fec55a4;

loc_2fec55ac:
    r0 = 0x0;
    return r0;

loc_2fec55a4:
    if (r0 != r4) goto loc_2fec55a0;
    goto loc_2fec55a8;

loc_2fec55a0:
    r0 = *(r0 + 0x4);
    goto loc_2fec55a2;

loc_2fec55a8:
    r0 = 0x1;
    return r0;
}

六、还原汇编代码到object-c代码

根据上面的汇编代码,不难可以还原成object-c的代码!

//逆向反编译得到的源码
+ (BOOL) isKindOfClassITX:(Class)class
{
    Class r0 = object_getClass(self);
    while (1) {
        if (r0 == 0) {
            return 0;
        }else{
            if (r0 != class) {
                r0 = [r0 superclass];
            }else{
                return 1;
            }
        }
    }
}

七、验证代码是否可行

  1. 新建NSObject的category文件,将isKindOfClassITX函数拷贝到category中。
    逆向分析isKindOfClass 内部实现_第7张图片

  2. 在main函数中运行下面代码

int main(int argc, const char * argv[]) {

        //NSString 对象
        if ([[NSString class] isKindOfClassITX:[NSString class]]) {
            NSLog(@"YES");
        }else{
            NSLog(@"NO");
        }
        if ([[NSString class] isKindOfClass:[NSString class]]) {
            NSLog(@"YES");
        }else{
            NSLog(@"NO");
        }
        //Object 对象
        if ([[NSObject class] isKindOfClassITX:[NSObject class]]) {
            NSLog(@"YES");
        }else{
            NSLog(@"NO");
        }
        if ([[NSObject class] isKindOfClass:[NSObject class]]) {
            NSLog(@"YES");
        }else{
            NSLog(@"NO");
        }

        return 0;
    }
  1. 输出结果
    从结果发现,与[NSObject isKindOfClass] 的结果一致!
2016-07-21 15:55:16.993 TestIsKindofCalss[14377:764296] NO
2016-07-21 15:55:16.994 TestIsKindofCalss[14377:764296] NO
2016-07-21 15:55:16.994 TestIsKindofCalss[14377:764296] YES
2016-07-21 15:55:16.994 TestIsKindofCalss[14377:764296] YES

逆向到这里,已经满足了我对isKindOfClass内部实现的好奇心。但是,但是,为什么[[NSObject class] isKindOfClass:[NSObject class]]的结果是YES?这个问题貌似还没有答案!

八、断点调试isKindOfClassITX

+ (BOOL) isKindOfClassITX:(Class)class添加注释,代码如下

//逆向反编译得到的源码
+ (BOOL) isKindOfClassITX:(Class)class
{

    Class r0 = object_getClass(self);
    while (1) {
        if (r0 == 0) {
            return 0;
        }else{
            NSLog(@"class->%@:%p",NSStringFromClass(class), class);
            NSLog(@"r0->%@:%p",NSStringFromClass(r0), r0);
            if (r0 != class) {
                r0 = [r0 superclass];
            }else{
                return 1;
            }
        }
    }
}

Class r0 = object_getClass(self)

  • 将参数带进来,代码可以写成:r0 = object_getClass([NSObject class])
  • [NSObject class]返回对象NSObject的class,那么object_getClass([NSObject class])则返回对象的metaclass。

r0 = [r0 superclass];

  • 将r0替换为object_getClass([NSObject class]),代码可以成:[object_getClass([NSObject class]) superclass], 也就相当于NSObject 的[NSObject->metaclass superclass]

在调试中,可以发现在while循环的第二次中,return 1了。也就是说以下的代码会成立!

if ([NSObject class] ==[ object_getClass([NSObject class]) superclass]) {
                NSLog(@"yes");
            }
//输出结果
2016-07-21 16:59:18.790 TestIsKindofCalss[14545:795562] yes
Program ended with exit code: 0
由此,已经清楚为什么`[[NSObject class] isKindOfClass:[NSObject class]]`的结果是YES了。

九、结论

[NSObject class] ==[ object_getClass([NSObject class]) superclass]等式成立可以得到结论:NSObject作为root对象 ,root 对象 的mateclass 的superclass == root 对象的class。
好吧,文字总是那么乏力,有图有真相!
逆向分析isKindOfClass 内部实现_第8张图片
instance object,class,metaclass 的 isa 与 super_class 关系图

十、验证上述关系图

在这里我写了四个方法,通过这四个方法不断尝试可以验证上述关系图。

//显示类关系
NSArray * showClassRelationship(Class currentClass)
{
    NSMutableArray *classList = [[NSMutableArray alloc] initWithCapacity:0];
    NSLog(@"--------------class关系--------------");
    Class preClass = nil;
    while (currentClass != preClass) {
        [classList addObject:currentClass];
        NSLog(@"%@:%p",  [NSString stringWithCString:class_getName(currentClass) encoding:NSUTF8StringEncoding],currentClass);
        preClass = currentClass;
        currentClass = object_getClass(currentClass);
    }
    return classList;
}

//显示继承关系
NSArray * showInheritRelationship(Class currentClass)
{
    NSMutableArray *classArray = [[NSMutableArray alloc] initWithCapacity:0];
    NSLog(@"--------------继承关系--------------");
    while (currentClass) {
        [classArray addObject:currentClass];
        NSLog(@"%@:%p", [NSString stringWithCString:class_getName(currentClass) encoding:NSUTF8StringEncoding],class_getSuperclass(currentClass));
        currentClass = class_getSuperclass(currentClass);
    }
    return classArray;
}

/*
 * 显示实例方法列表.
 * 通过遍历currentClass的isa指针,输出所有的实例方法
 */
void showInstanceMethodList(Class currentClass)
{
    NSArray *classList = showInheritRelationship(currentClass);
    int j = 0;
    for (Class currentClass in classList) {
        unsigned int methodCount;
        Method *methodList = class_copyMethodList(currentClass, &methodCount);
        unsigned int i = 0;
        NSLog(@"-------%d-------MethodList %@:%p--------------",j, [NSString stringWithCString:class_getName(currentClass) encoding:NSUTF8StringEncoding],currentClass);
        for (; i NSLog(@"%d:%@ ", i,[NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding]);
        }
        free(methodList);
        j++;
    }
}

/*
 * 显示类方法列表.
 * 通过遍历currentClass的isa的isa指针,输出所有的类方法
 */
void showClassMethodList(Class currentClass)
{
    NSArray *classList = showInheritRelationship(currentClass);
    int j = 0;
    for (Class currentClass in classList) {
        unsigned int methodCount;
        Method *methodList = class_copyMethodList(object_getClass(currentClass), &methodCount);
        unsigned int i = 0;
        NSLog(@"-------%d-------MethodList %@:%p--------------",j, [NSString stringWithCString:class_getName(currentClass) encoding:NSUTF8StringEncoding],currentClass);
        for (; i NSLog(@"%d:%@ ", i,[NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding]);
        }
        free(methodList);
        j++;
    }
}   NSLog(@"--------------class关系--------------");
    Class preClass = nil;
    while (currentClass != preClass) {
        [classList addObject:currentClass];
        NSLog(@"%@:%p",  [NSString stringWithCString:class_getName(currentClass) encoding:NSUTF8StringEncoding],currentClass);
        preClass = currentClass;
        currentClass = object_getClass(currentClass);
    }
    return classList;
}

调用一下方法,查看NSString的class、metaclass、继承关系以及方法列表信息

showMethodList([NSString class]);

输出结果

2016-07-22 00:30:06.500 TestIsKindofCalss[14915:869373] --------------class关系--------------
2016-07-22 00:30:06.501 TestIsKindofCalss[14915:869373] NSString:0x7fff726696d8
2016-07-22 00:30:06.501 TestIsKindofCalss[14915:869373] NSString:0x7fff72669700
2016-07-22 00:30:06.501 TestIsKindofCalss[14915:869373] NSObject:0x7fff7269d118
2016-07-22 00:30:06.502 TestIsKindofCalss[14915:869373] --------------继承关系--------------
2016-07-22 00:30:06.502 TestIsKindofCalss[14915:869373] NSString:0x7fff7269d0f0
2016-07-22 00:30:06.502 TestIsKindofCalss[14915:869373] NSObject:0x0
Program ended with exit code: 0 
  1. class关系
    • NSString:0x7fff726696d8 NSString的class 对于关系图的第2列
    • NSString:0x7fff72669700 NSString的metaclass 对于关系图的第3列
    • NSObject:0x7fff7269d118 NSString的rootclass 对于关系图的第3列,也就是NSObject的metaclass。调用showClassRelationship([NSObject class]);打印NSObject的class关系,可以发现NSObject的metaclass的地址和NSString的rootclass的地址一致!

      2016-07-21 23:58:08.515 TestIsKindofCalss[14791:851749] --------------class关系--------------
      2016-07-21 23:58:08.516 TestIsKindofCalss[14791:851749] NSObject:0x7fff7269d0f0
      2016-07-21 23:58:08.517 TestIsKindofCalss[14791:851749] NSObject:0x7fff7269d118
  2. 继承关系
    • NSString:0x7fff7269d0f0 自身是NSString类
    • NSObject:0x0 父类是NSObject
  3. 方法列表
    略,通过分别调用showClassMethodListshowInstanceMethodList函数查看类方法和实例方法!

相关链接

  • iOS ARM 汇编补脑推荐:iOS 逆向之ARM汇编
  • 关于class 以及metaclass 以及objc_class等相关知识补脑推荐:深入浅出Cocoa之类与对象
  • isKindOfClass 源码:Apple 运行时源码

你可能感兴趣的:(iOS学习笔记,iOS逆向)