优质广告供应商

广告是为了更好地支持作者创作

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

更新:编译参数添加对静态库的说明

一、目的:

实现链接时符号混淆
参考文档:
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

优质广告供应商

广告是为了更好地支持作者创作

推荐阅读更多精彩内容