吾愛破解 - LCG - LSG |安卓破解|病毒分析|破解軟件|www.dypczhxn.cn

 找回密碼
 注冊[Register]

QQ登錄

只需一步,快速開始

搜索
查看: 8101|回復: 60
上一主題 下一主題

[.NET逆向] 反混淆VMP.NET之Mutation

  [復制鏈接]
跳轉到指定樓層
樓主
wwh1004 發表于 2019-8-9 23:11 回帖獎勵
本帖最后由 wwh1004 于 2019-8-10 23:06 編輯

反混淆VMP.NET之Mutation

前言

這是.NET下VMP的反混淆,不是C++

VMP v3.4增加了對.NET程序的支持,我知道的功能有反調試,JIT保護(有些奇怪,因為調試過沒看到hook操作,但是確實不能直接dump),Mutation(變異),Virtualization(虛擬化)。Virtualization確實強,我搞不定,不過大概看懂結構了,可能寫工具自動重命名一下可以看得更明白,強度可能還是不如KoiVM。不過Mutation還是比較好搞定的,一種類似控制流混淆的東西,或者可以說是與控制流有關的常量加密。

分析

由于VMP Demo版本加殼的程序只能在自己電腦上運行,所以我自己寫了個.NET樣本。

Alt text

然后加殼,編譯模式選擇Mutation。用dnSpy打開,找到自己加殼的方法。可以發現有很多循環,這些循環是混淆器生成的。

Alt text

Alt text

仔細觀察,我們可以發現混淆比較單一,變量num是可以稱為Context(上下文),執行一條指令,上下文就會自己更新一次。為什么我會選一個有switch語句的樣本來加殼,因為我們需要知道進入不同的基本塊,再轉移到同一個基本塊時,上下文是不是相同的(一般來說是相同的,因為我也想不出有不同的情況...)。

可能這樣說不太容易明白,我直接調試,可以更明白些。樣本中的每個case塊對應了剛才說的不同的基本塊,最終都會轉移到同一個基本塊,也就是"Console.WriteLine("輸入完畢")"開始的這個基本塊。所以我們在這個基本塊下斷點。

我們分別輸入不同的數字,讓switch語句跳轉到不同的case塊,看看是不是最終執行到"Console.WriteLine("輸入完畢")"時,num的值是相同的。

Alt text

Alt text

Alt text

這樣,我們的猜想是正確的,Mutation的宏觀結構我們了解清除了,可以進行下一步分析。

我們要制訂一個最簡單的方案來清理掉Mutation,所以把dnSpy反編譯模式切換回IL,觀察num更新的語句究竟是什么樣的。

這個方法的入口點就是一個br跳轉,所以直接看br跳轉到的基本塊是怎么樣的。

Alt text

Alt text

由于這個基本塊是整個方法的入口點,那么這個基本塊也一定是Mutation初始化的地方,也會是我們使用模擬器模擬的入口點,具體的可以看我以前的《
.NET控制流分析(二)-反混淆》,搜一下搜得到,先看看里面清理ConfuserEx的switch混淆和清理VMP.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

最后失敗了,效果不太好,因為有些地方的特征并不是這樣,比如混淆分支跳轉指令前的(說是混淆分支跳轉,因為這個跳轉結果是確定的,每次跳轉結果都一樣)

Alt text

所以我想出了個非常投機取巧的方法,只替換

ldloc

ldc.i4

這樣可以適用于所有情況。

我在每次模擬后,判斷模擬的指令是不是讀取變量num的,如果是,我直接替換為

ldc.i4

但是發現這樣有個很大的問題。因為我沒辦法準確地識別被VMP.NET Mutation混淆的方法。肉眼看C#反編譯結果確實一眼看出,但是用代碼如何識別是一個非常大的難題。有的時候判斷有誤,把沒混淆的識別為混淆了,然后模擬出結果,直接替換,最后發現不該替換,這下就很麻煩了。解密后原地替換,還有其它的問題,總之非常不好。

最后我決定使用一個集合來保存解密結果,同時可以驗證每一次模擬的結果是不是相同的,如果不同那說明代碼有問題,或者VMP有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) {
                                        // 我們無法準確識別是否是VMP的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要么VMP有問題
#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);

Alt text

如果你們要用的話拿我之前放出的源碼改改就行。

回到反混淆器里面實現的Interceptor。為了提高穩定性,不模擬無關代碼,也為了提高速度,我過濾了很多指令,因為VMP.NET的Mutation只需要運算指令,比較指令,取值指令,分支指令這4類。

對于ldloc,也就是讀取變量的指令,還要特殊處理一次,防止讀取到無關Mutation的變量的值,防止模擬器計算不該計算的,導致分支模擬出現問題。

VMP.NET的Mutation會加密常量,還會添加混淆分支的代碼,所以我們要在模擬結束的之后,全部還原。這部分在OnEnd中實現。

總的來說VMP.NET的Mutation一般般,不能被C#反編譯結果嚇到,因為那是故意唬你的,試都不試怎么知道到底能不能脫呢?

至于VMP.NET的Virtualization模式我還沒搞定,不知道在研究反虛擬化的大佬愿不愿意分享一下經驗,我對這塊一直不太清楚,打算研究研究。

工具下載

https://pan.baidu.com/s/1UpCYi25dBLlxoha9eVEfbQ 提取碼: 94u3

目前工具很穩定,測試了很多次,無BUG,偶爾會提示無效分支指令,那個不用管,對程序運行完全無影響。

清理前后對比

Alt text

Alt text

效果還是很不錯的



修正一下:工具還是有問題,清理帶for循環的代碼可能出問題,修復方法可以自己思考一下,不會太難。

免費評分

參與人數 36吾愛幣 +50 熱心值 +36 收起 理由
youhen233 + 1 + 1 [email protected]
MYxx + 1 + 1 [email protected]
htuo + 1 + 1 [email protected]
thunderdanky + 1 + 1 膜拜大神!
siuhoapdou + 1 + 1 [email protected]
tail88 + 1 + 1 用心討論,共獲提升!
h1ck + 1 + 1 [email protected]
不諳世事的騷年 + 1 + 1 熱心回復!
secjia + 1 + 1 鼓勵轉貼優秀軟件安全工具和文檔!
殺豬用牛刀 + 1 + 1 [email protected]
濤的世界 + 1 + 1 用心討論,共獲提升!
錯的是世界 + 1 + 1 感謝發布原創作品,吾愛破解論壇因你更精彩!
you920928 + 1 + 1 [email protected]
CrazyNut + 3 + 1 大佬真的強
yicong135 + 1 + 1 [email protected]
thinkpad_420 + 1 + 1 熱心回復!
Sound + 6 + 1 歡迎分析討論交流,吾愛破解論壇有你更精彩!
夢游槍手 + 2 + 1 熱心回復!
chengan1998 + 1 [email protected]
jnez112358 + 1 + 1 [email protected]
悍匪戾 + 1 + 1 熱心回復!
nj001 + 1 + 1 熱心回復!
笙若 + 1 + 1 [email protected]
LOLQAQ + 1 + 1 我很贊同!
小菜鳥一枚 + 1 + 1 用心討論,共獲提升!
Ravey + 1 + 1 [email protected]
antiol + 3 + 1 如果需要vmp3.4旗艦版樣品可以找我
skip2 + 1 熱心回復!
wmsuper + 3 + 1 [email protected]
evea + 1 + 1 我很贊同!
cdzhoubin + 1 + 1 [email protected]
qqhsx + 1 + 1 我很贊同!
為海爾而戰 + 1 + 1 我很贊同!
ad666666 + 3 + 1 [email protected]
yAYa + 3 + 1 師傅又放干貨了
朱朱你墮落了 + 1 + 1 你原創精英當然可以的,而且是實至名歸!!!

查看全部評分

本帖被以下淘專輯推薦:

發帖前要善用論壇搜索功能,那里可能會有你要找的答案或者已經有人發布過相同內容了,請勿重復發帖。

來自 2#
 樓主| wwh1004 發表于 2019-8-11 18:29 <
本帖最后由 wwh1004 于 2019-8-11 18:35 編輯

糾正一下之前的錯誤,VMP的不是jit hook,開了壓縮輸出文件,vmp會把.text節和.vmp0節的RawAddress設置為0,在程序運行之后還原到內存

可以直接dump內存,更新一下所有節的RawAddress就好


如果開了內存保護,會有文件校驗,可以通過hook CreateFileW,第一個參數為當前進程的文件路徑的時候,換成修改前的備份文件的路徑就行了

我沒搞定vmp的虛擬化部分,不談虛擬化部分,VMP旗艦版和DEMO版本應該一樣,沒區別。

@antiol @Hmily @夢游槍手

感謝3位大佬幫助,如果以后要樣本可能還得找你們

VMPDumper & VMPHashBypass.7z
鏈接: https://pan.baidu.com/s/1hIpFRiJs8A4RYBOmMaEXaw 提取碼: mwsd

點評

vmprotect obsidium 最新旗艦版 公司版 都可以找我  詳情 回復 發表于 2019-8-11 22:14
推薦
antiol 發表于 2019-8-11 22:14
wwh1004 發表于 2019-8-11 18:29
糾正一下之前的錯誤,VMP的不是jit hook,開了壓縮輸出文件,vmp會把.text節和.vmp0節的RawAddress設置為0 ...

vmprotect obsidium 最新旗艦版 公司版 都可以找我
5#
 樓主| wwh1004 發表于 2019-8-9 23:16 <
話說現在還能弄到原創精英勛章么?

點評

沒問題,等我上線處理。 這么看vmp的net加密功能強度還是不錯的,正式版應該比demo版強度要高,回頭可以找個正版加一個測試下。 @Sound 有嗎,加一個試試。  詳情 回復 發表于 2019-8-10 00:59
6#
liucq 發表于 2019-8-10 00:39
大俠好厲害,過來打醬油
7#
Hmily 發表于 2019-8-10 00:59
wwh1004 發表于 2019-8-9 23:16
話說現在還能弄到原創精英勛章么?

沒問題,等我上線處理。

這么看vmp的net加密功能強度還是不錯的,正式版應該比demo版強度要高,回頭可以找個正版加一個測試下。

@Sound 有嗎,加一個試試。

點評

我有,可以發個樣品給我加密~  詳情 回復 發表于 2019-8-10 11:11
8#
sztxgg 發表于 2019-8-10 04:00
好久不見這么好的分析了,淋漓暢快呀
9#
laojiu 發表于 2019-8-10 08:21
好東西,學習一下
10#
霧落塵 發表于 2019-8-10 09:48
.net找個強殼真難
11#
skip2 發表于 2019-8-10 10:40
本帖最后由 skip2 于 2019-8-10 12:59 編輯

感謝大俠的分享!這個命令參數是啥可以說下么
12#
antiol 發表于 2019-8-10 11:11
Hmily 發表于 2019-8-10 00:59
沒問題,等我上線處理。

這么看vmp的net加密功能強度還是不錯的,正式版應該比demo版強度要高,回頭可 ...

我有,可以發個樣品給我加密~

點評

@wwh1004 給antiol大神來個沒加殼的試下。  詳情 回復 發表于 2019-8-10 11:31
13#
Hmily 發表于 2019-8-10 11:31
antiol 發表于 2019-8-10 11:11
我有,可以發個樣品給我加密~

@wwh1004 給antiol大神來個沒加殼的試下。
您需要登錄后才可以回帖 登錄 | 注冊[Register]

本版積分規則 警告:禁止回復與主題無關內容,違者重罰!

快速回復 收藏帖子 返回列表 搜索

RSS訂閱|小黑屋|聯系我們|吾愛破解 - LCG - LSG ( 京ICP備16042023號 | 京公網安備 11010502030087號 )

GMT+8, 2019-10-17 16:36

Powered by Discuz!

© 2001-2017 Comsenz Inc.

快速回復 返回頂部 返回列表
现在靠网络挣钱的方法