.NET 程序保护实战系列04 · 控制流混淆:打乱逻辑让分析者绕路

04 · 控制流混淆:打乱逻辑让分析者绕路

目录

  1. 什么是控制流混淆
  2. BlockParser:将线性 IL 解析为块树
  3. JumpMangler:拆分、跳转、打乱
  4. 不透明谓词与垃圾指令
  5. Debug 构建的自动兼容
  6. 异常处理边界的修复
  7. 完整混淆流程
  8. 结语

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 驱动。完整源码和工具请访问项目主页。

posted @ 2026-07-06 12:16  翼帆  阅读(25)  评论(0)    收藏  举报