2022年7月

0x01 指令替换基本介绍

指令替换就是将正常的二元运算指令(如加法、减法、异或等等),替换为等效而更复杂的指令序列,以达到混淆计算过程的目的。它仅支持整数运算的替换,因为替换浮点指令会造成舍入的错误和误差。
最近这四篇LLVM混淆的文章都是来自看雪“LLVM与代码混淆技术”这门课,推荐大家可以去看一下绝对物超所值,感谢34r7hm4n的倾情讲解。

例如:

a = b + c 
    ===> a = b - (-c)
    ===> a = -(-b + (-c))

该混淆唯一的难点就在于找到和原指令等效的复杂指令,并将所有的原指令替换成复杂指令即可,其他都是重复操作没啥特别的。

0x02 实现方法

实现复杂指令替换过程中主要注意要将创建的每条指令插到BI(当前原指令,这种只是物理上的插入并不是逻辑上的,所以对原来的指令没有影响)前面,后续利用replaceAllUsesWith函数将新创建的复杂指令把原指令在物理上替换掉就行。

保存原指令块,switch-case判断进入对应混淆

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了,原BI没有地方执行了
        BI->eraseFromParent();
    }
}

1. 加法替换

image-20220709211305441

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);
}

2. 减法替换

image-20220709211846015

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);
}

3. 与替换

image-20220709212107850

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);
}

4. 或替换

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);
}

5. 异或替换

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);
}

0x03 参考链接

https://www.kanxue.com/book-88-2113.htm

https://github.com/bluesadi/Pluto-Obfuscator/blob/kanxue/Transforms/src/Substitution.cpp

0x01 虚假控制流基本介绍

简单的说就是在原来的程序流程中克隆原基本块,利用不透明谓词将克隆的基本块作为永不可达的虚假分支,并在这个不可达的虚假分支中随机添加垃圾指令,以达到程序混淆的效果,得到的程序流程图和控制流平坦化不同,是程长条型的。

image-20220708202046640

所谓不透明谓词就是:

“不透明谓词是指一个表达式,他的值在执行到某处时,对程序员而言必然是已知的,但是由于某种原因,编译器或者说静态分析器无法推断出这个值,只能在运行时确定。”简单来说是程序员在编写的时候知道该表达式的结果,但是IDA在分析的时候无法分析出该结果,达到混淆的目的。

0x02 实现方法

image-20220708212444838

添加混淆轮次参数

// 混淆次数,混淆次数越多混淆结果越复杂
static cl::opt<int> obfuTimes("bcf_loop", cl::init(1), cl::desc("Obfuscate a function <bcf_loop> time(s)."));

1. 基本块拆分

将基本块拆分成头部、中部和尾部三个基本块。

通过 getFirstNonPHI 函数获取第一个不是 PHINode 的指令,以该指令为界限进行分割,得到 entryBB 和 bodyBB。以 bodyBB 的终结指令为界限进行分割,最终得到头部、中部和尾部三个基本块,也就是 entryBB, bodyBB 和 endBB。

// 第一步,拆分得到 entryBB, bodyBB, endBB
// 其中所有的 PHI 指令都在 entryBB(如果有的话)
// endBB 只包含一条终结指令
BasicBlock *bodyBB = entryBB->splitBasicBlock(entryBB->getFirstNonPHI(), "bodyBB");
BasicBlock *endBB = bodyBB->splitBasicBlock(bodyBB->getTerminator(), "endBB");

2. 基本块克隆

LLVM 自带 CloneBasicBlock 函数,但该函数为不完全克隆,在克隆的基本块中,仍然引用了原基本块中的 %a 变量,该引用是非法的,故需要将 %a 映射为 %a.clone,所以还需要做优化。

image-20220708212741779

// 第二步,克隆 bodyBB 得到克隆块 cloneBB
BasicBlock *cloneBB = createCloneBasicBlock(bodyBB);
BasicBlock* 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());
    }
    
    ValueToValueMapTy VMap;
    BasicBlock *cloneBB = CloneBasicBlock(BB, VMap, "cloneBB", BB->getParent());
    // 对克隆基本块的引用进行修复
    for(Instruction &I : *cloneBB){
        for(int i = 0;i < I.getNumOperands();i ++){
            Value *V = MapValue(I.getOperand(i), VMap);
            if(V){
                I.setOperand(i, V);
            }
        }
    }
    return cloneBB;
}

3. 构造虚假跳转

将 entryBB 到 bodyBB 的绝对跳转改为条件跳转;将 bodyBB 到 endBB 的绝对跳转改为条件跳转;添加 cloneBB 到 bodyBB 的绝对跳转

image-20220708213030706

// 第三步,构造虚假跳转
// 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);

createBogusCmp函数的具体实现,构造恒成立式子

Value* BogusControlFlow::createBogusCmp(BasicBlock *insertAfter){
    // if((y < 10 || x * (x + 1) % 2 == 0))
    // 等价于 if(true)
    Module *M = insertAfter->getModule();
    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 *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));
    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));
    return BinaryOperator::CreateOr(cond1, cond2, "", insertAfter);
}

0x03 参考资料

https://www.anquanke.com/post/id/212768

https://bbs.pediy.com/thread-266201.htm

https://www.zhihu.com/question/46259412/answer/199689652

https://github.com/obfuscator-llvm/obfuscator/blob/llvm-4.0/lib/Transforms/Obfuscation/BogusControlFlow.cpp

https://github.com/bluesadi/Pluto-Obfuscator/blob/kanxue/Transforms/src/BogusControlFlow.cpp

0x01 控制流平坦化基本介绍

控制流平坦化是指将正常程序控制流中基本块之间的跳转关练删除,用一个集中的主分发块来调度基本块的执行顺序。相当于把原有程序正常的逻辑改为一个循环嵌套一个switch的逻辑。

正常情况:

正常情况

控制流平坦化之后:

混淆后

控制流平坦化的基本结构如下:

image-20220708115813228

  • 入口块:进入函数第一个执行的基本块
  • 分发块:负责跳转到下一个要执行的原基本块
  • 原基本块:混淆之前的基本块,实际完成程序工作的基本块
  • 返回块:返回到主分发块

修改了程序的控制流,导致逆向分析人员不容易直接的理清程序执行流程,增加分析难度。

0x02 实现方式

本节以https://github.com/bluesadi/Pluto-Obfuscator/tree/kanxue 项目为基准进行分析。主要的代码实现分为五大块如下图所示:

image-20220708122724034

1. 保存原基本块

将除入口块以外的以外的基本块保存到 vector 容器中,方便后续处理。如果入口块的终结指令是条件分支指令,则将该指令单独分离出来作为一个基本块,加入到 vector 容器的最前面。

    // 将除入口块(第一个基本块)以外的基本块保存到一个 vector 容器中,便于后续处理
    // 首先保存所有基本块
    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())){
        if(br->isConditional()){
            BasicBlock *newBB = entryBB.splitBasicBlock(br, "newBB");
            origBB.insert(origBB.begin(), newBB);
        }
    }

2. 创建分发块和返回块

除了原基本块之外,我们还要续创建一个分发块来调度基本块的执行顺序。并建立入口块到分发块的绝对跳转。再创建一个返回块,原基本块执行完后都需要跳转到这个返回块,返回块会直接跳转到分发块进行下一次的基本块跳转。

// 创建分发块和返回块
    BasicBlock *dispatchBB = BasicBlock::Create(*CONTEXT, "dispatchBB", &F, &entryBB);
    BasicBlock *returnBB = BasicBlock::Create(*CONTEXT, "returnBB", &F, &entryBB);
    BranchInst::Create(dispatchBB, returnBB);
    entryBB.moveBefore(dispatchBB);
    // 去除第一个基本块末尾的跳转
    entryBB.getTerminator()->eraseFromParent();
    // 使第一个基本块跳转到dispatchBB
    BranchInst *brDispatchBB = BranchInst::Create(dispatchBB, &entryBB);

3. 实现分发块调度

在入口块中创建并初始化 switch 要使用的变量,在调度块中插入switch-case 指令实现分发功能。将原基本块移动到返回块之前,并给每一个原基本块分配随机的 case 值,并将其添加到 switch 指令的对应case分支中。

// 在入口块插入alloca和store指令创建并初始化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(*CONTEXT, "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();
    }

4. 实现调度变量自动调整

在每个原基本块最后添加修改 switch 要使用的变量值的指令,以便返回分发块之后,能够正确执行到下一个基本块。删除原基本块末尾的跳转,使其结束执行后跳转到返回块,这一步需要注意判断原基本块末尾跳转的语句。(类似于VMP3每一个handler的末尾指定下一个要跳转的handler)

     // 在每个基本块最后添加修改switch变量的指令和跳转到返回块的指令
    for(BasicBlock *BB : origBB){
        // retn BB
        if(BB->getTerminator()->getNumSuccessors() == 0){
            continue;
        }
        // 非条件跳转
        else if(BB->getTerminator()->getNumSuccessors() == 1){
            BasicBlock *sucBB = BB->getTerminator()->getSuccessor(0);
            BB->getTerminator()->eraseFromParent();
            ConstantInt *numCase = swInst->findCaseDest(sucBB);
            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());
            SelectInst *sel = SelectInst::Create(br->getCondition(), numCaseTrue, numCaseFalse, "", BB->getTerminator());
            BB->getTerminator()->eraseFromParent();
            new StoreInst(sel, swVarPtr, BB);
            BranchInst::Create(returnBB, BB);
        }
    }

当原基本块出现switch-case等大于2个分支的情况时,我们可以在优化过程中使用lowerswitch将其变成只有2个及以下分支的状态,使用命令如下:

opt -lowerswitch -S TestProgram_orig.ll -o TestProgram_lowerswitch.ll

关于lowerswitch的使用也可以直接在代码中实现,该项目是在LLVM12.0.1中编译的,所以不能直接使用createLowerSwitchPass函数否则会导致崩溃,应该在Flattening中添加如下函数

void getAnalysisUsage(AnalysisUsage &AU) const override{
    errs() << "Require LowerSwitchPass\r\n";
    AU.addRequiredID(LowerSwitchID);
    FunctionPass::getAnalysisUsage(AU);
}

image-20220708144027160

5. 修复PHI指令和逃逸变量

PHI 指令的值由前驱块决定,平坦化后所有原基本块的前驱块都变成了分发块,因此 PHI 指令发生了损坏。

逃逸变量指在一个基本块中定义,并且在另一个基本块被引用的变量。在原程序中某些基本块可能引用之前某个基本块中的变量,平坦化后原基本块之间不存在确定的前后关系了(由分发块决定),因此某些变量的引用可能会损坏。

修复的方法是,将 PHI 指令和逃逸变量都转化为内存存取指令。

void fixStack(Function &F) {
    vector<PHINode*> origPHI;
    vector<Instruction*> origReg;
    BasicBlock &entryBB = F.getEntryBlock();
    // 搜索PHI指令和逃逸变量添加到对应vector容器
    for(BasicBlock &BB : F){
        for(Instruction &I : BB){
            if(PHINode *PN = dyn_cast<PHINode>(&I)){
                origPHI.push_back(PN);
            }else if(!(isa<AllocaInst>(&I) && I.getParent() == &entryBB) 
                && I.isUsedOutsideOfBlock(&BB)){
                origReg.push_back(&I);
            }
        }
    }
    for(PHINode *PN : origPHI){
        DemotePHIToStack(PN, entryBB.getTerminator());
    }
    for(Instruction *I : origReg){
        DemoteRegToStack(*I, entryBB.getTerminator());
    }
}

0x03 参考链接

https://security.tencent.com/index.php/blog/msg/112

https://www.kanxue.com/book-88-2111.htm

https://github.com/bluesadi/Pluto-Obfuscator/blob/kanxue/Transforms/src/Flattening.cpp

https://github.com/obfuscator-llvm/obfuscator/blob/llvm-4.0/lib/Transforms/Obfuscation/Flattening.cpp

https://www.52pojie.cn/thread-1369130-1-1.html

0x01 LLVM环境搭建

该小节环境搭建和原版OLLVM混淆使用无关,仅为后续LLVM PASS编写做准备,如仅需要进行OLLVM混淆该小节可跳过

我选择的环境为Ubuntu 18.04、LLVM 12.0.1、CMake 3.16.6

先下载LLVM和Clang的源码并利用CMake进行源码编译

https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.1

image-20220706151310564

image-20220706151325543

因为Ubuntu 18.04利用包管理器默认安装的CMake最高版本达不到LLVM编译需求,所以我们需要自行安装,具体命令如下:

wget http://www.cmake.org/files/v3.16/cmake-3.16.6.tar.gz
tar xf cmake-3.16.6.tar.gz
cd cmake-3.16.6
sudo apt-get install build-essential
sudo apt-get install libssl-dev
sudo chmod -R 777 cmake-3.16.6
./bootstrap
make
sudo make install
cmake --version

然后如下图创建build、llvm、clang目录,将llvm和clang对应源码放入目录中,后面来编写build.sh文件

image-20220706152104662

cd build
cmake -G "Unix Makefiles" -DLLVM_ENABLE_PROJECTS="clang" \
-DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="X86" \
-DBUILD_SHARED_LIBS=On ../llvm
make
make install

最后给予build.sh执行权限并执行等待一段时间后就编译好了,可以通过clang --version进行验证是否编译成功,在编译的时候最好给虚拟机大一点的运行内存避免发生未知意外

LLVM编译基本命令

LLVM IR 有两种表现形式,一种是人类可阅读的文本形式,对应文件后缀为 .ll ;另一种是方便机器处理的二进制格式,对应文件后缀为 .bc 。

clang -S -emit-llvm hello.cpp -o hello.ll

clang -c -emit-llvm hello.cpp -o hello.bc

使用 opt 指令对 LLVM IR 进行优化

opt -load LLVMObfuscator.so -hlw -S hello.ll -o hello_opt.ll

-load 加载特定的 LLVM Pass (集合)进行优化(通常为.so文件)
-hlw 是 LLVM Pass 中自定义的参数,用来指定使用哪个 Pass 进行优化

从 LLVM IR 到可执行文件中间还有一系列复杂的流程,Clang 帮助我们整合了这个过程:

clang hello_opt.ll -o hello

0x02 Obfuscator-LLVM环境搭建

为避免出现冲突,这一节我是专门拿了个新的Ubuntu虚拟机进行操作的

使用 nickdiego/ollvm-build这个docker环境进行OLLVM源码编译,docker的安装参考网络上教程即可

docker pull nickdiego/ollvm-build

然后将源码下载下来

git clone https://github.com/nickdiego/docker-ollvm.git
git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git

在ollvm-build.sh的第150行添加DOCKER_CMD+=" -DLLVM_INCLUDE_TESTS=OFF"

image-20220707142910600

最后执行ollvm-build.sh

chmod 777 ollvm-build.sh
sudo ./ollvm-build.sh ../obfuscator

编译完成后在 obfuscator/build_release 目录执行指令创建硬链接

sudo ln ./bin/* /usr/bin/
clang --version

OLLVM基本用法

使用的demo(hello.cpp)

#include <stdio.h>
#include <stdlib.h>

int encryptFunc(int inputNum_1,int inputNum_2){
    int tmpNum_1 = 666, tmpNum_2 = 888, tmpNum_3 = 777;
    return tmpNum_1 ^ tmpNum_2 + tmpNum_3 * inputNum_1 - inputNum_2;
}

int main(int argc,char *argv[]){

    int printNum = 55;
    if (argc > 1)
    {
        printNum = encryptFunc(printNum, atoi(argv[1]));
    }else{
        printNum = encryptFunc(printNum, argc);
    }
    
    printf("Hello OLLVM %d\r\n", printNum);

    return 0;
}

控制流平坦化(Control Flow Flattening)

可用选项:

  • -mllvm -fla : 激活控制流平坦化
  • -mllvm -split : 激活基本块分割
  • -mllvm -split_num=3 : 指定基本块分割的数目
clang -mllvm -fla -mllvm -split -mllvm -split_num=3 hello.cpp -o hello_fla

image-20220707155342174

如果提示stdio.h头文件找不到可以尝试下载g++和gcc,如果提示stddef.h或者stdarg.h头文件找不到可以sudo find -L /usr -name "*stddef*" -type f将其复制到 /usr/include目录下

image-20220707155253075

虚假控制流(Bogus Control Flow)

可用选项:

  • -mllvm -bcf : 激活虚假控制流
  • -mllvm -bcf_loop=3 : 混淆次数,这里一个函数会被混淆3次,默认为 1
  • -mllvm -bcf_prob=40 : 每个基本块被混淆的概率,这里每个基本块被混淆的概率为40%,默认为 30 %
clang -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -bcf_prob=40 hello.cpp -o hello_bcf

image-20220707160139661

指令替换(Instruction Substitution)

可用选项:

  • -mllvm -sub : 激活指令替代
  • -mllvm -sub_loop=3 : 混淆次数,这里一个函数会被混淆3次,默认为 1次
clang -mllvm -sub -mllvm -sub_loop=3 hello.cpp -o hello_sub

image-20220707160516874

通过LLVM IR生成多平台可执行文件,以控制流平坦化为例

clang -mllvm -fla -mllvm -split -mllvm -split_num=3 -S -emit-llvm hello.cpp -o hello_fla.ll

# 切换至Windows(提前安装好clang)
clang hello_fla.ll -o hello_fla.exe

0x03 去混淆

可以参考TSRC的那篇文章使用符号执行脚本deflat的方式进行去混淆,对于指令替换可以使用D810这个IDA插件进行操作,详细的去混淆方法后续文章在深入讨论。

参考链接

https://www.cnblogs.com/jsdy/p/12689470.html

https://www.kanxue.com/book-section_list-88.htm

https://blog.wuxu92.com/stdargs.h-no-such-file-or-directory/

https://security.tencent.com/index.php/blog/msg/112

https://github.com/joydo/d810