В статье рассказывается об опыте боевого применения обфускатора ConfuserEx 0.6.0 для защиты сервиса .Net под Windows и Mono. Дело было в далеком 2016 году, но, я думаю, тема не потеряла актуальность и сейчас.
ConfuserEx (http://yck1509.github.io/ConfuserEx/)
один из бесплатных обфускаторов для .Net с открытым исходным кодом.
Поддерживает работу в среде Windows .NET Framework и Mono.
Содержит большое число модулей, реализующих различные
методы защиты кода (переименование, запутывание потока
выполнения, шифрование ресурсов и констант, защита от отладки и
профилирования, упаковщики). ConfuserEx дает возможность расширения
функциональности, путем написания собственных модулей защиты.
Открытый исходные код позволяет модифицировать систему защиты, изменять сигнатуру обфускатора, затрудняя тем самым работу программ де-обфускаторов и ручной реверс инженеринг.
Документация
У проекта имеется достаточно подробная документация в формате WiKi.
Пользовательский интерфейс
ConfuserEx поддерживает работу в UI режиме, а так же режиме командной строки.
Режим командной строки
ConfuserEx\bin\Confuser.CLI.exeConfuserEx.CLI: No input files specified.Usage:Confuser.CLI -n|noPause <project configuration>Confuser.CLI -n|noPause -o|out=<output directory> <modules> -n|noPause : no pause after finishing protection. -o|out : specifies output directory. -probe : specifies probe directory. -plugin : specifies plugin path. -debug : specifies debug symbol generation.</source>
ConfuserEx\bin\Confuser.CLI.exe -n LicenseManagerService.crproj [INFO] ConfuserEx v0.6.0-custom Copyright (C) Ki 2014 [INFO] Running on Microsoft Windows NT 6.1.7601 Service Pack 1, .NET Framework v4.0.30319.0, 64 bits[DEBUG] Discovering plugins... [INFO] Discovered 10 protections, 1 packers.[DEBUG] Resolving component dependency... [INFO] Loading input modules... [INFO] Loading 'LicenseManagerService\bin\x86\Release\LicenseManagerService.exe'... [INFO] Initializing...[DEBUG] Building pipeline... [INFO] Resolving dependencies...[DEBUG] Checking Strong Name...[DEBUG] Creating global .cctors...[DEBUG] Executing 'Name analysis' phase...[DEBUG] Building VTables & identifier list...[DEBUG] Analyzing... [INFO] Processing module 'LicenseManagerService.exe'...[DEBUG] Executing 'Invalid metadata addition' phase...[DEBUG] Executing 'Renaming' phase...[DEBUG] Renaming...[DEBUG] Executing 'Anti-debug injection' phase...[DEBUG] Executing 'Anti-dump injection' phase...[DEBUG] Executing 'Anti-ILDasm marking' phase...[DEBUG] Executing 'Encoding reference proxies' phase...[DEBUG] Executing 'Constant encryption helpers injection' phase...[DEBUG] Executing 'Resource encryption helpers injection' phase...[DEBUG] Executing 'Constants encoding' phase...[DEBUG] Executing 'Anti-tamper helpers injection' phase...[DEBUG] Executing 'Control flow mangling' phase...[DEBUG] Executing 'Post-renaming' phase...[DEBUG] Executing 'Anti-tamper metadata preparation' phase...[DEBUG] Executing 'Packer info extraction' phase... [INFO] Writing module 'LicenseManagerService.exe'...[DEBUG] Encrypting resources... [INFO] Finalizing... [INFO] Packing...[DEBUG] Encrypting modules... [INFO] Protecting packer stub...[DEBUG] Discovering plugins... [INFO] Discovered 11 protections, 1 packers.[DEBUG] Resolving component dependency... [INFO] Loading input modules... [INFO] Loading 'LicenseManagerService\bin\x86\Release\LicenseManagerService.exe'... [INFO] Initializing...[DEBUG] Building pipeline... [INFO] Resolving dependencies...[DEBUG] Checking Strong Name...[DEBUG] Creating global .cctors...[DEBUG] Executing 'Name analysis' phase...[DEBUG] Building VTables & identifier list...[DEBUG] Analyzing... [INFO] Processing module 'LicenseManagerService.exe'...[DEBUG] Executing 'Packer info encoding' phase...[DEBUG] Executing 'Invalid metadata addition' phase...[DEBUG] Executing 'Renaming' phase...[DEBUG] Renaming...[DEBUG] Executing 'Anti-debug injection' phase...[DEBUG] Executing 'Anti-dump injection' phase...[DEBUG] Executing 'Anti-ILDasm marking' phase...[DEBUG] Executing 'Encoding reference proxies' phase...[DEBUG] Executing 'Constant encryption helpers injection' phase...[DEBUG] Executing 'Resource encryption helpers injection' phase...[DEBUG] Executing 'Constants encoding' phase...[DEBUG] Executing 'Anti-tamper helpers injection' phase...[DEBUG] Executing 'Control flow mangling' phase...[DEBUG] Executing 'Post-renaming' phase...[DEBUG] Executing 'Anti-tamper metadata preparation' phase...[DEBUG] Executing 'Packer info extraction' phase... [INFO] Writing module 'LicenseManagerService.exe'...[DEBUG] Encrypting resources... [INFO] Finalizing...[DEBUG] Saving to 'C:\Users\pash76\AppData\Local\Temp\ehwkjzxt.brh\mqqtgvji.gxk\LicenseManagerService\bin\x86\Release\LicenseManagerService.exe'...[DEBUG] Executing 'Export symbol map' phase... [INFO] Finish protecting packer stub.[DEBUG] Saving to 'D:\pash76\Develop\License_manager\Confused\LicenseManagerService\bin\x86\Release\LicenseManagerService.exe'...[DEBUG] Executing 'Export symbol map' phase... [INFO] Done.Finished at 9:35, 0:03 elapsed.
Файл проекта
Структура файла проекта описана в документации. В файле проекта находится описание сборок, которые требуется защитить, настройки модулей защиты и правила, по которым происходит применение модулей защиты для защиты сборок.
Правила позволяют выборочно применять (или не применять) модули защиты к различным участкам кода. Во время обфускации, происходит проверка применимости правила к текущему защищаемому элементу кода. При этом оценивается соответствие так называемой сигнатуры элемента кода с правилом. В правилах могут содержать сложные логические выражения.
Альтернативой правил является декларативный способ пометкой кода атрибутами.
<project outputDir=".\Confused" baseDir=".\" xmlns="http://personeltest.ru/away/confuser.codeplex.com"> <rule pattern="true" inherit="false"> <protection id="anti ildasm" /> <protection id="anti tamper" action="remove" /> <protection id="constants"> <argument name="mode" value="dynamic" /> <argument name="decoderCount" value="13" /> <argument name="elements" value="SIP" /> <argument name="cfg" value="false" /> </protection> <protection id="ctrl flow" /> <protection id="anti dump" action="remove" /> <protection id="anti debug" /> <protection id="invalid metadata" action="remove" /> <protection id="ref proxy" /> <protection id="resources"> <argument name="mode" value="dynamic" /> </protection> <protection id="rename"> <argument name="mode" value="sequential" /> </protection> </rule> <packer id="compressor"> <argument name="key" value="dynamic" /> <argument name="compat" value="true" /> </packer> <module path="LicenseManagerService\bin\x86\Release\LicenseManagerService.exe"> <rule pattern=" match('UAVLicenseManager\.CentOSSystemInfoProvider::ShellCommand.*') or match(' ?LicenseManagerService\.Program(::)?') or match(' ?LicenseManagerService\.UAVLicenseManagerService(::)?') or ( match(' ?UAVLicenseManager\.License(::)?') and is-public() and (member-type('type') or member-type('field') or member-type('property')) ) or match(' ?UAVLicenseManager\.LicenseKey(::)?') or match(' ?UAVLicenseManager\.LicenseOwner(::)?') or match(' ?UAVLicenseManager\.Location(::)?') or match(' ?UAVLicenseManager\.LicenseLimit(::)?') " inherit="true"> <protection id="rename" action="remove" /> </rule> </module></project>
Модули защиты
В ConfuserEx прямо из коробки доступно большое количество разнообразным модулей защиты. Назначение и область применения понятно из названия, детали можно найти в документации по каждому модулю.
Защита Name Protection нарушает работу механизмов Reflection и сериализации. Типы, которые необходимо сериализовывать или обрабатывать с использованием рефлексии необходимо добавлять в исключения с помощью правил или с помощью атрибутов.
Не все модули совместимы с mono, некоторые режимы также могут быть несовместимыми с mono. Нужно быть особо аккуратным, и после применения защиты проводить тестирование и регрессию.
Модуль | Windows | Mono |
---|---|---|
Anti Debug Protection | ||
Anti Dump Protection | ||
Anti IL Dasm Protection | ||
Anti Tamper Protection | ||
Compressor | (в режиме совместимости) | |
Constants Protection | (режим cfg не поддерживается) | |
Control Flow Protection | ||
Invalid Metadata Protection | ||
Name Protection | ||
Reference Proxy Protection | ||
Resources Protection |
Упаковщики
Упаковщик, помимо уменьшения размера выходного файла, позволят закодировать скрыть весь исполняемый IL-код. В результате в dotPeek можно увидеть лишь служебный код ConfuserEx. Без упаковщика, в dotPeek становится доступным гораздо больше информации.
Кастомизация защиты
Доступность исходных кодов позволяет модифицировать принцип работы ConfuserEx. В частности можно и нужно отключить режим вставки водяного знака, атрибута модуля ConfusedByAttribute. По этому атрибуту определяют тип защиты многие де-обфускаторы.
ConfuserEx\src\Confuser.Core\ConfuserEngine.cs
static void Inspection(ConfuserContext context) { context.Logger.Info("Resolving dependencies..."); foreach (var dependency in context.Modules .SelectMany(module => module.GetAssemblyRefs().Select(asmRef => Tuple.Create(asmRef, module)))) { try { AssemblyDef assembly = context.Resolver.ResolveThrow(dependency.Item1, dependency.Item2); } catch (AssemblyResolveException ex) { context.Logger.ErrorException("Failed to resolve dependency of '" + dependency.Item2.Name + "'.", ex); throw new ConfuserException(ex); } } context.Logger.Debug("Checking Strong Name..."); foreach (ModuleDefMD module in context.Modules) { var snKey = context.Annotations.Get<StrongNameKey>(module, Marker.SNKey); if (snKey == null && module.IsStrongNameSigned) context.Logger.WarnFormat("[{0}] SN Key is not provided for a signed module, the output may not be working.", module.Name); else if (snKey != null && !module.IsStrongNameSigned) context.Logger.WarnFormat("[{0}] SN Key is provided for an unsigned module, the output may not be working.", module.Name); else if (snKey != null && module.IsStrongNameSigned && !module.Assembly.PublicKey.Data.SequenceEqual(snKey.PublicKey)) context.Logger.WarnFormat("[{0}] Provided SN Key and signed module's public key do not match, the output may not be working.", module.Name); } var marker = context.Registry.GetService<IMarkerService>(); context.Logger.Debug("Creating global .cctors..."); foreach (ModuleDefMD module in context.Modules) { TypeDef modType = module.GlobalType; if (modType == null) { modType = new TypeDefUser("", "<Module>", null); modType.Attributes = TypeAttributes.AnsiClass; module.Types.Add(modType); marker.Mark(modType, null); } MethodDef cctor = modType.FindOrCreateStaticConstructor(); if (!marker.IsMarked(cctor)) marker.Mark(cctor, null); } //context.Logger.Debug("Watermarking..."); //foreach (ModuleDefMD module in context.Modules) { // TypeRef attrRef = module.CorLibTypes.GetTypeRef("System", "Attribute"); // var attrType = new TypeDefUser("", "ConfusedByAttribute", attrRef); // module.Types.Add(attrType); // marker.Mark(attrType, null); // var ctor = new MethodDefUser( // ".ctor", // MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.String), // MethodImplAttributes.Managed, // MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); // ctor.Body = new CilBody(); // ctor.Body.MaxStack = 1; // ctor.Body.Instructions.Add(OpCodes.Ldarg_0.ToInstruction()); // ctor.Body.Instructions.Add(OpCodes.Call.ToInstruction(new MemberRefUser(module, ".ctor", MethodSig.CreateInstance(module.CorLibTypes.Void), attrRef))); // ctor.Body.Instructions.Add(OpCodes.Ret.ToInstruction()); // attrType.Methods.Add(ctor); // marker.Mark(ctor, null); // var attr = new CustomAttribute(ctor); // attr.ConstructorArguments.Add(new CAArgument(module.CorLibTypes.String, Version)); // module.CustomAttributes.Add(attr); //}}
Кроме того, рекомендуется изменить имя .net модуля ConfuserEx.
По умолчанию используется имя koi.
Искать поиском по исходникам
ConfuserEx\src\Confuser.Protections\Compress\Compressor.csConfuserEx\src\Confuser.Runtime\Compressor.csConfuserEx\src\Confuser.Protections\Compress\ExtractPhase.csConfuserEx\src\Confuser.Protections\Compress\StubProtection.cs
Примеры результатов
Сборка защищена без применения упаковщика (для возможности дизассемблера dotPeek). Файл проекта с настройками приведен выше.
Запутывание исполняемого кода
using System;using System.Collections.Generic;using System.Linq;using System.Runtime.CompilerServices;using System.ServiceProcess;using System.Text;using System.Threading.Tasks;[assembly: InternalsVisibleTo("LicenseManagerServiceTests")]namespace LicenseManagerService{ static class Program { /// <summary> /// Главная точка входа для приложения. /// </summary> /// static void Main() { ServiceBase[] ServicesToRun; ServicesToRun = new ServiceBase[] { new UAVLicenseManagerService() }; ServiceBase.Run(ServicesToRun); } }}
Дизассемблированый код в dotPeek
Для типа LicenseManagerService.Program, с помощью правил в файле проектов, был отключен модуль Name Protection (имена сохранились). Видны результаты работы модуля Control Flow Protection.
// Decompiled with JetBrains decompiler// Type: LicenseManagerService.Program// Assembly: LicenseManagerService, Version=1.0.5980.24716, Culture=neutral, PublicKeyToken=null// MVID: A6EB17CC-65EE-4E2D-B66C-24E166429A4A// Assembly location: D:\pash\Develop\License_manager\Confused\LicenseManagerService\bin\x86\Release\LicenseManagerService.exeusing System.Runtime.InteropServices;using System.ServiceProcess;namespace LicenseManagerService{ internal static class Program { private static void Main() { ServiceBase[] serviceBaseArray1 = new ServiceBase[1] { (ServiceBase) new UAVLicenseManagerService() };label_1: int num1 = 1005209177; ServiceBase[] serviceBaseArray2; while (true) { int num2 = 1280737639; uint num3; switch ((num3 = (uint) (num1 ^ num2)) % 3U) { case 0U: goto label_1; case 1U: serviceBaseArray2 = serviceBaseArray1; num1 = (int) num3 * 1248105312 ^ 483770479; continue; default: goto label_4; } }label_4: Program.\u200E(serviceBaseArray2); } static void \u200E([In] ServiceBase[] obj0) { ServiceBase.Run(obj0); } }}
Защита констант в коде
private readonly string ShellCommandNetworkAdapterMACAddress = @"ip -o link show | grep -m 1 'UP.*LOWER_UP.*ether\|LOWER_UP.*UP.*ether' | sed -n 's/.*ether \(.*\) brd.*/\1/p' | tr -d '\n[:blank:]'";
Для всех элементов кода, начинающихся с ShellCommand, с помощью правил в файле проектов, был отключен модуль Name Protection (имена сохранились). Видны результаты работы модуля Constants Protection.
internal sealed class _ob : _qA{ private readonly string ShellCommandNetworkAdapterMACAddress = \u003CModule\u003E.\u206E<string>(3331371713U); private readonly string ShellCommandNetworkAdapterCaption = \u003CModule\u003E.\u206F<string>(4243712535U);</source>