从0到1的llvm学习 (4) 间接跳转混淆

两只羊 Lv3

思路

间接跳转混淆,就是将汇编的跳转或者跳转指令,变化为通过值计算得到地址,再通过jmp 寄存器的形式的混淆

在现在Agent的能力下,其实单纯的CFF已很容易被分析了,但是遇到复杂的间接跳转控制流时,表现得还是不尽人意

image-20260429104827423

其实无论对于间接混淆还是别的混淆,最关键的一点就是要保证执行流的正常,并在此基础上做调整。

我的方案是·,对于每一个块都有一个key,获取出加密后的后继偏移地址,然后经过一个key XOR解密拿到真实偏移,再加上当前BasicBlock的地址,获得后继真实地址后进行跳转

预处理

那么老套路,我们第一步要做的,就是先获取每一个BasicBlock的后继,判断是直接跳转还是条件跳转(这里仍然暂时不考虑PHI和SwitchInst,因为最终可以降级处理)

先给每一个BasicBlock赋一个Key,毕竟我们不可能直接明文存储后继

这里我用了llvm独有的DenseMap,在处理llvm对象的时候似乎更有优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::vector<BasicBlock*> orignalBasicBlocks;
llvm::DenseMap<BasicBlock*, uint64_t> basicBlockKey;
llvm::DenseSet<uint64_t> usedKeySet;

for(BasicBlock& bb: F) {
orignalBasicBlocks.push_back(&bb);
}

std::default_random_engine e;
std::uniform_int_distribution<uint64_t> u(0,0xFFFFFFFFFFFFFFFF); // 左闭右闭区间
e.seed(time(0));

for(BasicBlock* BB: orignalBasicBlocks) {
uint64_t RandomSwitchVal = u(e);
while(usedKeySet.find(RandomSwitchVal) != usedKeySet.end()) {
RandomSwitchVal = u(e);
}
usedKeySet.insert(RandomSwitchVal);
basicBlockKey[BB] = RandomSwitchVal;
}

先处理并存储各Successor的地址

这里注意要跳过entryBlock,因为在llvm pass中它的地址是不能拿来运算的,编译时会直接报错

也就是说默认情况下,entryBlock只能保持原有的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BasicBlock* bb = orignalBasicBlocks[i];
Instruction* terminator = bb->getTerminator();
if(bb == entryBasicBlock) {
continue;
}

if(isa<ReturnInst>(terminator)) {
continue;
}

auto* Br = dyn_cast<BranchInst>(terminator);
if(!Br) {
continue;
}

然后是对地址值的计算,可以看到获取BlockAddress对象后,还需要通过ConstantExpr::getPtrToInt转化为Constant后才能参与运算

经过多次尝试,这里的存储必要要存放在全局变量,如果存放在栈上,编译出来后将不会看到预计算好的结果,而是它把地址的计算式完整的保留了下来,后继地址直接暴露,起不到任何保护效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if(Br->isUnconditional()) {
BasicBlock* successorBlock = terminator->getSuccessor(0);

BlockAddress* successorBlockAddress = BlockAddress::get(successorBlock);
BlockAddress* curBlockAddress = BlockAddress::get(bb);
//errs() << "test\n";
IRBuilder<> builder(bb);
//Value* blockAddressValue = builder.CreatePtrToInt(blockAddress, builder.getInt64Ty());
Constant* curBlockAddressConstant = ConstantExpr::getPtrToInt(curBlockAddress, builder.getInt64Ty());
Constant* successorBlockAddressConstant = ConstantExpr::getPtrToInt(successorBlockAddress, builder.getInt64Ty());
Constant* subKey = ConstantExpr::getSub(successorBlockAddressConstant, curBlockAddressConstant);
Constant* xorKey = ConstantInt::get(builder.getInt64Ty(), basicBlockKey[bb]);
Constant* secretKey = ConstantExpr::getXor(subKey, xorKey);

GlobalVariable* globalSubKey = new GlobalVariable(*M, builder.getInt64Ty(), true, GlobalValue::InternalLinkage,secretKey, "subKey" );
successorsSubKeyMap[bb].push_back(globalSubKey);


}

这里对于条件跳转的处理也类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
else {
BasicBlock* trueSuccessorBlock = terminator->getSuccessor(0);
BasicBlock* falseSuccessorBlock = terminator->getSuccessor(1);

BlockAddress* trueSuccessorBlockAddress = BlockAddress::get(trueSuccessorBlock);
BlockAddress* falseSuccessorBlockAddress = BlockAddress::get(falseSuccessorBlock);

BlockAddress* curBlockAddress = BlockAddress::get(bb);
//errs() << "test\n";
IRBuilder<> builder(bb);
//Value* blockAddressValue = builder.CreatePtrToInt(blockAddress, builder.getInt64Ty());
Constant* curBlockAddressConstant = ConstantExpr::getPtrToInt(curBlockAddress, builder.getInt64Ty());
Constant* xorKey = ConstantInt::get(builder.getInt64Ty(), basicBlockKey[bb]);

Constant* trueSuccessorBlockAddressConstant = ConstantExpr::getPtrToInt(trueSuccessorBlockAddress, builder.getInt64Ty());
Constant* trueSubKey = ConstantExpr::getSub(trueSuccessorBlockAddressConstant, curBlockAddressConstant);
Constant* trueSecretKey = ConstantExpr::getXor(trueSubKey, xorKey);

GlobalVariable* globalTrueSubKey = new GlobalVariable(*M, builder.getInt64Ty(), true, GlobalValue::InternalLinkage,trueSecretKey, "trueSubKey" );
successorsSubKeyMap[bb].push_back(globalTrueSubKey);


Constant* falseSuccessorBlockAddressConstant = ConstantExpr::getPtrToInt(falseSuccessorBlockAddress, builder.getInt64Ty());
Constant* falseSubKey = ConstantExpr::getSub(falseSuccessorBlockAddressConstant, curBlockAddressConstant);
Constant* falseSecretKey = ConstantExpr::getXor(falseSubKey, xorKey);

GlobalVariable* globalFalseSubKey = new GlobalVariable(*M, builder.getInt64Ty(), true, GlobalValue::InternalLinkage,falseSecretKey, "falseSubKey" );
successorsSubKeyMap[bb].push_back(globalFalseSubKey);

}

块处理

上面已经处理好了偏移的计算表达式,并已经存放到全局变量的中了,那么记下来就是取值并计算再跳转的过程

这里要十分注意的是,虽然我们已经通过CreateIndirectBr为块建立了一个terminator指令,但直接编译还是会报错,因为对于编译器来说,它不清楚你的这个计算地址是否有效。

因为需要用indirectInst->addDestination(successorBlock)明确指向一个后继。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*无条件跳转 */
if(Br->isUnconditional()) {
/*获取当前块的地址,并转化为可计算值 */
BasicBlock* successorBlock = terminator->getSuccessor(0);
BlockAddress* curBlockAddress = BlockAddress::get(bb);
Value* basicBlockAddressValue = builder.CreatePtrToInt(curBlockAddress, builder.getInt64Ty());

/*获取加密后的偏移 */
GlobalVariable* globalSubKey = successorsSubKeyMap[bb][0];
//Value* gep = builder.CreateInBoundsGEP(builder.getInt64Ty(), globalSubKey, {builder.getInt64(0), builder.getInt64(targetIdx)});
Value* loadSecretKey = builder.CreateLoad(Type::getInt64Ty(ctx), globalSubKey);

/*获取当前块的key,并解密出真实偏移*/
Value* xorKey = ConstantInt::get(builder.getInt64Ty(), basicBlockKey[bb]);
Value* loadSubKey = builder.CreateXor(loadSecretKey, xorKey);

/*计算得到真实后继地址,并进行跳转*/
Value* realBlockAddressValue = builder.CreateAdd(basicBlockAddressValue, loadSubKey);
Value* realBlockAddress = builder.CreateIntToPtr(realBlockAddressValue, builder.getInt8PtrTy());
IndirectBrInst* indirectInst = builder.CreateIndirectBr(realBlockAddress, 1);

indirectInst->addDestination(successorBlock);
terminator->eraseFromParent();

}

然后条件跳转也类似,唯一不同的就是,从中取出true和false的不同加密偏移,要添加一个当前块Condition下的SelectInst,来选择需要解密的偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*条件跳转 */
else {
BasicBlock* trueSuccessorBlock = terminator->getSuccessor(0);
BasicBlock* falseSuccessorBlock = terminator->getSuccessor(1);
BlockAddress* curBlockAddress = BlockAddress::get(bb);

Value* basicBlockAddressValue = builder.CreatePtrToInt(curBlockAddress, builder.getInt64Ty());
//Value* dynamicKey = builder.CreateLoad(builder.getInt64Ty(), XorKeyGV);
//int targetIdx = i;
GlobalVariable* globalTrueSubKey = successorsSubKeyMap[bb][0];
GlobalVariable* globalFalseSubKey = successorsSubKeyMap[bb][1];

Value* loadTrueSubKey = builder.CreateLoad(builder.getInt64Ty(), globalTrueSubKey);
Value* loadFalseSubKey = builder.CreateLoad(builder.getInt64Ty(), globalFalseSubKey);
//Value* gep = builder.CreateInBoundsGEP(builder.getInt64Ty(), globalSubKey, {builder.getInt64(0), builder.getInt64(targetIdx)});

/*获取当前块Terminator的Conditon,然后添加一个Select指令*/
Value* condition = Br->getCondition();
Value* loadSecretKey = builder.CreateSelect(condition, loadTrueSubKey, loadFalseSubKey);

Constant* xorKey = ConstantInt::get(builder.getInt64Ty(), basicBlockKey[bb]);
Value* loadSubKey = builder.CreateXor(loadSecretKey, xorKey);
//Value* calcSubKey = builder.CreateXor(loadEncrypted, dynamicKey);
Value* realBlockAddressValue = builder.CreateAdd(basicBlockAddressValue, loadSubKey);
Value* realBlockAddress = builder.CreateIntToPtr(realBlockAddressValue, builder.getInt8PtrTy());
IndirectBrInst* indirectInst = builder.CreateIndirectBr(realBlockAddress, 2);

indirectInst->addDestination(trueSuccessorBlock);
indirectInst->addDestination(falseSuccessorBlock);
terminator->eraseFromParent();

continue;
}
  • 标题: 从0到1的llvm学习 (4) 间接跳转混淆
  • 作者: 两只羊
  • 创建于 : 2026-03-25 10:46:59
  • 更新于 : 2026-04-29 11:26:30
  • 链接: https://twogoat.github.io/2026/03/25/从0到1的llvm学习-4-间接跳转混淆/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
从0到1的llvm学习 (4) 间接跳转混淆