.NET 程序保护实战系列04 · 控制流混淆:打乱逻辑让分析者绕路
04 · 控制流混淆:打乱逻辑让分析者绕路
目录
1. 什么是控制流混淆
正常方法:A → B → C → D → E
混淆后:A → jump → C → jump → D → junk → jump → B → jump → E → ...
2. BlockParser:将线性 IL 解析为块树
internal static class BlockParser
{
public static ScopeBlock ParseBody(CilBody body)
{
var root = new ScopeBlock();
// 1. 为每个 ExceptionHandler 创建 try/handler/filter ScopeBlock
foreach (var eh in body.ExceptionHandlers)
{
// Try 块
var tryBlock = new ScopeBlock();
RegisterBlock(tryBlock, eh.TryStart, eh.TryEnd);
root.Children.Add(tryBlock);
// Handler 块 (catch/finally/fault)
var handlerBlock = new ScopeBlock();
RegisterBlock(handlerBlock, eh.HandlerStart, eh.HandlerEnd);
root.Children.Add(handlerBlock);
}
// 2. 遍历指令:创建基本块
Block currentBlock = null;
foreach (var instr in body.Instructions)
{
// 检测基本块边界(跳转目标或跳转指令)
if (IsBlockBoundary(instr))
{
currentBlock = new Block();
GetEnclosingScope(currentBlock, instr).Children.Add(currentBlock);
}
currentBlock?.Instructions.Add(instr);
}
return root;
}
}
3. JumpMangler:拆分、跳转、打乱
public class JumpMangler
{
public void Mangle(CilBody body, ScopeBlock root, CFContext ctx)
{
body.MaxStack = (ushort)(body.MaxStack + 2);
foreach (var block in GetAllBlocks(root))
{
var fragments = SplitFragments(block, ctx);
if (fragments.Count < 4) continue; // 太小不值得混淆
// 为每个片段添加跳转
foreach (var frag in fragments)
{
// 末尾插入 br 指令跳转
frag.Add(Instruction.Create(OpCodes.Br, nextFrag.Start));
// 在片段之间插入垃圾指令
InsertJunkInstructions(frag, ctx);
}
// 保持首尾位置固定,打乱中间片段
var first = fragments.First; fragments.RemoveFirst();
var last = fragments.Last; fragments.RemoveLast();
var middle = fragments.ToList();
ctx.Random.Shuffle(middle); // 随机打乱
// 重建:first + 打乱后的 middle + last
block.Instructions = Rebuild(first, middle, last);
}
// 修复异常处理边界
FixEHOffsets(body);
}
// 拆分时需检测不可拆分序列
List<Fragment> SplitFragments(Block block, CFContext ctx)
{
foreach (var instr in block.Instructions)
{
// prefix 指令不能与下一条分开 (unaligned, volatile, constrained, readonly, tail)
if (instr.OpCode.OpCodeType == OpCodeType.Prefix) continue;
// Dup + Ldvirtftn + Newobj (虚方法委托创建) 必须保持连续
// Ldftn + Newobj (委托创建) 必须保持连续
currentFragment.Add(instr);
if (ctx.Random.NextDouble() < intensity && currentFragment.Count >= 2)
{
fragments.Add(currentFragment);
currentFragment = new Fragment();
}
}
}
}
4. 不透明谓词与垃圾指令
// 三种不透明谓词
Dictionary<PredicateType, Func<CilBody, Instruction[]>> _predicates = new()
{
// 恒假:ldc.i4.0 + brtrue → 永远不跳
[PredicateType.FalseTrue] = body =>
{
body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_0));
body.Instructions.Add(Instruction.Create(OpCodes.Brtrue, junkLabel));
// 垃圾代码 ...
body.Instructions.Add(junkLabel);
},
// 恒真:ldc.i4.1 + brfalse → 永远不跳
[PredicateType.TrueFalse] = body =>
{
body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4_1));
body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, junkLabel));
// 垃圾代码 ...
body.Instructions.Add(junkLabel);
},
// XOR 恒等式:(key1 ^ key2) == expected → 永远成立
[PredicateType.XorIdentity] = body =>
{
int key1 = random.Next(), key2 = random.Next();
int expected = key1 ^ key2;
body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4, key1));
body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4, key2));
body.Instructions.Add(Instruction.Create(OpCodes.Xor));
body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I4, expected));
body.Instructions.Add(Instruction.Create(OpCodes.Ceq));
body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, junkLabel));
// 垃圾代码 ...
body.Instructions.Add(junkLabel);
}
};
每次保护的 key 随机生成,使每个混淆后的程序互不相同。
5. Debug 构建的自动兼容
// 检测 DebuggableAttribute.DisableOptimizations
static bool IsDisableOptimizations(CustomAttribute attr)
{
const int DisableOptimizations = 0x100;
int modes = (int)attr.ConstructorArguments[0].Value;
return (modes & DisableOptimizations) != 0;
}
// Debug 构建自动跳过控制流混淆,避免 JIT 验证失败
if (IsDisableOptimizations(debugAttr))
{
context.Log("Debug 构建检测到,跳过控制流混淆");
return;
}
6. 异常处理边界的修复
混淆后的指令流需要更新所有异常处理器的边界:
static void FixEHOffsets(CilBody body, List<Instruction> newInstrs)
{
foreach (var eh in body.ExceptionHandlers)
{
var oldTryStart = eh.TryStart;
var oldTryEnd = eh.TryEnd;
// 在新指令流中查找对应的指令
eh.TryStart = newInstrs.First(i => i.SequencePoint?.Offset == oldTryStart.Offset);
eh.TryEnd = newInstrs.First(i => i.SequencePoint?.Offset == oldTryEnd.Offset);
// 同样处理 Handler
}
}
7. 完整混淆流程
static void ProcessMethod(CilBody body, CFContext ctx)
{
// 1. 计算正确 MaxStack(避免运行时 StackOverflowException)
if (MaxStackCalculator.GetMaxStack(body.Instructions,
body.ExceptionHandlers, out uint maxStack))
body.MaxStack = (ushort)maxStack;
// 2. 解析为基本块树
var root = BlockParser.ParseBody(body);
// 3. Jump 模式混淆
var mangler = new JumpMangler();
mangler.Mangle(body, root, ctx);
// 4. 清除旧指令,写入新指令
body.Instructions.Clear();
root.ToBody(body);
// 5. 修复分支目标
foreach (var instr in body.Instructions)
{
if (instr.Operand is Instruction target)
instr.Operand = newInstrMap[target];
}
}
8. 结语
控制流混淆是攻击者最头疼的保护层之一——即使反编译出 C# 代码,得到的也是满屏的 goto 和垃圾逻辑。配合字符串加密和符号混淆,三层叠加后基本无法通过静态分析理解代码逻辑。
下一篇将深入代码虚拟化——另一种更高强度的保护策略。
本文由 TWSoft.AssemblyProtector 驱动。完整源码和工具请访问项目主页。

浙公网安备 33010602011771号