[Из песочницы] Защита .Net кода от реверс инженеринга с помощью ConfuserEx 0.6.0

В статье рассказывается об опыте боевого применения обфускатора 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.exe
ConfuserEx.CLI: No input files specified.
Usage:
Confuser.CLI -n|noPause 
Confuser.CLI -n|noPause -o|out= 
    -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.
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.


Файл проекта

Структура файла проекта описана в документации. В файле проекта находится описание сборок, которые требуется защитить, настройки модулей защиты и правила, по которым происходит применение модулей защиты для защиты сборок.

Правила позволяют выборочно применять (или не применять) модули защиты к различным участкам кода. Во время обфускации, происходит проверка применимости правила к текущему защищаемому элементу кода. При этом оценивается соответствие так называемой сигнатуры элемента кода с правилом. В правилах могут содержать сложные логические выражения.

Альтернативой правил является декларативный способ пометкой кода атрибутами.


    
        
        
        
            
            
            
            
        
        
        
        
        
        
        
            
        
        
            
        
    
    
        
        
    
    
        
            
        
    


Модули защиты

В ConfuserEx прямо из коробки доступно большое количество разнообразным модулей защиты. Назначение и область применения понятно из названия, детали можно найти в документации по каждому модулю.

Защита Name Protection нарушает работу механизмов Reflection и сериализации. Типы, которые необходимо сериализовывать или обрабатывать с использованием рефлексии необходимо добавлять в исключения с помощью правил или с помощью атрибутов.

Не все модули совместимы с mono, некоторые режимы также могут быть несовместимыми с mono. Нужно быть особо аккуратным, и после применения защиты проводить тестирование и регрессию.


Упаковщики

Упаковщик, помимо уменьшения размера выходного файла, позволят закодировать скрыть весь исполняемый IL-код. В результате в dotPeek можно увидеть лишь служебный код ConfuserEx. Без упаковщика, в dotPeek становится доступным гораздо больше информации.

bls1wiys7ub5zxfens1gp5zbutg.png


Кастомизация защиты

Доступность исходных кодов позволяет модифицировать принцип работы 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(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();
    context.Logger.Debug("Creating global .cctors...");
    foreach (ModuleDefMD module in context.Modules) {
        TypeDef modType = module.GlobalType;
        if (modType == null) {
            modType = new TypeDefUser("", "", 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.cs
ConfuserEx\src\Confuser.Runtime\Compressor.cs
ConfuserEx\src\Confuser.Protections\Compress\ExtractPhase.cs
ConfuserEx\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
    {
        /// 
        /// Главная точка входа для приложения.
        /// 
        ///
        static void Main()
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            {
                new UAVLicenseManagerService()
            };
            ServiceBase.Run(ServicesToRun);
        }
    }
}

Дизассемблированый код в dotPeek

jx5euyud2dim6d4gtlnxj36zexc.png

Для типа 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.exe
using 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:]'";

xkazfxfzbe8jstkey76pxwva6is.png

Для всех элементов кода, начинающихся с ShellCommand, с помощью правил в файле проектов, был отключен модуль Name Protection (имена сохранились). Видны результаты работы модуля Constants Protection.

internal sealed class _ob : _qA
{
  private readonly string ShellCommandNetworkAdapterMACAddress = \u003CModule\u003E.\u206E‬‌‭‭‬‏‮‬‬‪‮‫‭‌‌‬‭‪‎‮(3331371713U);
  private readonly string ShellCommandNetworkAdapterCaption = \u003CModule\u003E.\u206F‌‬‍‫‭‍‌‏‪‬‫‎‭‎‮‮(4243712535U);

© Habrahabr.ru