Objective-C 的 MethodSwizzling

The use of method confusion in Objective-CChinese is relatively common. To understand its essence, we must first understand two concepts.

1. Runtime

Objective-CIt is a dynamic language with very flexible runtime features. It runtimeis a suite written in C, C++, and assembly language that provides object-oriented and runtime features.APIObjective-C

The essence of the method

Objective-C, the method is composed of SELand IMP, the former is called the method number, the latter is called the method implementation.
OCThe calling method is called sending a message. Before sending a message, the message will be searched. The search process is through the SELsearch IMPprocess. In addition, we often use @selector(someMethod)such a syntax in the code, @selector()called method selector, which returns the SELactual execution. This SELlookup corresponds IMP.

3. MethodSwizzling

3.1 Principle

Method obfuscation is achieved by exploiting runtimeproperties and the compositional nature of methods. For example, there are methods sel1corresponding imp1, sel2corresponding imp2, and after method obfuscation, so that the search result runtimewhen the method sel1is searched becomes imp2:

3.2 Actual combat

Create a new iOSproject, create a new class Person, and Personadd two methods -walkand -run:

// Person.h
@interface Person : NSObject

- (void)walk;

- (void)run;

@end

// Person.m
@implementation Person

- (void)walk {
    NSLog(@"walk");
}

- (void)run {
    NSLog(@"run");
}

@end
复制代码

Create a new NSObjectcategory MethodSwizzlingas follows:

// NSObject+MethodSwizzling.h
@interface NSObject (MethodSwizzling)

+ (void)methodswizzlingWithClass:(Class)cls orgSEL:(SEL)orgSEL targetSEL:(SEL)targetSEL;

@end

// NSObject+MethodSwizzling.m
#import <objc/runtime.h>

@implementation NSObject (MethodSwizzling)

+ (void)methodswizzlingWithClass:(Class)cls orgSEL:(SEL)orgSEL targetSEL:(SEL)targetSEL {
    Method orgMethod = class_getInstanceMethod(cls, orgSEL);
    Method tgtMethod = class_getInstanceMethod(cls, targetSEL);
    method_exchangeImplementations(orgMethod, tgtMethod);
}

@end
复制代码

Person.mOverride the +loadmethod in :

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodswizzlingWithClass:self
                                orgSEL:@selector(walk)
                             targetSEL:@selector(run)];
    });
}
复制代码

ViewController-viewDidLoad中调用-walk

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    [person walk];
}
复制代码

运行程序,控制台输出如下:

2020-02-09 21:26:07.613230+0800 TestObjC[1716:74628] run
复制代码

调用的是-walk,实际执行的是-run,完成了方法混淆。

3.3 方法混淆的坑点

如果只声明了-run,但是没有实现,运行后会执行-walk方法,也就是不会交换成功,但如果没实现-walk,实现了-run,程序并不会执行-run,而是直接会崩掉,因为我们是显示的调用了-walk方法。因此在做方法混淆时,需要考虑方法是否实现的场景。
如果有人把子类的方法和父类的方法进行交换,那么父类在调用该方法时就会出现问题,所以还得考虑交换的是否是本类的方法。
基于以上的坑点,为了程序的健壮性,需要把方法混淆的函数修改一下:

@implementation NSObject (MethodSwizzling)

+ (void)methodswizzlingWithClass:(Class)cls orgSEL:(SEL)orgSEL targetSEL:(SEL)targetSEL {
    if (nil == cls) {
        NSLog(@"methodswizzlingWithClass: nil class");
        return;
    }
    
    Method orgMethod = class_getInstanceMethod(cls, orgSEL);
    Method tgtMethod = class_getInstanceMethod(cls, targetSEL);
    
    // 1.当原方法未实现,添加方法,并设置IMP为空实现
    if (nil == orgMethod) {
        class_addMethod(cls, orgSEL, method_getImplementation(tgtMethod), method_getTypeEncoding(tgtMethod));
        method_setImplementation(tgtMethod,
                                 imp_implementationWithBlock(^(id self, SEL _cmd) {
            NSLog(@"methodswizzlingWithClass: %@, orgMethod is nil", cls);
        }));
        return;
    }
    
    // 2.在当前类添加原方法,添加失败,说明当前类实现了该方法
    BOOL didAddMethod = class_addMethod(cls,
                                        orgSEL,
                                        method_getImplementation(orgMethod),
                                        method_getTypeEncoding(orgMethod));
    if (didAddMethod) {
        // 添加成功,当前类未实现,父类实现了orgSEL
        // 此时不必做方法交换,直接将tgtMethod的IMP替换为父类orgMethod的IMP即可
        class_replaceMethod(cls,
                            targetSEL,
                            method_getImplementation(orgMethod),
                            method_getTypeEncoding(orgMethod));
    } else {
        // 正常情况,直接交换
        method_exchangeImplementations(orgMethod, tgtMethod);
    }
}

@end
复制代码

验证一下效果,(Person-walk-run都实现),并添加一个-jump方法:

// Person.m
- (void)jump {
    NSLog(@"jump");
}
复制代码

新建一个类Student继承自Person,并添加一个方法-study

// Student.h
@interface Student : Person

- (void)study;

@end

// Student.m
@implementation Student

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodswizzlingWithClass:self
                                orgSEL:@selector(jump)
                             targetSEL:@selector(study)];
    });
}

- (void)study {
    NSLog(@"study");
}

@end
复制代码

然后viewDidLoad中修改如下:

// ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    [person walk];
    Student *std = [[Student alloc] init];
    [std study];
    [person jump];
}
复制代码

运行以后,输出结果如下:

2020-02-09 22:38:55.180903+0800 TestObjC[2551:122647] run
2020-02-09 22:38:55.181070+0800 TestObjC[2551:122647] jump
2020-02-09 22:38:55.181185+0800 TestObjC[2551:122647] jump
复制代码

Student中,并没有实现-jump,但它把-jump-study做了交换(实际实现是在Student中添加了-jump,然后将其IMP置为Person-jumpIMP),这样以来,Student调用-study就执行了-jumpIMP,效果上相当于做了交换,而父类Person调用-jump并没有受到影响。
把父类Person-jump的实现注释掉,同时注释掉viewDidLoad中第8行[person jump],运行程序,输出如下:

2020-02-09 22:49:39.611851+0800 TestObjC[2667:129133] run
2020-02-09 22:49:39.612015+0800 TestObjC[2667:129133] methodswizzlingWithClass: Student, orgMethod is nil
复制代码

Guess you like

Origin juejin.im/post/7084160200176828446