返回
静态插桩让Hook Method不在局限于固定类
IOS
2023-10-26 05:27:43
基于fishhook的method swizzling
我们先了解fishhook原理以及基于fishhook的method swizzling方案,后续我们基于此实现我们的静态插桩方案。
fishhook原理
fishhook 是基于动态链接库dlopen和dlsym加载、替换C函数的一种方案,我们可以利用fishhook的机制,hook OC方法。具体原理如下:
- 定义一个与原类方法同名的C函数
- 用fishhook将这个C函数替换原类的OC方法实现
- 在这个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方法实现中。
具体实现步骤如下:
- 定义一个与原类方法同名的C函数
- 用clang插桩工具将这个C函数插入到原类的OC方法实现中
- 在这个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方法。