OLLVM簡單入門


目前市面上的許多安全公司都會在保護IOS應用程序或安卓APP時都會用到OLLVM技術。譬如說頂象IOS加固、網易IOS加固等等。故而我們今天研究下OLLVM是個什么。將從(1)OLLVM是什么?OLLVM與LLVM的關系;(2)OLLVM的三大功能;(3)OLLVM的配置過程;(4)OLLVM源碼分析。(4)OLLVM使用四個方面進行說明。

   (一)OLLVM是什么?

      OLLVM是一款是由瑞士西北科技大學開發的一套開源的針對LLVM的代碼混淆工具,旨在加強逆向的難度,整個項目包含數個包含獨立功能的LLVM Pass,每個Pass會對應實現一種特定的混淆方式,這些Pass將在后面進行細說,通過這些Pass可以改變源程序的CFG和源程序的結構。后期轉向商業項目strong.protect。Github目前已支持OLLVM-4.0.

      與此同時,LLVM與OLLVM最大的區別在於混淆Pass的不同。混淆Pass作用於LLVM的IR中間語言,通過Pass混淆IR,最后后端依據IR生成的目標語言也會得到相應的混淆。得益於LLVM的三段式結構,即前端對代碼進行語法分析詞法分析形成AST並轉換為中間IR語言,一系列優化Pass對IR中間語言進行優化操作,或混淆,或分析,或改變IR的操作碼等等。最終在后端解釋為相應平台嘚瑟機器碼。OLLVM支持LLVM所支持的所有前端語言:C,C++,Objective-C,Fortran等等和LLVM所支持的所有目標平台:x86,x86-64,PowerPC,PowerPC-64, ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ, 和 XCore。

   (二)OLLVM的三大功能

       OLLVM有三大功能,分別是:Instructions Substitution(指令替換)、Bogus Control Flow(混淆控制流)、Control Flow Flattening(控制流平展)。Github上也有OLLVM每個功能詳細的介紹和舉例:https://github.com/obfuscator-llvm/obfuscator/wiki/Features。操作指令可以是一個或多個參數。

      (1)指令替換功能:隨機選擇一種功能上等效但更復雜的指令序列替換標准二元運算符;適用范圍:加法操作、減法操作、布爾操作(與或非操作)且只能為整數類型。

       操作指令:

  1.  
    -mllvm - sub: activate instructions substitution
  2.  
    -mllvm -sub_loop= 3: if the pass is activated, applies it 3 times on a function. Default : 1.

      示例代碼: 

  1.  
    //替換前
  2.  
    a = b - (-c)
  3.  
    % 0 = load i32* %a, align 4
  4.  
    % 1 = load i32* %b, align 4
  5.  
    % 2 = sub i32 0, %1
  6.  
    % 3 = sub nsw i32 %0, %2
  7.  
    //替換后
  8.  
    a = -(-b + (-c))
  9.  
    % 0 = load i32* %a, align 4
  10.  
    % 1 = load i32* %b, align 4
  11.  
    % 2 = sub i32 0, %0
  12.  
    % 3 = sub i32 0, %1
  13.  
    % 4 = add i32 %2, %3
  14.  
    % 5 = sub nsw i32 0, %4

       (2)混淆控制流功能:1.在當前基本塊之前添加基本塊來修改函數調用圖。2.原始基本塊也被克隆並填充隨機選擇的垃圾指令。

     操作指令:

  1.  
    -mllvm -bcf: activates the bogus control flow pass
  2.  
    -mllvm -bcf_loop= 3: if the pass is activated, applies it 3 times on a function. Default: 1
  3.  
    -mllvm -bcf_prob=40: if the pass is activated, a basic bloc will be obfuscated with a probability of 40%. Default: 30

      (3)控制流平展功能:目的是完全展平程序的控制流程圖。我自己的理解是if...else變為switch..case..語句。

       操作指令:

  1.  
    -mllvm -fla: activates control flow flattening
  2.  
    -mllvm - split: activates basic block splitting. Improve the flattening when applied together.
  3.  
    -mllvm -split_num= 3: if the pass is activated, applies it 3 times on each basic block. Default: 1

       (三)OLLVM環境搭建:

        OLLVM版本號:OLLVM 4.0;Ubuntu環境:Ubuntu16.04;虛擬機中處理器數量為4個、運行內存3G,分配硬盤空間50g。

  1.  
    $ git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git
  2.  
    $ mkdir build
  3.  
    $ cd build
  4.  
    $ cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator/
  5.  
    $ make -j7

        若是git clone一直失敗,下不下來,嘗試:

 git config --global http.postBuffer 20000000

      若是cmake時一直報錯,則將cmake那句替換為:

cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator/

     若是make時時間太長,則重新cmake后,多分配一些內存和處理器。

  (四)OLLVM源碼分析

      參考博客:https://www.jianshu.com/p/942875aa73cc

       所有的混淆性Pass都位於/ollvm/obfuscator/lib/Transforms/Obfuscation,利用Clion軟件打開可以得到其結構。Obfuscation文件夾下包含以下文件:

    4.1指令切割功能:

      實現於SplitBasicBlock.cpp中,繼承自FunctionPass,並重寫了runOnFunction方法。

      第一步:判斷切割次數是否符合OLLVM的要求,對於splitNum在1~10 之外的情況,提示分割次數錯誤,即分割次數必須在1~10次之內。

      第二步:對於符合要求的splitNum,調用toObfuscate函數進行處理,處理方式如下(該函數在Utils.h文件中)。主要是各種檢查以及判斷是否啟用了split功能,判斷依據就是Functions annotationsflag。

  1.  
    bool SplitBasicBlock::runOnFunction(Function &F) {
  2.  
    // Check if the number of applications is correct
  3.  
    if (!((SplitNum > 1) && (SplitNum <= 10))) {
  4.  
    errs()<< "Split application basic block percentage\
  5.  
    -split_num=x must be 1 < x <= 10";
  6.  
    return false;
  7.  
    }
  8.  
    Function *tmp = &F;
  9.  
    // Do we obfuscate
  10.  
    if (toObfuscate(flag, tmp, "split")) {
  11.  
    split(tmp);
  12.  
    ++Split;
  13.  
    }
  14.  
     
  15.  
    return false;
  16.  
    }

      第三步:利用split函數進行分割處理。

    (1)定義了一個vector數組origBB用於保存所有的block塊,

    (2)遍歷origBB,對每一個blockcurr,如果它的size(即包含的指令數)只有1個或者包含PHI節點,則不分割該block。

    (3)待分割的block,首先生成分割點,用test數組存放分割點,用shuffle打亂指令的順序,使sort函數排序前splitN個數能盡量隨機。

    (4)分割block是調用splitBasicBlock函數分割基本塊。

  1.  
    void SplitBasicBlock::split(Function *f) {
  2.  
    std::vector<BasicBlock *> origBB;
  3.  
    int splitN = SplitNum;
  4.  
    // Save all basic blocks
  5.  
    for (Function::iterator I = f->begin(), IE = f->end(); I != IE; ++I) {
  6.  
    origBB.push_back(&*I);
  7.  
    }
  8.  
    for (std::vector<BasicBlock *>::iterator I = origBB.begin(),IE = origBB.end(); I != IE; ++I) {
  9.  
    BasicBlock *curr = *I;
  10.  
    // No need to split a 1 inst bb
  11.  
    // Or ones containing a PHI node
  12.  
    if (curr->size() < 2 || containsPHI(curr)) {
  13.  
    continue;
  14.  
    }
  15.  
    // Check splitN and current BB size
  16.  
    if ((size_t)splitN > curr->size()) {
  17.  
    splitN = curr->size() - 1;
  18.  
    }
  19.  
    // Generate splits point
  20.  
    std::vector<int> test;
  21.  
    for (unsigned i = 1; i < curr->size(); ++i) {
  22.  
    test.push_back(i);
  23.  
    }
  24.  
    // Shuffle
  25.  
    if (test.size() != 1) {
  26.  
    shuffle(test);
  27.  
    std::sort(test.begin(), test.begin() + splitN);
  28.  
    }
  29.  
    // Split
  30.  
    BasicBlock::iterator it = curr->begin();
  31.  
    BasicBlock *toSplit = curr;
  32.  
    int last = 0;
  33.  
    for (int i = 0; i < splitN; ++i) {
  34.  
    for (int j = 0; j < test[i] - last; ++j) {
  35.  
    ++it;
  36.  
    }
  37.  
    last = test[i];
  38.  
    if(toSplit->size() < 2)
  39.  
    continue;
  40.  
    toSplit = toSplit->splitBasicBlock(it, toSplit->getName() + ".split");
  41.  
    }
  42.  
    ++Split;
  43.  
    }
  44.  
    }

        參考博客:https://www.jianshu.com/p/942875aa73cc

     4.2.指令替換功能:

      實現於Substitution.cpp中,同樣繼承自FunctionPass,並重寫了runOnFunction方法。

      第一步:調用toObfuscate函數進行處理,進入至substitute方法后,在這個方法中,可以看到,ollvm只對加、減、或、與、異或這五種操作進行替換,funcXXX變量都是函數數組,隨機的選擇一種變換進行操作。ObfTimes對應的是指令切割次數:-sub_loop。

  1.  
    bool Substitution::substitute(Function *f) {
  2.  
    Function *tmp = f;
  3.  
     
  4.  
    // Loop for the number of time we run the pass on the function
  5.  
    int times = ObfTimes;
  6.  
    do {
  7.  
    for (Function::iterator bb = tmp->begin(); bb != tmp->end(); ++bb) {
  8.  
    for (BasicBlock::iterator inst = bb->begin(); inst != bb->end(); ++inst) {
  9.  
    if (inst->isBinaryOp()) {
  10.  
    switch (inst->getOpcode()) {
  11.  
    case BinaryOperator::Add:
  12.  
    // case BinaryOperator::FAdd:
  13.  
    // Substitute with random add operation
  14.  
    ( this->*funcAdd[llvm::cryptoutils->get_range(NUMBER_ADD_SUBST)])(
  15.  
    cast<BinaryOperator>(inst));
  16.  
    ++Add;
  17.  
    break;
  18.  
    case BinaryOperator::Sub:
  19.  
    // case BinaryOperator::FSub:
  20.  
    // Substitute with random sub operation
  21.  
    ( this->*funcSub[llvm::cryptoutils->get_range(NUMBER_SUB_SUBST)])(
  22.  
    cast<BinaryOperator>(inst));
  23.  
    ++Sub;
  24.  
    break;
  25.  
    case Instruction::AShr:
  26.  
    //++Shi;
  27.  
    break;
  28.  
    .....
  29.  
    break;
  30.  
    } // End switch
  31.  
    } // End isBinaryOp
  32.  
    } // End for basickblock
  33.  
    } // End for Function
  34.  
    } while (--times > 0); // for times
  35.  
    return false;
  36.  
    }

       第二步:以下代碼中對應着funcAdd數組的四種替換方法的實現。

      (1)將第二個操作數取反,然后改寫成減法指令。

     (2)將兩個操作數都取反,結果相加之后再次取反。

     (3)取一個隨機數,將隨機數與操作數1相加,然后將結果與操作數2相加,最后減去隨機數。

     (4)取一個隨機數,將操作數1減去隨機數,然后將結果與操作數2相加,最后加上隨機數。

  1.  
    // Implementation of a = b - (-c)
  2.  
    void Substitution::addNeg(BinaryOperator *bo) {
  3.  
    BinaryOperator *op = NULL;
  4.  
    // Create sub
  5.  
    if (bo->getOpcode() == Instruction::Add) {
  6.  
    op = BinaryOperator::CreateNeg(bo->getOperand( 1), "", bo);
  7.  
    op =
  8.  
    BinaryOperator::Create(Instruction::Sub, bo->getOperand( 0), op, "", bo);
  9.  
     
  10.  
    // Check signed wrap
  11.  
    //op->setHasNoSignedWrap(bo->hasNoSignedWrap());
  12.  
    //op->setHasNoUnsignedWrap(bo->hasNoUnsignedWrap());
  13.  
     
  14.  
    bo->replaceAllUsesWith(op);
  15.  
    } /* else {
  16.  
    op = BinaryOperator::CreateFNeg(bo->getOperand(1), "", bo);
  17.  
    op = BinaryOperator::Create(Instruction::FSub, bo->getOperand(0), op, "",
  18.  
    bo);
  19.  
    }*/
  20.  
    }
  21.  
    ......

    4.3.控制流平坦功能 :

       實現於Flattening.cpp中,同樣繼承自FunctionPass,並重寫了runOnFunction方法。 

       第一步:判斷是否能夠平展。若可以,則跳入flatten方法中執行。在函數開始,使用LowerSwitchPass去除switch,將switch結構換成if結構。保存所有的基本代碼塊,如果只有一個基本代碼塊,則不進行處理;如果第一個基本塊的末尾是有條件的跳轉指令,那么需要將它分割開,並且將它保存到origBB;

  1.  
    // Lower switch
  2.  
    FunctionPass *lower = createLowerSwitchPass();
  3.  
    lower->runOnFunction(*f);

        第二步:創建兩個基本塊,存放循環頭和尾的指令。然后將first bb移到到loopEntry的前面,並且創建一條跳轉指令,從first bb跳到loopEntry。緊接着創建了一條從loopEnd跳到loopEntry的指令。最后,創建了switch指令和switch default塊,並且創建相應的跳轉。

  1.  
    // Create main loop
  2.  
    loopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, insert);
  3.  
    loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, insert);
  4.  
    load = new LoadInst(switchVar, "switchVar", loopEntry);
  5.  
    // Move first BB on top
  6.  
    insert->moveBefore(loopEntry);
  7.  
    BranchInst::Create(loopEntry, insert);
  8.  
    // loopEnd jump to loopEntry
  9.  
    BranchInst::Create(loopEntry, loopEnd);
  10.  
    BasicBlock *swDefault =
  11.  
    BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd);
  12.  
    BranchInst::Create(loopEnd, swDefault);
  13.  
    // Create switch instruction itself and set condition
  14.  
    switchI = SwitchInst::Create(&*f->begin(), swDefault, 0, loopEntry);
  15.  
    switchI->setCondition(load);
  16.  
    ......

                                                       

        第三步,刪除first bb的跳轉指令,改為跳轉到loopEntry,將所有的基本塊加入switch結構.接下來是根據原先的跳轉來計算switch變量。

     (1)若為沒有后繼(return BB)的基本塊,直接跳過。

     (2)若為只有一個后繼的基本塊,首先刪除跳轉指令,並且通過后繼基本塊來搜索對應的switch case,根據case創建一條存儲指令,達到跳轉的目的。

     (3)兩個后繼的情況跟一個后繼的處理方法相似,不同的是,創建一條select指令,根據條件的結果來選擇分支。

       4.4.虛假控制流功能 :

     保護前后代碼代碼塊CFG的變化:

實現過程:

      第一步:進入runOnFunction后,調用bogus方法,這是實現控制流混淆的核心。我們一起來看看他干了啥事:(1)先是根據傳遞進來的參數值輸出相應的信息,主要判斷ObfTimes,混淆次數是否大於0.NumObfTimes關聯着-bcf_loop選項的值;(2)跟之前一樣保存基本塊;(3)遍歷基本塊,隨機決定當前基本塊是否需要修改,ObfProbRate變量關聯着-bcf_prob選項的值。如果命中,則調用addBogusFlow函數。

  1.  
    void bogus(Function &F) {
  2.  
    // For statistics and debug
  3.  
    ...
  4.  
    //First Step:
  5.  
    DEBUG_WITH_TYPE( "opt", errs() << "bcf: How many times: "<< ObfTimes<< "\n");
  6.  
    if(ObfTimes <= 0){
  7.  
    DEBUG_WITH_TYPE( "opt", errs() << "bcf: Incorrect value,"
  8.  
    << " must be greater than 1. Set to default: "
  9.  
    << defaultObfTime << " \n");
  10.  
    ObfTimes = defaultObfTime;
  11.  
    }
  12.  
    NumTimesOnFunctions = ObfTimes;
  13.  
    int NumObfTimes = ObfTimes;
  14.  
    ...
  15.  
    //Second step:Put all the function's block in a list
  16.  
    std::list<BasicBlock *> basicBlocks;
  17.  
    for (Function::iterator i=F.begin();i!=F.end();++i) {
  18.  
    basicBlocks.push_back(&*i);
  19.  
    }
  20.  
    ...
  21.  
    //Third Step:Basic Blocks' selection
  22.  
    if((int)llvm::cryptoutils->get_range(100) <= ObfProbRate){
  23.  
    DEBUG_WITH_TYPE( "opt", errs() << "bcf: Block "
  24.  
    << NumBasicBlocks << " selected. \n");
  25.  
    ...
  26.  
    // Add bogus flow to the given Basic Block (see description)
  27.  
    BasicBlock *basicBlock = basicBlocks.front();
  28.  
    addBogusFlow(basicBlock, F);
  29.  
    }
  30.  
    }}

第二步:進入到addBogusFlow函數后;(1)先切割基本塊,將其分為兩塊,一部分是phi節點信息、調試信息等等;另一部分是原始塊中的所有指令;(2)復制基本塊的所有信息,添加花指令信息;(3)現在相當於有三個模塊,一塊是與混淆無關的basickbloak,一塊是由basickboak切割出來的originalBB,一塊是由addBogusFlow產生的alteredBB,將三者拼湊起來成下圖左所示。(4)在addBogusFlow函數的最后,將originalBB的最后一條語句分割出來,然后拼接成下圖右所示:

  1.  
    // Creating the altered basic block on which the first basicBlock will jump
  2.  
    Twine * var3 = new Twine("alteredBB");
  3.  
    BasicBlock *alteredBB = createAlteredBasicBlock(originalBB, *var3, &F);
  4.  
    DEBUG_WITH_TYPE( "gen", errs() << "bcf: Altered basic block: ok\n");
  1.  
    // Jump to the original basic block if the condition is true or
  2.  
    // to the altered block if false.
  3.  
    BranchInst::Create(originalBB, alteredBB, (Value *)condition, basicBlock);
  4.  
    DEBUG_WITH_TYPE( "gen",errs() << "bcf: Terminator instruction in first basic block: ok\n");
  5.  
    // The altered block loop back on the original one.
  6.  
    BranchInst::Create(originalBB, alteredBB);
  7.  
    DEBUG_WITH_TYPE( "gen", errs() << "bcf: Terminator instruction in altered block: ok\n");

                                                            

第三步,接着執行dof函數,遍歷模塊的所有基本塊,搜索出條件永遠為true的比較語句。用(x - 1) * x % 2 == 0 || y < 0這一永真句替換掉我們這找到的true的比較語句。

  1.  
    doF(*F.getParent());
  2.  
    ...
  3.  
    bool doF(Module &M){
  4.  
    ...
  5.  
    // The global values
  6.  
    Twine * varX = new Twine("x");
  7.  
    Twine * varY = new Twine("y");
  8.  
    ...
  9.  
    GlobalVariable * x = new GlobalVariable(M, Type::getInt32Ty(M.getContext()), false,
  10.  
    GlobalValue::CommonLinkage, (Constant * )x1,

參考博客:http://www.ench4nt3r.com/2018/02/26/post/#%E8%99%9A%E5%81%87%E6%8E%A7%E5%88%B6%E6%B5%81

(五)OLLVM混淆前后示例

保護前CPP源碼:

  1.  
    #include <stdio.h>
  2.  
    int main() {
  3.  
    int t=3;
  4.  
    if(t<4){
  5.  
    t++;
  6.  
    } else{
  7.  
    }
  8.  
    printf("hello llvm\n");
  9.  
    return 0;
  10.  
    }

5.1 指令替換功能:

保護命令:

  1.  
    '/home/kyriehe/Desktop/ollvm/build/bin/clang' -emit-llvm test.c -mllvm -sub -S -o testsub.ll
  2.  
     
  3.  
    '/home/kyriehe/Desktop/ollvm/build/bin/clang' test.c -mllvm -sub -o test

保護前后的.ll文件關鍵代碼段的對比:

                                                   

 5.2 控制流平坦功能 :

保護命令:

  1.  
    '/home/kyriehe/Desktop/ollvm/build/bin/clang' -emit-llvm test.c -mllvm -fla -S -o testfla.ll
  2.  
     
  3.  
    '/home/kyriehe/Desktop/ollvm/build/bin/clang' test.c -mllvm -fla -o test

保護前后的.ll文件關鍵代碼段的對比:

                                      

 

5.2 混淆控制流功能 :

保護命令:

  1.  
    '/home/kyriehe/Desktop/ollvm/build/bin/clang' -emit-llvm test.c -mllvm -bcf -S -o testfla.ll
  2.  
     
  3.  
    '/home/kyriehe/Desktop/ollvm/build/bin/clang' test.c -mllvm -bcf -o test

保護前后的.ll文件關鍵代碼段的對比:

                                              

總結一下,相比於指令替換和控制流平坦功能,混淆控制流更為復雜,相對較難破解,但目前市面上已經有了針對於OLLVM混淆的反混淆腳本,能夠輕易干掉經OLLVM保護后的Android應用程序,所以我們可能還需要更深入的思考保護方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM