LLVM學習總結與OLLVM項目分析

學習了一段時間的LLVM后,難免需要對其做一個總結,同時準備下一階段的學習工作——基于LLVM自定制代碼混淆器。在此只記錄學習內容,不表達實現方式。

LLVM、clang、IR概述

對于LLVM,個人認為可以將它理解為是一個編譯器,或者是一個完整的編譯架構。它將源代碼(.c或者.cpp或者.m等文件代碼)生成與機器無關的中間代碼,稱之為IR。然后對產生的IR進行優化,生成對應的機器匯編語言。這和傳統編譯器前端,中間優化,后端的設計模式很相似。而不同之處在于,可以通過自定制前端或者后端來使之支持編譯你的語言,對應的就是將源碼轉為中間IR代碼,或者中間IR代碼轉為指定的機器代碼,即只需要實現指定的前端后者后端即可。這就是LLVM強大的可擴展性。

對于LLVM來說,其前端是clang,在編譯源碼文件的時候使用的編譯工具也是clang。而生成中間IR代碼后需要對IR代碼進行一些操作,例如添加一些代碼混淆功能。LLVM的做法是通過編寫Pass(其實就是對應的一個個類,每個類實現不同的功能)來實現混淆的功能。所以實現混淆,其實就是編寫功能性的Pass。怎樣編寫pass在之前的文章可以找到。

加固與保護

如果你是安卓開發從業者,那么我相信你應該聽說過VMP保護,VMP(虛擬軟件保護技術)的思路是自定義一套虛擬機指令和對應的解釋器,并將標準的指令轉換成自己的指令,然后由解釋器將自己的指令給對應的解釋器。由于安卓系統后端使用了LLVM,并且smali2c的技術已經漸漸成熟,所以OLLVM(一個開源的代碼混淆器)變成了一個可選項,但是對于加固來說,它的保護是基于代碼級別的,需要提供源碼或者編譯的中間代碼。

這當然不是企業能接受的事情,所以需要做二進制加固。但是二進制加固離不開反匯編解析引擎(capstone),它可以將指令抽出來,然后轉為自己的虛擬指令,例如將LLVM IR虛擬為自己的虛擬指令,但是這種方法難度較大。對于代碼混淆來說,只用對IR代碼進行處理就可以了。

如何開始

當然首先想到的是Google,但是Google出來的文章對于真正想做一個有意義的項目的人來說意義并不大。對于本人而言,目前學習了LLVM,了解了其架構與簡單的實現,下面要學習的自然是該如何仿照OLLVM或者是Hikari或者上交的Armariris(孤挺花)等一些開源項目來實現一些自己的混淆功能。感謝這些開源作者。

從熟悉項目開始

下載以上的項目中的一個,用CLion或者其他IDE打開項目查看項目結構(以OLLVM為例):


OLLVM項目結構

讓我們只關注如下的文件夾,其它的暫且不管:
include文件夾

include文件夾

其實從文件夾名稱就能判斷include文件夾是頭文件所在的地方,include文件夾之下包含兩個文件夾:llvm和llvm-c。
llvm文件夾下有如下目錄:llvm\Transforms\Obfuscation,可以看到此文件夾下有一些頭文件:

Obfuscation頭文件

此處是存放OLLVM項目中自己寫的pass的頭文件的地方,由此可知,如果我們需要些自己的pass的話,那么對應的pass類的頭文件也需要在include\llvm\Transforms新建一個文件夾專門用來存放頭文件。頭文件的具體內容暫且不管,接下來再去看看實現文件在哪里。

打開與include文件夾平行的lib文件夾并進入lib\Transforms\Obfuscation目錄:

Obfuscation所在目錄

打開Obfuscation目錄,可以看到與之前的頭文件一一對應的實現文件:
實現文件

至此,與我們編寫自己的pass一樣,在include\llvm\Transforms\Obfuscation定義頭文件,在lib\Transforms\Obfuscation寫實現文件。這樣,我們就明白了該如何開始寫自己的項目。不過要注意的是,不管是LLVM還是OLLVM,它們都是通過編寫makefile來實現項目的運行的,所以我們得熟練掌握makefile的編寫與依賴,才能玩轉自己的項目。

OLLVM簡單源碼分析

在分析源碼之前,首先介紹一下IR的基本結構:
IR代碼是由一個個Module組成的,每個Module之間互相聯系,而Module又是由一個個Function組成,Function又是由一個個BasicBlock組成,在BasicBlock中又包含了一條條Instruction。


IR代碼結構

以基本塊的分割為例

對于OLLVM的每個pass,其主要的工作繼承對應的pass類,就是對相應的方法進行重寫,例如SplitBasicBlock的實現,它繼承自FunctionPass,并重寫了runOnFunction方法:

bool SplitBasicBlock::runOnFunction(Function &F) {
  // Check if the number of applications is correct
  if (!((SplitNum > 1) && (SplitNum <= 10))) {
    errs()<<"Split application basic block percentage\
            -split_num=x must be 1 < x <= 10";
    return false;
  }

  Function *tmp = &F;

  // Do we obfuscate
  if (toObfuscate(flag, tmp, "split")) {
    split(tmp);
    ++Split;
  }

  return false;
}

1、SplitBasicBlock 首先對SplitNum進行判斷,SplitNum定義如下:

static cl::opt<int> SplitNum("split_num", cl::init(2),
                             cl::desc("Split <split_num> time each BB"));

此處是對用clang編譯源文件的時候選用的參數split做的定義:

clang -mllvm -split test.c
clang -mllvm -split_num=3 test.c

第一條命令表示啟用對基本block的分割,使之扁平化。
第二條命令表示對基本block分割次數為3次(前提是必須已啟用split),默認是1次。
2、對于splitNum在1~10 之外的情況,提示分割次數錯誤,即分割次數必須在1~10次之內。
3、對于符合要求的splitNum,調用toObfuscate函數進行處理,處理方式如下(該函數在Utils.h文件中):

bool toObfuscate(bool flag, Function *f, std::string attribute) {
  std::string attr = attribute;
  std::string attrNo = "no" + attr;

  // Check if declaration
  if (f->isDeclaration()) {
    return false;
  }

  // Check external linkage
  if(f->hasAvailableExternallyLinkage() != 0) {
    return false;
  }

  // We have to check the nofla flag first
  // Because .find("fla") is true for a string like "fla" or
  // "nofla"
  if (readAnnotate(f).find(attrNo) != std::string::npos) {
    return false;
  }

  // If fla annotations
  if (readAnnotate(f).find(attr) != std::string::npos) {
    return true;
  }

  // If fla flag is set
  if (flag == true) {
    /* Check if the number of applications is correct
    if (!((Percentage > 0) && (Percentage <= 100))) {
      LLVMContext &ctx = llvm::getGlobalContext();
      ctx.emitError(Twine("Flattening application function\
              percentage -perFLA=x must be 0 < x <= 100"));
    }
    // Check name
    else if (func.size() != 0 && func.find(f->getName()) != std::string::npos) {
      return true;
    }

    if ((((int)llvm::cryptoutils->get_range(100))) < Percentage) {
      return true;
    }
    */
    return true;
  }

  return false;
}

可以看到該函數主要是各種檢查以及判斷是否啟用了split功能,判斷依據就是Functions annotationsflag。關于Functions annotations的介紹請看這里

接下來看分割處理的函數split

void SplitBasicBlock::split(Function *f) {
  std::vector<BasicBlock *> origBB;
  int splitN = SplitNum;

  // Save all basic blocks
  for (Function::iterator I = f->begin(), IE = f->end(); I != IE; ++I) {
    origBB.push_back(&*I);
  }

  for (std::vector<BasicBlock *>::iterator I = origBB.begin(),
                                           IE = origBB.end();
       I != IE; ++I) {
    BasicBlock *curr = *I;

    // No need to split a 1 inst bb
    // Or ones containing a PHI node
    if (curr->size() < 2 || containsPHI(curr)) {
      continue;
    }

    // Check splitN and current BB size
    if ((size_t)splitN > curr->size()) {
      splitN = curr->size() - 1;
    }

    // Generate splits point
    std::vector<int> test;
    for (unsigned i = 1; i < curr->size(); ++i) {
      test.push_back(i);
    }

    // Shuffle
    if (test.size() != 1) {
      shuffle(test);
      std::sort(test.begin(), test.begin() + splitN);
    }

    // Split
    BasicBlock::iterator it = curr->begin();
    BasicBlock *toSplit = curr;
    int last = 0;
    for (int i = 0; i < splitN; ++i) {
      for (int j = 0; j < test[i] - last; ++j) {
        ++it;
      }
      last = test[i];
      if(toSplit->size() < 2)
        continue;
      toSplit = toSplit->splitBasicBlock(it, toSplit->getName() + ".split");
    }

    ++Split;
  }
}

該函數首先定義了一個vector數組origBB用于保存所有的block塊,然后遍歷origBB,對每一個blockcurr,如果它的size(即包含的指令數)只有1個或者包含PHI節點,則不分割該block。
對于待分割的block,首先生成分割點,用test數組存放分割點,用shuffle打亂指令的順序,使sort函數排序前splitN個數能盡量隨機。
最后分割block是調用splitBasicBlock函數分割基本塊。

以上就是對分割基本塊的一個簡單介紹。OLLVM還有控制流平坦化,虛假控制流、指令替換、字符串加密等功能,對于這些內容還需要進一步的研究。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容

  • 近來,ollvm在國內移動安全,尤其是安全加固上的使用越來越廣泛,ollvm的混淆和反混淆也被視為比較高等的知識之...
    that_is_this閱讀 3,002評論 4 0
  • 前言 2000年,伊利諾伊大學厄巴納-香檳分校(University of Illinois at Urbana-...
    星光社的戴銘閱讀 15,934評論 8 180
  • LLVM架構介紹 本文主要介紹了LLVM的架構設計。LLVM命名源自于底層虛擬機(Low Level Virtua...
    愛笑的云里看夢閱讀 6,446評論 2 11
  • 三人行————眾 兩人行————從 一人行————one,靜靜的,一個人呆著沒有什么不好的,至少你可以沉思,至少你...
    嵐山閱讀 170評論 0 0
  • 姓名: 芳紫 公司: 新時代健康產業集團 組別: 365期 利他一組(組長) 【5月10日精進打卡第39天】 【知...
    芳紫芳香紫氣東來閱讀 171評論 0 4