Опыт и некоторые аспекты работы в Spices.Net
Вводная часть.
Материал статьи несет не только сугубо
информативный характер касающийся эффективного использования Spices.Net но и может быть интересен начинающим или
интересующимся технологией .Net и различными аспектами
использования Spices.Net в .Net
разработках. В этой статье авторы постарались ознакомить читателей с кругом
проблем и задач возникающих при создании .Net приложений а
также с решениями этих проблем и задач, предлагаемыми Spices.Net.
Небольшой словарик терминов употребляемых в статье:
.Net (.Net Framework)– технология Microsoft являющаяся
следующим поколением СOM технологий, позволяющая создавать
т.н управляемые (managed) приложения, именуемые сборками.
Основным языком программирования является C#, но для
создания .Net приложения разработчик может использовать VB.Net, managed C++ (with
managed extensions), Delphi.Net (Pascal for .Net), Fortran.Net. Технология претендует на кросс-платформменность, т.к есть порты под Linux (Mono, Portable.Net (PNet)) а также для BeOS и FreeBSD
(SSCLI). Также существует .Net CompactFramework – реализация .Net для PocketPC/ Windows.Mobile.
PE- portable executable, архитектура executable файлов (.exe, .dll)
Assembly, сборка – таково название executable файлов, создаваемых
для исполнения в .Net
Метаданные – структура
классов, их взаимоотношения, а также вся необходимая информация о содержимом .Net
сборки описаны метаданными, которые представляют из себя набор
таблиц, сформированных по определенным принципам.
Член сборки –
элемент метаданных.
Член класса –
элемент класса, обычно это поле(field), метод(method), свойство(property) или событие(event)
Spices.Net - Что это за программа?
Spices.Net – мощный и гибкий
инструмент для .Net разработчиков, предоставляющий широкий
спектр инструментов для просмотра и навигации, детального исследования
структуры .Net сборок (от структуры PE и метаданных до структуры классов и дизассемблирования/декомпиляции
тел методов), построения моделей отображающих различные виды взаимоотношений между
членами .Net сборок, а также позволяющий эффективно
защищать .Net сборки, создавать и управлять документацией
к сборкам, подготавливать для локализации и локализовывать Net для различных
культур и языков.
Spices.Net имеет
плагинно-ориентированную архитектуру и состоит из нескольких подключаемых
модулей (плагинов), которые представляют собой различные, ориентированные на
разные виды задач наборы инструментов, работающие под управлением и полностью
интегрированные в Spices.Net
Возможности и перспективы Spices.Net
Знакомство с модулями, входящими в состав Spices.Net
Базовые возможности и базовые модули Spices.Net
Spices.Informer
Spices.Investigator
Spices.Modeler
Spices.Obfuscator
Зачем защищать .Net сборки?
Как это работает?
Различные конфигурации проектов и
типы сборок и предлагаемые для них виды защиты
Рекомендации (best
practices):
1. Before coding:
Take into account possibility of obfuscation in step of designing(before coding)
of your software.
2. Designing:
Separate classes on 2 groups - public(this group will not obufscated: serialization,
reflection, managed resources) and hidden (core functionality, secret
resources).
3. Separate
classes on subclasses (and place them into nested classes) on functionality (serialization,
public part - that can be used in reflection and serialization)- hidden classes
can be nested, each classes can be directed onto decision of certained task.
4. Place core
functionality into nested classes. As result of obfuscation you can receive all
public classes (that should load serialization info and display property
values) untouched and core classes - obfuscated.
5. Place secret
resources/protected resource into byte arrays.
6. Mask parts of
functionality by intensive use of the interfaces
for example:
Public class
MyPublicClass : IServiceProvider
{
//nested
private class
MyNestedClass : ISomeInterface
{
}
//properties
internal
ISomeInterface MyHiddenProperty {...}
//events
public object
GetService(Type servicetype)
{
if
(serviceType == ISomeInterface)
return
MyHiddenProperty;
if
(serviceType == ISomeInterface1)
return new
MyNestedClass(this);
}
}
7. Mask interface
implementations:
internal class
SomeClass : IServiceProvider
{
private
IServiceProvider parent
public
SomeClass(IServiceProvider parent)
{
this.parent =
parent;
}
public object
GetService(Type serviceType)
{
if
(serviceType == MyProperty.GetType())
return
MyProperty;
if
(serviceType == typeof (ISomeService))
return new
SomeService(this);
if (parent !=
null)
return
parent.GetService(serviceType);
return null;
}
public
SomeAnotherService MyProperty
{
get {return
new SomeAnotherService(this);}
}
}
А теперь
замаскируем:
internal class
SomeBaseClass : IServiceProvider
{
protected
IServiceProvider parent = null;
public object
GetService(Type serviceType)
{
if
(serviceType == MyProperty.GetType())
return
MyProperty;
if
(serviceType == typeof (ISomeService))
return new
SomeService(this);
if (parent !=
null)
return
parent.GetService(serviceType);
return null;
}
public
SomeAnotherService MyProperty
{
get {return
new SomeAnotherService(this);}
}
}
internal class
SomeClass : SomeBaseClass
{
public
SomeClass(IServiceProvider parent)
{
base.parent =
parent;
}
}
5. Не используйте
литерные строчные константы (string const someField = “Hello, world!”), используйте вместо них string static someField
= “Hello, world!”, это будет оптимально как с точки зрения
метаданных (в первом случае инициализационые данные будут записаны в метаданные
2 раза – не оптимально, во втором случае - один).
6. Necessarily!!!:
Sign your assemblies, protect your software from viruses and reproducing. Damaged
assembly can't be run, signed assembly can't be re-signed by another
strong-name key.
7. Review
assemblies in different assembly browsers to see the quality of obfuscation.
Рекомендации по использованию Reflection
Reflection предоставляет оргомные возможности для
разработчика для того чтобы «добраться» до каких-либо данных через информацию о
типе. Но после обфускации наименования членов сборки могут измениться и код
использующий Reflection перестанет работать.
Не зависьте от
названий.
Для того чтобы снять с себя эту головную боль и искать, где-же все таки
не работает код необходимо не зависеть от названий членов сборки.
Ниже приводятся некторые решения этого вопроса.
Использование
интерфейсов.
Языки программирования позволяют имплементировать несколько интерфейсов
на одном типе, вы можете этим воспользоваться для экстрагирования необходимой
информации:
private
string ExtractSomeInfo(object obj)
{
ISomeInfo si = obj as ISomeInfo;
if (si != null)
return si.SomeInfo;
return null;
}
Декларативный
способ использования Reflection:
Используя богатые возможности использования атрибутов вы можете помечать
необходимые члены сборки атрибутами-метками и по этим атрибутам находить
необходимую информацию:
public
enum MarkerType
{
unknown,
what,
which
}
public
class MarkerAttribute : Attribute
{
private MarkerType type = MarkerType.unknown;
public Marker(MarkerType type)
{
this.type = type;
}
public MarkerType Type
{
get
{
return type;
}
}
}
internal
class SomeClass
{
[Marker(MarkerType.what)]
private static string what = "12345";
[Marker(MarkerType.which)]
private static string which = "54321";
}
private
string GetString(SomeClass someClass, MarkerType markerType)
{
if (someClass != null)
{
foreach(FieldInfo fi in someClass.GetType.GetFields())
{
foreach(MarkerAttribute marker in fi.GetCustomAttributes(typeof(MarkerAttribute),
false))
{
if (marker.Type == markerType)
return fi.GetValue(someClass);
}
}
}
return null;
}
Включение в обфускацию(использование атрибутов
ObfuscateAttribute, ObfuscateMembersAttribute):
В конце концов вы можете исключить необходимые для использования в Reflection
члены сборки, достаточно пометить их специальным атрибутом NotObfuscate:
using
NineRays.Obfuscator;
[Obfuscate]
public
class ReflectionTest
{
[Obfuscate]
public
int which;
[Obfuscate]
public
int what;
internal ReflectionTest(int data) : base()
{
this.which = data;
this.what = data;
}
}
Вы также можете воспользоваться атрибутом ObfuscateMembers
для задания маски включенных в обфускацию членов сборки:
Например – при использовании ObfuscateMembersAttribute(“MyCompany.Foo*”) будут
включены все классы их члены и nested классы подходящие под это выражение.
В данном случае используется полное имя члена (Namespace.DeclaringType.MemberName)
Вы можете «прицепить»(attach) несколько ObfuscateMembersAttribute атрибутов к деларации сборки для определения
нескольких pattern-ов включения в обфускацию.
Помните: оценка включенности члена в обфускацию производится после
оценки исключенности члена, если член исключен из обфускации оценка его
включенности уже не производится.
Исключение из
обфускации(использование атрибутов NotObfuscateAttribute, NotObfuscateMembersAttribute):
В конце концов вы можете исключить необходимые для использования в Reflection
члены сборки, достаточно пометить их специальным атрибутом NotObfuscate:
using
NineRays.Obfuscator;
class
ReflectionTest
{
[NotObfuscate]
private int which;
[NotObfuscate]
private int what;
internal ReflectionTest(int data) : base()
{
this.which = data;
this.what = data;
}
}
Вы также можете воспользоваться атрибутом NotObfuscateMembers для задания маски исключенных из обфускации членов сборки:
Например – при использовании NotObfuscateMembersAttribute(“MyCompany.Foo*”) будут
исключены все классы их члены и nested классы подходящие под это выражение.
В данном случае используется полное имя члена (Namespace.DeclaringType.MemberName)
Вы можете «прицепить»(attach) несколько NotObfuscateMembersAttribute атрибутов
к декларации сборки для определения нескольких pattern-ов исключения из
обфускации.
Отладка обфусцированных сборок
Для того
чтобы создать отлаживаемые версии сборок используйте следующие рекомендации:
1.
Установите свойство StripDebugInfo в значение False
для того чтобы запретить обфускатору удаление debug info из сборок.
2.
Используйте «читабельные» режимы именования
обфусцированных членов сборки в ObfuscationOptions.Naming
– любой кроме Naming.NonDisplayable для облегчения процесса отладки.
3.
Скопируйте .PDB файлы, ассоциированные
со сборками в место их обфускации (SaveToDirectory).
Отлаживать сборки
вы можете как в DbgClr.exe – GUI
дебаггере входящем в состав .Net Framework,
так и в самой Visual Studio.
Отладка
обфусцированных сборок в Microsoft Visual Studio
Для того чтобы
организовать отладку в Visual Studio вам необходимо иметь Spices.Obfuscator c Visual Studio Integration Package
– VSIP (Spices.VSIP.Net
Suite или Spices.VSIP.Net
Obfuscator).
В свойстве проекта SaveToDirectory установите каталог из которого будет запускаться startup project – обычно .exe файл (для примера: D:\work\MyApp\bin\Debug\MyApp.exe – то необходимо указать D:\work\MyApp\bin\Debug). Это необходимо для того чтобы обфускатор поместил все обфусцированные
для отладки сборки в каталог запуска и Visual Studio
Debugger использовал бы эти сборки для отладки.
Специфика обфускации сборок CompactFramework
Есть
специфика J
Прячем ресурсы:
Byte[] rules!
Проблемы и их решение
Q:Как обфусцировать сборку чтобы она была CLSComplaint
и устанавливалась в GAC?
A:
Q: Как правильно обфусцировать сборку для
использования в средствах разработки?
Q: Как максимально обфусцировать сборку?
Q: Как обфусцировать сборки проекта как единое
целое?
Q: Как обфусцировать сборки, которые в
дальнейшем необходимо патчить?
Q: Как обфусцировать сборки чтобы имена
обфусированных элементов при каждой обфускации оставались одинаковыми?
Q: Поддерживает ли Spices.Net Obfuscator Incremental Obfuscation?
Рекомендации
На этапе проектирования
1. Подписывайте сборки, для того чтобы их невозможно было подделать,
внедрить вредоносный код. Помните что strong named assemblies невозможно модифицировать не подписав их еще раз, но в этом случае
полное имя сборки будет уже другим, не идентичным исходному.
2. Храните .snk (strong-name
key file) ключ в безопасном месте, для того чтобы злоумышленник
не смог получить ключ для подписания взломанных сборок.
3. Помните, что после защиты ващего продукта обфускатором, он остается
дизассемблируемым. И
4. Планируйте видимость (visibility) классов и членов
класса.
5. Разделяйте функциональность на различные виды (layers). Простейшим разделением может быть разделение на сериализуемые
классы, классы для публичного предоставления и модификации settings/properties и классов реально реализующих
функциональность сборки. Бывает этого трудно достигнуть, поэтому лучше всего об
этом подумать заранее.
6. Просматривайте сборки, получающиеся в результате обфускации. Это
позволит сформировать наиболее эффективные настройки для защиты.
7. На этапе тестирования альфа и бета версий тестируйте эти версии в
обфусцированном виде. Это позволит быстрее решать проблемы с эффективностью
защиты в публичных релизах. Это позволит вам заодно «обкатать» возможности
патча, отдельной обфускации сборок входящих в ваш продукт, быстрой локализации
и локализации.
8. Не используйте информацию о типе в виде строки, если не уверены что
этот тип не может быть офбусцирован.
Пример:
[SomeAttribute(typeof(MyType))]
– правильно
[SomeAttribute(“MyNamespace.MyType”)] – неправильно,
обфускатор не знает о том что в качестве аргумента используется информация о
типе.
9. Интересуйтесь новинками – разработчики находятся в постоянном развитии
продукта, возможно сегодня они вам смогут предложить более эффективное средство
защиты чем вчера.
На этапе кодирования
1.
Не используйте имена классов в коде (string someName = typeof(SomeClass).Name
or string someName = this.GetType().Name), если не уверены
что класс точно не будет обфусцирован. Это достаточно часто встречающаяся
ошибка.
2. Use
try-catch statements to determine place of exception:
try
{
//something...
}
catch
(Exception e)
{
// Gui scheme
// MessageBox.Show(string.Format("Exception:
{0}\r\nStackTrace:\r\n{1}", e.Message, //e.StackTrace);
//Debug scheme
System.Diagnostics.Debug.WriteLine(string.Format("Exception: {0}",
e.Message));
System.Diagnostics.Debug.WriteLine(string.Format("Stack Trace: {0}",
e.StackTrace));
}
Also in the exe you can attach
handler to AppDomain.UnhandledException and Application.ThreadException to
trace unhandled exceptions. To show exception messages you can use
ThreadExceptionDialog class
Ограничения Evaluation version
Spices.Decompiler
Возможности Spices.Decompiler
Ограничения Evaluation version
Spices.Documenter
Возможности Spices.Documenter
Ограничения Evaluation
version
Spices.Localizer
Возможности Spices.Localizer
Использование вместе со Spices.Obfuscator
Ограничения Evaluation
version
Visual Studio Integration Package
Итоги и выводы