wwh1004 发表于 2019-2-3 22:00

.NET控制流分析(二)-反混淆

# .NET控制流分析(二)-反混淆

## 前言

本来想分成2块写的,因为有一部分简单,还有一部分很难,非常难。但是想想还是算了,一篇文章写完也可以,因为都是和反混淆有关的内容。不过估计文章会非常长,字数非常多。

这篇文章还是要按顺序看,文章前半部分讲的是简单的,后半部分是难的,并且需要前半部分的一些知识。

前半部分比较简单,会说一些比较通用的代码和思路。

后半部分更有征对性,实战讲解ConfuserEx的控制流混淆清除。为什么讲ConfuserEx的控制流混淆?因为我觉得ConfuserEx的控制流混淆是最难的,至少在我遇到的壳里面来说。其它壳都是一个简单的switch(num),case里面一条num=x;就没了,而ConfuserEx的控制流混淆是线性的,下一个被执行的基本块与上一个被执行的基本块有关,无法静态解密switch混淆,必需虚拟执行。

## 基础

### 递归模型

很多时候一个方法里面会有try块,这样的话,一个方法块里面就会有小作用域,也就是那个try块。控制流混淆只会在同一个作用域里面进行混淆,没见过会跨作用域混淆的。什么意思的?

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/1.png)

比如这个大的红框,就是一个作用域,try包括的地方可以看作一个整体,控制流混淆的时候不会把try这个整体拆成多个部分。而try内部是一个作用域,不是一个整体,可以拆成多个部分,继续混淆。

所以我们要写出Helper类能简化遍历每一个作用域来反混淆的操作。这个类就叫做BlockRecursiveModel,代码在这个系列的上一篇文章的附件里有。这里就截图看看大概样子。

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/2.png)

### 块排序器

为什么要排序?首先是减小代码体积,如果不排序,IL代码可能就会像这样,到处是无条件跳转指令br,让我们几乎无法阅读IL代码。

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/3.png)

而排序之后逻辑清晰很多:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/4.png)

这只是一个非常简单的方法体。如果是复杂的方法体,代码会膨胀很多,排序显得非常重要。

当然,排序只是对生成的指令流有影响,对我们分析树状结构的控制流,也就是分成块之后的控制流是没有影响的。

分块之后无论是什么顺序储存在List<T>里面,结构其实都是一样的:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/5.png)

这个是我写的工具,文章末尾会附上编译好的程序。

有了刚才说的BlockRecursiveModel,块排序的代码其实非常简单,我们要先分析出在相同作用域内,块与块直接的引用关系,再使用拓扑排序就行了。有人会说拓扑排序只能排有向无环图,但是我们这里是可以使用拓扑排序的,至于为什么我就不解释了,自己试试就知道。

要分析引用关系,我们还是要先定义个额外信息,来储存我们分析的结果:

``` csharp
private sealed class BlockInfo {
        private readonly List<IBlock> _references;
        private bool _isVisited;

        public List<IBlock> References => _references;

        public bool IsVisited {
                get => _isVisited;
                set => _isVisited = value;
        }
}
```

References表示的是引用,意思是会跳转到哪些块。

``` csharp
private void AddXref(BasicBlock source, BasicBlock target) {
        IBlock targetRoot;
        List<IBlock> references;

        targetRoot = target.GetRootBlock(_scope);
        if (targetRoot == null)
                // 跳出scope范围的我们不做处理
                return;
        references = source.GetRootBlock(_scope).PeekExtraData<BlockInfo>().References;
        if (!references.Contains(targetRoot))
                references.Add(targetRoot);
}

public static IBlock GetRootBlock(this IBlock block, IBlock scope) {
        if (block == null)
                throw new ArgumentNullException(nameof(block));
        if (scope == null)
                throw new ArgumentNullException(nameof(scope));

        while (true) {
                if (block.Scope == scope)
                        return block;
                else
                        block = block.Scope;
                if (block == null)
                        return null;
        }
}
```

代码里面的source代表会发生跳转的基本块,target表示会跳转到的基本块。

分析出所有引用关系之后,我们直接使用拓扑排序就行:

``` csharp
private sealed class TopologicalSorter {
        private readonly List<IBlock> _blocks;
        private readonly Stack<IBlock> _blockStack;

        public TopologicalSorter(List<IBlock> blocks) {
                if (blocks == null)
                        throw new ArgumentNullException(nameof(blocks));

                _blocks = blocks;
                _blockStack = new Stack<IBlock>(_blocks.Count);
        }

        public Stack<IBlock> Sort() {
                DfsSort(_blocks);
                return _blockStack;
        }

        private void DfsSort(IBlock block) {
                BlockInfo blockInfo;

                blockInfo = block.PeekExtraData<BlockInfo>();
                blockInfo.IsVisited = true;
                for (int i = blockInfo.References.Count - 1; i >= 0; i--)
                        if (!blockInfo.References.PeekExtraData<BlockInfo>().IsVisited)
                                DfsSort(blockInfo.References);
                _blockStack.Push(block);
        }
}
```

整个排序的完整代码都在上一篇文章的附件的压缩包里面,叫BlockSorter.cs

### 移除NOP

移除NOP这个操作非常简单,因为我们已经转换成了块,遍历每个基本块块,把每个基本块的NOP都移除就好。

既然简单,那为什么还要提这个呢?

因为我们的目标是把所有情况尽可能的简化,变成一种情况,这样我们处理起来就方便很多。而NOP的存在可能会影响我们识别特征。

### 基本块内联

什么叫内联,比如这样:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/6.png)

红框中3个基本块就是可以内联到一起的,为什么呢?因为BLK_0002只被一个基本块引用了,BLK_0001也是,只被一个基本块引用,并且引用方是无条件跳转,那就可以内联起来。BLK_0004虽然只被一个基本块引用,但是引用方BLK_0002是条件跳转,我们就不能内联。

内联之后:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/7.png)

是不是效果明显,控制流清晰了很多,没有冗余的东西?

这里再扯一下我的FlowGraph,这个工具有个Optimization选项,开了之后就会把可以内联的块都内联到一起,并且清除NOP,对块进行排序。

除了这种情况,我们还有一种情况可以内联,就是某个基本块是空块,并且跳转指令是无条件跳转指令br。这种情况下,无论引用方有几个,引用方是什么跳转指令,我们都可以内联。

比如这样的:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/8.png)

这里的BLK_0007就是一个空块(nop指令等于不存在,我们会优化掉),而且是跳转指令是无条件跳转指令br。虽然BLK_0007的引用方有BLK_0002 BLK_0003 BLK_0004 BLK_0005 BLK_0006共5个,但是我们都可以内联起来。

内联起来的效果:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/9.png)

而这一段控制流的源代码其实非常简单,就是Debug模式下编译一个switch+goto

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/10.png)

脑补一下,这个代码生成是不是更像我们开了优化之后的控制流图?

这个就是内联的牛逼之处,可以极大地简化控制流。虽然牛逼,但是实现就比上面地代码更复杂了。

实际上也没复杂多少,代码直接放出来,其实就200行。

``` csharp
using System.Collections.Generic;
using ControlFlow.Blocks;
using dnlib.DotNet.Emit;

namespace ControlFlow.Deobfuscation {
        /// <summary>
        /// 可以移除的基本块A,不移除可能会妨碍我们分析:
        /// 1. 一个基本块A没有其它指令,只有br这个无条件跳转指令
        /// 2. 一个基本块B无条件跳转到一个作用域相同的基本块A,且A只被B引用
        /// </summary>
        public sealed class BlockInliner : BlockRecursiveModel {
                private BlockInliner(List<IBlock> blocks, IBlock scope) : base(blocks, scope) {
                }

                /// <summary>
                /// 内联
                /// </summary>
                /// <param name="methodBlock"></param>
                /// <returns></returns>
                public static bool Inline(MethodBlock methodBlock) {
                        bool result;

                        methodBlock.PushExtraDataAllBasicBlocks(() => new BlockInfo());
                        new BlockXref(methodBlock, AddXref).Analyze();
                        // 我们需要分析出所有块间的引用关系之后才能彻底完成内联
                        result = Execute(methodBlock, (blocks, scope) => new BlockInliner(blocks, scope));
                        methodBlock.PopExtraDataAllBasicBlocks();
                        return result;
                }

                private static void AddXref(BasicBlock source, BasicBlock target) {
                        List<BasicBlock> references;
                        List<BasicBlock> dereferences;

                        references = source.PeekExtraData<BlockInfo>().References;
                        if (!references.Contains(target))
                                references.Add(target);
                        dereferences = target.PeekExtraData<BlockInfo>().Dereferences;
                        if (!dereferences.Contains(source))
                                dereferences.Add(source);
                }

                /// <summary />
                protected override bool Execute() {
                        bool isModified;
                        bool next;

                        if (_blocks.Count < 2)
                                return false;
                        isModified = FixEntryBlockIfBrOnly();
                        // 如果作用域的入口(也就是作用域的第一个块)是空块,我们特殊处理
                        do {
                                for (int i = 1; i < _blocks.Count; i++) {
                                        // 跳过入口块,这段代码处理不了入口块
                                        BasicBlock target;
                                        BlockInfo targetInfo;

                                        target = _blocks as BasicBlock;
                                        // target代表可能可以被合并的块
                                        if (target == null)
                                                // 作用域块不能合并到其它块
                                                continue;
                                        targetInfo = target.PeekExtraData<BlockInfo>();
                                        if (CanInline(target, targetInfo)) {
                                                UpdateReferencesOfDereferences(target, targetInfo);
                                                // 更新target反向引用的引用
                                                UpdateDereferencesOfReferences(target, targetInfo);
                                                // 更新target引用的反向引用
                                                targetInfo.IsInlineed = true;
                                        }
                                }
                                next = _blocks.RemoveAll(block => block is BasicBlock && block.PeekExtraData<BlockInfo>().IsInlineed) != 0;
                                if (next)
                                        isModified = true;
                        } while (next);
                        return isModified;
                }

                private static bool CanInline(BasicBlock target, BlockInfo targetInfo) {
                        if (target.IsEmpty && target.BranchOpcode.Code == Code.Br) {
                                // 空的br跳转块,可以无条件合并
                                return true;
                        }
                        else {
                                BasicBlock dereference;

                                if (targetInfo.Dereferences.Count != 1)
                                        // 当target只被一个块引用时才可以被内联
                                        return false;
                                dereference = targetInfo.Dereferences;
                                if (dereference.BranchOpcode.Code != Code.Br)
                                        // 引用当前块的块必须为基本块,并且最后一条指令为br
                                        // 如果为leave,表示反向引用来自其它作用域,target和反向引用不在同一作用域,这时我们不能对target进行内联
                                        return false;
                                return true;
                        }
                }

                private static void UpdateReferencesOfDereferences(BasicBlock target, BlockInfo targetInfo) {
                        foreach (BasicBlock dereference in targetInfo.Dereferences) {
                                if (dereference.BranchOpcode.Code == Code.Br) {
                                        // br无条件跳转,直接内联基本块
                                        if (!target.IsEmpty)
                                                dereference.Instructions.AddRange(target.Instructions);
                                        dereference.BranchOpcode = target.BranchOpcode;
                                        dereference.FallThrough = target.FallThrough;
                                        dereference.ConditionalTarget = target.ConditionalTarget;
                                        dereference.SwitchTargets = target.SwitchTargets;
                                }
                                else {
                                        // 要逐个判断哪里使用了target
                                        if (dereference.FallThrough == target)
                                                dereference.FallThrough = target.FallThrough;
                                        if (dereference.ConditionalTarget == target)
                                                dereference.ConditionalTarget = target.FallThrough;
                                        if (dereference.SwitchTargets != null)
                                                for (int j = 0; j < dereference.SwitchTargets.Count; j++)
                                                        if (dereference.SwitchTargets == target)
                                                                dereference.SwitchTargets = target.FallThrough;
                                }
                                ListReplace(dereference.PeekExtraData<BlockInfo>().References, target, targetInfo.References);
                                // 将target反向引用的引用中的target换成target的引用
                        }
                }

                private static void UpdateDereferencesOfReferences(BasicBlock target, BlockInfo targetInfo) {
                        foreach (BasicBlock reference in targetInfo.References)
                                ListReplace(reference.PeekExtraData<BlockInfo>().Dereferences, target, targetInfo.Dereferences);
                        // 将target引用的反向引用中的target换成target的反向引用
                }

                private static void ListReplace<T>(List<T> list, T oldItem, List<T> newItems) {
                        if (newItems.Count > 1) {
                                list.Remove(oldItem);
                                foreach (T newItem in newItems)
                                        if (!list.Contains(newItem))
                                                list.Add(newItem);
                        }
                        else if (newItems.Count == 1) {
                                for (int i = 0; i < list.Count; i++)
                                        if (ReferenceEquals(list, oldItem))
                                                list = newItems;
                        }
                }

                private bool FixEntryBlockIfBrOnly() {
                        if (!IsBrOnlyBlock(_blocks))
                                return false;

                        BasicBlock entryBlock;
                        IBlock fallThroughRoot;

                        entryBlock = (BasicBlock)_blocks;
                        fallThroughRoot = GetNonBrOnlyFallThrough(entryBlock).GetRootBlock(_scope);
                        _blocks = entryBlock;
                        _blocks = fallThroughRoot;
                        // 我们只交换入口基本块和br-only最终到达的块的位置
                        // 所以FixEntryBlockIfBrOnly必须在最开始被调用,然后当前作用域块的入口才能被修复
                        return false;
                }

                private static bool IsBrOnlyBlock(IBlock block) {
                        BasicBlock basicBlock;

                        basicBlock = block as BasicBlock;
                        return basicBlock != null && IsBrOnlyBlock(basicBlock);
                }

                private static bool IsBrOnlyBlock(BasicBlock basicBlock) {
                        return basicBlock.IsEmpty && basicBlock.BranchOpcode.Code == Code.Br;
                }

                private static BasicBlock GetNonBrOnlyFallThrough(BasicBlock basicBlock) {
                        return IsBrOnlyBlock(basicBlock) ? GetNonBrOnlyFallThrough(basicBlock.FallThrough) : basicBlock;
                }

                private sealed class BlockInfo {
                        private List<BasicBlock> _references;
                        private List<BasicBlock> _dereferences;
                        private bool _isInlineed;

                        public List<BasicBlock> References {
                                get => _references;
                                set => _references = value;
                        }

                        public List<BasicBlock> Dereferences {
                                get => _dereferences;
                                set => _dereferences = value;
                        }

                        public bool IsInlineed {
                                get => _isInlineed;
                                set => _isInlineed = value;
                        }

                        public BlockInfo() {
                                _references = new List<BasicBlock>();
                                _dereferences = new List<BasicBlock>();
                        }
                }
        }
}
```

还是再强调一下,控制流分析系列的文章肯定不会简单,走马观花地看是不行的,要完全地看懂上面贴出的代码,还是要自己编译上面的代码(放到上一篇文章放出的源码里面编译),到VS里面单步调试,一点一点看完整的流程。

### 标准化

之前写的3个小节,都是标准化需要的操作。什么是标准化?把控制流化简到最简,就是标准化。对控制流进行标准化之后,我们匹配特征将会非常的容易,清理效果可以提升很多。

代码:

``` csharp
/// <summary>
/// 创建标准化的方法块
/// </summary>
/// <param name="methodDef"></param>
/// <returns></returns>
public static MethodBlock CreateStandardMethodBlock(this MethodDef methodDef) {
        if (methodDef == null)
                throw new ArgumentNullException(nameof(methodDef));

        MethodBlock methodBlock;

        methodBlock = methodDef.CreateMethodBlock();
        methodBlock.Standardize();
        return methodBlock;
}

/// <summary>
/// 对方法块进行标准化(移除NOP,内联,排序)
/// </summary>
/// <param name="methodBlock"></param>
public static void Standardize(this MethodBlock methodBlock) {
        if (methodBlock == null)
                throw new ArgumentNullException(nameof(methodBlock));

        NopRemover.Remove(methodBlock);
        BlockSorter.Sort(methodBlock);
        // 这里不是为了排序,而是为了清除无效块,否则BlockInliner内联可能不彻底
        BlockInliner.Inline(methodBlock);
        BlockSorter.Sort(methodBlock);
        // 拓扑排序
}
```

## Switch混淆

我目前遇到的最难的控制流混淆应该就是ConfuserEx的Switch混淆,能搞定ConfuserEx的Switch混淆,其它控制流混淆应该没问题了,所以这里只讲ConfuserEx。编译好的工具文章末尾也有。

ConfuserEx的控制流混淆有很多种模式,这里只说ConfuserEx-GUI加出的控制流混淆,也就是Switch-Normal模式。其它模式可以看官方文档 (https://github.com/yck1509/ConfuserEx/wiki/Control-Flow-Protection)。其它模式的反混淆,原理都差不多的,就不重复讲了。

然后关于ConfuserEx的一些Mod版本,控制流混淆的变化都不是特别大,反混淆原理也是相同的。

### 分析

找个ConfuserEx加控制流混淆的程序,用dnSpy先看看特征。

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/11.png)

很明显的,这种不能静态解密,要跳转到的下一个case与上一个case有关。dnSpy看着有2个局部变量控制着控制流,事实上是这样吗?

不是!其中有一个num是反编译器生成的。

我们看看IL:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/12.png)

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/13.png)

只用到了局部变量V_1。

为什么ConfuserEx生成的控制流混淆里面的常量都特别的大?关键还是一个求余运算,比如 x % 7,那么结果的取值范围就是{0, 1, 2, 3, 4, 5, 6},恰好7个结果。

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/14.png)

比如这个switch,有7个条件跳转目标,那么就是% 7,也就是除以7求余数。

我们还会注意到,对num赋值有2种情况,一种是和num本身的值有关的,一种是无关的:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/15.png)

为什么会出现直接一条num = ????;就完事的代码呢?全部使用上下文相关,也就是线性编码不是强度更高么?这个肯定不是ConfuserEx作者故意的,这个也是有原因的。我们可以看看ConfuserEx源码,在这里可以找到答案:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/16.png)

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/17.png)

这段代码的意思是,如果一个基本块A有未知来源,意思就是有非已知的基本块会跳转基本块A,那么就不生成线性解码的代码。因为如果是一个未知的基本块跳转到了基本块A,那么此时的num的值是不确定的,如果还是用num = num * xxxx ^ xxxx;,就会导致解码出的num是错误的。

所以我们可以得出这种线性Switch混淆的一个结论:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/18.png)

线性Switch混淆就像图中一坨混在一起的线,直接进入内部,是清理不了混淆的。而线性Switch混淆有且至少有一个为未知源准备的入口点,也就是图中箭头指着的几个很细的线条,也就是ConfuserEx Switch混淆中的直接对num进行赋值的地方。

我们再用工具FlowGraph看看(开了优化的):

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/19.png)

蓝色框圈出来的就是这个线性Switch的一个入口点。

我们可以再看看其它方法体,也是如此:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/20.png)

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/21.png)

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/22.png)

和之前总结的特征一样。

所以,我们要清理线性Switch混淆,只能从这种入口点进入,虚拟执行部分代码,才可以达到效果。

### 虚拟机

虚拟执行需要虚拟机。虽然有现成的虚拟机,比如de4dot.blocks里面的虚拟机,但是我就是喜欢造轮子,自己写的用着舒服,修改起来也方便,看别人的代码太累了,看懂了还要自己修改,不如从头写一个。

虚拟机完整代码文章末尾也有。

#### 操作码分类

我们可以先对所有指令操作码进行分类,对我们需要的操作码进行模拟,不需要的就不模拟。

这里我贴上我分类好的:

``` csharp
Add
Add_Ovf
Add_Ovf_Un
And
Div
Div_Un
Mul
Mul_Ovf
Mul_Ovf_Un
Neg
Not
Or
Rem
Rem_Un
Shl
Shr
Shr_Un
Sub
Sub_Ovf
Sub_Ovf_Un
Xor
// 运算

Ceq
Cgt
Cgt_Un
Ckfinite
Clt
Clt_Un
// 判断

Box
Castclass
Conv_I
Conv_I1
Conv_I2
Conv_I4
Conv_I8
Conv_Ovf_I
Conv_Ovf_I_Un
Conv_Ovf_I1
Conv_Ovf_I1_Un
Conv_Ovf_I2
Conv_Ovf_I2_Un
Conv_Ovf_I4
Conv_Ovf_I4_Un
Conv_Ovf_I8
Conv_Ovf_I8_Un
Conv_Ovf_U
Conv_Ovf_U_Un
Conv_Ovf_U1
Conv_Ovf_U1_Un
Conv_Ovf_U2
Conv_Ovf_U2_Un
Conv_Ovf_U4
Conv_Ovf_U4_Un
Conv_Ovf_U8
Conv_Ovf_U8_Un
Conv_R_Un
Conv_R4
Conv_R8
Conv_U
Conv_U1
Conv_U2
Conv_U4
Conv_U8
Unbox
Unbox_Any
// 转换

Dup
Ldarg
Ldarga
Ldc_I4
Ldc_I8
Ldc_R4
Ldc_R8
Ldelem
Ldelem_I
Ldelem_I1
Ldelem_I2
Ldelem_I4
Ldelem_I8
Ldelem_R4
Ldelem_R8
Ldelem_Ref
Ldelem_U1
Ldelem_U2
Ldelem_U4
Ldelema
Ldfld
Ldflda
Ldftn
Ldind_I
Ldind_I1
Ldind_I2
Ldind_I4
Ldind_I8
Ldind_R4
Ldind_R8
Ldind_Ref
Ldind_U1
Ldind_U2
Ldind_U4
Ldlen
Ldloc
Ldloca
Ldnull
Ldobj
Ldsfld
Ldsflda
Ldstr
Ldtoken
Ldvirtftn
Newarr
Newobj
Pop
Starg
Stelem
Stelem_I
Stelem_I1
Stelem_I2
Stelem_I4
Stelem_I8
Stelem_R4
Stelem_R8
Stelem_Ref
Stfld
Stind_I
Stind_I1
Stind_I2
Stind_I4
Stind_I8
Stind_R4
Stind_R8
Stind_Ref
Stloc
Stobj
Stsfld
// 取值赋值

Beq
Bge
Bge_Un
Bgt
Bgt_Un
Ble
Ble_Un
Blt
Blt_Un
Bne_Un
Br
Brfalse
Brtrue
Endfilter
Endfinally
Leave
Ret
Rethrow
Switch
Throw
// 分支

Call
Calli
Callvirt
// 调用

Arglist
Cpblk
Cpobj
Initblk
Initobj
Isinst
Localloc
Mkrefany
Refanytype
Refanyval
Sizeof
// 其它
```

比如处理ConfuserEx控制流混淆,我们实现部分取值赋值,分配指令的虚拟化,还有所有运算指令的虚拟化就够了,非常简单。

#### 虚拟值

我把虚拟机中的值分成了几种常见类型:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/23.png)

再写一个一个接口,表示虚拟值就行。

``` csharp
/// <summary>
/// 值标志
/// </summary>
public enum ValueType {
        /// <summary>
        /// <see cref="object"/>
        /// </summary>
        Object,

        /// <summary>
        /// <see cref="bool"/>, <see cref="sbyte"/>, <see cref="byte"/>, <see cref="short"/>, <see cref="ushort"/>, <see cref="int"/>, <see cref="uint"/>
        /// 在CLR内最小单位是4字节
        /// </summary>
        Int32,

        /// <summary>
        /// <see cref="long"/>, <see cref="ulong"/>
        /// </summary>
        Int64,

        /// <summary>
        /// 空值,使用 <see cref="AnyValue"/> 表示
        /// </summary>
        Null,

        /// <summary>
        /// 未知值,使用任意继承自 <see cref="IValue"/> 的类型表示
        /// 比如使用 <see cref="Int32Value"/> 类表示,意思是类型为 <see cref="Int32Value"/>,但是值不确定
        /// </summary>
        Unknown,

        /// <summary>
        /// 数组,使用 <see cref="AnyValue"/> 表示。<see cref="AnyValue.Value"/> 将为 <see cref="IValue"/> 的数组
        /// </summary>
        Array,

        /// <summary>
        ///用户定义类型
        /// </summary>
        User
}

/// <summary>
/// 表示一个值
/// </summary>
public interface IValue {
        /// <summary>
        /// 标志
        /// </summary>
        ValueType Type { get; set; }

        /// <summary>
        /// 值类型返回 this 指针,引用类型深度克隆自身
        /// </summary>
        /// <returns></returns>
        IValue Clone();
}
```

#### 架构

我用的是de4dot.blocks里面的架构,稍微修改了一下,把虚拟机和上下文(Context)本身分离了。

``` csharp
/*
* 虚拟机本身参考了de4dot的设计
*
* 以下代码应与ControlFlow.Blocks项目没有任何关系
* 转换部分应该由扩展类Extensions完成
* 模拟器Emulator类只需要完成模拟的功能,不需要关心是什么样的Block
* 也不需要关心异常处理块是什么样的
* 只需要返回失败,由用户进行判断和处理
* 用户需要判断是何种原因造成了模拟的失败
*
* 这个项目和ControlFlow.Blocks项目一样
* 需要先使用ControlFlow.Blocks.Extensions.SimplifyMacros(MethodDef)化简指令
* 否则可能模拟失败
*/

/// <summary>
/// 模拟器上下文
/// </summary>
public sealed class EmulationContext {
        private readonly Dictionary<Local, IValue> _variables;
        private readonly Stack<IValue> _evaluationStack;

        /// <summary>
        /// 局部变量
        /// </summary>
        public Dictionary<Local, IValue> Variables => _variables;

        /// <summary>
        /// 计算堆栈
        /// </summary>
        public Stack<IValue> EvaluationStack => _evaluationStack;

        /// <summary>
        /// 构造器
        /// </summary>
        public EmulationContext() {
                _evaluationStack = new Stack<IValue>();
                _variables = new Dictionary<Local, IValue>();
        }

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="variables"></param>
        public EmulationContext(IEnumerable<Local> variables) : this() {
                if (variables == null)
                        throw new ArgumentNullException(nameof(variables));

                foreach (Local variable in variables)
                        _variables.Add(variable, null);
        }

        private EmulationContext(Dictionary<Local, IValue> variables, Stack<IValue> evaluationStack) {
                if (variables == null)
                        throw new ArgumentNullException(nameof(variables));
                if (evaluationStack == null)
                        throw new ArgumentNullException(nameof(evaluationStack));

                _variables = variables;
                _evaluationStack = evaluationStack;
        }

        /// <summary>
        /// 克隆当前实例
        /// </summary>
        /// <returns></returns>
        public EmulationContext Clone() {
                IValue[] array;
                Stack<IValue> evaluationStack;
                Dictionary<Local, IValue> variables;

                array = _evaluationStack.ToArray();
                evaluationStack = new Stack<IValue>(_evaluationStack.Count);
                for (int i = array.Length - 1; i >= 0; i--)
                        evaluationStack.Push(array.Clone());
                variables = new Dictionary<Local, IValue>(_variables.Count);
                foreach (KeyValuePair<Local, IValue> variable in _variables)
                        variables.Add(variable.Key, variable.Value?.Clone());
                return new EmulationContext(variables, evaluationStack);
        }
}

/// <summary>
/// 模拟器结果
/// </summary>
public sealed class EmulationResult {
        private readonly bool _success;
        private readonly Instruction _failedInstruction;
        private readonly Exception _exception;

        /// <summary>
        /// 是否成功
        /// </summary>
        public bool Success => _success;

        /// <summary>
        /// 模拟失败的指令
        /// </summary>
        public Instruction FailedInstruction => _failedInstruction;

        /// <summary>
        /// 异常(如果有)
        /// </summary>
        public Exception Exception => _exception;

        internal EmulationResult(bool success, Instruction failedInstruction, Exception exception) {
                _success = success;
                _failedInstruction = failedInstruction;
                _exception = exception;
        }
}
```

给虚拟机一个上下文,传入要虚拟执行的指令,返回执行结果,就是这么简单,并不复杂。

比如我们要虚拟执行运算指令,我们要用好C#的lambda。

``` csharp
private bool Template_Arithmetic(Func<int, int, int> operation32, Func<long, long, long> operation64) {
        IValue x;
        IValue y;
        IValue result;

        y = EvaluationStack.Pop();
        x = EvaluationStack.Pop();
        result = CheckAndTryGetUnknownValue_Arithmetic(x, y);
        if (result != null) {
                EvaluationStack.Push(result);
                return true;
        }
        if (x is Int32Value && y is Int32Value) {
                if (operation32 == null)
                        ThrowNotImpl();
                result = new Int32Value(operation32(((Int32Value)x).Int32, ((Int32Value)y).Int32));
        }
        else {
                if (operation32 == null)
                        ThrowNotImpl();
                result = new Int64Value(operation64(GetInt64_Arithmetic(x), GetInt64_Arithmetic(y)));
        }
        EvaluationStack.Push(result);
        return true;
}

private static IValue CheckAndTryGetUnknownValue_Arithmetic(IValue x) {
        if (!(x is Int32Value) && !(x is Int64Value))
                ThrowErrorType();
        if (x.Type == ValueType.Unknown)
                return x is Int32Value ? (IValue)Int32Value.Unknown : Int64Value.Unknown;
        else
                return null;
}

private static IValue CheckAndTryGetUnknownValue_Arithmetic(IValue x, IValue y) {
        if ((!(x is Int32Value) && !(x is Int64Value)) || (!(y is Int32Value) && !(y is Int64Value)))
                ThrowErrorType();
        if (x.Type == ValueType.Unknown || y.Type == ValueType.Unknown)
                return x is Int32Value ? (IValue)Int32Value.Unknown : Int64Value.Unknown;
        else
                return null;
}

private static long GetInt64_Arithmetic(IValue value) {
        return value is Int32Value ? ((Int32Value)value).Int32 : ((Int64Value)value).Int64;
}
```

要模拟运算指令,调用Template_Arithmetic就行,非常简单。

``` csharp
protected virtual bool Emulate_Add(Instruction instruction) {
        return Template_Arithmetic((x, y) => x + y, (x, y) => x + y);
}

protected virtual bool Emulate_And(Instruction instruction) {
        return Template_Arithmetic((x, y) => x & y, (x, y) => x & y);
}

protected virtual bool Emulate_Div(Instruction instruction) {
        return Template_Arithmetic((x, y) => x / y, (x, y) => x / y);
}
```

剩下的非常简单,就是大循环套着Switch判断操作码,然后调用对应的方法进行虚拟执行,代码不贴了。

### 清除

有了虚拟机之后,我们要清除Switch混淆就方便很多了。我们可以开始清除Switch混淆了。

先把部分情况进行特殊处理。

ConfuserEx会把条件跳转指令转换成这样的形式:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/24.png)

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/25.png)

这里的dup和pop就是ConfuserEx故意干扰我们的代码,这里的dup和pop完全可以直接移除。

移除这种dup和pop的核心代码(其它代码没贴,了解个思路就行):

``` csharp
private void HandleMultiDupWithOnePop(BasicBlock popBlock) {
        // 我们暂时只处理这种情况,多个dup块对应单个pop块(ConfuserEx)
        // 还没见到单个dup块对应多个pop块的
        int popCount;
        List<BasicBlock> dupBlocks;
        int dupCount;

        popCount = GetPopCount(popBlock);
        if (popCount == 0)
                return;
        dupBlocks = popBlock.PeekExtraData<BlockInfo>().Dereferences;
        // 假设反向引用都有dup
        if (dupBlocks.Count == 0)
                // 作用域入口点可能没有反向引用,比如方法块入口点,Catch块入口点
                return;
        foreach (BasicBlock dupBlock in dupBlocks)
                if (dupBlock.BranchOpcode.Code != Code.Br)
                        // 必须是无条件跳转到pop块
                        return;
        dupCount = int.MaxValue;
        foreach (BasicBlock dupBlock in dupBlocks) {
                int temp;

                temp = GetDupCount(dupBlock);
                if (temp < dupCount)
                        dupCount = temp;
        }
        // 找出最小dup数量
        if (dupCount == 0)
                return;
        if (popCount < dupCount)
                dupCount = popCount;
        // 找出最小配对的dup-pop数量
        popBlock.Instructions.RemoveRange(0, dupCount);
        // pop块移除开头的pop
        foreach (BasicBlock dupBlock in dupBlocks)
                dupBlock.Instructions.RemoveRange(dupBlock.Instructions.Count - dupCount, dupCount);
        // dup块移除结尾的dup
        _dupCount += dupCount;
}
```

和之前的BlockInliner一样,我们还要对ConfuserEx混淆过的If进行内联,方便我们标记要模拟的指令来进行清理。

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/26.png)

比如这种,红框中的基本块可以内联到上面2个基本块中。

我们要先定义一个抽象类,写清理线性Switch混淆的逻辑,而识别部分放到子类里面进行实现,达到代码复用。

抽象类的代码我直接贴上来了:

``` csharp
/// <summary>
/// 线性Switch反混淆(比如ConfuserEx)
/// 我们一次只清理一个线性Switch,否则代码会极其复杂
/// </summary>
public abstract class LinearSwitchDeobfuscatorBase : BlockRecursiveModel {
        /// <summary>
        /// 指令模拟器
        /// </summary>
        protected readonly Emulator _emulator;
        /// <summary>
        /// Switch块
        /// </summary>
        protected BasicBlock _switchBlock;
        private bool _isModified;

        /// <summary />
        protected LinearSwitchDeobfuscatorBase(List<IBlock> blocks, IBlock scope, EmulationContext emulationContext) : base(blocks, scope) {
                _emulator = new Emulator(emulationContext);
        }

        /// <summary />
        protected static bool Deobfuscate(MethodBlock methodBlock, BlockRecursiveModelCreator deobfuscatorCreator) {
                return Execute(methodBlock, deobfuscatorCreator);
        }

        /// <summary />
        protected override bool Execute() {
                if (_blocks.Count < 2)
                        return false;
                OnBegin();
                if (_switchBlock == null)
                        return false;
                foreach (BasicBlock entry in GetEntries())
                        VisitAllBasicBlocks(entry);
                OnEnd();
                return _isModified;
        }

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

                if (basicBlock.Scope != _scope)
                        // 指定基本块不在当前作用域,不需要继续访问了
                        return;
                blockInfo = basicBlock.PeekExtraData<BlockInfoBase>();
                if (blockInfo.IsVisited && basicBlock != _switchBlock)
                        // 如果基本块已经访问过并且基本块不是Switch块,直接返回
                        return;
                blockInfo.IsVisited = true;
                if (blockInfo.EmulationInfo != null) {
                        // 如果需要模拟
                        EmulationInfo emulationInfo;
                        EmulationResult emulationResult;

                        emulationInfo = blockInfo.EmulationInfo;
                        _isModified |= OnEmulateBegin(basicBlock);
                        emulationResult = _emulator.Emulate(basicBlock.Instructions, emulationInfo.StartIndex, emulationInfo.Length);
                        _isModified |= OnEmulateEnd(basicBlock);
                        if (!emulationResult.Success)
                                throw new NotImplementedException("暂未实现模拟失败处理,需要更新反混淆模型,或者检查是否模拟了不需要模拟的指令");
                }
                if (basicBlock == _switchBlock)
                        // 我们要设置下一个要访问的基本块
                        VisitAllBasicBlocks(GetNextBasicBlock());
                else
                        // 如果不是Switch块,我们使用递归访问下一个基本块
                        switch (basicBlock.BranchOpcode.FlowControl) {
                        case FlowControl.Branch:
                                // 无条件跳转,不需要备份当前模拟器上下文
                                VisitAllBasicBlocks(basicBlock.FallThrough);
                                break;
                        case FlowControl.Cond_Branch:
                                CallNextVisitAllBasicBlocksConditional(basicBlock);
                                break;
                        }
        }

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

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

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

        /// <summary>
        /// 在指定基本块模拟前触发,返回当前基本块是否修改
        /// </summary>
        /// <param name="basicBlock"></param>
        /// <returns></returns>
        protected abstract bool OnEmulateBegin(BasicBlock basicBlock);

        /// <summary>
        /// 在指定基本块模拟后触发,返回当前基本块是否修改
        /// </summary>
        /// <param name="basicBlock"></param>
        /// <returns></returns>
        protected abstract bool OnEmulateEnd(BasicBlock basicBlock);

        /// <summary>
        /// 在遇到Switch块后,通过模拟器获取下一个基本块
        /// </summary>
        /// <returns></returns>
        protected virtual BasicBlock GetNextBasicBlock() {
                Int32Value value;

                value = _emulator.EvaluationStack.Pop() as Int32Value;
                if (value == null)
                        throw new InvalidOperationException();
                return _switchBlock.SwitchTargets;
        }

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

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

        /// <summary>
        /// 基本块额外信息基类
        /// </summary>
        protected abstract class BlockInfoBase {
                /// <summary />
                protected bool _isVisited;
                /// <summary />
                protected EmulationInfo _emulationInfo;

                /// <summary>
                /// 是否访问过这个基本块
                /// </summary>
                public bool IsVisited {
                        get => _isVisited;
                        set => _isVisited = value;
                }

                /// <summary>
                /// 模拟相关信息
                /// 如果需要模拟,将这个属性设置为非 <see langword="null"/>,反之保持默认,即为 <see langword="null"/>
                /// </summary>
                public EmulationInfo EmulationInfo {
                        get => _emulationInfo;
                        set => _emulationInfo = value;
                }
        }

        /// <summary>
        /// 提供模拟所需信息
        /// </summary>
        protected sealed class EmulationInfo {
                private readonly int _startIndex;
                private readonly int _length;

                /// <summary>
                /// 从指定索引的指令开始模拟
                /// </summary>
                public int StartIndex => _startIndex;

                /// <summary>
                /// 要模拟的指令的数量
                /// </summary>
                public int Length => _length;

                /// <summary>
                /// 构造器
                /// </summary>
                /// <param name="startIndex"></param>
                /// <param name="length"></param>
                public EmulationInfo(int startIndex, int length) {
                        _startIndex = startIndex;
                        _length = length;
                }
        }
}
```

代码不多,核心的部分还是在方法名带了VisitAllBasicBlocks的方法里。比如VisitAllBasicBlocks,就是模拟程序正常执行流程,遇到了Switch混淆的地方,就模拟执行,然后在OnEmulateEnd里面完成Switch混淆的解密。

做完那么多清理操作,ConfuserEx的Switch差不多原形毕露了,特征变得非常明显,我们继承LinearSwitchDeobfuscatorBase再识别一下特征就可以了。

``` csharp
public sealed class LinearSwitchDeobfuscator : LinearSwitchDeobfuscatorBase {
        private BasicBlock _lastCaseBlockAny;

        private LinearSwitchDeobfuscator(List<IBlock> blocks, IBlock scope, EmulationContext emulationContext) : base(blocks, scope, emulationContext) {
        }

        public static bool Deobfuscate(MethodBlock methodBlock) {
                bool isModified;

                isModified = false;
                while (Deobfuscate(methodBlock, (blocks, scope) => new LinearSwitchDeobfuscator(blocks, scope, methodBlock.CreateEmulationContext()))) {
                        // 我们一次只能清除一个LinearSwitch,所以用while循环
                        methodBlock.Standardize();
                        isModified = true;
                }
                return isModified;
        }

        protected override void OnBegin() {
                foreach (BasicBlock basicBlock in _blocks.EnumerateAllBasicBlocks())
                        if (IsLinearSwitchBlock(basicBlock)) {
                                _switchBlock = basicBlock;
                                break;
                        }
                // 先寻找Switch块
                if (_switchBlock == null)
                        return;
                foreach (BasicBlock basicBlock in _blocks.EnumerateAllBasicBlocks()) {
                        if (basicBlock == _switchBlock)
                                basicBlock.PushExtraData(new BlockInfo(BlockType.LinearSwitch) {
                                        EmulationInfo = new EmulationInfo(0, SwitchConstants.LinearSwitchCodes.Length)
                                });
                        else if (IsCaseBlock(basicBlock))
                                basicBlock.PushExtraData(new BlockInfo(BlockType.Case) {
                                        EmulationInfo = new EmulationInfo(basicBlock.Instructions.Count - SwitchConstants.CaseCodes.Length, SwitchConstants.CaseCodes.Length)
                                });
                        else if (IsLinearCaseBlock(basicBlock))
                                basicBlock.PushExtraData(new BlockInfo(BlockType.LinearCase) {
                                        EmulationInfo = new EmulationInfo(basicBlock.Instructions.Count - SwitchConstants.LinearCaseCodes1.Length, SwitchConstants.LinearCaseCodes1.Length)
                                        // LinearCaseCodes1和LinearCaseCodes2长度一样
                                });
                        else
                                basicBlock.PushExtraData(new BlockInfo(BlockType.Normal));
                }
        }

        private bool IsLinearSwitchBlock(BasicBlock basicBlock) {
                return basicBlock.BranchOpcode.Code == Code.Switch && basicBlock.Instructions.CodeEquals(SwitchConstants.LinearSwitchCodes);
        }

        private bool IsCaseBlock(BasicBlock basicBlock) {
                return basicBlock.BranchOpcode.Code == Code.Br && basicBlock.FallThrough == _switchBlock && basicBlock.Instructions.EndsWith(SwitchConstants.CaseCodes);
        }

        private bool IsLinearCaseBlock(BasicBlock basicBlock) {
                return basicBlock.BranchOpcode.Code == Code.Br &&
                        basicBlock.FallThrough == _switchBlock &&
                        (basicBlock.Instructions.EndsWith(SwitchConstants.LinearCaseCodes1) ||
                        basicBlock.Instructions.EndsWith(SwitchConstants.LinearCaseCodes2));
        }

        protected override void OnEnd() {
                foreach (BasicBlock basicBlock in _blocks.EnumerateAllBasicBlocks())
                        basicBlock.PopExtraData();
        }

        protected override IEnumerable<BasicBlock> GetEntries() {
                foreach (BasicBlock basicBlock in _blocks.EnumerateAllBasicBlocks())
                        if (basicBlock.PeekExtraData<BlockInfo>().Type == BlockType.Case)
                                yield return basicBlock;
        }

        protected override bool OnEmulateBegin(BasicBlock basicBlock) {
                return false;
        }

        protected override bool OnEmulateEnd(BasicBlock basicBlock) {
                BlockInfo blockInfo;

                blockInfo = basicBlock.PeekExtraData<BlockInfo>();
                switch (blockInfo.Type) {
                case BlockType.LinearSwitch:
                        Int32Value value;

                        if (_lastCaseBlockAny == null)
                                throw new InvalidOperationException();
                        value = _emulator.EvaluationStack.Peek() as Int32Value;
                        if (value == null)
                                throw new InvalidOperationException();
                        _lastCaseBlockAny.FallThrough = _switchBlock.SwitchTargets;
                        _lastCaseBlockAny = null;
                        return true;
                case BlockType.Case:
                        basicBlock.Instructions.RemoveTrailingRange(SwitchConstants.CaseCodes.Length);
                        _lastCaseBlockAny = basicBlock;
                        break;
                case BlockType.LinearCase:
                        basicBlock.Instructions.RemoveTrailingRange(SwitchConstants.LinearCaseCodes1.Length);
                        _lastCaseBlockAny = basicBlock;
                        break;
                }
                return false;
        }

        private enum BlockType {
                Normal,
                LinearSwitch,
                Case,
                LinearCase
        }

        private sealed class BlockInfo : BlockInfoBase {
                private readonly BlockType _type;

                public BlockType Type => _type;

                public BlockInfo(BlockType type) {
                        _type = type;
                }
        }
}
```

这段代码中的清理部分在OnEmulateEnd,这个OnEmulateEnd很像一个Hook,我们在switch跳转到目标前截获当前的计算堆栈,得到switch(num)中的num,这样我们就可以知道上一个基本块要跳转到哪里了,然后我们把上一个基本块的跳转目标修改掉,就完成了一个清理操作。

如果ConfuserEx的switch混淆加了N多层,我们还要再判断一次,是否真的是线性Switch,比如这样的,就不是线性Switch:

!(https://raw.githubusercontent.com/wwh1004/blog/master/%5B.NET%5D.NET控制流分析(二)-反混淆/27.png)

如果我们不先清理掉这些非线性的Switch就清理线性Switch,很可能导致出错。

## 下载

控制流图绘制工具FlowGraph:[百度云](https://pan.baidu.com/s/1eCu6U0ZWsIwQBM3F9ZIcDw) 提取码:csb1

反混淆工具ConfuserExSwitchDeobfuscator:[百度云](https://pan.baidu.com/s/1Cesv95OBRb_llP4zQ_QbTQ) 提取码:6hvr

虚拟机ControlFlow.Emulation:[百度云](https://pan.baidu.com/s/10k372M1LAJQkg8Z6jsPPNw) 提取码:d687

用来测试ConfuserExSwitchDeobfuscator的UnpackMe,我加了15层控制流混淆:[百度云](https://pan.baidu.com/s/1VegZtzj4avjIXS4qrWL8Bg) 提取码:xbj6

wwh1004 发表于 2019-2-7 12:18

aisht 发表于 2019-2-6 20:39
非常感谢.
但...授人以鱼不如授人以渔~~
以其发您修改过的成品. 我更感兴趣的是... 修改了哪些地方~:lo ...

就发这里好了,发新主题也没什么人看。



这里可以加上for循环,比如我这里设置3就是加3层控制流混淆。



然后GUI不能指定参数,可以手动修改默认参数。

PredicateType一定改成Expression,我帖子里发的工具可以完美清除Normal模式的混淆,但是还没添加Expression模式的清除。

chen4321 发表于 2019-2-3 22:39

谢谢楼主分享,很好很强大

chenjingyes 发表于 2019-2-3 23:50

谢谢楼主分享:lol很详细呀

hackysh 发表于 2019-2-4 00:20

脑袋要炸了, 学的东西太多了

无极实验室 发表于 2019-2-4 03:40

牛啊,初学者看不懂,要回去看一遍编译原理去了

jnez112358 发表于 2019-2-4 08:32

继续学习,谢谢楼主,猪年吉祥

caoxin 发表于 2019-2-4 09:19

认真学习,早成大神

razhangchao 发表于 2019-2-4 09:37

支持一下

dongwin 发表于 2019-2-4 11:01

好,支持,学习了。

newpowersky 发表于 2019-2-4 15:18

牛B了,去github上给你加star了大神!!!!!!
页: [1] 2 3 4 5 6
查看完整版本: .NET控制流分析(二)-反混淆