• XSS.stack #1 – первый литературный журнал от юзеров форума

Мануал/Книга [C#] Mono.Cecil (часть 1) - библиотека и как с ней работать?

r3xq1

(L3) cache
Пользователь
Регистрация
14.01.2020
Сообщения
233
Реакции
146
Всем привет!
В этой теме Вы увидите как можно работать с библиотекой Mono.Cecil. - Данная библиотека работает только с исполняемыми файлами (.exe, .dll)
Установить данную библиотеку можно через Nuget

Mono.Cecil — это библиотека, написанная на C#, которая предоставляет полную и точную, полнофункциональную библиотеку общего назначения для создания и проверки программ и библиотек в форме ECMA CIL.
Его можно использовать для различных задач, в том числе:
- Генерация сборок и модулей с нуля
- Генерация кода или данных, которые можно вставить в существующие сборки
- Проверка, анализ и модификация существующих сборок
- Декомпиляция существующих сборок и.т.д

DNlib и Mono.Cecil — это библиотеки, используемые для модификации сборок .NET.
Однако DNlib более легкий и предоставляет больше возможностей для модификации сборок .NET, чем Mono.Cecil.
В частности, DNlib имеет функции для добавления, удаления и редактирования метаданных, а также для сжатия, шифрования и подписи.
Он также поддерживает .NET Core, .NET Standard и .NET Framework, тогда как Mono.Cecil имеет ограниченную поддержку только .NET Framework.

Демонстрирую два варианта загрузки, 1- через ресурсы, 2-ой локально.

1)
C#:
// Сначала загружаем в память файл из ресурсов StubDnlib - это .exe файл
using System.IO.MemoryStream memoryStream = new(Properties.Resources.StubDnlib);
// Читаем сборку из памяти
using AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(memoryStream, new ReaderParameters(ReadingMode.Immediate));
2)
C#:
// Путь до файла
string path = @"D:\Projects\MonoProject\bin\Debug\myFile.exe";
// Читаем сборку
using AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters(ReadingMode.Immediate));

Описание классов и методов и.т.п:
AssemblyDefinition— это класс в библиотеке Mono.Cecil, который используется для представления сборки .NET.
Он содержит информацию о сборке, такую как имя, версия и токен открытого ключа, а также список модулей и ссылки на другие сборки.
Он также содержит корень древовидной структуры, содержащей все типы и элементы, определенные в сборке, а также методы, поля и свойства этих типов.
Этот класс также позволяет изменять содержимое сборки, предоставляя методы для добавления, удаления и замены типов, элементов и ссылок.
AssemblyDefinition.ReadAssembly() - используется для загрузки сборки из файла.
ReaderParameters- Этот класс используется для предоставления внешней сборке набора параметров, используемых библиотекой Mono.Cecil для чтения сборки.
ReadingMode.Immediate - этот параметр используется, когда требуется немедленный доступ к содержимому сборки.
В этом режиме библиотека Mono.Cecil будет читать сборку, как только ей будут переданы параметры, что обеспечивает быстрый доступ к содержимому.
В классе AssemblyDefinitionтак же существует ещё один метод CreateAssembly.
AssemblyDefinition.CreateAssembly- это метод, который используется для создания новой сборки.
Он принимает два параметра: имя и путь, по которому необходимо сохранить созданную сборку, а также принимает параметр для настройки атрибутов сборки и других параметров.
После создания сборки вы можете добавлять модули и типы в эту сборку, используя различные методы библиотеки.

C#:
// Читаем сборку
using AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(memoryStream, new ReaderParameters(ReadingMode.Immediate));
// Проверяем что в сборке есть атрибуты
if (assembly.CustomAttributes.Count > 0)
{
    // Проходимся по циклу и ищим нужный нам атрибут, в данном случае TargetFrameworkAttribute
    foreach (CustomAttribute attribute in assembly.CustomAttributes.Where(attribute => attribute.AttributeType.Name == "TargetFrameworkAttribute"))
    {
        // Заменяем значение атрибута на новую версию .NetFramework
        attribute.ConstructorArguments[0] = new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, ".NETFramework,Version=v4.6.2"); // обязательно: .NETFramework,Version=vВашаВерсия
        assembly.EntryPoint.Module.RuntimeVersion = "v4.0.30319"; // "v4.0.30319"; // "v4.8.3928.0"
        assembly.EntryPoint.Module.Runtime = TargetRuntime.Net_4_0; // Максимальная Net_4_0
    }
}
или можно по аналогичной схеме сделать так:
C#:
// Перебираем все атрибуты
foreach (CustomAttribute list in assembly.CustomAttributes)
{
   /* Вывести список атрибутов: $"{list.AttributeType.Name}\r\n" */
 
   // Находим имя атрибута
   if (list.AttributeType.Name.Contains("TargetFrameworkAttribute"))
   {
      // Заменяем на нашу переменную типа String
      // через ветку реестра: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP - смотрим доступные версии .NetFramework, в случае не правильной версии, будет ошибка с уведомлением об установке новой версии пакета .NetFramework'a
      list.ConstructorArguments[0] = new CustomAttributeArgument(assembly.MainModule.TypeSystem.String, ".NETFramework,Version=v4.6.2");
      assembly.EntryPoint.Module.RuntimeVersion = "v4.0.30319"; // "v4.0.30319"; // "v4.8.3928.0"
      assembly.EntryPoint.Module.Runtime = TargetRuntime.Net_4_0; // Максимальная Net_4_0
   }
   // и.т.д с остальными атрибутами.
}
Через Linq
C#:
// Находим атрибут "TargetFrameworkAttribute"
CustomAttribute attr = assembly.CustomAttributes.Where(c => c.AttributeType.Name == "TargetFrameworkAttribute").FirstOrDefault();
// Заменяем на новое значение
attr.ConstructorArguments[0] = new CustomAttributeArgument(attr.ConstructorArguments[0].Type, ".NETFramework,Version=v4.6.2");
// Так же применяем остальное
assembly.EntryPoint.Module.RuntimeVersion = "v4.0.30319"; // "v4.8.3928.0", "v2.0.50727.4927" и.т.д
assembly.EntryPoint.Module.Runtime = TargetRuntime.Net_4_0; // Максимальная Net_4_0

Описание атрибутов и.т.д:
* ConstructorArguments- это коллекция аргументов, используемая для инициализации конструктора определенного объекта.
Она представляет собой набор пар имен и значений, которые передаются в качестве параметров для инициализации объекта.
* CustomAttributeArgumentпредставляет собой аргумент для атрибута класса. Возможны два типа аргументов: значения и типы.
Для значений могут быть использованы любые поддерживаемые цецильными типы, включая строки, массивы и перечисления. Для этого типа используется тип TypeReference.
* EntryPoint- это точка входа приложения
* RuntimeVersion- это версия общеязыковой среды выполнения (CLR), необходимая для запуска сборки. (Значение Runtime Version будет иметь вид «vX.X.XXXXX», где первые две цифры обозначают основной номер версии CLR, а вторые две цифры — дополнительный номер версии.)
* Runtime- это возвращает экземпляр среды выполнения, используемый для запуска программы.
* CustomAttributes- это атрибуты, которые можно применять к типам и конструкциям управляемого кода.
Они предоставляют дополнительные сведения о классах, методах, структурах, перечислениях, интерфейсах и полях.
Они могут содержать информацию о происхождении кода, версиях, авторстве, и т.д.
* MainModule.TypeSystem.String - это стандартный тип данных System.String в Mono.Cecil. Он используется для представления строковых констант в скомпилированном коде.
Этот тип обычно используется для представления текстовых данных в других типах данных, таких как массивы и кортежи.

Список атрибутов можете найти в классе: AssemblyInfo.cs или загрузив сборку в любой дизассемблер по типу: ILSpy, DnSpy и там смотреть список атрибутов.
Вот общий список стандартных атрибутов сборки .NET:
CompilationRelaxationsAttribute - int
RuntimeCompatibilityAttribute - bool
DebuggableAttribute - enum
AssemblyTitleAttribute - string
AssemblyDescriptionAttribute - string
AssemblyConfigurationAttribute - string
AssemblyCompanyAttribute - string
AssemblyProductAttribute - string
AssemblyCopyrightAttribute - string
AssemblyTrademarkAttribute - string
ComVisibleAttribute - bool
GuidAttribute - Guid
AssemblyVersion - string
AssemblyFileVersionAttribute - string
TargetFrameworkAttribute - string

Значение архитектуры зависит от процессорной платформы, на которой был собран модуль.
Это может быть одна из следующих архитектур:
* MSIL- это абстрактный процессор, предназначенный для интерпретации или трансляции в другую архитектуру;
* I386 (X86) - архитектура 32-разрядного процессора Intel;
* ARM- архитектура 32-разрядного процессора для мобильных устройств;
* IA64- архитектура 64-разрядного процессора Intel;
* AMD64- архитектура 64-разрядного процессора AMD.

C#:
// Читаем сборку
using AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(memoryStream, new ReaderParameters(ReadingMode.Immediate));
// Заменяем архитектуру процессора
assembly.MainModule.Architecture = TargetArchitecture.I386; // AnyCPU (предпочтительно 32-бит)
assembly.MainModule.Attributes |= ModuleAttributes.Required32Bit; // Устанавливаем нужный атрибут
Флаг ModuleAttributes.Required32Bit указывает, что для модуля требуется загрузка 32-разрядного образа, даже если процессор способен запускать 64-разрядный образ.
Другими атрибутами, составляющими перечисление ModuleAttributes, являются:

* ModuleAttributes.ILOnly — указывает, что модуль содержит только инструкции промежуточного языка (IL) и не содержит собственного кода.
* ModuleAttributes.Required — указывает, что модуль требует загрузки изображения.
* ModuleAttributes.StrongNameSigned — указывает, что модуль подписан подписью строгого имени.
* ModuleAttributes.Preferred32Bit — указывает, что модуль должен загружаться как 32-битный образ, даже если процессор способен запускать 64-битный образ.
* ModuleAttributes.PE32Plus — указывает, что модуль представляет собой 64-битный образ.
* ModuleAttributes.APTCA — указывает, что модуль является сборкой, подписанной Authenticode.
* ModuleAttributes.UnmanagedExport — указывает, что модуль содержит неуправляемые экспорты.

Ну и по стандарту, нужно же сохранить как-то модифицированную сборку
C#:
assembly.Write("application.exe"); // Метод Write - сохраняет изменения перезаписывая файл.
 
Последнее редактирование:
Вопрос по Mono.Cecil, как изменить экземпляр класса.
Допустим есть класс
C#:
namespace Test
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            
            var test = new A();
            
        }

    }
}

Как через mono.cecil, изменить с new A();, на new B();. В сборке существует этот класс, при поиске он находится,
Код:
var findBClass = module.GetType("Test.B");

Так же находится его конструктор
Код:
var constructor = findBClass.Methods.FirstOrDefault(m => m.IsConstructor && m.Parameters.Count == 0);

Нахожу инструкцию создания экземпляра класса A();
Код:
var createInstanceInstruction = mainMethod.Body.Instructions.FirstOrDefault(instruction =>
                    instruction.OpCode == OpCodes.Newobj &&
                    instruction.Operand is MethodReference methodReference &&
                    methodReference.DeclaringType.FullName == findAClass.FullName);

Создаю инструкцию которая создает B класс
Код:
// Создаем новую инструкцию, которая создает экземпляр B
var newInstruction = Instruction.Create(OpCodes.Newobj, module.ImportReference(constructor));

// Заменяем старую инструкцию на новую
var index = mainMethod.Body.Instructions.IndexOf(createInstanceInstruction);
mainMethod.Body.Instructions[index] = newInstruction;

// Заменяем все ссылки на старый тип на новый тип внутри метода Main
foreach (var instruction in mainMethod.Body.Instructions)
{
    if (instruction.Operand is MethodReference methodRef && methodRef.DeclaringType.FullName == AClass.FullName)
    {
        methodRef.DeclaringType = module.ImportReference(Bclass);
    }
}

При получение конечной сборки, класс A, не меняется на класс B, в чем причина?
 
Вопрос по Mono.Cecil, как изменить экземпляр класса.
Допустим есть класс
C#:
namespace Test
{
    internal class Program
    {
        private static void Main(string[] args)
        {
           
            var test = new A();
           
        }

    }
}

Как через mono.cecil, изменить с new A();, на new B();. В сборке существует этот класс, при поиске он находится,
Код:
var findBClass = module.GetType("Test.B");

Так же находится его конструктор
Код:
var constructor = findBClass.Methods.FirstOrDefault(m => m.IsConstructor && m.Parameters.Count == 0);

Нахожу инструкцию создания экземпляра класса A();
Код:
var createInstanceInstruction = mainMethod.Body.Instructions.FirstOrDefault(instruction =>
                    instruction.OpCode == OpCodes.Newobj &&
                    instruction.Operand is MethodReference methodReference &&
                    methodReference.DeclaringType.FullName == findAClass.FullName);

Создаю инструкцию которая создает B класс
Код:
// Создаем новую инструкцию, которая создает экземпляр B
var newInstruction = Instruction.Create(OpCodes.Newobj, module.ImportReference(constructor));

// Заменяем старую инструкцию на новую
var index = mainMethod.Body.Instructions.IndexOf(createInstanceInstruction);
mainMethod.Body.Instructions[index] = newInstruction;

// Заменяем все ссылки на старый тип на новый тип внутри метода Main
foreach (var instruction in mainMethod.Body.Instructions)
{
    if (instruction.Operand is MethodReference methodRef && methodRef.DeclaringType.FullName == AClass.FullName)
    {
        methodRef.DeclaringType = module.ImportReference(Bclass);
    }
}

При получение конечной сборки, класс A, не меняется на класс B, в чем причина?
ты не правильно меняешь ссылки на тип в инструкциях, тебе нужно обновить операнд инструкции, указывая на новый тип
C#:
// Создаем новую инструкцию, которая создает экземпляр B
var newInstruction = Instruction.Create(OpCodes.Newobj, module.ImportReference(constructor));

// Заменяем старую инструкцию на новую
var index = mainMethod.Body.Instructions.IndexOf(createInstanceInstruction);
mainMethod.Body.Instructions[index] = newInstruction;

// Заменяем все ссылки на старый тип на новый тип внутри метода Main
foreach (var instruction in mainMethod.Body.Instructions)
{
    if (instruction.Operand is MethodReference methodRef && methodRef.DeclaringType.FullName == findAClass.FullName)
    {
        methodRef.DeclaringType = module.ImportReference(findBClass);
    }
}
 
ты не правильно меняешь ссылки на тип в инструкциях, тебе нужно обновить операнд инструкции, указывая на новый тип
C#:
// Создаем новую инструкцию, которая создает экземпляр B
var newInstruction = Instruction.Create(OpCodes.Newobj, module.ImportReference(constructor));

// Заменяем старую инструкцию на новую
var index = mainMethod.Body.Instructions.IndexOf(createInstanceInstruction);
mainMethod.Body.Instructions[index] = newInstruction;

// Заменяем все ссылки на старый тип на новый тип внутри метода Main
foreach (var instruction in mainMethod.Body.Instructions)
{
    if (instruction.Operand is MethodReference methodRef && methodRef.DeclaringType.FullName == findAClass.FullName)
    {
        methodRef.DeclaringType = module.ImportReference(findBClass);
    }
}
Да, спасибо помогло
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх