移动端安全 OLLVM
北海 发表于 广东 移动安全 324浏览 · 2024-11-07 04:30

OLLVM移植

将ollvm移植到llvm-14.0.6
https://github.com/buffcow/ollvm-project

ollvm项目相关头文件

在llvm项目中的llvm/include/llvm/Transforms文件夹下创建Obfuscation目录,然后照着这个commit里面一个一个添加

ollvm项目相关源文件

在llvm项目中的llvm/lib/Transforms文件夹下创建Obfuscation目录,然后照着commit(llvm support obfuscator)里面一个一个添加

ollvm项目相关支持文件及配置

按照commit(llvm support obfuscator)对llvm项目中相关文件进行补充或修改

编译

编译release版本的

mkdir build
sudo cmake -S llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS="clang" -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_NEW_PASS_MANAGER=OFF
sudo cmake --build build -j4

编译完成后就跟可以查看一下build/bin文件夹

三个混淆功能

指令替换

随机选择一种功能上等效但更复杂的指令序列替换标准二元运算符。
-mllvm -sub:激活指令替换操作
-mllvm -sub_loop=3:如果该操作被激活,就在一个函数上应用它 3 次。默认值:1

算数运算:

a = b + c =>
a = b - (-c)
a = -(-b + (-c))
r = rand (); a = b + r; a = a + c; a = a - r
r = rand (); a = b - r; a = a + b; a = a + r

a = b - c =>
a = -(-b + c)  
r = rand (); a = b + r; a = a - c; a = a - r
r = rand (); a = b - r; a = a - c; a = a + r

逻辑运算:

a = b & c =>
a = (b ^ ~c) & b

a = b | c => 
a = (b & c) | (b ^ c)

a = a ^ b => 
a = (~a & b) | (a & ~b)

这个功能效果会被ida的F5优化,下面我们重点看看源码Substitution.cpp。
可以看到Substitution继承自LLVM中的FunctionPass类

而runOnFunction是FunctionPass类的一个虚函数,Substitution覆盖runOnFunction来实现自己的逻辑。可以看到toObfuscate函数根据传入的 flag 和其他参数来决定是否应该混淆这个函数。如果决定混淆,就调用substitute方法来实际执行混淆逻辑

substitute

substitute代码如下

bool Substitution::substitute(Function *f) {
  Function *tmp = f;

  // Loop for the number of time we run the pass on the function
  int times = ObfTimes;
  do {
    for (Function::iterator bb = tmp->begin(); bb != tmp->end(); ++bb) {
      for (BasicBlock::iterator inst = bb->begin(); inst != bb->end(); ++inst) {
        if (inst->isBinaryOp()) {
          switch (inst->getOpcode()) {
          case BinaryOperator::Add:
            // case BinaryOperator::FAdd:
            // Substitute with random add operation
            (this->*funcAdd[llvm::cryptoutils->get_range(NUMBER_ADD_SUBST)])(
                cast<BinaryOperator>(inst));
            ++Add;
            break;
          case BinaryOperator::Sub:
            // case BinaryOperator::FSub:
            // Substitute with random sub operation
            (this->*funcSub[llvm::cryptoutils->get_range(NUMBER_SUB_SUBST)])(
                cast<BinaryOperator>(inst));
            ++Sub;
            break;
          case BinaryOperator::Mul:
          case BinaryOperator::FMul:
            //++Mul;
            break;
          case BinaryOperator::UDiv:
          case BinaryOperator::SDiv:
          case BinaryOperator::FDiv:
            //++Div;
            break;
          case BinaryOperator::URem:
          case BinaryOperator::SRem:
          case BinaryOperator::FRem:
            //++Rem;
            break;
          case Instruction::Shl:
            //++Shi;
            break;
          case Instruction::LShr:
            //++Shi;
            break;
          case Instruction::AShr:
            //++Shi;
            break;
          case Instruction::And:
            (this->*funcAnd[llvm::cryptoutils->get_range(2)])(
                cast<BinaryOperator>(inst));
            ++And;
            break;
          case Instruction::Or:
            (this->*funcOr[llvm::cryptoutils->get_range(2)])(
                cast<BinaryOperator>(inst));
            ++Or;
            break;
          case Instruction::Xor:
            (this->*funcXor[llvm::cryptoutils->get_range(2)])(
                cast<BinaryOperator>(inst));
            ++Xor;
            break;
          default:
            break;
          }              // End switch
        }                // End isBinaryOp
      }                  // End for basickblock
    }                    // End for Function
  } while (--times > 0); // for times
  return false;
}

这里使用两个嵌套的循环来遍历函数中的所有基本块(BasicBlock)和每个基本块中的所有指令(Instruction)

补充 llvm pass操作对象结构

  • Module:c,cpp文件

  • Function:c,cpp代码中的函数。

  • BasicBlock:每个函数会被划分为若干基本块。一个block只有一个入口和一个出口。

  • Instruction:具体的指令

    接着对于每种二元操作符,使用一个函数指针数组来存储不同的混淆策略,并随机选择一个策略来应用

虚假控制流

在程序的控制流图(CFG)中引入虚假的分支和基本块
-mllvm -bcf: 虚假控制流
-mllvm -bcf_loop=3: 表示要对基本块混淆这个操作连续进行3次
-mllvm -bcf_prob=40: 表示这些操作发生的概率为 40%

下面来一个例子体验一下

#include <stdlib.h>
int main(int argc, char** argv) {
  int a = atoi(argv[1]);
  if(a == 0)
    return 1;
  else
    return 10;
  return 0;
}

混淆前

混淆后

下面来分析一下源码BogusControlFlow.cpp
和符号替换一样,BogusControlFlow继承自FunctionPass

接着看向runOnFunction
这里同样调用toObfuscate函数来决定是否对函数F应用混淆,接着关注函数bogus和doF

bogus

bogus函数首先将本function的所有basicblock存放到一个list容器中


然后使用一个while循环调用addBogusFlow函数对选中的basicblock进行增加虚假控制流

addBogusFlow

接着进入addBogusFlow
先使用getFirstNonPHIOrDbgOrLifetime函数找到第一个既不是Phi指令、也不是Dbg指令、也不是Lifetime指令的指令的地址,然后调用splitBasicBlock函数根据上述指令的地址将一个basicblock一分为二

接着调用createAlteredBasicBlock函数对Original Basicblock进行拷贝生成一个名为"altered basicblock"(alteredBB)的basicblock,并对该basicblock加入一些垃圾指令

接着清除first basicblock和altered basicblock跟父节点的关系


设置跳转条件

接着创建了一个分支跳转指令(BranchInst).根据前面创建的恒为真的条件判断指令condition,如果条件为真(实际上总是为真),则跳转到原始基本块originalBB,如果条件为假(实际上不会出现这种情况,因为条件恒为真),则跳转到经过修改的基本块alteredBB

然后在alteredBB中设置了一个跳转指令,直接跳转到原始基本块originalBB

此时ir图如下

先获取获取到指向原始基本块末尾的迭代器i,在迭代器i所指向的指令位置前将原始基本块originalBB分割成两部分(OriginalBB和OriginalBBpart2)
然后关键的部分就是接着调用BranchInst的Create 方法创建一个分支跳转指令。传入的参数包括originalBBpart2、alteredBB、condition2以及originalBB。这样设置的目的是根据新创建的恒为真的条件判断指令condition2,如果条件为真(实际上由于设置为恒为真,总是为真),则跳转到originalBBpart2,如果条件为假(实际上不会出现这种情况),则跳转到 alteredBB。

ir控制流程图最终变为:

doF

遍历Module内的所有指令,并将所有的FCMP_TRUE分支指令替换为永真式(y<10 || x*x(x-1)%2 == 0)

控制流平坦化

把程序原有逻辑转化成多个case-swich的扁平状结构
-mllvm -fla: 激活控制流平坦化功能
-mllvm -split:激活基本块分裂
-mllvm -split_num=3: 使每个基本块上分裂3次

还是使用虚假控制流的示例代码


增加分裂次数

分析Flattening.cpp
开始还是一样的流程,转到runOnFunction函数

这里先调用createLowerSwitchPass去除switch,将switch结构换成if结构

下面关注到loopEntry(循环的入口基本块)和loopEnd(循环的出口基本块)

下面使创建loopEntry和loopEnd,且loopEntry,loopEnd基本块将在insert(firstBB)基本块之后插入到函数f的基本块序列中

接下来:

  • 将insert指向的基本块移动到loopEntry之前,然后创建从insert到loopEntry和loopEnd到loopEntry的跳转指令
  • 接着创建名为swDefault的基本块,并建立从swDefault到loopEnd的跳转
  • 创建指令switchI,设置其条件为load,以根据load的值决定指令的跳转方向
  • 移除函数f第一个基本块原有的终止器指令,然后创建从函数f到loopEntry第一个基本块的跳转指令

此时ir控制流程图就变成了这样

然后将保存在vector中的每一个basicblock都添加到switch-case语句中,每一个basicblock对应一个case,并且每个case的值都是一个随机值。同时修改每个basicblock块之间的跳转关系

最后ir的控制流程图就变成了大概下图的样子

ollvm移植到ndk

LLVM中的bin、lib、include目录覆盖NDK的toolchains/llvm/prebuilt/linux-86_64/目录下的bin,lib,include

在NDK目录下,将toolchains/llvm/prebuilt/linux-86_64/lib64目录下的clang文件夹覆盖toolchains/llvm/prebuilt/linux-86_64/lib目录下clang

在cpp目录下的CMakeLists.txt中添加混淆配置
例如:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -fla -mllvm -split -mllvm -split_num=3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -fla -mllvm -split -mllvm -split_num=3")

0 条评论
某人
表情
可输入 255