反混淆VMProtect.NET之Mutation

发布时间 2023-08-10 15:21:14作者: wwh1004
title: 反混淆VMProtect.NET之Mutation
date: 2019-08-09
updated: 2023-04-11
lang: zh-CN
categories:
- [.NET逆向]
tags:
- .NET
- 逆向工程
- 脱壳
- VMProtect
- 变异
toc: true

文章首发于 https://wwh1004.com/deobfuscating-mutation-of-vmprotect_net/

本文介绍了VMProtect在.NET程序集保护中的Mutation保护,以及如何使用控制流分析技术将它还原。

前言

这是.NET下VMProtect的反混淆,不是C++

VMProtect v3.4增加了对.NET程序的支持,我知道的功能有反调试,反Dump,Mutation(变异),Virtualization(虚拟化)。Virtualization确实强,我搞不定,不过大概看懂结构了,可能写工具自动重命名一下可以看得更明白,强度可能还是不如KoiVM。不过Mutation还是比较好搞定的,一种类似控制流混淆的东西,或者可以说是与控制流有关的常量加密。

分析

由于VMProtect Demo版本加壳的程序只能在自己电脑上运行,所以我自己写了个.NET样本。

然后加壳,编译模式选择Mutation。用dnSpy打开,找到自己加壳的方法。可以发现有很多循环,这些循环是混淆器生成的。

仔细观察,我们可以发现混淆比较单一,变量num是可以称为Context(上下文),执行一条指令,上下文就会自己更新一次。为什么我会选一个有switch语句的样本来加壳,因为我们需要知道进入不同的基本块,再转移到同一个基本块时,上下文是不是相同的(一般来说是相同的,因为我也想不出有不同的情况...)。

可能这样说不太容易明白,我直接调试,可以更明白些。样本中的每个case块对应了刚才说的不同的基本块,最终都会转移到同一个基本块,也就是"Console.WriteLine("输入完毕")"开始的这个基本块。所以我们在这个基本块下断点。

我们分别输入不同的数字,让switch语句跳转到不同的case块,看看是不是最终执行到"Console.WriteLine("输入完毕")"时,num的值是相同的。

这样,我们的猜想是正确的,Mutation的宏观结构我们了解清楚了,可以进行下一步分析。

我们要制订一个最简单的方案来清理掉Mutation,所以把dnSpy反编译模式切换回IL,观察num更新的语句究竟是什么样的。

这个方法的入口点就是一个br跳转,所以直接看br跳转到的基本块是怎么样的。

由于这个基本块是整个方法的入口点,那么这个基本块也一定是Mutation初始化的地方,也会是我们使用模拟器模拟的入口点,具体的可以看我以前的{% post_link net-control-flow-analysis-2-deobfuscation %},搜一下搜得到,先看看里面清理ConfuserEx的switch混淆和清理VMProtect.NET的Mutation很相似。

第一个箭头指着的

ldc.i4    1149763845
stloc.0

就是上下文num的初始化,后面会使用类似如下的代码来更新num

ldc.i4    1099382934
ldloc.0
sub
stloc.0

如果代码中还有常量,常量的解密和这个也是相似的,因为写之前已经分析过了,所以就不再写一遍了。

反混淆

这类混淆虽然简单,但是实际清理起来有些麻烦,为什么这么说?因为你要模拟整个控制流,模拟每一种可能的分支情况,才能确保你的解密结果是万无一失的。要模拟就非常麻烦了,可能造成死循环。模拟时,解密得到的结果如何处理,又是一个问题。

尝试

以下内容均为我的尝试(只是部分),我也是在各种尝试之后找到的解决方案。

我最开始的想法是特征匹配

ldc.i4
ldloc
add/sub/mul/div ...

这样的代码,遇到了直接替换成

nop
nop
ldc.i4

最后失败了,效果不太好,因为有些地方的特征并不是这样,比如混淆分支跳转指令前的(说是混淆分支跳转,因为这个跳转结果是确定的,每次跳转结果都一样)

所以我想出了个非常投机取巧的方法,只替换

ldloc

ldc.i4

这样可以适用于所有情况。

我在每次模拟后,判断模拟的指令是不是读取变量num的,如果是,我直接替换为

ldc.i4

但是发现这样有个很大的问题。因为我没办法准确地识别被VMProtect.NET Mutation混淆的方法。肉眼看C#反编译结果确实一眼看出,但是用代码如何识别是一个非常大的难题。有的时候判断有误,把没混淆的识别为混淆了,然后模拟出结果,直接替换,最后发现不该替换,这下就很麻烦了。解密后原地替换,还有其它的问题,总之非常不好。

最后我决定使用一个集合来保存解密结果,同时可以验证每一次模拟的结果是不是相同的,如果不同那说明代码有问题,或者VMProtect有BUG,能增强稳定性。

写好逻辑

事实上最开始我是把逻辑和具体实现写在一起了,因为这样修改起来很方便,到后期基本上稳定,BUG没几个的时候,我才抽象出了逻辑,把底层的解密操作分开了。

因为我是写好了工具的,所以按我的源代码来讲解,先说抽象类。

以下是我的抽象类的成员列表

namespace ControlFlow.Deobfuscation {
    /// <summary>
    /// 混合控制流的常量解密
    /// </summary>
    public abstract class ConstantFlowDeobfuscatorBase {
        /// <summary>
        /// 要解密的方法块
        /// </summary>
        protected readonly MethodBlock _methodBlock;
        /// <summary>
        /// 指令模拟器
        /// </summary>
        protected readonly Emulator _emulator;
        /// <summary>
        /// 控制流相关的变量
        /// </summary>
        protected Local _flowContext;
        /// <summary>
        /// 解密个数
        /// </summary>
        protected int _decryptedCount;

#if DEBUG
        /// <summary />
        protected int _indent;
        /// <summary />
        public bool DEBUG;
#endif

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="methodBlock"></param>
        protected ConstantFlowDeobfuscatorBase(MethodBlock methodBlock);

        /// <summary>
        /// 解密
        /// </summary>
        protected virtual void Deobfuscate();

        /// <summary>
        /// 访问指定基本块,并且通过递归访问这个基本块的所有跳转目标
        /// </summary>
        /// <param name="basicBlock"></param>
        protected virtual void VisitAllBasicBlocks(BasicBlock basicBlock);

        /// <summary>
        /// 在所有操作开始前触发
        /// 在这个方法中,必需为所有基本块添加额外信息,并且设置字段 <see cref="_flowContext"/>
        /// 如果没有找到 <see cref="_flowContext"/>,直接返回,而不是抛出异常
        /// </summary>
        protected abstract void OnBegin();

        /// <summary>
        /// 在所有操作完成后触发
        /// 在这个方法中,必需移除所有基本块的额外信息
        /// </summary>
        protected abstract void OnEnd();

        /// <summary>
        /// 获取可用的模拟入口点
        /// </summary>
        /// <returns></returns>
        protected abstract IEnumerable<BasicBlock> GetEntries();

        /// <summary>
        /// 在指定基本块的指定指令模拟前触发
        /// </summary>
        /// <param name="basicBlock"></param>
        /// <param name="index">指令的索引</param>
        protected abstract void OnEmulateBegin(BasicBlock basicBlock, int index);

        /// <summary>
        /// 在指定基本块的指定指令模拟后触发
        /// </summary>
        /// <param name="basicBlock"></param>
        /// <param name="index">指令的索引</param>
        protected abstract void OnEmulateEnd(BasicBlock basicBlock, int index);

        /// <summary>
        /// 在指定基本块模拟前触发
        /// </summary>
        /// <param name="basicBlock"></param>
        protected virtual void OnEmulateBegin(BasicBlock basicBlock);

        /// <summary>
        /// 在指定基本块模拟后触发
        /// </summary>
        /// <param name="basicBlock"></param>
        protected virtual void OnEmulateEnd(BasicBlock basicBlock);

        /// <summary>
        /// 在模拟运行一个基本块结束后,通过模拟分支指令来获取下一个基本块,如果无法获取,返回 <see langword="null"/>
        /// 无论是否成功,一定要平衡堆栈
        /// </summary>
        /// <param name="basicBlock"></param>
        /// <returns></returns>
        protected virtual BasicBlock EmulateBranch(BasicBlock basicBlock);

        /// <summary>
        /// 遇到条件跳转时,递归调用 <see cref="VisitAllBasicBlocks"/>
        /// </summary>
        /// <param name="basicBlock">为条件跳转的基本块</param>
        protected virtual void CallNextVisitAllBasicBlocksConditional(BasicBlock basicBlock);

#if DEBUG
        private static string DEBUG_ToString(BasicBlock basicBlock);
#endif

        /// <summary>
        /// 基本块额外信息基类
        /// </summary>
        protected abstract class BlockInfoBase {
            /// <summary>
            /// 模拟标记,如果指定指令需要模拟,将对应索引的元素设置为 <see langword="true"/>
            /// </summary>
            public bool[] EmulationMarks;

            /// <summary>
            /// 下一个要模拟的基本块(只是可能,但如果 <see cref="HashSet{T}.Count"/> 为 1 ,那就是一定)
            /// </summary>
            public List<BasicBlock> NextBasicBlocks;

            /// <summary>
            /// 是否已经进入当前基本块,防止出现循环
            /// </summary>
            public bool IsEntered;

            /// <summary>
            /// 构造器
            /// </summary>
            /// <param name="basicBlock"></param>
            protected BlockInfoBase(BasicBlock basicBlock);
        }
    }
}

如果看过我原来的ConfuserEx的switch混淆的清理文章,可以发现这个和那个switch清理的抽象类很像,都是需要提供所有可用入口点,模拟到结束,从而覆盖整个方法,达到解密效果。

最关键的逻辑还是在VisitAllBasicBlocks这里,就这个方法,我改过N次,N次bug都是这里逻辑问题导致的。

所以我不贴有bug的代码了,直接贴现在正常工作的代码,里面有因为bug让我写的注释。

protected virtual void VisitAllBasicBlocks(BasicBlock basicBlock) {
    BlockInfoBase blockInfo;

    blockInfo = basicBlock.PeekExtraData<BlockInfoBase>();
    if (blockInfo.IsEntered)
        // 如果已经进入基本块,防止循环,直接返回
        return;
#if DEBUG
    if (DEBUG)
        Console.WriteLine($"{new string(' ', _indent)}{DEBUG_ToString(basicBlock)}: {_emulator.Locals[_flowContext]}");
#endif
    blockInfo.IsEntered = true;
    OnEmulateBegin(basicBlock);
    for (int i = 0; i < basicBlock.Instructions.Count; i++)
        if (blockInfo.EmulationMarks[i]) {
            OnEmulateBegin(basicBlock, i);
            if (!_emulator.Emulate(basicBlock.Instructions[i]))
                throw new NotImplementedException("暂未实现模拟失败处理,需要更新反混淆模型,或者检查是否模拟了不需要模拟的指令");
            OnEmulateEnd(basicBlock, i);
        }
    OnEmulateEnd(basicBlock);
    switch (basicBlock.BranchOpcode.FlowControl) {
    case FlowControl.Return:
    case FlowControl.Throw:
        break;
    default:
        BasicBlock nextBasicBlock;

        nextBasicBlock = EmulateBranch(basicBlock);
        if (nextBasicBlock is null) {
#if DEBUG
            _indent += 2;
            if (DEBUG)
                Console.WriteLine(new string(' ', _indent) + "conditional");
#endif
            CallNextVisitAllBasicBlocksConditional(basicBlock);
#if DEBUG
            _indent -= 2;
#endif
        }
        else {
            // 当前模拟结果是基于当前基本块之前的基本块的,
            // 因为之前的基本块模拟结果是不一定会发生(参考CallNextVisitAllBasicBlocksConditional),所以我们不能在这里直接清理分支,而是保存起来。
            //nextBasicBlock.PeekExtraData<BlockInfoBase>().IsEntered = false;
            // 如果有while(true){}循环,这行代码可能导致死循环
            // 之前的想法是,如果下一个基本块A,也就是分支指令是可以求值的,那么就强制进入下一个基本块A模拟,不管当前是否已经处于基本块A
            // 可能暂时不需要这样做
            if (!blockInfo.NextBasicBlocks.Contains(nextBasicBlock))
                blockInfo.NextBasicBlocks.Add(nextBasicBlock);
            // 如果所有情况下,分支结果都是一样的,那么我们可以断定这里有混淆分支指令,可以清理
            // 假设之前的基本块模拟结果发生了,那么这个基本块的模拟结果是正确的,所以要强制模拟下一个基本块
            VisitAllBasicBlocks(nextBasicBlock);
        }
        break;
    }
    blockInfo.IsEntered = false;
}

这里的blockInfo.IsEntered原来是IsVisited,被我改成了IsEntered。为了防止模拟进入死循环,必须要防止一个基本块重复执行,但是这个度需要把握好。最开始打算如果已经模拟过这个基本块,那么就不模拟了。显然这个想法有问题。就比如文中的样本,在不同的分支下,都会跳转到那个Console.WriteLine的基本块,如果使用IsVisited来表示基本块只能模拟一次,那么就会出现不能模拟每一种分支情况,有可能导致误判。

为什么说可能误判,比如这个

uint num = 0;
if (xxx)
    num = RandomUInt32();
Console.WriteLine(num);

假设逻辑是一个基本块只能执行一次,我们先模拟的情况是if语句不执行,那么执行到

Console.WriteLine(num);

的时候,num的值是确定的,为0

然后我们模拟if分支执行的情况,因为Console.WriteLine的基本块是执行过的,所以我们不执行了。

那么最后,我们得到了一个解密结果,Console.WriteLine用的num的值是0,但是事实不是这样。

所以我们应该使用IsEntered来表示,如果已经处于一个基本块中,那么我们不能重复模拟这个基本块,来防止while(true)之类的死循环。在模拟一个基本块之前将基本块的IsEntered设置为true,在模拟完这个基本块的分支指令之后,设置为false,可以完美解决问题。

CallNextVisitAllBasicBlocksConditional的代码和ConfuserEx的switch混淆清理的代码一样(之前那个帖子的CallNextVisitAllBasicBlocksConditional的代码有bug,仔细对比下我等下发的,就知道为什么了,注意我的EmulationContext是引用类型)

protected virtual void CallNextVisitAllBasicBlocksConditional(BasicBlock basicBlock) {
    EmulationContext context;

    context = _emulator.Context.Clone();
    // 条件跳转,有多个跳转目标,需要备份当前模拟器上下文
    if (!(basicBlock.FallThrough is null)) {
        VisitAllBasicBlocks(basicBlock.FallThrough);
        _emulator.Context = context.Clone();
        // 恢复模拟器上下文
    }
    if (!(basicBlock.ConditionalTarget is null)) {
        VisitAllBasicBlocks(basicBlock.ConditionalTarget);
        _emulator.Context = context.Clone();
    }
    if (!(basicBlock.SwitchTargets is null))
        foreach (BasicBlock target in basicBlock.SwitchTargets) {
            VisitAllBasicBlocks(target);
            _emulator.Context = context.Clone();
        }
}

接下来是实现所有抽象方法和部分虚方法。代码直接贴出来了。

namespace ControlFlow.Deobfuscation.Specials.VMProtect {
    public sealed class MutationDeobfuscator : ConstantFlowDeobfuscatorBase {
        private static readonly Code[] InitializeFlowContextCodes = new Code[] { Code.Ldc_I4, Code.Stloc };
        private static readonly Code[] CanBeEmulatedCodes = new Code[] {
            Code.Add, Code.Add_Ovf, Code.Add_Ovf_Un, Code.And, Code.Div, Code.Div_Un, Code.Mul, Code.Mul_Ovf, Code.Mul_Ovf_Un, Code.Neg, Code.Not, Code.Or, Code.Rem, Code.Rem_Un, Code.Shl, Code.Shr, Code.Shr_Un, Code.Sub, Code.Sub_Ovf, Code.Sub_Ovf_Un, Code.Xor,
            Code.Ceq, Code.Cgt, Code.Cgt_Un, Code.Clt, Code.Clt_Un,
            Code.Ldc_I4,
            Code.Ldloc, Code.Stloc,
            Code.Beq, Code.Bge, Code.Bge_Un, Code.Bgt, Code.Bgt_Un, Code.Ble, Code.Ble_Un, Code.Blt, Code.Blt_Un, Code.Bne_Un, Code.Br, Code.Brfalse, Code.Brtrue, Code.Endfilter, Code.Endfinally, Code.Leave, Code.Ret, Code.Rethrow, Code.Switch, Code.Throw
        };

        private List<BasicBlock> _basicBlocks;
        private List<BasicBlock> _entries;
        private bool _isNotMutation;

        private MutationDeobfuscator(MethodBlock methodBlock) : base(methodBlock) {
        }

        public static int Deobfuscate(MethodBlock methodBlock) {
            MutationDeobfuscator deobfuscator;

            deobfuscator = new MutationDeobfuscator(methodBlock);
            deobfuscator.Deobfuscate();
            if (deobfuscator._decryptedCount > 0) {
                NopRemover.Remove(methodBlock);
                ConstantArithmeticRemover.Remove(methodBlock);
            }
            return deobfuscator._decryptedCount;
        }

        protected override void OnBegin() {
            Dictionary<Local, int> frequencies;
            int maxFrequency;
            Local flowContext;

            frequencies = new Dictionary<Local, int>();
            // 用于统计局部变量出现频率
            _basicBlocks = _methodBlock.GetAllBasicBlocks();
            foreach (BasicBlock basicBlock in _basicBlocks)
                foreach (Instruction instruction in basicBlock.Instructions) {
                    Local local;

                    if (instruction.OpCode.Code != Code.Ldloc && instruction.OpCode.Code != Code.Stloc)
                        // 没有ldloca,因为mutation不会使用ldloca
                        continue;
                    local = (Local)instruction.Operand;
                    if (!frequencies.ContainsKey(local))
                        frequencies[local] = 1;
                    else
                        frequencies[local]++;
                }
            maxFrequency = 0;
            flowContext = null;
            foreach (KeyValuePair<Local, int> frequency in frequencies)
                if (frequency.Value > maxFrequency) {
                    maxFrequency = frequency.Value;
                    flowContext = frequency.Key;
                }
            if (!(flowContext is null) && (flowContext.Type.ElementType != ElementType.U4 /*|| !MayBeEntry(_methodBlock.GetFirstBasicBlock(), flowContext)*/))
                flowContext = null;
            // 判断有没有可能是flowContext
            if (flowContext is null)
                return;
            _flowContext = flowContext;
            _entries = new List<BasicBlock>();
            foreach (BasicBlock basicBlock in _basicBlocks)
                if (MayBeEntry(basicBlock, flowContext))
                    _entries.Add(basicBlock);
            // 获取所有入口点
            foreach (BasicBlock basicBlock in _basicBlocks)
                basicBlock.PushExtraData(new BlockInfo(basicBlock));
            _emulator.Interceptor = Interceptor;
        }

        private static bool MayBeEntry(BasicBlock basicBlock, Local flowContext) {
            int index;

            index = basicBlock.Instructions.IndexOf(InitializeFlowContextCodes);
            if (index == -1)
                // 没有特征
                return false;
            if (basicBlock.Instructions[index + 1].Operand != flowContext)
                // 操作数不是flowContext
                return false;
            for (int i = 0; i < index; i++)
                if (basicBlock.Instructions[i].Operand == flowContext)
                    // 在初始化前使用了flowContext
                    return false;
            return true;
        }

        private bool Interceptor(Emulator emulator, Instruction instruction) {
            if (!CanBeEmulatedCodes.Contains(instruction.OpCode.Code)) {
                // 不在列表里的不要模拟
                emulator.UpdateStack(instruction);
                return true;
            }
            if (instruction.Operand is Local && instruction.Operand != _flowContext) {
                // 操作数不是_flowContext的ldloc和stloc不要模拟
                emulator.UpdateStack(instruction);
                return true;
            }
            return false;
        }

        protected override void OnEnd() {
            if (_isNotMutation)
                _decryptedCount = 0;
            foreach (BasicBlock basicBlock in _basicBlocks) {
                if (_decryptedCount != 0) {
                    List<BasicBlock> nextBasicBlocks;

                    foreach (KeyValuePair<int, List<int>> decryptedValue in basicBlock.PeekExtraData<BlockInfo>().DecryptedValues) {
                        if (decryptedValue.Value.Count != 1)
                            // 解密出不同的值不要替换
                            continue;
                        basicBlock.Instructions[decryptedValue.Key] = OpCodes.Ldc_I4.ToInstruction(decryptedValue.Value[0]);
                    }
                    // 替换指令
                    nextBasicBlocks = basicBlock.PeekExtraData<BlockInfoBase>().NextBasicBlocks;
                    if (nextBasicBlocks.Count == 1)
                        switch (basicBlock.BranchOpcode.StackBehaviourPop) {
                        case StackBehaviour.Popi:
                            // brfalse brtrue
                            basicBlock.Instructions.Add(OpCodes.Pop.ToInstruction());
                            basicBlock.SetBr(nextBasicBlocks[0]);
                            break;
                        case StackBehaviour.Pop1_pop1:
                            // bgt bge blt ble ...
                            basicBlock.Instructions.Add(OpCodes.Pop.ToInstruction());
                            basicBlock.Instructions.Add(OpCodes.Pop.ToInstruction());
                            basicBlock.SetBr(nextBasicBlocks[0]);
                            break;
                        }
#if DEBUG
                    if (nextBasicBlocks.Count > 1) {
                        System.Console.WriteLine(BlockPrinter.ToString(_methodBlock));
                        System.Diagnostics.Debug.Assert(false, "存在不同的分支模拟结果");
                    }
#endif
                    // 清理分支
                }
                basicBlock.PopExtraData();
            }
        }

        protected override IEnumerable<BasicBlock> GetEntries() {
            return _entries;
        }

        protected override void OnEmulateBegin(BasicBlock basicBlock, int index) {
        }

        protected override void OnEmulateEnd(BasicBlock basicBlock, int index) {
            List<Instruction> instructions;

            if (_isNotMutation)
                return;
            instructions = basicBlock.Instructions;
            if (instructions[index].OpCode.Code == Code.Ldloc && instructions[index].Operand == _flowContext) {
                // ldloc flowContext 应该是常量,直接替换
                Int32Value value;
                Dictionary<int, List<int>> decryptedValues;
                List<int> existingValues;

                value = _emulator.EvaluationStack.Peek() as Int32Value;
                if (value is null) {
                    // 我们无法准确识别是否是VMProtect的Mutation,如果出现异常,认为不是Mutation
                    _isNotMutation = true;
                    return;
                }
                decryptedValues = basicBlock.PeekExtraData<BlockInfo>().DecryptedValues;
                if (!decryptedValues.TryGetValue(index, out existingValues)) {
                    existingValues = new List<int>();
                    decryptedValues.Add(index, existingValues);
                }
                if (!existingValues.Contains(value.Signed)) {
                    existingValues.Add(value.Signed);
                    _decryptedCount++;
                }
                // 保存模拟结果
                if (existingValues.Count > 1) {
                    _decryptedCount--;
                    _isNotMutation = true;
                    // 要么不是mutation要么VMProtect有问题
#if DEBUG
                    System.Console.WriteLine(BlockPrinter.ToString(_methodBlock));
                    System.Diagnostics.Debug.Assert(false, "存在不同的解密结果");
#endif
                }
            }
        }

        protected override void VisitAllBasicBlocks(BasicBlock basicBlock) {
            if (_isNotMutation)
                return;
            base.VisitAllBasicBlocks(basicBlock);
        }

        protected override void CallNextVisitAllBasicBlocksConditional(BasicBlock basicBlock) {
            if (_isNotMutation)
                return;
            base.CallNextVisitAllBasicBlocksConditional(basicBlock);
        }

        private sealed class BlockInfo : BlockInfoBase {
            private readonly Dictionary<int, List<int>> _decryptedValues;

            /// <summary>
            /// 解密的值,key是指令的索引,value是解密后的值,使用ldc.i4替换上
            /// </summary>
            public Dictionary<int, List<int>> DecryptedValues => _decryptedValues;

            public BlockInfo(BasicBlock basicBlock) : base(basicBlock) {
                _decryptedValues = new Dictionary<int, List<int>>();
                for (int i = 0; i < EmulationMarks.Length; i++)
                    EmulationMarks[i] = true;
            }
        }
    }
}

首先我们要找出哪个局部变量是Mutation的上下文,但实际上我只能大概的判断是不是,这个不模拟一遍确实很难确定。这个我们写在OnBegin里面,因为这是初始化步骤。具体的实现在上面有。

OnBegin的最后一行有个

_emulator.Interceptor = Interceptor;

这个Interceptor是我新加入的,之前放出的模拟器源码里面没这个。这个东西相当于Hook。

/// <summary>
/// 拦截器,如果返回 <see langword="true"/>,<see cref="Emulator"/> 将不再模拟当前指令
/// </summary>
/// <param name="emulator"></param>
/// <param name="instruction"></param>
/// <returns></returns>
public delegate bool Interceptor(Emulator emulator, Instruction instruction);

如果你们要用的话拿我之前放出的源码改改就行。

回到反混淆器里面实现的Interceptor。为了提高稳定性,不模拟无关代码,也为了提高速度,我过滤了很多指令,因为VMProtect.NET的Mutation只需要运算指令,比较指令,取值指令,分支指令这4类。

对于ldloc,也就是读取变量的指令,还要特殊处理一次,防止读取到无关Mutation的变量的值,防止模拟器计算不该计算的,导致分支模拟出现问题。

VMProtect.NET的Mutation会加密常量,还会添加混淆分支的代码,所以我们要在模拟结束的之后,全部还原。这部分在OnEnd中实现。

总的来说VMProtect.NET的Mutation一般般,不能被C#反编译结果吓到,因为那是故意唬你的,试都不试怎么知道到底能不能脱呢?

至于VMProtect.NET的Virtualization模式我还没搞定,不知道在研究反虚拟化的大佬愿不愿意分享一下经验,我对这块一直不太清楚,打算研究研究。

工具下载

DeMutation.zip

目前工具很稳定,测试了很多次,无BUG,偶尔会提示无效分支指令,那个不用管,对程序运行完全无影响。

清理前后对比

效果还是很不错的