返回

静态插桩让Hook Method不在局限于固定类

IOS

基于fishhook的method swizzling

我们先了解fishhook原理以及基于fishhook的method swizzling方案,后续我们基于此实现我们的静态插桩方案。

fishhook原理

fishhook 是基于动态链接库dlopen和dlsym加载、替换C函数的一种方案,我们可以利用fishhook的机制,hook OC方法。具体原理如下:

  1. 定义一个与原类方法同名的C函数
  2. 用fishhook将这个C函数替换原类的OC方法实现
  3. 在这个C函数中实现自己的逻辑,并调用原类的OC方法实现

具体代码如下:

#include <dlfcn.h>

// 原类的OC方法实现
void original_method(id self, SEL _cmd) {
    NSLog(@"original_method called");
}

// 同名C函数
void new_method(id self, SEL _cmd) {
    NSLog(@"new_method called");

    // 调用原类的OC方法实现
    void (*original_function)(id self, SEL _cmd) = dlsym(RTLD_DEFAULT, "original_method");
    original_function(self, _cmd);
}

// 用fishhook将new_method替换original_method
__attribute__((constructor)) static void init(void) {
    fishhook(original_method, new_method);
}

基于fishhook的method swizzling方案

我们基于fishhook的原理,实现一个基于fishhook的method swizzling方案。具体代码如下:

#import "fishhook.h"

@implementation NSObject (MethodSwizzling)

+ (void)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector {
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);

    BOOL success = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if (success) {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end

基于静态插桩的method swizzling方案

基于fishhook的method swizzling方案虽然简单易用,但是它有局限性,不能选择hook部分类的OC方法。这是因为fishhook是基于动态链接库dlopen和dlsym加载、替换C函数的一种方案,而OC方法实现并不是C函数,所以不能直接用fishhook来hook OC方法。

为了解决这个问题,我们可以基于静态插桩来实现一个method swizzling方案。静态插桩是指在编译时,将一段代码插入到另一段代码中。我们可以利用静态插桩的机制,在编译时将我们的hook代码插入到原类的OC方法实现中。

具体实现步骤如下:

  1. 定义一个与原类方法同名的C函数
  2. 用clang插桩工具将这个C函数插入到原类的OC方法实现中
  3. 在这个C函数中实现自己的逻辑,并调用原类的OC方法实现

具体代码如下:

#include <clang/AST/ASTConsumer.h>
#include <clang/AST/ASTContext.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Rewrite/Core/Rewriter.h>
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>

using namespace clang;
using namespace tooling;

// 定义一个与原类方法同名的C函数
void new_method(id self, SEL _cmd) {
    NSLog(@"new_method called");

    // 调用原类的OC方法实现
    void (*original_function)(id self, SEL _cmd) = dlsym(RTLD_DEFAULT, "original_method");
    original_function(self, _cmd);
}

// 插桩ASTVisitor
class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> {
public:
    MyASTVisitor(Rewriter &R) : TheRewriter(R) {}

    bool VisitObjCMethodDecl(ObjCMethodDecl *D) {
        // 如果方法名与我们想要hook的方法名相同,则进行插桩
        if (D->getSelector().getAsString() == "original_method") {
            // 在方法实现的开头插入new_method的调用
            TheRewriter.InsertTextBefore(D->getBody()->getBeginLoc(), "new_method(self, _cmd);\n");
        }

        return true;
    }

private:
    Rewriter &TheRewriter;
};

// 插桩FrontendAction
class MyFrontendAction : public ASTFrontendAction {
public:
    MyFrontendAction() {}

    std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override {
        // 创建一个插桩ASTVisitor
        Rewriter R;
        MyASTVisitor *Visitor = new MyASTVisitor(R);

        // 返回插桩ASTVisitor
        return std::unique_ptr<ASTConsumer>(Visitor);
    }
};

// 插桩Tooling
class MyTooling : public Tool {
public:
    MyTooling() : Tool("MyTooling", "My tooling") {}

    bool run(const std::vector<std::string> &Args) override {
        // 创建一个CompilerInstance
        CompilerInstance CI;

        // 创建一个FrontendActionFactory
        FrontendActionFactory *Factory = newFrontendActionFactory<MyFrontendAction>();

        // 解析命令行参数
        CommonOptionsParser Opts(Args, &CI);
        Opts.parse(Args);

        // 创建一个FrontendInputFile
        FrontendInputFile InputFile(Opts.getSourcePathList().get(0), Opts.getCompilations());

        // 创建一个FrontendOutputFile
        FrontendOutputFile OutputFile(Opts.getOutputPath(), Opts.getDiagnostics());

        // 执行插桩
        bool Success = Factory->runAndSaveFromIsysroot(CI, InputFile, OutputFile);

        // 返回执行结果
        return Success;
    }
};

int main(int argc, const char *argv[]) {
    // 创建一个MyTooling实例
    MyTooling Tooling;

    // 运行Tooling
    return Tooling.run(std::vector<std::string>(argv, argv + argc));
}

总结

本文详细剖析了fishhook原理,并基于fishhook实现了一个基于fishhook的method swizzling方案。同时,我们还基于静态插桩实现了一个method swizzling方案。静态插桩方案打破了原有基于fishhook方案的局限性,可以hook部分类的OC方法。