LLVM 進階一:符號混淆(LTO)

更新:編譯參數(shù)添加對靜態(tài)庫的說明

一、目的:

實現(xiàn)鏈接時符號混淆
參考文檔:
https://llvm.org/docs/LinkTimeOptimization.html
https://mayuyu.io/2017/06/01/LLVMHacking-0x1/#more

二、思路:

  • 編譯階段處理符號存在硬傷,拋棄
  • 鏈接器 ld 只是一個調(diào)度器,核心邏輯在庫 libLTO.dylib
  • pass注冊在文件 LTOBackend.cpp 里
  • 鏈接時的module整合成了一個總的module, 所以鏈接時可以修改符號

三、實現(xiàn)流程:

從這篇文章開始基于LLVM13.0,pass的注冊與實現(xiàn)不同于以前的版本,反復折騰,QAQ;

1、pass代碼:
不再使用runOnModule實現(xiàn),我參考了 StripSymbols.cpp 的實現(xiàn)方式,官方在老的pass里加入了新的pass流程,官方都支持,我這里使用了新的pass調(diào)用方式,如下:

  • 頭文件 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文件添加,選擇在這里添加是因為我們的目的是實現(xiàn)鏈接時優(yōu)化。

  • 添加命令行參數(shù):


    image.png
  • 函數(shù) runNewPMPasses 添加:


    image.png

四、測試:

  • 上述在llvm13.0編譯通過后,可以先找一個簡單的cpp文件編譯測試下效果,例:


    image.png
  • 為了展示更好的效果,我舉例一個iOS的Xcode項目

確保原始ios項目運行沒問題后,只需要添加幾個參數(shù)即可使用,替換掉xcode的clang就可以了
1、xcode添加編譯參數(shù):

  • 目標二進制的編譯參數(shù):-flto
  • 如果依賴了靜態(tài)庫,需要在靜態(tài)庫的target也配置編譯參數(shù)-flto,如果依賴庫不需要混淆符號,則該target不需要添加-flto
    image.png

2、xcode添加鏈接參數(shù):


image.png
  • -Xlinker 將 clang 參數(shù)傳遞給 鏈接器 ld
  • -lto_library 指定LLVM的鏈接庫

3、編譯,首先查看鏈接日志如下,說明鏈接時pass執(zhí)行成功了:

image.png

4、自測運行該app正常,二進制包大小也沒有變化:
沒有做過批量測試
5、nm查看二進制符號如下:


image.png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容