.NET 程序保护实战系列 09 · 反调试与反转储
攻击者的两大利器
攻击方式 工具 目标
动态调试 dnSpy、Windbg、x64dbg 单步执行、修改变量、修改逻辑
内存转储 ProcessHacker、MegaDumper 提取解密后的 IL、字符串、密钥AntiDebug:托管 + 原生双检测
internal static class AntiDebug
{
public static void Initialize()
{
// 方式1:托管调试器检测(Visual Studio、dnSpy 托管调试)
if (Debugger.IsAttached)
Environment.Exit(-1);// 方式2:原生调试器检测(Windbg、x64dbg、OllyDbg) try { if (IsDebuggerPresent()) Environment.Exit(-1); } catch { } // 非 Windows 平台不调用此 API}
[DllImport(“kernel32.dll”)]
static extern bool IsDebuggerPresent();
}
关联的 AdditionalProtectionsStep 将 AntiDebug.Initialize() 插入 …cctor(),确保在任何用户代码之前执行。AntiDump:unsafe 指针清零 PE 信息
MegaDumper 等工具通过读取内存中的 PE 头重建程序集文件。我们的对策是直接清零 PE 关键结构:
internal static unsafe class AntiDump
{
public static void Initialize()
{
// 1. 获取模块基址 — 通过 GetModuleHandle
var module = GetModuleHandle(null);
// 2. 解析 PE 头结构 byte* basePtr = (byte*)module; var dosHeader = (IMAGE_DOS_HEADER*)basePtr; var peOffset = dosHeader->e_lfanew; var ntHeaders = (IMAGE_NT_HEADERS*)(basePtr + peOffset); var sections = (IMAGE_SECTION_HEADER*)( basePtr + peOffset + sizeof(IMAGE_NT_HEADERS) - // 其实是 sizeof(IMAGE_NT_HEADERS64) sizeof(IMAGE_OPTIONAL_HEADER64) + ntHeaders->FileHeader.SizeOfOptionalHeader); // 3. 清零 PE 节区名称(每个节区 Name 字段 8 字节) for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) *(ulong*)sections[i].Name = 0; // "ILT\0\0\0\0" → "\0\0\0\0\0\0\0\0" // 4. 清零 CLR 元数据目录 var corHeader = GetCorHeader(basePtr, ntHeaders); *(uint*)&corHeader->cb = 0; // 大小清零 *(uint*)&corHeader->MajorRuntimeVersion = 0; *(uint*)&corHeader->MinorRuntimeVersion = 0; // 5. 清零元数据流名称 // #~, #Strings, #US, #GUID, #Blob 流名称统一清零 var metaData = GetMetadataRoot(corHeader); for (int i = 0; i < metaData->StreamHeaders.Streams; i++) *(ulong*)metaData->StreamHeaders.Stream[i].Name = 0; // 6. 修改导入表中的 VirtualProtect 调用痕迹 var importTable = GetImportTable(basePtr, ntHeaders); for (int i = 0; i < importTable->Count; i++) { importTable->Entries[i].ModuleName[0] = '?'; // 隐藏模块名 importTable->Entries[i].FuncName[0] = '?'; // 隐藏函数名 } }}
关键:使用 GetModuleHandle 替代 Marshal.GetHINSTANCE,避免 .NET 版本跨平台兼容性问题。
元数据混淆:虚假 TypeDef + MVID 修改
// 添加 5-8 个虚假 TypeDef
int count = random.Next(5, 9);
for (int i = 0; i < count; i++)
{
var fakeType = new TypeDefUser(
“”, // 空命名空间
GenerateInvisibleName(random), // 不可见 Unicode 名称
module.CorLibTypes.Object.TypeDefOrRef);
fakeType.Attributes = TypeAttributes.Public | TypeAttributes.Class;// 每个虚假类型添加 1-2 个空方法
int methodCount = random.Next(1, 3);
for (int j = 0; j < methodCount; j++)
{
var method = new MethodDefUser(
GenerateInvisibleName(random),
MethodSig.CreateStatic(module.CorLibTypes.Void),
MethodAttributes.Public | MethodAttributes.Static);
method.Body = new CilBody();
method.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
fakeType.Methods.Add(method);
}module.Types.Add(fakeType);
}
// 修改 MVID 破坏反编译器缓存
module.Mvid = Guid.NewGuid();
5. AdditionalProtectionsStep:四项保护统筹
public class AdditionalProtectionsStep : IProtectionStep
{
public string Name => “其它推荐保护”;
public int Order => 40;
public bool IsEnabled(ProtectOptions options) => options.Additional; public void Inject(ProtectionContext context) { // 根据配置选择性注入 if (context.Options.AntiDebug) InjectAntiDebug(context); // AntiDebug 类型 if (context.Options.AntiDump) InjectAntiDump(context); // AntiDump 类型 if (context.Options.EncryptResources) EncryptResources(context); // SPN 三阶段加密(详见第10篇) if (context.Options.MetadataConfusion) InjectFakeTypes(context); // 虚假 TypeDef + MVID 修改 } public void Execute(ProtectionContext context) { // 在 <Module>.cctor 开头插入初始化调用 InsertCctorCalls(context.Module, new[] { context.Options.AntiDebug ? "AntiDebug.Initialize" : null, context.Options.AntiDump ? "AntiDump.Initialize" : null, context.Options.EncryptResources ? "ResourceEncryptor.Initialize" : null, }); }}
6. 结语
反调试和反转储是动态防御的核心。对抗有经验的逆向工程师需要层层设防——从调试器检测到内存清除再到元数据混淆。这些技术与之前文章中的静态保护形成完整的立体防护。