写一套自己的OLLVM
OLLVM
看完llvm就直奔Ollvm了
简介与安装
Obfuscator-LLVM(简称OLLVM)是2010年6月由瑞士西部应用科学大学(HEIG-VD)的信息安全小组发起的一个项目。 这个项目的目的是提供一个 LLVM 编译套件的开源分支,能够通过代码混淆和防篡改提供更高的软件安全性。
OLLVM 提供了三种经典的代码混淆:
-
控制流平坦化(Control Flow Flattening)
-
虚假控制流(Bogus Control Flow)
-
指令替代(Instruction Subsititution)
我们采用Docker来完成OLLVM的编译安装
先安docker吧
sudo apt install curl
curl -sSL https://get.daocloud.io/docker | sh
然后pull docker容器
docker pull nickdiego/ollvm-build
然后下载安装脚本和ollvm源码
git clone https://github.com/nickdiego/docker-ollvm.git
git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git
在docker-ollvm内的ollvm-build.sh的150行加入
DOCKER_CMD+=" -DLLVM_INCLUDE_TESTS=OFF"
大概长这样
改完就执行
chmod 777 ollvm-build.sh
sudo ./ollvm-build.sh ../obfuscator
在 obfuscator/build_release 目录执行指令创建软链接:
sudo ln ./bin/* /usr/bin/
就差不多了
控制流平坦化
按理来说应该是常量替代最简单,但这玩意太经典了,先看这个吧
原理
控制流平坦化指的是将正常控制流中基本块之间的跳转关系删除,用一个集中的分发块来调度基本块的执行顺序。他是以函数为单位进行混淆的。
可以看到,以前的控制流看不见了,现在是由一个分发器来控制基本块2-6的执行。
随便拿一个控制流平坦化的CFG来看看
最上面那个最大的框框就是入口块,入口块下面那个很小的框框是主分发块,左边那些小框框是子分发块,他们的作用都是负责跳转到下一个要执行的原基本块。
倒数第二排是混淆前的基本块,也就是说他们是程序真正有用的那些部分,上面的东西只是为了让他们按正确的执行流程来执行的玩意,最下面那一个是返回块,他会返回到主分发块。
可以发现CFG之前的图只有一个分发器,但CFG中有很多子分发块。这是因为控制流平坦化会采用一些大整数对每一个基本块编号,然后以switch case的形式选择基本块来执行,但x86汇编中没有switch,只能用jz jnz这种指令来代替执行。
现在我们应该能够理解控制流平坦化的混淆效果了。在看正常代码时,我们跟着正常的控制流很容易能分析出程序的执行顺序,以及每一段代码完成的工作,最终掌握代码逻辑。但是控制流平坦化后的控制流会使用一个隐晦的调度逻辑来执行基本块,想要通过大眼瞪的方法理清基本块的执行顺序是十分困难的。
实现流程
保存原基本块
将除入口块以外的以外的基本块保存到 vector 容器中,方便后续处理。如果入口块的终结指令是条件分支指令,则将该指令单独分离出来作为一个基本块,加入到 vector 容器的最前面,来保证入口块最后一条指令一定是非条件跳转指令。为什么这么做看CFG也差不多能想到了。
// 保存所有基本块
vector <BasicBlock *> origBB;
for (BasicBlock &BB : F) {
origBB.push_back(&BB);
}
// 去除vector的第一个基本块,也就是入口块
origBB.erase(origBB.begin());
BasicBlock &entryBB = F.getEntryBlock();
// 若第一个基本块的末尾是条件跳转,就将其作为新的基本块单独分离出来
if (BranchInst *br = dyn_cast <BranchInst> (entryBB.getTerminator())) {
// dyn_cast是一个类型判断+转换,若给定参数是我们想要的类型(<>里面的),就进行转换,得到BranchInst的指针,否则得到NULL指针
if (br -> isConditional()) {
BasicBlock *newBB = entryBB.splitBasicBlock(br, "newBB");
origBB.insert(origBB.begin(), newBB);
}
}
创建分发块和返回块
分发块用来调度基本块的执行顺序,我们需要一个入口块到分发块的非条件跳转。基本块执行完毕后都需要跳转到返回块,返回块会直接跳转到分发块。
// 创建分发块和返回块
BasicBlock *dispatchBB = BasicBlock::Create(F.getContext(), "dispatchBB", &F, &entryBB);
BasicBlock *returnBB = BasicBlock::Create(F.getContext(), "returnBB", &F, &entryBB);
BranchInst::Create(dispatchBB, returnBB);
entryBB.moveBefore(dispatchBB); // 这个是物理上的移动不是逻辑上的
// 去掉入口块末尾的跳转,并使他跳转到分发块dispatchBB
entryBB.getTerminator() -> eraseFromParent();
BranchInst *brDispatchBB = BranchInst::Create(dispatchBB, &entryBB);
实现分发块调度
在入口块中创建并初始化switch变量,在调度块中插入switch指令实现分发功能。将原基本块移动到返回块之前,并分配随机的case值,并将其添加到switch指令的分支中。
这里的移动也是物理移动,为了保证物理顺序和逻辑顺序一致。
// 在入口块插入alloca和store指令,来开辟空间给switch变量,switch变量初始值为随机值
int randNumCase = rand();
AllocaInst *swVarPtr = new AllocaInst(TYPE_I32, 0, "swVar.ptr", brDispatchBB);
new StoreInst(CONST_I32(randNumCase), swVarPtr, brDispatchBB);
// 在分发块插入load指令来读取switch变量
LoadInst *swVar = new LoadInst(TYPE_I32, swVarPtr, "swVar", false, dispatchBB);
// 在分发块插入switch指令实现基本块调度
BasicBlock *swDefault = BasicBlock::Create(F.getContext(), "swDefault", &F, returnBB);
BranchInst::Create(returnBB, swDefault);
SwitchInst *swInst = SwitchInst::Create(swVar, swDefault, 0, dispatchBB);
// 将原基本块插入到返回块之前,并分配case值
for (BasicBlock *BB : origBB) {
BB -> moveBefore(returnBB);
swInst -> addCase(CONST_I32(randNumCase), BB);
randNumCase = rand();
}
实现调度变量自动调整
在每个原基本块最后添加修改 switch 变量值的指令,以便返回分发块之后,能够正确执行到下一个基本块。我们需要删除原基本块末尾的跳转,使其结束执行后跳转到返回块,以便返回分发块进行下一次基本块的调度。
for (BasicBlock *BB : origBB) {
// BB -> getTerminator() -> getNumSuccessors()获取后继块的数量,以此判断当前终结指令的类型
// retn BB
if (BB -> getTerminator() -> getNumSuccessors() == 0) {
continue;
}
// 非条件跳转
else if (BB -> getTerminator() -> getNumSuccessors() == 1) {
BasicBlock *sucBB = BB -> getTerminator() -> getSuccessor(0);
BB -> getTerminator() -> eraseFromParent();
// 获取后继块对应的case值
ConstantInt *numCase = swInst -> findCaseDest(sucBB);
// 通过store将switch的值修改为后继块的case
new StoreInst(numCase, swVarPtr, BB);
BranchInst::Create(returnBB, BB);
}
// 条件跳转
else if (BB -> getTerminator() -> getNumSuccessors() == 2) {
// 会有真和假两个后继块
ConstantInt *numCaseTrue = swInst -> findCaseDest(BB -> getTerminator() -> getSuccessor(0));
ConstantInt *numCaseFalse = swInst -> findCaseDest(BB -> getTerminator() -> getSuccessor(1));
BranchInst *br = cast<BranchInst>(BB -> getTerminator());
// 通过select来决定修改为哪个后继块的case
SelectInst *sel = SelectInst::Create(br -> getCondition(), numCaseTrue, numCaseFalse, "", BB -> getTerminator());
BB -> getTerminator() -> eraseFromParent();
new StoreInst(sel, swVarPtr, BB);
BranchInst::Create(returnBB, BB);
}
}
修复 PHI 指令和逃逸变量
PHI 指令的值由前驱块决定,平坦化后所有原基本块的前驱块都变成了分发块,因此 PHI 指令发生了损坏。
逃逸变量指在一个基本块中定义,并且在另一个基本块被引用的变量。在原程序中某些基本块可能引用之前某个基本块中的变量,平坦化后原基本块之间不存在确定的前后关系了(由分发块决定),因此某些变量的引用可能会损坏。
修复的方法:将 PHI 指令和逃逸变量都转化为内存存取指令。
vector<PHINode*> origPHI;
vector<Instruction*> origReg;
BasicBlock &entryBB = F.getEntryBlock();
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
if (PHINode *PN = dyn_cast<PHINode>(&I)) {
origPHI.push_back(PN);
}
// 该变量在入口块中,并且他是一条alloca指令的话,他就不是逃逸变量;否则,如果他在其他基本块内被使用了,那他就是逃逸变量
else if (!(isa<AllocaInst>(&I) && I.getParent() == &entryBB)
&& I.isUsedOutsideOfBlock(&BB)) {
origReg.push_back(&I);
}
}
}
// DemotePHIToStack就是把PHI降级到栈中,reg就是寄存器,下面就是把寄存器降级到栈中,这样就改成了内存读取
for(PHINode *PN : origPHI) {
DemotePHIToStack(PN, entryBB.getTerminator());
}
for(Instruction *I : origReg) {
DemoteRegToStack(*I, entryBB.getTerminator());
}
最后这里可以考虑写个 Utils.cpp 来保存并调用,因为后面会用到很多次。在实现的时候可以考虑加入前面写的基本块分割。
虚假控制流
虚假控制流,即通过向正常控制流中插入若干不可达基本块(永远不会被执行的基本块)和由不透明谓词造成的虚假跳转,以产生大量垃圾代码干扰攻击者分析的混淆。
不透明谓词就是我们自己知道的的值,但是逆向者难以识别的变量。我们将通过这些变量创建虚假跳转来产生垃圾代码干扰分析。
举个例子
int main()
{
printf("Always True.");
}
int main()
{
int x = 0, y = 0;
if (10 * x <= 0 || y >= 0) printf("Always True.");
else printf("Always False.");
}
上面是源程序,下面是简单的虚假控制流的例子。他们都永远执行printf("Always True.")
。那么 else 语句和 if 语句都是干扰代码。
这只是个十分简单的情况,真实的情况更加复杂。虚假的跳转和冗余的不可达基本块导致了大量垃圾代码,严重干扰了攻击者的分析。拿 -J 的 ez_maze 看看
看起来十分麻烦,用 Clovershrub 的话来说就是:感觉壳没脱干净
可以发现,与控制流平坦化不同,经过虚假控制流混淆的控制流图呈长条状。
原理
虚假控制流是以基本块为单位进行混淆的,每个基本块要经过分裂、克隆、构建虚假跳转等操作。
首先把原基本块分裂成三个部分,然后再将 bodyBB 克隆为一个 cloneBB,并且,将以前的绝对跳转改成条件跳转。当然,修改后程序的执行流程依然和以前一样,因为他的条件将会是永真/永假的。红色箭头就是虚假跳转,他们根本不会被执行。
实现流程
基本块拆分
将基本块拆分成头部、中部和尾部三个基本块。
根据我们的经验,PHI 指令会堆积在基本块的最前端的部分。直接获取第一个不是 PHINode 的指令,那么所有的 PHI 指令都会在他之前。如果有例外再说吧(
void BogusControlFlow::bogus(BasicBlock *entryBB){
// 第一步,拆分得到 entryBB, bodyBB, endBB
// 其中所有的 PHI 指令都在 entryBB(如果有的话)
// endBB 只包含一条终结指令
// 通过 getFirstNonPHI 函数获取第一个不是 PHINode 的指令,以该指令为界限进行分割,得到 entryBB 和 bodyBB。
BasicBlock *bodyBB = entryBB->splitBasicBlock(entryBB->getFirstNonPHI(), "bodyBB");
// 以 bodyBB 的终结指令为界限进行分割,最终得到头部、中部和尾部三个基本块,也就是 entryBB, bodyBB 和 endBB。
BasicBlock *endBB = bodyBB->splitBasicBlock(bodyBB->getTerminator(), "endBB");
基本块克隆
克隆中间的 bodyBB,得到克隆块 cloneBB。这里的克隆块仍是一个独立的块,未添加任何跳转。
LLVM 实际上自带 CloneBasicBlock 函数,但该函数为不完全克隆,还需要做一些补充处理。
可以来看看 CloneBasicBlock 会产生些什么问题
orig:
%a = ...
%b = fadd %a, ....
clone:
%a.clone = ...
%b.clone = fadd %a, ... ; This references the old %a and not %a.clone!
fadd 这个操作,在克隆后的参数应该为 %a.clone,但是这里仍然使用的是原基本块的 %a,我们自写的函数要补充处理的就是改写这个错误。
我们把基本块的克隆操作写到 createCloneBasicBlock 函数中。
BasicBlock* llvm::createCloneBasicBlock(BasicBlock *BB){
// 克隆之前先修复所有逃逸变量
vector<Instruction*> origReg;
BasicBlock &entryBB = BB->getParent()->getEntryBlock();
for(Instruction &I : *BB){
if(!(isa<AllocaInst>(&I) && I.getParent() == &entryBB)
&& I.isUsedOutsideOfBlock(BB)){
origReg.push_back(&I);
}
}
for(Instruction *I : origReg){
DemoteRegToStack(*I, entryBB.getTerminator());
}
// CloneBasicBlock会提供一个返回值 ValueToValueMapTy,这相当于一个变量的映射表,他可以把 %a 映射为 %a.clone,我们可以通过这个东西对变量进行修复
ValueToValueMapTy VMap;
BasicBlock *cloneBB = CloneBasicBlock(BB, VMap, "cloneBB", BB->getParent());
// 对克隆基本块的引用进行修复
for(Instruction &I : *cloneBB){
for(int i = 0;i < I.getNumOperands();i ++){
// 通过 MapValue 根据映射表对指令的操作数进行替换
Value *V = MapValue(I.getOperand(i), VMap);
if(V){
I.setOperand(i, V);
}
}
}
return cloneBB;
}
构造虚假跳转
将 entryBB 到 bodyBB 的绝对跳转改为条件跳转,将 bodyBB 到 endBB 的绝对跳转改为条件跳转,添加 cloneBB 到 bodyBB 的绝对跳转。
这里 cloneBB 将是不可达基本块,与其有关的跳转都是虚假跳转。
// 1. 将 entryBB, bodyBB, cloneBB 末尾的绝对跳转移除
entryBB->getTerminator()->eraseFromParent();
bodyBB->getTerminator()->eraseFromParent();
cloneBB->getTerminator()->eraseFromParent();
// 2. 在 entryBB 和 bodyBB 的末尾插入条件恒为真的虚假比较指令
Value *cond1 = createBogusCmp(entryBB);
Value *cond2 = createBogusCmp(bodyBB);
// 3. 将 entryBB 到 bodyBB 的绝对跳转改为条件跳转
BranchInst::Create(bodyBB, cloneBB, cond1, entryBB);
// 4. 将 bodyBB 到 endBB的绝对跳转改为条件跳转
BranchInst::Create(endBB, cloneBB, cond2, bodyBB);
// 5. 添加 bodyBB.clone 到 bodyBB 的绝对跳转
BranchInst::Create(bodyBB, cloneBB);
看看恒为真的比较指令是怎么构造的
Value* BogusControlFlow::createBogusCmp(BasicBlock *insertAfter){
// if((y < 10 || x * (x + 1) % 2 == 0))
// 等价于 if(true)
Module *M = insertAfter->getModule();
// 创建全局变量x, y, 他们的值恒为0
// CommonLinkage 是连接方式
GlobalVariable *xptr = new GlobalVariable(*M, TYPE_I32, false, GlobalValue::CommonLinkage, CONST_I32(0), "x");
GlobalVariable *yptr = new GlobalVariable(*M, TYPE_I32, false, GlobalValue::CommonLinkage, CONST_I32(0), "y");
// 全局变量要靠 LoadInst 读取
LoadInst *x = new LoadInst(TYPE_I32, xptr, "", insertAfter);
LoadInst *y = new LoadInst(TYPE_I32, yptr, "", insertAfter);
ICmpInst *cond1 = new ICmpInst(*insertAfter, CmpInst::ICMP_SLT, y, CONST_I32(10)); // SLT: signed less than
BinaryOperator *op1 = BinaryOperator::CreateAdd(x, CONST_I32(1), "", insertAfter);
BinaryOperator *op2 = BinaryOperator::CreateMul(op1, x, "", insertAfter);
BinaryOperator *op3 = BinaryOperator::CreateURem(op2, CONST_I32(2), "", insertAfter);
ICmpInst *cond2 = new ICmpInst(*insertAfter, CmpInst::ICMP_EQ, op3, CONST_I32(0)); // EQ: equal
return BinaryOperator::CreateOr(cond1, cond2, "", insertAfter);
}
到此为止,虚假控制流的代码就实现了。实际上,在 OLLVM 的源码中,他对克隆块做了一些变异操作:插入了一些随机指令、替换了一些指令等。实际上这没啥必要,现存的去除虚假控制流的手段并没有去比较原块和克隆块是否相同,而且这样写出来的源码得有几百行(懒得写了.jpg)。
我们还会发现,虚假控制流并没有去修复 PHI 指令和逃逸变量。因为在基本块拆分的时候,我们的 PHI 指令全部集中在分裂后的第一个基本块内,也就是说他的前驱并没有改变,所以就不需要去修复他了;对于逃逸变量,虚假控制流的混淆前后块之间相对的执行顺序并没有改变,所以也不需要修复逃逸变量。
指令替代
原理
指令替代指将正常的二元运算指令(如加法、减法、异或等等),替换为等效而更复杂的指令序列,以达到混淆计算过程的目的。例如将 a+b
替换为 a - (-b)
,将 a ^ b
替换为 (~a & b) | (a & ~b)
等等。但是目前指令替代仅支持整数运算的替换,因为替换浮点指令有精度问题,会造成舍入的错误和误差。
指令替代是以指令为基本单位的混淆方式,函数的控制流没有发生变化,但是运算过程变得难以分辨。
指令替代的实现思路十分简单。扫描所有指令,对目标指令(加法、减法、与或非、异或)进行替换
bool Substitution::runOnFunction(Function &F){
for(int i = 0;i < ObfuTime;i ++){
for(BasicBlock &BB : F){
vector<Instruction*> origInst;
for(Instruction &I : BB){
origInst.push_back(&I);
}
for(Instruction *I : origInst){
if(isa<BinaryOperator>(I)){
BinaryOperator *BI = cast<BinaryOperator>(I);
substitute(BI);
}
}
}
}
}
实现流程
加法替换
形如 a = b + c 的式子有四种替换方案
- addNeg: \(a = b - (-c)\)
- addDoubleNeg: \(a = -(-b + (-c))\)
- addRand: \(r = rand(); a = b + r; a = a + c; a = a - r\)
- addRand2: \(r = rand(); a = b - r; a = a + c; a = a + r\)
减法替换
形如 a = b - c 的式子有三种替换方案
- subNeg: \(a = b + (-c)\)
- subRand: \(r = rand(); a = b + r; a = a - c; a = a - r\)
- subRand2: \(r = rand(); a = b - r; a = a - c; a = a + r\)
与替换
形如 a = b & c 的式子有两种替换方案
- andSubstitude:
a = (b ^ ~c) & b
- andSubstitudeRand:
a = ~(~b|~c)&(r|~r)
可以画一下真值表看看效果
或替换
形如 a = b |c 的式子有两种替换方案
- orSubstitude:
a = (b & c) | (b ^ c)
- orSUbstitudeRand:
a = ~(~b & ~c) & (r | ~r)
可以画一下真值表看看效果 z
异或替换
形如 a = b ^ c 的式子有两种替换方案
- xorSubstitude:
a = (~a & b) | (a & ~b)
- xorSubstitudeRand:
a = (b ^ r) ^ (c ^ r) = (~b & r | b & ~r) ^ (~c & r | c & ~r)
#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/CommandLine.h"
#include "Utils.h"
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace llvm;
using std::vector;
#define NUMBER_ADD_SUBST 4
#define NUMBER_SUB_SUBST 3
#define NUMBER_AND_SUBST 2
#define NUMBER_OR_SUBST 2
#define NUMBER_XOR_SUBST 2
// 混淆次数,混淆次数越多混淆结果越复杂
static cl::opt<int> ObfuTime("sub_loop", cl::init(1), cl::desc("Obfuscate a function <obfu_time> time(s)."));
namespace{
class Substitution : public FunctionPass {
public:
static char ID;
Substitution() : FunctionPass(ID) {
srand(time(NULL));
}
bool runOnFunction(Function &F);
void substitute(BinaryOperator *BI);
// 替换 Add 指令
void substituteAdd(BinaryOperator *BI);
// 加法替换:a = b + c -> a = b - (-c)
void addNeg(BinaryOperator *BI);
// 加法替换:a = b + c -> a = -(-b + (-c))
void addDoubleNeg(BinaryOperator *BI);
// 加法替换:a = b + c -> r = rand (); a = b + r; a = a + c; a = a - r
void addRand(BinaryOperator *BI);
// 加法替换:a = b + c -> r = rand (); a = b - r; a = a + b; a = a + r
void addRand2(BinaryOperator *BI);
// 替换 Sub 指令
void substituteSub(BinaryOperator *BI);
// 减法替换:a = b - c -> a = b + (-c)
void subNeg(BinaryOperator *BI);
// 减法替换:a = b - c -> r = rand (); a = b + r; a = a - c; a = a - r
void subRand(BinaryOperator *BI);
// 减法替换:a = b - c -> a = b - r; a = a - c; a = a + r
void subRand2(BinaryOperator *BI);
// 替换 And 指令
void substituteAnd(BinaryOperator *BI);
// 与替换:a = b & c -> a = (b ^ ~c) & b
void andSubstitute(BinaryOperator *BI);
// 与替换:a = b & c -> a = ~(~b | ~c) & (r | ~r)
void andSubstituteRand(BinaryOperator *BI);
// 替换 Or 指令
void substituteOr(BinaryOperator *BI);
// 或替换:a = b | c -> a = (b & c) | (b ^ c)
void orSubstitute(BinaryOperator *BI);
// 或替换:a = b | c -> a = ~(~b & ~c) & (r | ~r)
void orSubstituteRand(BinaryOperator *BI);
// 替换 Xor 指令
void substituteXor(BinaryOperator *BI);
// 异或替换:a = b ^ c -> a = ~b & c | b & ~c
void xorSubstitute(BinaryOperator *BI);
// 异或替换:a = b ^ c -> (b ^ r) ^ (c ^ r) <=> (~b & r | b & ~r) ^ (~c & r | c & ~r)
void xorSubstituteRand(BinaryOperator *BI);
};
}
bool Substitution::runOnFunction(Function &F){
for(int i = 0;i < ObfuTime;i ++){
for(BasicBlock &BB : F){
vector<Instruction*> origInst;
for(Instruction &I : BB){
origInst.push_back(&I);
}
for(Instruction *I : origInst){
if(isa<BinaryOperator>(I)){
BinaryOperator *BI = cast<BinaryOperator>(I);
substitute(BI);
}
}
}
}
}
void Substitution::substitute(BinaryOperator *BI){
bool flag = true;
switch (BI->getOpcode()) {
case BinaryOperator::Add:
substituteAdd(BI);
break;
case BinaryOperator::Sub:
substituteSub(BI);
break;
case BinaryOperator::And:
substituteAnd(BI);
break;
case BinaryOperator::Or:
substituteOr(BI);
break;
case BinaryOperator::Xor:
substituteXor(BI);
break;
default:
flag = false;
break;
}
if(flag){
BI->eraseFromParent();
}
}
void Substitution::substituteAdd(BinaryOperator *BI){
int choice = rand() % NUMBER_ADD_SUBST;
switch (choice) {
case 0:
addNeg(BI);
break;
case 1:
addDoubleNeg(BI);
break;
case 2:
addRand(BI);
break;
case 3:
addRand2(BI);
break;
default:
break;
}
}
void Substitution::addNeg(BinaryOperator *BI){
BinaryOperator *op;
op = BinaryOperator::CreateNeg(BI->getOperand(1), "", BI);
op = BinaryOperator::CreateSub(BI->getOperand(0), op, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::addDoubleNeg(BinaryOperator *BI){
BinaryOperator *op, *op1, *op2;
op1 = BinaryOperator::CreateNeg(BI->getOperand(0), "", BI);
op2 = BinaryOperator::CreateNeg(BI->getOperand(1), "", BI);
op = BinaryOperator::CreateAdd(op1, op2, "", BI);
op = BinaryOperator::CreateNeg(op, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::addRand(BinaryOperator *BI){
ConstantInt *r = (ConstantInt*)CONST(BI->getType(), rand());
BinaryOperator *op, *op1, *op2;
op = BinaryOperator::CreateAdd(BI->getOperand(0), r, "", BI);
op = BinaryOperator::CreateAdd(op, BI->getOperand(1), "", BI);
op = BinaryOperator::CreateSub(op, r, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::addRand2(BinaryOperator *BI){
ConstantInt *r = (ConstantInt*)CONST(BI->getType(), rand());
BinaryOperator *op, *op1, *op2;
op = BinaryOperator::CreateSub(BI->getOperand(0), r, "", BI);
op = BinaryOperator::CreateAdd(op, BI->getOperand(1), "", BI);
op = BinaryOperator::CreateAdd(op, r, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::substituteSub(BinaryOperator *BI){
int choice = rand() % NUMBER_SUB_SUBST;
switch (choice) {
case 0:
subNeg(BI);
break;
case 1:
subRand(BI);
break;
case 2:
subRand2(BI);
break;
default:
break;
}
}
void Substitution::subNeg(BinaryOperator *BI){
BinaryOperator *op;
op = BinaryOperator::CreateNeg(BI->getOperand(1), "", BI);
op = BinaryOperator::CreateAdd(BI->getOperand(0), op, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::subRand(BinaryOperator *BI){
ConstantInt *r = (ConstantInt*)CONST(BI->getType(), rand());
BinaryOperator *op, *op1, *op2;
op = BinaryOperator::CreateAdd(BI->getOperand(0), r, "", BI);
op = BinaryOperator::CreateSub(op, BI->getOperand(1), "", BI);
op = BinaryOperator::CreateSub(op, r, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::subRand2(BinaryOperator *BI){
ConstantInt *r = (ConstantInt*)CONST(BI->getType(), rand());
BinaryOperator *op, *op1, *op2;
op = BinaryOperator::CreateSub(BI->getOperand(0), r, "", BI);
op = BinaryOperator::CreateSub(op, BI->getOperand(1), "", BI);
op = BinaryOperator::CreateAdd(op, r, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::substituteXor(BinaryOperator *BI){
int choice = rand() % NUMBER_XOR_SUBST;
switch (choice) {
case 0:
xorSubstitute(BI);
break;
case 1:
xorSubstituteRand(BI);
break;
default:
break;
}
}
void Substitution::xorSubstitute(BinaryOperator *BI){
BinaryOperator *op, *op1, *op2, *op3;
op1 = BinaryOperator::CreateNot(BI->getOperand(0), "", BI);
op1 = BinaryOperator::CreateAnd(op1, BI->getOperand(1), "", BI);
op2 = BinaryOperator::CreateNot(BI->getOperand(1), "", BI);
op2 = BinaryOperator::CreateAnd(BI->getOperand(0), op2, "", BI);
op = BinaryOperator::CreateOr(op1, op2, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::xorSubstituteRand(BinaryOperator *BI){
ConstantInt *r = (ConstantInt*)CONST(BI->getType(), rand());
BinaryOperator *op, *op1, *op2, *op3;
op1 = BinaryOperator::CreateNot(BI->getOperand(0), "", BI);
op1 = BinaryOperator::CreateAnd(op1, r, "", BI);
op2 = BinaryOperator::CreateNot(r, "", BI);
op2 = BinaryOperator::CreateAnd(BI->getOperand(0), op2, "", BI);
op = BinaryOperator::CreateOr(op1, op2, "", BI);
op1 = BinaryOperator::CreateNot(BI->getOperand(1), "", BI);
op1 = BinaryOperator::CreateAnd(op1, r, "", BI);
op2 = BinaryOperator::CreateNot(r, "", BI);
op2 = BinaryOperator::CreateAnd(BI->getOperand(1), op2, "", BI);
op3 = BinaryOperator::CreateOr(op1, op2, "", BI);
op = BinaryOperator::CreateXor(op, op3, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::substituteAnd(BinaryOperator *BI){
int choice = rand() % NUMBER_AND_SUBST;
switch (choice) {
case 0:
andSubstitute(BI);
break;
case 1:
andSubstituteRand(BI);
break;
default:
break;
}
}
void Substitution::andSubstitute(BinaryOperator *BI){
BinaryOperator *op;
op = BinaryOperator::CreateNot(BI->getOperand(1), "", BI);
op = BinaryOperator::CreateXor(BI->getOperand(0), op, "", BI);
op = BinaryOperator::CreateAnd(op, BI->getOperand(0), "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::andSubstituteRand(BinaryOperator *BI){
ConstantInt *r = (ConstantInt*)CONST(BI->getType(), rand());
BinaryOperator *op, *op1;
op = BinaryOperator::CreateNot(BI->getOperand(0), "", BI);
op1 = BinaryOperator::CreateNot(BI->getOperand(1), "", BI);
op = BinaryOperator::CreateOr(op, op1, "", BI);
op = BinaryOperator::CreateNot(op, "", BI);
op1 = BinaryOperator::CreateNot(r, "", BI);
op1 = BinaryOperator::CreateOr(r, op1, "", BI);
op = BinaryOperator::CreateAnd(op, op1, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::substituteOr(BinaryOperator *BI){
int choice = rand() % NUMBER_OR_SUBST;
switch (choice) {
case 0:
orSubstitute(BI);
break;
case 1:
orSubstituteRand(BI);
break;
default:
break;
}
}
void Substitution::orSubstitute(BinaryOperator *BI){
BinaryOperator *op, *op1;
op = BinaryOperator::CreateAnd(BI->getOperand(0), BI->getOperand(1), "", BI);
op1 = BinaryOperator::CreateXor(BI->getOperand(0), BI->getOperand(1), "", BI);
op = BinaryOperator::CreateOr(op, op1, "", BI);
BI->replaceAllUsesWith(op);
}
void Substitution::orSubstituteRand(BinaryOperator *BI){
ConstantInt *r = (ConstantInt*)CONST(BI->getType(), rand());
BinaryOperator *op, *op1;
op = BinaryOperator::CreateNot(BI->getOperand(0), "", BI);
op1 = BinaryOperator::CreateNot(BI->getOperand(1), "", BI);
op = BinaryOperator::CreateAnd(op, op1, "", BI);
op = BinaryOperator::CreateNot(op, "", BI);
op1 = BinaryOperator::CreateNot(r, "", BI);
op1 = BinaryOperator::CreateOr(r, op1, "", BI);
op = BinaryOperator::CreateAnd(op, op1, "", BI);
BI->replaceAllUsesWith(op);
}
char Substitution::ID = 0;
static RegisterPass<Substitution> X("sub", "Replace a binary instruction with equivalent instructions.");
指令替代的流行程度不如前面那俩,因为他比较容易被 ida 给优化掉。
变体 - 随机控制流
随机控制流是虚假控制流的一种变体,随机控制流通过克隆基本块,以及添加随机跳转(随机跳转到两个功能相同的基本块中的一个)来混淆控制流。与虚假控制流不同,随机控制流中不存在不可达基本块和不透明谓词,因此用于去除虚假控制流的手段(消除不透明谓词、符号执行获得不可达基本块后去除)失效。
随机控制流的控制流图与虚假控制流类似,都呈长条形。随机的跳转和冗余的不可达基本块导致了大量垃圾代码,严重干扰了攻击者的分析。并且 rdrand 指令(一条十分偏僻的指令)可以干扰某些符号执行引擎(如 angr)的分析。(我只会angr,好死)
原理
随机控制流同样是以基本块为单位进行混淆的,每个基本块要经过分裂、克隆、构造随机跳转和构造虚假随机跳转四个操作。
randVar % 2
结果不是 1 就是 0 ,最终的执行流程是 entryBB -> bodyBB -> endBB 或 entryBB -> cloneBB -> endBB 中的一种,两种执行是一样的效果。他的主要效果就是构造随机的跳转来干扰分析。
实现流程
基本块拆分
和虚假控制流完全一致
基本块克隆
和虚假控制流差不多。将中间的基本块进行克隆,这里可以选择对基本块进行变异,但不能改变基本块的功能。这一点和虚假控制流不一样,虚假控制流的克隆块是不可达的,但是随机控制流里面的克隆块是可达的。
克隆时代码基本可以沿用虚假控制流的代码,但是在克隆前需要额外修复逃逸变量,这个的原理和控制流平坦化里面的是一样的。
构造随机跳转
将生成随机数的指令插入到 entryBB ,将生成的随机数命名为 randVar,并在 entryBB 后插入基于 randVar 的随机跳转指令。
向 entryBB 中插入生成随机数的指令和随机跳转,使其能够随机跳转到 bodyBB 或者 bodyBB 的克隆块。其中随机数指令我们可以使用 LLVM 的内置函数 rdrand。
// 在 entryBB 后插入随机跳转,使其能够随机跳转到第 bodyBB 或其克隆基本块 cloneBB
entryBB->getTerminator()->eraseFromParent();
Function *rdrand = Intrinsic::getDeclaration(entryBB->getModule(), Intrinsic::x86_rdrand_32);
CallInst *randVarStruct = CallInst::Create(rdrand->getFunctionType(), rdrand, "", entryBB);
// 通过 rdrand 内置函数获取随机数
Value *randVar = ExtractValueInst::Create(randVarStruct, 0, "", entryBB);
void RandomControlFlow::insertRandomBranch(Value *randVar, BasicBlock *ifTrue, BasicBlock *ifFalse, BasicBlock *insertAfter){
// 对随机数进行等价变换
Value *alteredRandVar = alterVal(randVar, insertAfter);
Value *randMod2 = BinaryOperator::Create(Instruction::And, alteredRandVar, CONST_I32(1), "", insertAfter);
ICmpInst *condition = new ICmpInst(*insertAfter, ICmpInst::ICMP_EQ, randMod2, CONST_I32(1));
BranchInst::Create(ifTrue, ifFalse, condition, insertAfter);
}
Value* RandomControlFlow::alterVal(Value *startVar,BasicBlock *insertAfter){
uint32_t code = rand() % 3;
Value *result;
if(code == 0){
//x = x * (x + 1) - x^2
BinaryOperator *op1 = BinaryOperator::Create(Instruction::Add, startVar, CONST_I32(1), "", insertAfter);
BinaryOperator *op2 = BinaryOperator::Create(Instruction::Mul, startVar, op1, "", insertAfter);
BinaryOperator *op3 = BinaryOperator::Create(Instruction::Mul, startVar, startVar, "", insertAfter);
BinaryOperator *op4 = BinaryOperator::Create(Instruction::Sub, op2, op3, "", insertAfter);
result = op4;
}else if(code == 1){
//x = 3 * x * (x - 2) - 3 * x^2 + 7 * x
BinaryOperator *op1 = BinaryOperator::Create(Instruction::Mul, startVar, CONST_I32(3), "", insertAfter);
BinaryOperator *op2 = BinaryOperator::Create(Instruction::Sub, startVar, CONST_I32(2), "", insertAfter);
BinaryOperator *op3 = BinaryOperator::Create(Instruction::Mul, op1, op2, "", insertAfter);
BinaryOperator *op4 = BinaryOperator::Create(Instruction::Mul, startVar, startVar, "", insertAfter);
BinaryOperator *op5 = BinaryOperator::Create(Instruction::Mul, op4, CONST_I32(3), "", insertAfter);
BinaryOperator *op6 = BinaryOperator::Create(Instruction::Mul, startVar, CONST_I32(7), "", insertAfter);
BinaryOperator *op7 = BinaryOperator::Create(Instruction::Sub, op3, op5, "", insertAfter);
BinaryOperator *op8 = BinaryOperator::Create(Instruction::Add, op6, op7, "", insertAfter);
result = op8;
}else if(code == 2){
//x = (x - 1) * (x + 3) - (x + 4) * (x - 3) - 9
BinaryOperator *op1 = BinaryOperator::Create(Instruction::Sub, startVar, CONST_I32(1), "", insertAfter);
BinaryOperator *op2 = BinaryOperator::Create(Instruction::Add, startVar, CONST_I32(3), "", insertAfter);
BinaryOperator *op3 = BinaryOperator::Create(Instruction::Add, startVar, CONST_I32(4), "", insertAfter);
BinaryOperator *op4 = BinaryOperator::Create(Instruction::Sub, startVar, CONST_I32(3), "", insertAfter);
BinaryOperator *op5 = BinaryOperator::Create(Instruction::Mul, op1, op2, "", insertAfter);
BinaryOperator *op6 = BinaryOperator::Create(Instruction::Mul, op3, op4, "", insertAfter);
BinaryOperator *op7 = BinaryOperator::Create(Instruction::Sub, op5, op6, "", insertAfter);
BinaryOperator *op8 = BinaryOperator::Create(Instruction::Sub, op7, CONST_I32(9), "", insertAfter);
result = op8;
}
return result;
}
构造虚假随机跳转
在 bodyBB 和 cloneBB 后插入虚假随机跳转指令(实际上仍会直接跳转到 endBB)
差不多就是这个效果
// 添加 bodyBB 到 bodyBB.clone 的虚假随机跳转
bodyBB->getTerminator()->eraseFromParent();
insertRandomBranch(randVar, endBB, cloneBB, bodyBB);
// 添加 bodyBB.clone 到 bodyBB 的虚假随机跳转
cloneBB->getTerminator()->eraseFromParent();
insertRandomBranch(randVar, bodyBB, endBB, cloneBB);
变体 - 常量替代
原理
常量替代指将二元运算指令(如加法、减法、异或等等)中使用的常数,替换为等效而更复杂的表达式,以达到混淆计算过程或某些特殊常量的目的。例如将 TEA 加密中使用的常量 0x9e3779b 替换为 \(12167*16715+18858*32146-643678438\)。这种特征值替换应该能卡住 findcrypt 之类的插件。与指令替代相同,常量替代目前仅支持整数常量的替换,这次的代码仅支持 32 位整数。
类似于指令替代,函数的控制流没有发生变化,但是运算过程变得难以分辨。
另外,常量替代可进一步拓展为常量数组的替代和字符串替代,例如常量数组替代可以抹去 AES, DES 等加密算法中特征数组,字符串替代可以防止攻击者通过字符串定位关键代码。
实现流程
大体思路
扫描所有指令,对目标指令(操作数类型为32位整数)进行替换
for(Instruction *I : origInst){
// 只对二元运算指令中的常量进行替换
if(BinaryOperator *BI = dyn_cast<BinaryOperator>(I)){
// 仅对整数进行替换
if(BI->getType()->isIntegerTy(32)){
substitute(BI);
}
}
}
替换方案
- 线性替换:val -> ax + by + c,其中 val 为原常量 a, b 为随机常量 x, y 为随机全局变量 c = val - (ax + by)
- 按位运算替换:val -> (x << 5 | y >> 3) ^ c,其中 val 为原常量x, y 为随机全局变量 c = val ^ (x << 5 | y >> 3)
现在该看反混淆了呜呜呜