美文网首页iOS一点通LLVM
LLVM 进阶一:符号混淆(LTO)

LLVM 进阶一:符号混淆(LTO)

作者: 鸣人的大哥 | 来源:发表于2021-11-16 23:13 被阅读0次

    更新:编译参数添加对静态库的说明
    二次更新:实际业务中意义不是很大

    一、目的:

    实现链接时符号混淆
    参考文档:
    https://llvm.org/docs/LinkTimeOptimization.html
    https://mayuyu.io/2017/06/01/LLVMHacking-0x1/#more

    二、思路:

    • 编译阶段处理符号存在硬伤,抛弃
    • 链接器 ld 只是一个调度器,核心逻辑在库 libLTO.dylib
    • pass注册在文件 LTOBackend.cpp 里
    • 链接时的module整合成了一个总的module, 所以链接时可以修改符号

    三、实现流程:

    从这篇文章开始基于LLVM13.0,pass的注册与实现不同于以前的版本,反复折腾,QAQ;

    1、pass代码:
    不再使用runOnModule实现,我参考了 StripSymbols.cpp 的实现方式,官方在老的pass里加入了新的pass流程,官方都支持,我这里使用了新的pass调用方式,如下:

    • 头文件 SymbolObfuscation.h:
    #include "llvm/IR/PassManager.h"
    namespace llvm {
        struct SymbolObfuscationPass : PassInfoMixin<SymbolObfuscationPass> {
          PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
        };
    }
    
    • cpp 文件 SymbolObfuscation.cpp:
    
    #include "llvm/IR/Module.h"
    #include "llvm/Support/raw_ostream.h"
    #include "llvm/Transforms/SymbolObfuscation/SymbolObfuscation.h"
    #include <string>
    #include <iostream>
    #include <cstdlib>
    
    using namespace llvm;
    using namespace std;
    
    static string obfcharacters="-_.|/\\`+,=()*:";
    
    int seed = 0;
    string randomString(int length){
        string name;
        name.resize(length);
        srand(seed);
        seed++;
        for(int i=0;i<length;i++){
            name[i]=obfcharacters[rand()%(obfcharacters.length())];
        }
        return "f_" + name;
    }
    
    PreservedAnalyses SymbolObfuscationPass::run(Module &M, ModuleAnalysisManager &AM) {
        //F.setName(randomString(16));
        errs()<<"Start Symbol Rewrite!\n";
        for(Module::iterator Fun=M.begin();Fun!=M.end();Fun++){
            Function &F=*Fun;
            if (F.getName().str().compare("main")==0){
                errs()<<"Skipping main\n";
            }
            else if(F.empty()==false){
                //Rename
                string newname = randomString(16);
                errs()<<"Renaming Function: "<<F.getName()<<"\n";
                errs()<<"New Function Name: "<<newname<<"\n";
                F.setName(newname);
            }
            else{
                errs()<<"Skipping External Function: "<<F.getName()<<"\n";
            }
        }
      return PreservedAnalyses::all();
    }
    
    

    2、如何添加该pass:
    llvm/LTO/LTOBackend.cpp文件添加,选择在这里添加是因为我们的目的是实现链接时优化。

    • 添加命令行参数:


      image.png
    • 函数 runNewPMPasses 添加:


      image.png

    四、测试:

    • 上述在llvm13.0编译通过后,可以先找一个简单的cpp文件编译测试下效果,例:


      image.png
    • 为了展示更好的效果,我举例一个iOS的Xcode项目

    确保原始ios项目运行没问题后,只需要添加几个参数即可使用,替换掉xcode的clang就可以了
    1、xcode添加编译参数:

    • 目标二进制的编译参数:-flto
    • 如果依赖了静态库,需要在静态库的target也配置编译参数-flto,如果依赖库不需要混淆符号,则该target不需要添加-flto
      image.png

    2、xcode添加链接参数:


    image.png
    • -Xlinker 将 clang 参数传递给 链接器 ld
    • -lto_library 指定LLVM的链接库

    3、编译,首先查看链接日志如下,说明链接时pass执行成功了:

    image.png

    4、自测运行该app正常,二进制包大小也没有变化:
    没有做过批量测试
    5、nm查看二进制符号如下:


    image.png

    相关文章

      网友评论

        本文标题:LLVM 进阶一:符号混淆(LTO)

        本文链接:https://www.haomeiwen.com/subject/qlcsgltx.html