欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LLVM学习总结与OLLVM项目分析

程序员文章站 2023-12-25 16:27:45
...

学习了一段时间的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为例):

 

LLVM学习总结与OLLVM项目分析

OLLVM项目结构

让我们只关注如下的文件夹,其它的暂且不管:
include文件夹

LLVM学习总结与OLLVM项目分析

include文件夹

 

其实从文件夹名称就能判断include文件夹是头文件所在的地方,include文件夹之下包含两个文件夹:llvm和llvm-c。
llvm文件夹下有如下目录:llvm\Transforms\Obfuscation,可以看到此文件夹下有一些头文件:

LLVM学习总结与OLLVM项目分析

Obfuscation头文件

 

此处是存放OLLVM项目中自己写的pass的头文件的地方,由此可知,如果我们需要些自己的pass的话,那么对应的pass类的头文件也需要在include\llvm\Transforms新建一个文件夹专门用来存放头文件。头文件的具体内容暂且不管,接下来再去看看实现文件在哪里。

打开与include文件夹平行的lib文件夹并进入lib\Transforms\Obfuscation目录:

LLVM学习总结与OLLVM项目分析

Obfuscation所在目录


打开Obfuscation目录,可以看到与之前的头文件一一对应的实现文件:

LLVM学习总结与OLLVM项目分析

实现文件


至此,与我们编写自己的pass一样,在include\llvm\Transforms\Obfuscation定义头文件,在lib\Transforms\Obfuscation写实现文件。这样,我们就明白了该如何开始写自己的项目。不过要注意的是,不管是LLVM还是OLLVM,它们都是通过编写makefile来实现项目的运行的,所以我们得熟练掌握makefile的编写与依赖,才能玩转自己的项目。

 

OLLVM简单源码分析

在分析源码之前,首先介绍一下IR的基本结构:
IR代码是由一个个Module组成的,每个Module之间互相联系,而Module又是由一个个Function组成,Function又是由一个个BasicBlock组成,在BasicBlock中又包含了一条条Instruction。

 

LLVM学习总结与OLLVM项目分析

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还有控制流平坦化,虚假控制流、指令替换、字符串加密等功能,对于这些内容还需要进一步的研究。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/weixin_34038293/article/details/87116553

相关标签: LLVM

上一篇:

下一篇: