博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Objective-C混淆之方法名混淆
阅读量:5862 次
发布时间:2019-06-19

本文共 8856 字,大约阅读时间需要 29 分钟。

本文通过clang的LibTooling来实现Objective-C源码中方法名的混淆。

1.准备环境

需要下载Clang和LLVM。简单说一下,Clang是编译器的前端,能够解析C/C++/OC代码,Clang生成Intermediate Representation代码(简称IR);LLVM是编译器的后端,使用Clang生成的IR来生成不同平台的目标代码。

本人使用的是Xcode 9.4,下载的Clang和LLVM都是release_39这个分支的代码,亲测可用。

$ git clone -b release_39 http://llvm.org/git/llvm llvm$ cd llvm/tools$ git clone -b release_39 http://llvm.org/git/clang clang复制代码

下载后,进入llvm目录,创建llvm_build目录,然后进入:

$ cd llvm && mkdir llvm_build && cd llvm_build复制代码

使用Xcode完成cmake:

$ cmake -G "Xcode" ..复制代码

等待完成。

2.创建工程

Clang的源码在llvm/tools/clang/下主要是include和lib文件夹,tools文件夹下是使用Clang的库实现的一些工具。我们的混淆工具也是基于Clang库,所以项目也创建在tools下边,与其他工具在同一层级下,创建我们的工程目录clang-autostats:

$ cd llvm/tools/clang/tools && mkdir clang-autostats$ cd clang-autostats复制代码

然后创建源文件ClangAutoStats.cpp。在相同目录下添加CMakeLists.txt文件,内容如下:

set(LLVM_LINK_COMPONENTS  Support  )add_clang_executable(ClangAutoStats  ClangAutoStats.cpp)target_link_libraries(ClangAutoStats    clangAST    clangBasic    clangDriver    clangFormat    clangLex    clangParse    clangSema    clangFrontend    clangTooling    clangToolingCore    clangRewrite    clangRewriteFrontend)if(UNIX)  set(CLANGXX_LINK_OR_COPY create_symlink)else()  set(CLANGXX_LINK_OR_COPY copy)endif()复制代码

接下来将ClangAutoStats工程添加到LLVM中,进入llvm/tools/clang/tools目录,在CMakeList.txt最后一行加入文字:

echo 'add_subdirectory(clang-autostats)' >> ./CMakeLists.txt复制代码

回到llvm_build,再次执行“cmake -G "Xcode" ..”。打开llvm_build/LLVM.xcodeproj,在Clang executables文件夹下会出现我们的工程目录。

使用Xcode打开LLVM.xcodeproj时,创建新的scheme,选择ClangAutoStats,如下图所示。

3.替换方法名

main函数

int main(int argc, const char **argv) {    CommonOptionsParser op(argc, argv, OptsCategory);     vector
commands; ClangTool Tool(op.getCompilations(), commands); // 1> 添加要遍历的文件 commands.push_back("/Users/tom555cat/develop/RewriteDir/Hello1.m"); // 2> 运行Clang Tool,创建一个新的FrontedAction int result = Tool.run(newFrontendActionFactory
().get()); return result;}复制代码

这里要关注的有两点: 1>提供需要遍历语法树的Objective-C源文件,即.m文件;当然.h文件也可以,但是有个坑,后面会说明。 2> 创建了一个新的FrontedAction,接下来关注创建的ExampleFrontedAction类。

ExampleFrontedAction

class ExampleFrontendAction : public ASTFrontendAction {    private:    Rewriter rewriter;public:    virtual unique_ptr
CreateASTConsumer(CompilerInstance &CI, StringRef file) { rewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts()); CI.getPreprocessor(); // 1> 需要为每一个translation unit提供一个ASTConsumer return make_unique
(rewriter); } // 2> 解析完成后,将内容回写到对应文件中 void EndSourceFileAction() override { SourceManager &SM = rewriter.getSourceMgr(); llvm::errs() << "** EndSourceFileAction for: " << SM.getFileEntryForID(SM.getMainFileID())->getName() << "\n"; string Filename = SM.getFileEntryForID(SM.getMainFileID())->getName(); std::error_code error_code; llvm::raw_fd_ostream outFile(Filename, error_code, llvm::sys::fs::F_None); // 将Rewriter结果输出到文件中 rewriter.getEditBuffer(SM.getMainFileID()).write(outFile); // 将Rewriter结果输出在控制台上 // rewriter.getEditBuffer(SM.getMainFileID()).write(llvm::outs()); }};复制代码

ExampleFrontedAction->ASTFrontedAction->FrontedAction,这是这三个类的继承关系。 FrontendAction是一个在编译过程中允许执行用户特殊操作的接口。为了获取语法树AST,clang提供了ASTFrontendAction,而对语法树进行操作则需要用户为每一个translation unit提供一个ASTConsumer,对应标注1>。

FrontendAction:Abstract base class for actions which can be performed by the frontend. FrontendAction有三个public interface。 BeginSourceFile():该函数运行在options和FrontendAction初始化完成之后,每个文件Parse之前。如果该函数返回false,则后面的步骤不会执行。 Excute():Set the source manager's main input file, and run the action. EndSourceFile():每个文件在parse完之后,做一些清理和内存释放工作。(Perform any per-file post processing, deallocate per-file objects, and run statistics and output file cleanup code)。 我们通过遍历AST过程中,对方法名进行了修改,需要处理完一个文件后就将内容回写到对应的文件中,因此我们重载了EndSourceFileAction()方法。对应标注2>。

ExampleASTConsumer

class ExampleASTConsumer : public ASTConsumer {private:    ExampleVisitor visitor;     public:    // override the constructor in order to pass CI    explicit ExampleASTConsumer(Rewriter &R)    : visitor(R) // initialize the visitor    { }        // override this to call our ExampleVisitor on the entire source file    virtual void HandleTranslationUnit(ASTContext &Context) {        /* we can use ASTContext to get the TranslationUnitDecl, which is         a single Decl that collectively represents the entire source file */        visitor.TraverseDecl(Context.getTranslationUnitDecl());    }};复制代码

ASTConsumer提供了对AST进行操作的接口,具体的操作定义在了属性ExampleVisitor visitor中。

ExampleVisitor

class ExampleVisitor : public RecursiveASTVisitor
{private: //ASTContext *astContext; // used for getting additional AST info //typedef clang::RecursiveASTVisitor
Base; Rewriter &rewriter;public: explicit ExampleVisitor(Rewriter &R) : rewriter{R} // initialize private members {} // 判断函数是否能够混淆 bool canObfuscate(ObjCMethodDecl *MD) { // 如果该方法是协议方法,不进行混淆 ObjCInterfaceDecl *ID = MD->getClassInterface(); if (!ID) { return false; } for (ObjCProtocolDecl *protocol : ID->all_referenced_protocols()) { if (protocol->lookupMethod(MD->getSelector(), MD->isInstanceMethod())) { return false; } } // 不混淆读写方法/系统方法/init前缀方法/set前缀方法/zdd_前缀方法 string methodName = MD->getNameAsString(); if (MD->isPropertyAccessor() || isInSystem(MD) || methodName.find("set") == 0 || methodName.find("init") == 0 || methodName.find("zdd_") == 0) { return false; } return true; } // 1> 混淆方法声明/定义处的名字 bool VisitObjCMethodDecl(ObjCMethodDecl *D) { this->renameFunctionName(D); return true; } // 2> 混淆发送消息处的方法名字 bool VisitObjCMessageExpr(ObjCMessageExpr *messageExpr) { // 跳过系统类 ObjCMethodDecl *MD = messageExpr->getMethodDecl(); if (MD) { if(canObfuscate(MD) == false) { return true; } Selector selector = messageExpr->getSelector(); // 方法是通过.调用还是通过发消息调用 string funcNameWithPrefix = "zdd_" + selector.getNameForSlot(0).str(); errs() << "first selector slot size:" << selector.getNameForSlot(0).size() << "\n"; rewriter.ReplaceText(messageExpr->getSelectorStartLoc(), selector.getNameForSlot(0).size(), funcNameWithPrefix); } return true; } // 修改函数声明处的函数名字 void renameFunctionName(ObjCMethodDecl *MD) { // 判断是否应该混淆方法名 if (canObfuscate(MD) == false) { return; } string funcName = MD->getNameAsString(); Selector selector = MD->getSelector(); string funcNameWithPrefix = "zdd_" + selector.getNameForSlot(0).str(); rewriter.ReplaceText(MD->getSelectorStartLoc(), selector.getNameForSlot(0).size(), funcNameWithPrefix); } bool isInSystem(Decl *decl) { SourceManager &SM = rewriter.getSourceMgr(); if (SM.isInSystemHeader(decl->getLocation()) || SM.isInExternCSystemHeader(decl->getLocation())) { return true; } return false; }};复制代码

RecursiveASTVisitor提供了对大多数AST node节点访问的hook方法。我们要对方法名进行混淆,也就是重写,需要能够访问到Interface/Category中方法名的定义;Interface/Category中Implementation中方法的实现;发送消息处的方法名。

修改方法声明/定义处的方法名字

**VisitObjCMethodDecl()**回调能够获取到AST中方法的声明/定义节点,唯一的区别是:在方法声明处,D->hasBody()为false,而在方法定义处,D->hasBody()为true,但是这并不影响我们修改方法名字。

但是有些方法名字不能混淆,比如ViewController中实现协议UITableViewDelegate的方法的名字,在函数**canObfuscate()**中,通过查找该方法是否是类实现的协议方法来过滤掉。

目前没有做属性读写方法的混淆,直接过滤掉了,通过ObjCMethodDecl的** isPropertyAccessor()方法可以判断是否是属性读写方法。有时候在category中定义关联属性,并自定义读写方法,这时通过isPropertyAccessor()是无法判断方法是否是属性读写方法的,我们在canObfuscate()中判断前缀是否是"set"**来判断。

修改发送消息处的方法名

VisitObjCMessageExpr回调能够获取AST中发送消息的节点,通过canObfuscate判断是否能够混淆,然后进行混淆。

代码中的混淆方法

代码中的混淆方法仅仅是在方法selector第一个slot前加上了zdd_前缀,可以替换为自己的混淆方法。

4.运行代码

项目源码地址:

将源代码粘贴进ClangAutoStats.cpp之后,还需要设置Xcode中Scheme的参数:

/Users/tongleiming/Documents/test/RewriteDir---mios-simulator-version-min=9.0-isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.1.0/include-I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1-I/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include-F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks-ferror-limit=9999999-ObjC复制代码

第一行参数为要修改代码的目录。 最后一行参数"-ObjC"主要是针对修改头文件中方法名添加的。

转载于:https://juejin.im/post/5b9f69f8e51d450e9e43fd01

你可能感兴趣的文章
软件测试流程
查看>>
JS(JavaScript)的进一步了解1(更新中···)
查看>>
Model、ModelMap、ModelAndView的作用及区别
查看>>
Ruby on Rail学习笔记
查看>>
LaTeX插入jpg图片: 使用graphicx
查看>>
TP为什么这个if判断什么都不显示?
查看>>
Java NIO Related
查看>>
(转)职场上为什么你的“努力”一文不值?
查看>>
ArcGIS中巧用智能标注(Maplex)对点抽稀(转)
查看>>
Python Dictionary
查看>>
向文件输出数据的输出字节流
查看>>
ACM 算法模板
查看>>
图片在保存的时候===》出现这个异常:GDI+ 中发生一般性错误
查看>>
vue watch监控路由变化
查看>>
转:经典论文翻译导读之《Google File System》
查看>>
jQuery方法大全
查看>>
PAT Acute Stroke (30)
查看>>
CSS/块级元素与内联元素的深入理解
查看>>
什么是SSH
查看>>
随笔小绪
查看>>