You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 10 Next »

Общие замечания

Автоматическая защита программ основана на таких технологиях, как обфускация, шифрование и виртуализация.

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

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

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

В Windows необходимо отключить защиту стека (с помощью утилиты Безопасность Windows), как указано в инструкции. Эта опция может быть отключена по умолчанию. Она отображается не на всех машинах, так как необходима аппаратная поддержка со стороны оборудования, а также версия Windows 10 или Windows 11 не ниже 21H2 (в зависимости от оборудования).

В Astra Linux необходимо отключить контроль разрешения исполнения кода в программном стеке (задав в GRUB параметру ядра parsec.execstack значение 1), как указано в инструкции. По умолчанию эта опция включена.

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

Нативные программы

На данный момент поддерживается защита программ форматов PE (исполняемых файлов и DLL-библиотек) и ELF (исполняемых файлов и разделяемых библиотек (shared object)). Для PE-файлов поддерживаются архитектуры x86 (i386) и x86-64 (AMD64, x64), для ELF-файлов - только x86-64.

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

Программы .NET

На данный момент поддерживается защита программ (сборок) .NET (версии 5.0 и выше), .NET Core (версии 2.0 и выше), .NET Standard (версии 2.0 и выше), .NET Framework (версии 4.0 и выше).

При выборе файлов программ .NET и .NET Core необходимо понимать, какие файлы генерируются в процессе сборки проекта (решения) для C#. Например, для проекта консольного или графического приложения с названием Project в процессе сборки генерируются:

  • Project.dll - собственно файл программы, содержащий байткод на .NET,
  • Project.exe (для Windows) либо Project (для Linux) - файл нативной заглушки для запуска собранной программы в среде .NET,
  • Project.runtimeconfig.json - файл конфигурации среды, содержащий информацию о поддерживаемой целевой платформе, среде выполнения и ее настройках,
  • Project.deps.json - файл, содержащий информацию о зависимостях, необходимых для работы файла программы.

Цель файла заглушки - просто вызвать среду .NET с нужными параметрами (заданными в файле конфигурации среды). Таким образом, вызов программы может происходить или с помощью вызова (например, двойным кликом по файлу) Project.exe (для Windows) либо Project (для Linux), или посредством команды dotnet Project.dll - это касается как исходной программы, так и защищенной.

Необходимо защищать файл программы с байткодом на .NET (файл Project.dll в примере), а не файл заглушки (файл Project.exe либо Project в примере).

Для работы программы .NET в ОС Linux может понадобиться настроить переменную среды LD_LIBRARY_PATH для поиска нативной библиотеки защиты (имя файла по умолчанию libprotectstorage64.so). Нативная библиотека защиты находится в корне папки с защищенными файлами, поэтому автоматическая защита указывает все необходимые пути во время защиты в соответствии в файловой структурой проекта. Поиск библиотеки защиты реализован аналогично тому, как устроен поиск библиотек для DllImportAttribute.

Технологии защиты

Обфускация символов

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

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

Не гарантируется поддержка сборок с Reflection и GUI-фреймворков, реализующих MVVM (в частности, WPF, MAUI и AvaloniaUI). Также во избежание ошибок не следует использовать метод DependencyProperty.RegisterAttached().

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

Технические ограничения

Обфускации не подвергаются следующие символы:

  • классы:
    • имеющие атрибут GeneratedCodeAttribute (автоматически сгенерированные),
    • наследующие ApplicationSettingsBase (используется для Windows Forms), причем также не переименовываются члены (вложенные типы, методы, поля, свойства и события) этих классов,
    • реализующие INotifyPropertyChanged (используется для WPF),
    • использующиеся в XAML/BAML-ресурсах (используются для WPF), причем также не переименовываются члены этих классов;
  • методы:
Обфускация внешних интерфейсов

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

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

Другой - напротив, скрыть всю логику вместе с интерфейсами, чтобы они были доступны только ограниченному множеству приложений. Для этого библиотека и эти приложения должны защищаться вместе, в одном проекте.

Для обоих сценариев полезна опция обфускации внешних интерфейсов. Если эта опция отключена (по умолчанию), то внешние интерфейсы остаются нетронутыми, поэтому возможно обращение к символам защищенной сборки извне. Если же эта опция включена, то внешние интерфейсы переименовываются, а значит, обращение к ним становится невозможным.

Обфускация потока управления

Поток управления - абстракция уровня кода метода. Код представляется последовательностью инструкций, которая и определяет логику работы приложения. Совокупность инструкций и переходов между ними (в C# переходы связаны с такими конструкциями, как if, switch, for), образует поток управления, который можно обфусцировать (скрыть), разбив код на блоки, перемешав их и сделав логику перехода непрозрачной. Кроме того, данные, определяющие последовательность переходов между блоками, зашифровываются и выносятся в библиотеку защиты.

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

Технические ограничения

Обфускации не подвергаются следующие методы:

  • деструкторы и методы, вызываемые из деструкторов,
  • методы с обработчиками исключений,
  • методы, не имеющие достаточного количества блоков (инструкций перехода) в коде.

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

Шифрование строк

Опция шифрования строк позволяет скрывать используемые в коде константные строки. Содержимое строки в зашифрованном виде хранится в библиотеке защиты. Повсеместное применение шифрования строк может приводить к существенному замедлению работы приложения.

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

Защита методов

Защита методов - технология, позволяющая хранить код метода в зашифрованном виде в библиотеке защиты. При обращении к коду выбранного для защиты метода он расшифровывается и виртуализируется в защищенном приложении.

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

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

Технические ограничения

Защита невозможна для методов:

  • вызывающих нативный (unmanaged) код,
  • принадлежащих структурам,
  • для обобщенных методов,
  • для деструкторов и методов, вызываемых из деструкторов,
  • имеющих обработчики исключений типов Filter и Fault (встречается только в Visual Basic и F#).

Применение атрибутов обфускации

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

ObfuscationAttribute

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

Автозащита определяет, будет ли применяться этот атрибут к текущей стадии защиты, исходя из значения его строкового свойства Feature:

  • "Default", "All" или значение по умолчанию (если Feature не задано) - применяется к стадиям обфускации символов, обфускации потока управления и шифрования строк (но не применяется к стадии защиты методов),
  • "Renaming" - применяется к стадии обфускации символов,
  • "ControlFlow" - применяется к стадии обфускации потока управления,
  • "StringEncryption" - применяется к стадии шифрования строк,
  • "MethodProtection" - применяется к стадии защиты методов.

Регистр символов игнорируется, но требуется четкое соответствие строки (необходимо указывать только одно из перечисленных значений, без пробелов). Задавать несколько стадий в одном атрибуте нельзя, но можно задавать несколько атрибутов на символ. Если для одного символа и одной стадии задано несколько атрибутов, используется только последний из подходящих атрибутов.

Во избежание конфликтов и неоднозначности, рекомендуется задавать не более одного атрибута обфускации на символ для каждой из стадий ("Renaming", "ControlFlow", "StringEncryption", "MethodProtection") или задавать только один атрибут обфускации для первых трех стадий одновременно ("All").

Логика для стадии защиты методов несколько отличается, поэтому значения "Default" и "All" не касаются ее. Методы, помеченные атрибутом обфускации с соответствующим значением свойства Feature, автоматически выбираются для защиты во время добавления файла программы в проект защиты. Но выбор метода, помеченного для защиты, невозможно отменить.

Автозащита определяет, исключить ли символ из обфускации (шифрования) или нет, исходя из значений свойства-флага Exclude. Его значение (если оно явно не задано, то используется значение по умолчанию true) указывает, необходимо ли обработать символ (для каждой из стадий: обфусцировать символ, обфусцировать поток управления в коде метода, зашифровать константные строки в методе, защитить метод).

Также используется свойство-флаг ApplyToMembers. Его значение (по умолчанию true) указывает, необходимо ли применить ту же самую логику исключения (или отмены исключения) к символам-членам. Этот флаг имеет смысл задавать только для классов, в т.ч. для вложенных классов.

ObfuscateAssemblyAttribute

Этот атрибут применяется к целой сборке (целому файлу программы). Его свойство-флаг AssemblyIsPrivate (по умолчанию false) указывает, необходимо ли обфусцировать символы сборки. Если свойство имеет значение false, то сборка не считается частной, и поэтому ее символы не обфусцируются.

Общие замечания

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

Обработка исключений

Опция использования исключений для сборок .NET позволяет перехватывать ошибки автоматической защиты. Ошибки передаются в виде исключений Exception, а соответствующие данные указываются в свойстве Data. Использование класса, предоставляемого стандартной библиотекой, позволяет избежать использования сторонних библиотек при создании защищаемого приложения. Это требует некоторых дополнительных усилий, в простейшем случае достаточно лишь обернуть основной код метода Main() приложения в конструкцию try-catch. Код, связанный с проверкой и обработкой исключений, рекомендуется выносить в отдельный метод, помеченный атрибутом обфускации для исключения из обфускации потока управления и из шифрования строк, в противном случае из-за обращения к зашифрованным данным возникнет повторная ошибка автоматический защиты.

Пример модификации метода Main() для приложения на WPF
[STAThread]
static void Main()
{
    try
    {
        My.App app = new My.App();
        app.InitializeComponent();
        app.Run();
    }
    catch (System.Reflection.TargetInvocationException ex) when (ex.InnerException != null)
    {
        Exception inner = ex.InnerException;
        object obj = inner.Data["Mark"];
        if (obj != null && obj is string && obj as string == "GUARDANT")
            MessageBox.Show(string.Format(
                "Guardant exception:\n" +
                "Mark = GUARDANT\n" +
                "Message = {0}\n" +
                "Code = {1}",
                inner.Message,
                (int)inner.Data["Code"]));
    }
    catch (Exception ex)
    {
        MessageBox.Show("Unexpected exception:\n" + ex.Message);
    }
}

Обработка ошибок в примере выше соответствует исключениям, генерируемым автоматической защитой. В поле Message записывается сообщение об ошибке, а в поле Data записываются следующие значения:

  • по ключу "Mark" записывается строка "GUARDANT";
  • по ключу "Type" записывается строка "Guardant Software Licensing Kit";
  • по ключу "Code" записывается целочисленный код исключения от автоматической защиты;
  • по ключу "Expected" (только для ошибки среды с кодом -2) записывается ожидаемое имя библиотеки защиты (по умолчанию "protectstorage").

Таблица кодов исключений от автоматической защиты, соответствующих кодов Licensing API (синтаксис для языка C) и краткого описания:

Код исключенияКод APIОписание
1GRD_FEATURE_NOT_FOUND (2)не найдено подходящей лицензии
2GRD_FEATURE_EXPIRED (29)срок действия подходящей лицензии истек
3

GRD_FEATURE_RUNCOUNTER_EXHAUST (28)

счетчик запусков в подходящей лицензии исчерпан
4

GRD_FEATURE_RESOURCE_EXHAUST (3)

сетевой ресурс в подходящей лицензии исчерпан
5GRD_UNABLE_SEND_REQUEST_TO_ADMIN_RUNTIME (32)необходимо запустить Guardant Control Center (для Linux)
6

GRD_VIRTUAL_RUNTIME_DETECTED (42)

использование виртуализации недопустимо
7GRD_REMOTE_DESKTOP_DETECTED (43)использование удаленного доступа недопустимо
8cross markнесоответствие между файлом защищенной программы и файлом библиотеки защиты
9GRD_DONGLE_NOT_FOUND (33)аппаратный ключ не найден
10GRD_LICENSE_NOT_ACTIVATED (7)подходящая лицензия не активирована
11GRD_RTC_ERROR (60)ошибка таймера в подходящей лицензии
12GRD_TRIAL_LICENSE_EXPIRED (58)срок действия лицензии истек (только для триальных лицензий)
-1cross markнеожиданная ошибка (внутренняя ошибка)
-2cross markошибка взаимодействия среды .NET и нативной среды (внутренняя ошибка)
-3cross markошибка при поиске файла библиотеки защиты

Исключение, генерируемое защищенным приложением, обертывается системными библиотеками .NET в TargetInvocationException, поэтому оказывается необходимо перехватывать именно его. Естественно, необходимо убедиться, что методы, используемые при обработке исключений, сгенерированных автоматической защитой, не защищаются - достаточно убедиться, что они не выбраны для защиты, и добавить их в исключения из обфускации потока управления и из шифрования строк, пометив эти методы атрибутами обфускации.

Службы Windows

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

Таблица кодов сообщений от автоматической защиты, соответствующих кодов Licensing API (синтаксис для языка C) и краткого описания:

Код сообщенияКод APIОписание
1GRD_FEATURE_NOT_FOUND (2)не найдено подходящей лицензии
2GRD_FEATURE_RESOURCE_EXHAUST (3)сетевой ресурс в подходящей лицензии исчерпан
3GRD_UNABLE_SEND_REQUEST_TO_ADMIN_RUNTIME (32)необходимо запустить Guardant Control Center (для Linux)
4GRD_FEATURE_EXPIRED (29)срок действия подходящей лицензии истек
5cross markуказывается количество оставшихся дней (информационное сообщение)
6GRD_FEATURE_RUNCOUNTER_EXHAUST (28)счетчик запусков в подходящей лицензии исчерпан
7cross markуказывается количество оставшихся запусков (информационное сообщение)
8cross markнеожиданная ошибка (внутренняя ошибка)
9cross markуказывается демо-режим защиты (информационное сообщение)
10GRD_VIRTUAL_RUNTIME_DETECTED (42)использование виртуализации недопустимо
11GRD_REMOTE_DESKTOP_DETECTED (43)использование удаленного доступа недопустимо
12cross markнесоответствие между файлом защищенной программы и файлом библиотеки защиты
13GRD_DONGLE_NOT_FOUND (33)аппаратный ключ не найден
14GRD_LICENSE_NOT_ACTIVATED (7)подходящая лицензия не активирована
15GRD_RTC_ERROR (60)ошибка таймера в подходящей лицензии
16GRD_TRIAL_LICENSE_EXPIRED (58)срок действия лицензии истек (только для триальных лицензий)

Коды сообщений отличаются от кодов исключений для программ .NET по ряду причин.

Особое поведение касается только традиционно реализованных служб Windows, зарегистрированных и управляемых с помощью API для Диспетчера служб. Это подразумевает использование определенных функций и классов: для C/C++ - таких, как RegisterServiceCtrlHandler() и SetServiceStatus(), для C# - System.ServiceProcess.ServiceBase.


  • No labels