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 追踪完整的继承链:
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;
var overrides = FindAllOverrides(baseMethod);
var slot = new VTableSlot(baseMethod, overrides);
_vtableSlots.Add(slot);
}
}
}
接口方法通过 MethodImpl 映射与隐式接口实现自动关联。
3. NameService:ConditionalWeakTable 驱动的重命名引擎
使用 ConditionalWeakTable 替代传统 Dictionary,实现零侵入的元数据注解:
public class NameService
{
readonly ConditionalWeakTable<IDnlibDef, NameAnnotation> _annotations = new();
static readonly char[] unicodeCharset = {
'\u200b','\u200c','\u200d','\u200e','\u200f',
'\u202a','\u202b','\u202c','\u202d','\u202e', ...
};
static readonly char[] letterCharset = ...;
static readonly char[] asciiCharset = ...;
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),
};
}
public void AnalyzeAll() { ... }
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;
NameService.AnalyzeAll();
NameService.RenameAll();
}
}
关键:EXE 中引用的 DLL 类型,在 DLL 重命名后同步更新引用。
6. 引用追踪:INameReference 体系
public interface INameReference
{
string Name { get; set; }
bool ShouldUpdate { get; }
void UpdateName(string newName);
}
class TypeRefReference : INameReference { ... }
class MemberRefReference : INameReference { ... }
class OverrideDirectiveReference : INameReference { ... }
class MemberSiblingReference : INameReference { ... }
7. 可见性过滤与不可重命名标记
if (member.IsPublic && !options.RenamePublic)
continue;
MarkAsNotRenameable(injectedType);
MarkAsNotRenameable(injectedMethod);
bool CanRename(IDnlibDef def) => !HasAttribute<NotRenameableAttribute>(def);
8. 结语
符号混淆看似简单,实际上要处理 VTable 继承链、接口映射、跨模块引用、ConditionalWeakTable 注解等大量细节。好的混淆器不是"全改就完事",而是精准地只改该改的部分。
下一篇将深入控制流混淆——如何打乱方法的执行逻辑。
本文由 TWSoft.AssemblyProtector 驱动。完整源码和工具请访问项目主页。
转自https://www.cnblogs.com/gsyifan/p/21163086
该文章在 2026/7/10 8:19:30 编辑过