<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    OLLVM控制流平坦化的改進

    VSole2022-11-03 09:45:06

    一、傳統平坦化

    控制流平坦化這個混淆方式第一次出現于Ollvm項目中,作為其中的三種混淆方式之一fla。該混淆方式主要思想就是以基本塊為單位,通過一個主分發器來控制程序的執行流程。在這種結構下,原有的程序控制邏輯被打碎,通過其中的控制變量配合Switch結構來實現控制流的轉移。

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

    具體的混淆流程如下:

    1.收集原函數中所有的基本塊,并初始化隨機數種子。

    2.對入口基本塊進行處理,切分基本塊保證入口基本塊只有一個后繼。

    3.給每一個基本塊分配一個隨機數字,并新建一個變量var,在入口基本塊中賦值為入口基本塊后繼基本塊對應的數字。

    4.構造出基本的switch結構和循環框架,使得switch鏈接所有原有基本塊。

    5.修正每個原有基本塊的后繼,使其跳轉至switch結構,并在跳轉之前根據后繼和跳轉條件構造對var的賦值語句。

    而在原版的框架中,該混淆的普適性并不好。在針對于存在異常處理的C++代碼中,是無法進行混淆的,同時由于該方案提出時間較早,早已有多種方法能夠很好的去除該混淆,因此在該篇文章中,對原有的混淆進行改進,使其能夠支持異常處理和防止被輕易去除。本文是基于控制流平坦化Pass的改進,主要提出的為改進原理,不會基于源碼一行行分析,最后會將源碼放出,可自行閱讀。

    二、支持異常處理

    在傳統的平坦化中,假定了每個函數的Terminator都是跳轉指令BranchInst,然而在實際處理過程中并不一定是BranchInst,還有可能是SwitchInst,由于平坦化只處理后繼小于等于2的情況,對于這種有多個后繼的Terminator是無法處理的。針對于這種情況比較好處理,直接在上述第5步不修正其Terminator,讓其保持原有的代碼,直接continue即可,這樣就可以保證其正常運行。

    但是除此之外,有一個指令為InvokeInst指令,這個指令有兩個后繼,但是一個是正常的基本塊處,另一個是跳轉到unwind相關的邏輯中,這個unwind基本塊的開頭必須為LandingPadInst,且只能通過InvokeInst指令來到達。所以當LandingPadInst被插入到SwitchInst中去的時候,由于不是通過InvokeInst到達的,所以會報錯。

    修正這種情況的思路也是非常的簡單,首先需要確定每個基本塊的Terminator是否是InvokeInst,是的話則從需要平坦化的基本塊中剔除掉InvokeInst對應的UnwindBasicBlock。同時在進行第五步時判斷當且僅當Terminator為BranchInst時才進行修正后繼,否則不做任何處理。最后處理后結果如下所示。

    • 代碼修改如下(新增了判斷Terminator是否為BranchInst):
    • 同時去除了LandingPad所在的基本塊。

    三、增強反反混淆效果

    在現存的對抗思路中,無外乎就是模擬執行和靜態分析兩種思路。模擬執行首先需要定位到混淆后的控制流圖中的真實塊,即原控制流圖中的基本塊,然后從每個基本塊開始模擬執行,給所有真實塊(原函數中的基本塊)下斷點,當從基本塊A出發,位于B中的斷點觸發,便能很容易的確定基本塊后繼(B是A的后繼),如果遇到存在兩個后繼的情況則將對應的條件判斷取反,從而使其獲得兩條路徑,這樣的思路往往采用angr或者unicorn實現,已經存在很多腳本了。而靜態分析往往更加的困難,需要定位到對應的控制變量,并根據程序邏輯,找出每個基本塊對應的值,并通過判斷計算出每個基本塊對應的后繼。

    對于靜態分析,更加偏向于經驗化,通過少量的修改即可使得其適用性降低,因此靜態方法去除控制流平坦化的思路往往被使用的較少,且普適性較差。

    而對于模擬執行,為了實現恢復的完整性,往往都是單獨的從一個基本塊出發,設下斷點,確定該基本塊的后繼是什么。(當然也存在動態trace整個程序的執行流程,只要確定真實塊即可簡單的trace恢復執行流程,但是往往程序邏輯的觸發條件是復雜的,有可能并不能覆蓋所有的分支,因此這里不做討論)

    這里主要針對第二種思路進行對抗,即模擬執行。在模擬執行中,存在兩個關鍵點,一是真實塊的確定,二是后繼的確定。由于大部分思路都是從某個基本塊直接出發確定,這個方法能夠正確運行的原因,是因為在去除混淆時,我們并不關心原有代碼基本塊之間的運行關系,程序狀態并不會影響控制流的轉移。換句話說控制流平坦化中,由于完全依靠一個變量簡單確定后繼,且每個真實塊中該變量的賦值并沒有蘊含基本塊之間的關系,因此可以單獨的確定每個基本塊的后繼。

    基于此,如果構造出一種能夠實現控制流的轉移需要基于現在已執行基本塊的信息的方式,即可很好的防護這種逐個擊破的攻擊方式。

    對于一個函數的控制流圖,如果需要運行到節點(基本塊)B,存在一些必定經過的節點,而這些節點即必經節點。

    定義如果從起點E出發,要到達節點B必須經過節點A,則可稱A支配節點B。

    通過支配節點的定義,不難構造出一個方案來防御逐個擊破的手段:

    每個節點都存在一個標志位,用于標記每個節點(基本塊)是否被訪問。

    如果運行到某個節點B,支配B的節點的標志位必定被設置為1(被訪問)。

    每個節點存在一個key變量,只有通過這個正確的key才能計算出正確的后繼。

    一個節點B被第一次執行時會更新其他節點的key,這些其他節點被節點B支配。

    例如下圖,每個節點都有一個變量來標記是否被經過,都有數組v存儲,v[A]=1代表A已經被訪問,且初始化結果都為0,因為剛開始時所有節點都沒有被訪問。

    當一個節點A執行時,會賦值v[A]=1,如果發現它被賦值前為0的話說明為第一次經過,那么就根據自身分配到的隨機值修改其他節點的key值,這些其他節點都是被A支配的節點。

    所以在這種情況下,在程序具體執行中的每個節點的key是一個特定的值(因為一個節點有固定多個必經節點,當節點被執行時,其必經節點也必然被執行,且保證了每個節點只會更新一次其他節點的key值),如果將節點單獨抽取出來執行的話,沒有必經節點更新他的key值,則必然與具體執行中的key不一樣。

    由于該特性,計算出每個節點的特定的值,并對原有的控制流平坦化中的控制變量switchVar進行異或運算,只有key是正確的才能保證switchVar的值是正確的,從而跳轉到正確的后繼節點。而如果單純的抽出某個節點開始執行的話,由于沒有執行節點的必經節點,導致其key值不正確,因此switchVar也是錯誤的,從而無法獲得正確的后繼,從而保護代碼防止被去混淆。

    具體的修改代碼如下:

    插入一個函數,通過給定的隨機值更新其他節點的key值。

    Function *buildUpdateKeyFunc(Module *m){  std::vector<Type*> params;  params.push_back(Type::getInt8Ty(m->getContext()));         params.push_back(Type::getInt32Ty(m->getContext()));  params.push_back(Type::getInt32Ty(m->getContext())->getPointerTo());  params.push_back(Type::getInt32Ty(m->getContext())->getPointerTo());  params.push_back(Type::getInt32Ty(m->getContext()));  FunctionType *funcType=FunctionType::get(Type::getVoidTy(m->getContext()),params,false);  Function *func=Function::Create(funcType,GlobalValue::PrivateLinkage,Twine("ollvm"),m);  BasicBlock *entry=BasicBlock::Create(m->getContext(),"entry",func);  BasicBlock *cond=BasicBlock::Create(m->getContext(),"cond",func);  BasicBlock *update=BasicBlock::Create(m->getContext(),"update",func);  BasicBlock *end=BasicBlock::Create(m->getContext(),"end",func);  Function::arg_iterator iter=func->arg_begin();  Value *flag=iter;  Value *len=++iter;  Value *posArray=++iter;  Value *keyArray=++iter;  Value *num=++iter;  IRBuilder<> irb(entry);  Value *i=irb.CreateAlloca(irb.getInt32Ty());  irb.CreateStore(irb.getInt32(0),i);  irb.CreateCondBr(irb.CreateICmpEQ(flag,irb.getInt8(0)),cond,end);   irb.SetInsertPoint(cond);  irb.CreateCondBr(irb.CreateICmpSLT(irb.CreateLoad(i),len),update,end);   irb.SetInsertPoint(update);   Value *pos=irb.CreateLoad(irb.CreateGEP(posArray,irb.CreateLoad(i)));  Value *key=irb.CreateGEP(keyArray,pos);  irb.CreateStore(irb.CreateXor(irb.CreateLoad(key),num),key);  irb.CreateStore(irb.CreateAdd(irb.CreateLoad(i),irb.getInt32(1)),i);  irb.CreateBr(cond);   irb.SetInsertPoint(end);  irb.CreateRetVoid();  return func;}
    

    插入標記數組和key數組,計算出每個基本塊的key值數組(key_map),為每個基本塊分配一個隨機值用于更新其他節點的key,并在基本塊后添加更新標記數組和key數組的代碼。

    IRBuilder<> irb(&*oldEntry->getFirstInsertionPt());                                // generate context info key for each blockValue *visitedArray=irb.CreateAlloca(irb.getInt8Ty(),irb.getInt32(origBB.size()));Value *keyArray=irb.CreateAlloca(irb.getInt32Ty(),irb.getInt32(origBB.size()));irb.CreateMemSet(visitedArray,irb.getInt8(0),origBB.size(),(MaybeAlign)0);irb.CreateMemSet(keyArray,irb.getInt8(0),origBB.size()*4,(MaybeAlign)0);int idx=0;std::vector<unsigned int> key_list;DominatorTree tree(*f);std::map<BasicBlock*,unsigned int> key_map;std::map<BasicBlock*,unsigned int> index_map;for(std::vector<BasicBlock *>::iterator b=origBB.begin();b!=origBB.end();b++){  BasicBlock *block=*b;  unsigned int num=getUniqueNumber(&key_list);  key_list.push_back(num);  key_map[block]=0;}for(std::vector<BasicBlock *>::iterator b=origBB.begin();b!=origBB.end();b++,idx++){  BasicBlock *block=*b;  std::vector<Constant*> doms;  int i=0;  for(std::vector<BasicBlock *>::iterator bb=origBB.begin();bb!=origBB.end();bb++,i++)  {      BasicBlock *block0=*bb;      if(block0!=block && tree.dominates(block,block0))      {          doms.push_back(irb.getInt32(i));          key_map[block0]^=key_list[idx];      }   }  irb.SetInsertPoint(block->getTerminator());  Value *ptr=irb.CreateGEP(irb.getInt8Ty(),visitedArray,irb.getInt32(idx));  Value *visited=irb.CreateLoad(ptr);  if(doms.size()!=0)  {      ArrayType *arrayType=ArrayType::get(irb.getInt32Ty(),doms.size());      Constant *doms_array=ConstantArray::get(arrayType,ArrayRef<Constant*>(doms));      GlobalVariable *dom_variable=new GlobalVariable(*(f->getParent()),arrayType,false,GlobalValue::LinkageTypes::PrivateLinkage,doms_array,"doms");      irb.CreateCall(FunctionCallee(updateFunc),{visited,irb.getInt32(doms.size()),irb.CreateGEP(dom_variable,{irb.getInt32(0),irb.getInt32(0)}),keyArray,irb.getInt32(key_list[idx])});  }   irb.CreateStore(irb.getInt8(1),ptr);  index_map[block]=idx;}
    

    在基本塊結尾是switchVar異或該基本塊對應的key。

    irb.SetInsertPoint(block);irb.CreateStore(irb.CreateXor(irb.CreateLoad(irb.CreateGEP(keyArray,irb.getInt32(index_map[succ]))),ConstantInt::get(sw->getCondition()->getType(),fixNum)),switchVar);BranchInst::Create(loopEnd,block);
    


    最終結果如圖所示,每個基本塊后加入了一串新的代碼。

    同時采用基于angr的deflat腳本無法正確的去除混淆了。


    四
    結語
    

    最后修改后的控制流平坦化Pass被整合至了Pluto-Obfuscator,這個是正在維護的OLLVM項目,同時其中會不定期更新一些新的Pass。

    https://github.com/bluesadi/Pluto-Obfuscator

    該改進后的控制流平坦化混淆的代碼可在這個網址(https://github.com/bluesadi/Pluto-Obfuscator/blob/main/llvm/lib/Transforms/Obfuscation/FlatteningEnhanced.cpp)見到。

    代碼混淆
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    本文介紹如何利用可逆多項式和線性MBA表達式構造多項式MBA表達式,并用LLVM Pass實現一種簡單的多項式MBA混淆。MATH WARNING: 本文涉及少量抽象代數知識,基本上都是網安專業信安數學必修課中學到的內容。
    Bashfuscator是一款完全可配置可擴展的Bash代碼混淆框架,該工具專為紅隊和藍隊研究人員設計,它通過將不同的混淆技術和方法組織到框架內的模塊中來實現其功能,而這些技術和方法我們將其稱之為“Mutator”。
    針對惡意包的新型混淆技術可以在圖像中隱藏代碼
    安全公司Checkmarx報告稱,自今年1月以來,共有八個不同的開發工具中包含隱藏的惡意負載。最近一個是上個月發布的名為"pyobfgood"的工具。與之前的七個軟件包一樣,pyobfgood偽裝成一款合法的混淆工具,開發人員可以使用它來防止代碼的逆向工程和篡改。一旦執行,它會安裝一個惡意負載,使攻擊者幾乎完全控制開發人員的機器。
    據安全公司 Checkmarx報道,自 1 月份以來,八個獨立的開發人員工具包含具有各種惡意功能的隱藏有效負載。最新版本于上個月發布,名稱為“pyobfgood”。與之前的七個軟件包一樣,pyobfgood 也是一個合法的混淆工具,開發人員可以用它來阻止逆向工程和篡改他們的代碼
    結果表明,那些想要保持匿名的程序員需要采取極端的應對措施來保護他們的隱私。因此,我們為可執行二進制作者溯源設計了一個特征集,目標是準確地表示與程序員風格相關的可執行二進制文件的屬性。從頭到尾反匯編二進制文件,遇到無效指令時跳過該字節。特別而言,反編譯器可以重構控制結構,如不同類型的循環和分支結構。因此,需要進行特征選擇的降維操作。
    針對于這種情況比較好處理,直接在上述第5步不修正其Terminator,讓其保持原有的代碼,直接continue即可,這樣就可以保證其正常運行。同時在進行第五步時判斷當且僅當Terminator為BranchInst時才進行修正后繼,否則不做任何處理。
    JSP Webshell的檢測工具
    2021-12-13 12:04:53
    在11月初,我做了一些JSP Webshell的免殺研究,主要參考了三夢師傅開源的代碼。然后加入了一些代碼混淆手段,編寫了一個免殺馬生成器JSPHorse,沒想到在Github上已收獲500+的Star
    代碼混淆是幫助威脅參與者向人工審查者或安全軟件隱藏其代碼的惡意性質,以便他們能夠逃避檢測。
    北京奇安盤古實驗室發布報告(PDF),披露了 NSA 的黑客行動。報告稱,2013 年盤古實驗室研究員在針對某國內要害部門主機的調查過程中,提取了一個經過復雜加密的 Linux 平臺后門,其使用的基于 SYN 包的高級隱蔽信道行為和自身的代碼混淆、系統隱藏、自毀設計前所未見。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类