.NET 程序保护实战系列 11 · 类型注入:无 DLL 依赖的运行时交付

11 · 类型注入:无 DLL 依赖的运行时交付

目录

  1. 设计哲学:零外部依赖
  2. [InjectHelper:dnlib 深度克隆引擎](#InjectHelper:dnlib 深度克隆引擎)
  3. [InjectContext:ImportMapper 与框架重定向](#InjectContext:ImportMapper 与框架重定向)
  4. MutationHelper:占位符替换引擎
  5. RuntimeService:从保护工具自身加载运行时
  6. 完整注入流程示例
  7. 结语

1. 设计哲学:零外部依赖

许多保护工具要求用户分发运行时 DLL(如 VMProtectSDK32.dll)。我们的设计:所有运行时类型在保护时完整克隆到目标程序集


2. InjectHelper:dnlib 深度克隆引擎

csharp 复制代码
public static class InjectHelper
{
    // === 入口 1:克隆整个类型到目标模块 ===
    public static TypeDef Inject(TypeDef typeDef, ModuleDef target)
    {
        var ctx = new InjectContext(typeDef.Module, target);
        return (TypeDef)ctx.Import(typeDef);
    }

    // === 入口 2:克隆单个方法 ===
    public static MethodDef Inject(MethodDef methodDef, ModuleDef target)
    {
        var ctx = new InjectContext(methodDef.Module, target);
        return (MethodDef)ctx.Import(methodDef);
    }

    // === 入口 3:批量克隆(共享上下文,处理交叉引用) ===
    public static List<TypeDef> InjectMany(
        IEnumerable<TypeDef> typeDefs, ModuleDef target)
    {
        var ctx = new InjectContext(typeDefs.First().Module, target);
        return typeDefs.Select(t => (TypeDef)ctx.Import(t)).ToList();
    }
}

克隆管道:浅拷贝 → 深拷贝 → 签名修复

csharp 复制代码
class InjectContext : ImportMapper
{
    // 映射表:origin → clone
    Dictionary<IMemberRef, IMemberRef> _map = new();

    IMemberRef Import(IMemberRef member)
    {
        // 1. 已克隆?→ 直接返回映射
        if (_map.TryGetValue(member, out var mapped))
            return mapped;

        // 2. 根据类型深度克隆
        return member switch
        {
            TypeDef td     => Clone(td),
            MethodDef md   => Clone(md),
            FieldDef fd    => Clone(fd),
            TypeRef tr     => TargetModule.Import(tr),
            MemberRef mr   => TargetModule.Import(mr),
            _              => member
        };
    }

    TypeDef Clone(TypeDef origin)
    {
        var clone = new TypeDefUser(origin.Namespace, origin.Name)
        {
            Attributes = origin.Attributes,
            BaseType = Import(origin.BaseType)
        };

        // 记录映射
        _map[origin] = clone;

        // 克隆接口
        foreach (var iface in origin.Interfaces)
            clone.Interfaces.Add(new InterfaceImplUser(Import(iface.Interface)));

        // 克隆属性
        foreach (var prop in origin.Properties)
            clone.Properties.Add(Clone(prop));

        // 克隆字段
        foreach (var field in origin.Fields)
            clone.Fields.Add(Clone(field));

        // 克隆方法(含方法体 IL)
        foreach (var method in origin.Methods)
            clone.Methods.Add(Clone(method));

        return clone;
    }

    MethodDef Clone(MethodDef origin)
    {
        var clone = new MethodDefUser(
            origin.Name,
            Import(origin.MethodSig),
            origin.ImplAttributes,
            origin.Attributes);

        _map[origin] = clone;

        // 克隆方法体(IL 指令、局部变量、异常处理器)
        if (origin.HasBody)
        {
            clone.Body = new CilBody();
            clone.Body.InitLocals = origin.Body.InitLocals;
            clone.Body.MaxStack = origin.Body.MaxStack;

            // 复制局部变量
            foreach (var local in origin.Body.Variables)
                clone.Body.Variables.Add(new Local(Import(local.Type)));

            // 复制 IL 指令(映射操作数)
            foreach (var instr in origin.Body.Instructions)
            {
                var cloneInstr = new Instruction(
                    instr.OpCode,
                    MapOperand(instr.Operand));
                clone.Body.Instructions.Add(cloneInstr);
            }

            // 复制异常处理器
            foreach (var eh in origin.Body.ExceptionHandlers)
            {
                clone.Body.ExceptionHandlers.Add(new ExceptionHandler(eh.HandlerType)
                {
                    CatchType = Import(eh.CatchType),
                    TryStart  = Map(eh.TryStart),
                    TryEnd    = Map(eh.TryEnd),
                    HandlerStart = Map(eh.HandlerStart),
                    HandlerEnd   = Map(eh.HandlerEnd),
                });
            }
        }

        return clone;
    }
}

3. InjectContext:ImportMapper 与框架重定向

.NET Core 的 UI 外观程序集(System.Runtime.dll 等)需要重定向到核心库:

csharp 复制代码
public override IMemberRef Map(IMemberRef member)
{
    var asm = member.DeclaringType?.DefinitionAssembly;
    if (asm == null) return base.Map(member);

    // 0a. .NET Framework 目标遇到 System.Private.CoreLib → 重定向到 mscorlib
    if (!TargetIsNetCore && IsCoreLib(asm))
        return RedirectTo(asm, TargetModule.CorLibTypes.AssemblyRef);

    // 0. 外观程序集 → 重定向到 System.Private.CoreLib 或 mscorlib
    if (IsFacadeAssembly(asm))
    {
        var corlib = TargetModule.CorLibTypes.AssemblyRef;
        return RedirectTo(asm, corlib);
    }

    // 1. 目标模块已有同名引用?→ 使用已有的
    var existingRef = TargetModule.GetAssemblyRef(asm.Name);
    if (existingRef != null && existingRef.Version == asm.Version)
        return existingRef;

    return base.Map(member);
}

static readonly HashSet<string> _facadeAsmNames = new()
{
    "System.Runtime", "System.IO", "System.Linq", "System.Collections",
    "System.Threading", "System.Reflection", "System.Diagnostics",
    "netstandard", "System.Net.Http", "System.ComponentModel",
    // ... 40+ 个框架外观程序集
};

4. MutationHelper:占位符替换引擎

csharp 复制代码
public static class MutationHelper
{
    // 映射:Mutation 字段名 → key 索引
    static readonly Dictionary<string, int> field2index = new()
    {
        { "KeyI0", 0 }, { "KeyI1", 1 }, { "KeyI2", 2 }, { "KeyI3", 3 },
        { "KeyI4", 4 }, { "KeyI5", 5 }, { "KeyI6", 6 }, { "KeyI7", 7 },
        { "KeyI8", 8 }, { "KeyI9", 9 }, { "KeyI10", 10 }, { "KeyI11", 11 },
        { "KeyI12", 12 }, { "KeyI13", 13 }, { "KeyI14", 14 }, { "KeyI15", 15 },
    };

    // 替换 Key 占位符
    public static void InjectKey(MethodDef method, int keyId, int value)
    {
        foreach (var instr in method.Body.Instructions)
        {
            if (instr.OpCode != OpCodes.Ldsfld) continue;
            var field = (IField)instr.Operand;

            // TWSoft.Runtime.Mutation.KeyI0 → ldc.i4 <value>
            if (field.DeclaringType.Name == "Mutation" &&
                field2index.TryGetValue(field.Name, out int id) && id == keyId)
            {
                instr.OpCode = OpCodes.Ldc_I4;
                instr.Operand = value;
            }
        }
    }

    // 替换 Placeholder 调用
    public static void ReplacePlaceholder(
        MethodDef method,
        Func<Instruction[], Instruction[]> repl)
    {
        foreach (var instr in method.Body.Instructions.ToList())
        {
            if (instr.OpCode != OpCodes.Call) continue;
            var called = (IMethod)instr.Operand;

            // Mutation.Placeholder<T>(T val) → 替换为真实指令序列
            if (called.Name == "Placeholder")
            {
                // 追踪参数来源
                Instruction[] argInstructions = TraceArguments(instr);

                // 移除原 call 指令
                int index = method.Body.Instructions.IndexOf(instr);
                method.Body.Instructions.RemoveAt(index);

                // 插入替换指令
                var replacement = repl(argInstructions);
                foreach (var replInstr in replacement.Reverse())
                    method.Body.Instructions.Insert(index, replInstr);
            }
        }
    }
}

5. RuntimeService:从保护工具自身加载运行时

csharp 复制代码
public sealed class RuntimeService
{
    ModuleDef? _rtModule;

    public TypeDef GetRuntimeType(string fullName)
    {
        _rtModule ??= LoadSelfModule();  // 首次调用懒加载
        return _rtModule.Find(fullName, true)
            ?? throw new InvalidOperationException(
                $"Runtime type '{fullName}' not found");
    }

    static ModuleDef LoadSelfModule()
    {
        var asm = typeof(RuntimeService).Assembly;
        return ModuleDefMD.Load(asm.Location,
            new ModuleCreationOptions { TryToLoadPdbFromDisk = false });
    }
}

使用示例:

csharp 复制代码
var runtime = context.Runtime;                          // 从 ProtectionContext 获取
var constantType = runtime.GetRuntimeType("TWSoft.Runtime.Constant");
var clonedConstant = InjectHelper.Inject(constantType, context.Module);  // 克隆

6. 完整注入流程示例

以字符串加密为例,完整注入流程:

csharp 复制代码
// Step 1: 获取运行时类型模板
var constantType = context.Runtime.GetRuntimeType("TWSoft.Runtime.Constant");

// Step 2: 深度克隆到目标模块
var clonedType = InjectHelper.Inject(constantType, context.Module);

// Step 3: 替换占位符为真实值
var initMethod = clonedType.FindMethod("Initialize");
MutationHelper.InjectKey(initMethod, 0, bufferLength);   // KeyI0 → 真实长度
MutationHelper.InjectKey(initMethod, 1, seed);           // KeyI1 → 真实种子
MutationHelper.ReplacePlaceholder(initMethod, args =>    // Placeholder → 数组初始化 IL
    CreateArrayInitInstructions(args));

// Step 4: 重命名注入成员为不可见 Unicode
foreach (var method in clonedType.Methods)
    method.Name = GenerateInvisibleName(random);

// Step 5: 标记不可被混淆
MarkAsNotRenameable(clonedType);

// Step 6: 插入 <Module>.cctor 初始化调用
InsertCctorCall(context.Module, clonedType, "Initialize");

7. 结语

InjectHelper + MutationHelper + RuntimeService 构成了整个保护引擎的基础设施。所有运行时类型(Constant、MethodGuard、VirtualMachine、AntiTamper、AntiDebug、AntiDump、ResourceEncryptor)都通过这条管道交付给目标程序集------零外部依赖、完全自包含。

下一篇(最后一篇)将讲解授权保护集成------如何将商业授权验证嵌入保护流程。


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