.NET 程序保护实战系列03 · 符号混淆:名称就是第一道防线

目录

  1. 为什么名称本身就能构成防线
  2. VTable:跨继承链的名称一致性
  3. NameService:ConditionalWeakTable 驱动的重命名引擎
  4. 六种命名模式
  5. 跨模块协同:ProtectionSession.RunRename
  6. 引用追踪:INameReference 体系
  7. 可见性过滤与不可重命名标记
  8. 结语

1. 为什么名称本身就能构成防线

混淆前:

MyApp.LicenseValidator.Validate(string key)
MyApp.PaymentProcessor.ProcessTransaction(decimal amount)
MyApp.CryptoHelper.Encrypt(byte[] data)

混淆后:

\u200b.\u200c\u200d(int a)
\u200b.\u200b\u200c(decimal a)

消除名称中的语义信息 = 攻击者无法通过名称判断"该分析哪个类"。


2. VTable:跨继承链的名称一致性

基类和子类的 override 方法必须使用同一个新名称。我们的 VTableAnalyzer 追踪完整的继承链:

// 构建 VTable 关系链
public static void Analyze(IEnumerable<TypeDef> types)
{
    foreach (var type in types)
    {
        foreach (var method in type.Methods)
        {
            if (!method.IsVirtual || method.IsRuntime) continue;

            // 追溯基类中的原始方法声明
            var baseMethod = method.GetBaseMethod();
            if (baseMethod == null) continue;

            // 收集整个 override 链上的所有方法
            var overrides = FindAllOverrides(baseMethod);

            // 所有同链方法分配同一个 VTableSlot
            // 确保重命名后名称一致
            var slot = new VTableSlot(baseMethod, overrides);
            _vtableSlots.Add(slot);
        }
    }
}

接口方法通过 MethodImpl 映射与隐式接口实现自动关联。


3. NameService:ConditionalWeakTable 驱动的重命名引擎

使用 ConditionalWeakTable 替代传统 Dictionary,实现零侵入的元数据注解:

public class NameService
{
    // ConditionalWeakTable 不阻止 GC 回收,比 Dictionary 更安全
    readonly ConditionalWeakTable<IDnlibDef, NameAnnotation> _annotations = new();

    // 6 种命名模式
    static readonly char[] unicodeCharset = {
        '\u200b','\u200c','\u200d','\u200e','\u200f',
        '\u202a','\u202b','\u202c','\u202d','\u202e', ...
    };
    static readonly char[] letterCharset = ...;  // 字母表
    static readonly char[] asciiCharset = ...;   // ASCII
    static readonly char[] alphaNumCharset = ...;// 字母+数字

    // 核心方法:根据哈希值和模式生成新名称
    string ObfuscateNameInternal(byte[] hash, RenameMode mode)
    {
        return mode switch
        {
            RenameMode.Empty      => "",
            RenameMode.Unicode    => NameUtils.EncodeString(hash, unicodeCharset),
            RenameMode.Letters    => NameUtils.EncodeString(hash, letterCharset),
            RenameMode.ASCII      => NameUtils.EncodeString(hash, asciiCharset),
            RenameMode.Decodable  => "_" + NameUtils.EncodeString(hash, alphaNumCharset),
            RenameMode.Sequential => "_" + Interlocked.Increment(ref _seqCounter),
        };
    }

    // AnalyzeAll 三阶段:收集 → 构建VTable → 分析引用
    public void AnalyzeAll() { ... }

    // RenameAll:延迟重命名 + 迭代收敛 + 死锁检测
    public void RenameAll() { ... }
}

4. 六种命名模式

模式 字符集 示例名称 适用
Empty "" 空字符串 极简
Unicode U+200B-U+206F \u200b\u200c\u200d 不可见 最大隐蔽性
ASCII a-z abc, xyz, aab 轻型混淆
Letters 拉丁字符 cnffjbe (ROT13 风格) 可读但无意义
Decodable 字母+数字 _A1B2C3D4 调试友好
Sequential 递增数字 _1, _2, _3 调试友好

通过 --rename-mode <mode> 选择。


5. 跨模块协同:ProtectionSession.RunRename

public class ProtectionSession
{
    public IList<ProtectionContext> Contexts { get; }    // 所有模块上下文
    public NameService? NameService { get; }             // 共享名称服务
    public RandomService Random { get; }                 // 共享随机数

    public void RunRename()
    {
        if (NameService == null) return;

        // 阶段1:收集所有模块的可重命名成员
        // 阶段2:构建跨模块 VTable 关系图
        NameService.AnalyzeAll();

        // 阶段3:统一分配新名称 + 更新所有跨模块引用
        NameService.RenameAll();
    }
}

关键:EXE 中引用的 DLL 类型,在 DLL 重命名后同步更新引用。


6. 引用追踪:INameReference 体系

// 每种引用类型实现 INameReference
public interface INameReference
{
    string Name { get; set; }
    bool ShouldUpdate { get; }
    void UpdateName(string newName);
}

// TypeRef 引用:A.dll 的 TypeRef "MyClass" 在 B.exe 中
class TypeRefReference : INameReference { ... }

// MemberRef 引用:如调用 A.dll 的方法
class MemberRefReference : INameReference { ... }

// Override 指令:.override IL 指令
class OverrideDirectiveReference : INameReference { ... }

// 兄弟方法:同 class 中隐藏/重写的方法
class MemberSiblingReference : INameReference { ... }

7. 可见性过滤与不可重命名标记

// 默认不重命名 public 成员
if (member.IsPublic && !options.RenamePublic)
    continue;

// 标记为不可重命名(运行时注入类型)
MarkAsNotRenameable(injectedType);
MarkAsNotRenameable(injectedMethod);

// 在 ObfuscationStep.IsEnabled 中检查
bool CanRename(IDnlibDef def) => !HasAttribute<NotRenameableAttribute>(def);

8. 结语

符号混淆看似简单,实际上要处理 VTable 继承链、接口映射、跨模块引用、ConditionalWeakTable 注解等大量细节。好的混淆器不是"全改就完事",而是精准地只改该改的部分

下一篇将深入控制流混淆——如何打乱方法的执行逻辑。


本文由 TWSoft.AssemblyProtector 驱动。完整源码和工具请访问项目主页。

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