Организация Кода С Самоанализом В Контексте Обфускации И Рефакторинга

Платформа .

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

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

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

Решением этой проблемы является использование синтаксического сахара, доступного в рамках технологии.

Деревья выражений и язык C#.

Чтобы не заходить слишком далеко, в качестве примера напишем простой класс.

  
  
  
   

public class SimpleClass { private readonly string theValue; public SimpleClass(string value, int ratio) { theValue = value; } public string Value { get { return theValue; } } }

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

internal static PropertyInfo ValueProperty { get { return typeof(SimpleClass).

GetProperty("Value"); } } internal static FieldInfo ValueField { get { return typeof(SimpleClass).

GetField("theValue", BindingFlags.NonPublic | BindingFlags.Instance); } } internal static ConstructorInfo Constructor { get { return typeof(SimpleClass).

GetConstructor(new[] { typeof(string), typeof( }); } }

Если вывести значения этих свойств в консоль, то мы получим строковые представления этих элементов программы.

Однако если вы переставите параметры конструктора с помощью механизма рефакторинга, встроенного в IDE (например, в Visual Studio это меню «Рефакторинг», пункт «Изменить порядок параметров»), то свойство, отражающее метаданные конструктора, будет возвращать пустую ссылку (т.е.

null).

.

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

Чтобы выйти из ситуации, вооружимся деревьями выражений, лямбда-выражениями и обобщениями.

Из всего этого склеим вспомогательный метод:

static TMember Reflect<TDelegate, TMember>(Expression<TDelegate> memberAccess) where TDelegate : class where TMember : MemberInfo { if (memberAccess.Body is MemberExpression) return ((MemberExpression)memberAccess.Body).

Member as TMember; else if (memberAccess.Body is NewExpression) return ((NewExpression)memberAccess.Body).

Constructor as TMember; else if (memberAccess.Body is MethodCallExpression) return ((MethodCallExpression)memberAccess.Body).

Method as TMember; else return null; }

Этот метод принимает два параметра типа: первый принимает делегат, используемый для разрешения подписи лямбда-выражения, которое будет передано в качестве аргумента, второй принимает тип отражаемого члена класса (например, FieldInfo для поля).

Лямбда-выражение, переданное в качестве аргумента, описывает доступ к интересующему нас члену класса.

Внутри метода осуществляется доступ к телу лямбда-выражения, представленному в виде дерева выражений.

Поскольку тело представляет собой доступ к члену класса, мы анализируем тип тела на предмет возможных вариантов:

  • Доступ к собственности или полю;
  • Вызов нового оператора (это выражение содержит ссылку на конструктор);
  • Вызов метода.

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

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



internal static PropertyInfo ValueProperty { get { return Reflect<Func<SimpleClass, string>, PropertyInfo>(v => v.Value); } } internal static FieldInfo ValueField { get { return Reflect<Func<SimpleClass, string>, FieldInfo>(v => v.theValue); } } internal static ConstructorInfo Constructor { get { return Reflect<Func<string, int, SimpleClass>, ConstructorInfo>((a1, a2) => new SimpleClass(a1, a2)); } }

В этом подходе не используются строковые литералы для представления имен членов класса и массивы типов для описания сигнатуры конструктора.

Почему этот код продолжает работать после запуска обфускатора? Ответ заключается в том, как компилятор C# генерирует код для деревьев выражений.

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



Всегда есть НО

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

Теги: #reflection #c-sharp #Chulan #.

net 4.0

Вместе с данным постом часто просматривают: