使用LLVM编写简单混淆Pass(二)
上次使用 LLVM Pass 实现了控制流平坦化混淆,这次实现的是间接跳转(Indirect Call)。
原理
效果:主要是起到了一个静态混淆的作用,打断了函数之间的调用链,无法通过交叉引用定位敏感函数,也不能直接看出某处调用的函数是哪个。
基本想法是把所有的正常 call 和 jmp 都改成先获取一个加密的地址,再通过运算解密这个地址,最后跳转到解密的地址去。
那么需要定义一个全局数组存储加密后的地址。
实现
收集跳转指令
针对 CallInst
1
| auto *call = dyn_cast<CallInst>(&I)
|
分配索引:
1 2 3 4
| unsigned idx = callCounter++; unsigned pos = std::rand() % maxCallEntries; while (usedPos.count(pos)) pos = std::rand() % maxCallEntries; usedPos.insert(pos);
|
然后修改原始 call 指令。
针对 BranchInst
1
| auto *br = dyn_cast<BranchInst>(&I)
|
分配索引:
由于条件跳转有两个目标地址,则选取两个相邻的槽位存放:
1 2 3 4
| unsigned pos = std::rand() % (maxEntries - 2); while (usedPos.count(pos) || usedPos.count(pos + 1)) pos = std::rand() % (maxEntries - 2); usedPos.insert(pos); usedPos.insert(pos + 1);
|
然后修改原始 BranchInst 指令。
初始化跳转表
创建一个 LLVM 数组类型用来存储跳转地址表,每次调用先判断是否定义,如果是则直接返回该数组,否则定义:
1 2 3 4 5 6 7 8 9 10 11 12 13
| if (auto* GV = M->getGlobalVariable("bb_jump_table")) return GV;
ArrayType* arrType = ArrayType::get(intTy, maxEntries); auto* table = new GlobalVariable( *M, arrType, false, GlobalValue::PrivateLinkage, ConstantAggregateZero::get(arrType), "bb_jump_table" ); return table;
|
对跳转索引表做同样操作。
设计间接跳转指令
把原地址加密一下,再存到地址表中,然后在 call 或 jmp 前从表中取出数据,还原地址,然后跳转。
加密逻辑使用加减,计算加密地址:
1 2 3 4 5 6 7 8 9
| uint64_t mask = 0x200000 + (((uint64_t)std::rand() << 32) | std::rand()) % 0x200000; switch (rand_num) { case 0: encPtrConst = ConstantExpr::getAdd(encPtrConst, ConstantInt::get(ptrValueType, mask)); break; case 1; encPtrConst = ConstantExpr::getSub(encPtrConst, ConstantInt::get(ptrValueType, mask)); break; }
|
解密与加密相反即可。
替换原指令
针对 CallInst
获取对应的 idx 后,从表中取出地址,生成指令:
1 2 3
| Value* runtimeIdx = irb.CreateLoad(idxPtr); Value* gep = irb.CreateInBoundsGEP(JumpTable->getValueType(), JumpTable, {irb.getInt64(0), runtimeIdx}); Value* encFuncPtr = irb.CreateLoad(ptrValueType, gep);
|
最后安装加密的解密方法生成解密指令:
1 2 3 4 5 6 7 8 9
| Value* realFuncPtr = nullptr; switch (rand_num) { case 0: realFuncPtr = irb.CreateSub(encFuncPtr, irb.getInt64(mask)); break; case 1: realFuncPtr = irb.CreateAdd(encFuncPtr, irb.getInt64(mask)); break; }
|
最后添加 call reg 即可。
针对 BranchInst
加解密方式与 call 相同,区别在需要先根据条件选择索引
1 2 3 4
| Value* sel = irb.CreateSelect(cond, posVal, posVal2);
Value* gep = irb.CreateInBoundsGEP(BBTable->getValueType(), BBTable, { irb.getInt64(0), sel }); Value* encVal = irb.CreateLoad(int64Ty, gep);
|
最后添加条件跳转:
1
| IndirectBrInst* ind = irb.CreateIndirectBr(targetPtr);
|
效果
混淆前:
混淆后:
写在最后
待改进的地方有:加密逻辑过于简单,跳转表未加密等
现在 AI 这么强了,搓个脚本去混淆轻而易举,甚至可能用 mcp 分析的时候顺手就给去掉了。
目前这种混淆只能放在 CTF 新生赛里面逗逗新生了,现在想要对抗 AI 可能需要一些故意的错误引导,让 AI 的分析也变慢。
是时候拥抱 AI 了。🤗
引用:
https://bbs.kanxue.com/thread-283706.htm
https://bbs.kanxue.com/thread-289668.htm
