.NET 中各种混淆(Obfuscation)的含义、原理、实际效果和不同级别的差异(使用 SmartAssembly)

本文详细介绍了.NET混淆工具SmartAssembly的各项混淆参数,包括名称混淆、流程混淆、动态代理、字符串编码与加密等,以及它们对程序集的实际影响。SmartAssembly通过混淆来提高代码的逆向难度,如类名和方法名的混淆、执行逻辑的修改等。在实际项目中,建议选择流程混淆、名称混淆、动态代理和字符串压缩加密以增强保护。
摘要由CSDN通过智能技术生成

长文预警!!!

UWP 程序有 .NET Native 可以将程序集编译为本机代码,逆向的难度会大很多;而基于 .NET Framework 和 .NET Core 的程序却没有 .NET Native 的支持。虽然有 Ngen.exe 可以编译为本机代码,但那只是在用户计算机上编译完后放入了缓存中,而不是在开发者端编译。

于是有很多款混淆工具来帮助混淆基于 .NET 的程序集,使其稍微难以逆向。本文介绍 Smart Assembly 各项混淆参数的作用以及其实际对程序集的影响。


本文不会讲 SmartAssembly 的用法,因为你只需打开它就能明白其基本的使用。

感兴趣可以先下载:.NET Obfuscator, Error Reporting, DLL Merging - SmartAssembly

准备

我们先需要准备程序集来进行混淆试验。这里,我使用 Whitman 来试验。它在 GitHub 上开源,并且有两个程序集可以试验它们之间的相互影响。

准备程序集

额外想吐槽一下,SmartAssembly 的公司 Red Gate 一定不喜欢这款软件,因为界面做成下面这样竟然还长期不更新:

无力吐槽的界面

而且,如果要成功编译,还得用上同为 Red Gate 家出品的 SQL Server,如果不装,软件到处弹窗报错。只是报告错误而已,干嘛还要开发者装一个那么重量级的 SQL Server 啊!详见:Why is SQL Server required — Redgate forums

SmartAssembly

SmartAssembly 本质上是保护应用程序不被逆向或恶意篡改。目前我使用的版本是 6,它提供了对 .NET Framework 程序的多种保护方式:

  • 强签名 Strong Name Signing
  • 自动错误上报 Automated Error Reporting
    • SmartAssembly 会自动向 exe 程序注入异常捕获与上报的逻辑。
  • 功能使用率上报 Feature Usage Reporting
    • SmartAssembly 会修改每个方法,记录这些方法的调用次数并上报。
  • 依赖合并 Dependencies Merging
    • SmartAssembly 会将程序集中你勾选的的依赖与此程序集合并成一个整的程序集。
  • 依赖嵌入 Dependencies Embedding
    • SmartAssembly 会将依赖以加密并压缩的方式嵌入到程序集中,运行时进行解压缩与解密。
    • 其实这只是方便了部署(一个 exe 就能发给别人),并不能真正保护程序集,因为实际运行时还是解压并解密出来了。
  • 裁剪 Pruning
    • SmartAssembly 会将没有用到的字段、属性、方法、事件等删除。它声称删除了这些就能让程序逆向后代码更难读懂。
  • 名称混淆 Obfuscation
    • 修改类型、字段、属性、方法等的名称。
  • 流程混淆 Control Flow Obfuscation
    • 修改方法内的执行逻辑,使其执行错综复杂。
  • 动态代理 References Dynamic Proxy
    • SmartAssembly 会将方法的调用转到动态代理上。
  • 资源压缩加密 Resources Compression and Encryption
    • SmartAssembly 会将资源以加密并压缩的方式嵌入到程序集中,运行时进行解压缩与解密。
  • 字符串压缩加密 Strings Encoding
    • SmartAssembly 会将字符串都进行加密,运行时自动对其进行解密。
  • 防止 MSIL Disassembler 对其进行反编译 MSIL Disassembler Protection
    • 在程序集中加一个 Attribute,这样 MSIL Disassembler 就不会反编译这个程序集。
  • 密封类
    • 如果 SmartAssembly 发现一个类可以被密封,就会把它密封,这样能获得一点点性能提升。
  • 生成调试信息 Generate Debugging Information
    • 可以生成混淆后的 pdb 文件

以上所有 SmartAssembly 对程序集的修改中,我标为 粗体 的是真的在做混淆,而标为 斜体 的是一些辅助功能。

后面我只会说明其混淆功能。

裁剪 Pruning

我故意在 Whitman.Core 中写了一个没有被用到的 internalUnusedClass,如果我们开启了裁剪,那么这个类将消失。

消失的类
▲ 没用到的类将消失

特别注意,如果标记了 InternalsVisibleTo,尤其注意不要不小心被误删了。

名称混淆 Obfuscation

类/方法名与字段名的混淆

名称混淆中,类名和方法名的混淆有三个不同级别:

  • 等级 1 是使用 ASCII 字符集
  • 等级 2 是使用不可见的 Unicode 字符集
  • 等级 3 是使用高级重命名算法的不可见的 Unicode 字符集

需要注意:对于部分程序集,类与方法名(NameMangling)的等级只能选为 3,否则混淆程序会无法完成编译

字段名的混淆有三个不同级别:

  • 等级 1 是源码中字段名称和混淆后字段名称一一对应
  • 等级 2 是在一个类中的不同字段使用不同名称即可(这不废话吗,不过 SmartAssembly 应该是为了强调与等级 1 和等级 3 的不同,必须写一个描述)
  • 等级 3 是允许不同类中的字段使用相同的名字(这样能够更加让人难以理解)

需要注意:对于部分程序集,字段名(FieldsNameMangling)的等级只能选为 2 或 3,否则混淆程序会无法完成编译

实际试验中,以上各种组合经常会出现无法编译的情况。

下面是 Whitman 中 RandomIdentifier 类中的部分字段在混淆后的效果:

// Token: 0x04000001 RID: 1
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private int \u0001;

// Token: 0x04000002 RID: 2
private readonly Random \u0001 = new Random();

// Token: 0x04000003 RID: 3
private static readonly Dictionary<int, int> \u0001 = new Dictionary<int, int>();

这部分的原始代码可以在 冷算法:自动生成代码标识符(类名、方法名、变量名) 找到。

如果你需要在混淆时使用名称混淆,你只需要在以上两者的组合中找到一个能够编译通过的组合即可,不需要特别在意等级 1~3 的区别,因为实际上都做了混淆,1~3 的差异对逆向来说难度差异非常小的。

需要 特别小心如果有 InternalsVisibleTo 或者依据名称的反射调用,这种混淆下极有可能挂掉!!!请充分测试你的软件,切记!!!

转移方法 ChangeMethodParent

如果开启了 ChangeMethodParent,那么混淆可能会将一个类中的方法转移到另一个类中,这使得逆向时对类型含义的解读更加匪夷所思。

排除特定的命名空间

如果你的程序集中确实存在需要被按照名称反射调用的类型,或者有 internal 的类/方法需要被友元程序集调用,请排除这些命名空间。

流程混淆 Control Flow Obfuscation

列举我在 Whitman.Core 中的方法:

public string Generate(bool pascal)
{
    var builder = new StringBuilder();
    var wordCount = WordCount <= 0 ? 4 - (int) Math.Sqrt(_random.Next(0, 9)) : WordCount;
    for (var i = 0; i < wordCount; i++)
    {
        var syllableCount = 4 - (int) Math.Sqrt(_random.Next(0, 16));
        syllableCount = SyllableMapping[syllableCount];
        for (var j = 0; j < syllableCount; j++)
        {
            var consonant = Consonants[_random.Next(Consonants.Count)];
            var vowel = Vowels[_random.Next(Vowels.Count)];
            if ((pascal || i != 0) && j == 0)
            {
                consonant = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(consonant);
            }

            builder.Append(consonant);
            builder.Append(vowel);
        }
    }

    return builder.ToString();
}

▲ 这个方法可以在 冷算法:自动生成代码标识符(类名、方法名、变量名) 找到。

流程混淆修改方法内部的实现。为了了解各种不同的流程混淆级别对代码的影响,我为每一个混淆级别都进行反编译查看。

没有混淆
▲ 没有混淆

0 级流程混淆

0 级流程混淆
▲ 0 级流程混淆

1 级流程混淆

1 级流程混淆
▲ 1 级流程混淆

可以发现 0 和 1 其实完全一样。又被 SmartAssembly 耍了。

2 级流程混淆

2 级流程混淆代码很长,所以我没有贴图:

// Token: 0x06000004 RID: 4 RVA: 0x00002070 File Offset: 0x00000270
public string Generate(bool pascal)
{
    StringBuilder stringBuilder = new StringBuilder();
    StringBuilder stringBuilder2;
    if (-1 != 
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值