Наша совместная команда Banwar.org

Связаться с нами

  • (097) ?601-88-87
    (067) ?493-44-27
    (096) ?830-00-01

Статьи

Перехоплюємо .NET: Теорія і практика перехоплення викликів .NET-функцій

  1. Зміст статті ... в проге виявився баг. Перше звернення до бази відвалюється з таймаут, але такі...
  2. Metadata
  3. Як далеко можна зайти в ліс?
  4. Перехоплення? Легко!
  5. ковбаса код
  6. висновок
  7. Links

Зміст статті

... в проге виявився баг. Перше звернення до бази відвалюється з таймаут, але такі йшли нормально. З'ясувалося, що індуси наколбасілі метод в 75000 рядків, і підключення до бд відвалюється за той час, поки йшла jit-компіляція методу ... По-моєму, метод в 75к рядків на С # швидше закличе диявола, ніж запрацює.

Пам'ятаю, кілька років тому ми з друзями кепкували з приводу того, що скоро мобільні телефони вже не будуть відставати від комп'ютерів по потужності мікропроцесора. І ось, будь ласка - в тому місці, де я працюю, 20% комп'ютерів ВЖЕ поступаються за потужністю сучасному GalaxyS.

Аналогічна ситуація складається і з розробкою програмного забезпечення - пару років тому. Прогресивне людство було впевнене, що перехоплення виклику .NET функцій - доля збоченців. І я б з ними погодився.

Про перехоплення API-функцій написано море книг і статей, знято купа відеоуроків. Якщо раніше ця тема здавалася долею гурупрограммістов, то тепер написати код, який буде перехоплювати системні виклики, не складе проблеми навіть для новачка - там і насправді немає нічого складного. Зрозуміло, тільки якщо мова йде про стандартний WinAPI-інтерфейсі. Ідея перехоплення викликів функцій в .NET спочатку викликала лише усмішки програмістів. Посміхається і спроби написання вірусів під .NET Framework.

Часи змінилися, і разом з ними змінилися вимоги, які потенційний замовник ставить для розробника. Тепер перехоплення викликів в .NET-прогах - не така вже нікчемна і божевільна завдання, як здавалося раніше. І тепер після прочитання цієї статті ти легко зможеш взяти під контроль свої .NETпріложенія!

збираємо мозаїку

Тепер можна з усією сміливістю стверджувати, що відповідь Майкрософта всюдисущої Java вдався - розробник на .NET на сьогоднішній день так само затребуваний, як і Java-програміст. З впровадженням .NET Framework принципово змінилася схема «мову програмування - ОС». Якщо раніше мова йшла про адаптацію однієї мови для різних платформ, то зараз - про адаптацію різних мов для однієї платформи. Давай коротко згадаємо, що таке .NET, і що нам потрібно знати, якщо ми хочемо навчитися основам перехоплення в його середовищі. Справа в тому, що в нашому випадку мало буде просто знати мову програмування середовища .NET, потрібно ще мати чітке уявлення про те, як він працює.

Платформа .NET містить загальномовного середовища виконання (Common Language Runtime - CLR). Загальномовне Виконавча CLR підтримує кероване виконання, яке характеризується низкою переваг. Спільно із загальною системою типів, загальномовне Виконавча CLR підтримує можливість взаємодії мов платформи .NET. Крім того, платформа .NET надає велику повнофункціональну бібліотеку класів .NET Framework. Ну і звичайно ж, метадані (Metadata) - це інформація про збірки, модулях і типах, складових програми в .NET.

Компілятор генерує метадані, а CLR і наші програми їх використовують. Коли завантажуються сборка и пов'язані з нею модулі і типи, метадані завантажуються разом з ними.

Metadata

На метаданих, як на одній з найбільш важливих і принципових тем, ми зараз і зупинимося.
Отже, в них зберігаються всі класи, типи, константи і рядки, які використовуються .NET-додатком. Metadata, в свою чергу, ділиться на кілька окремих куп (heaps) або потоків. У Microsoft .NET передбачені п'ять куп: #US, #Strings, #Blob #GUID і # ~.

  • # US-купа зберігає всі рядки, які програміст «заготовив» в своєму коді. Наприклад, якщо програма виводить на екран рядок функцією Print ( «hello»), то hello буде зберігатися в # US-купі.
  • # Strings-купа зберігає в собі такі речі, як імена методів і імена файлів.
  • # Blob-купа містить в собі бінарні дані, на які посилається збірка, такі як, наприклад, сигнатура методів.
  • # ~ -Куча містить набір таблиць, які визначають важливий вміст .NET-збірки. Наприклад, там містяться таблиці AssemblyRef, MethodRef, MethodDef, і таблиці Param. Таблиця AssemblyRef включає набір зовнішніх збірок, від яких залежить сама збірка.

Таблиця MethodRef включає в себе лист зовнішніх методів, які використовуються складанням. Таблиця MethodDef містить всі методи, які визначені в збірці.

Param, в свою чергу, містить всі параметри, які використовуються методами, визначеними в таблиці MethodDef. «До чого ця Нудота?», - запитаєш ти. Спокійно! Зверни килим нетерпіння і поклади його в скриню очікування, адже без розуміння того, «як же ця хрень працює», сенс статті до тебе може і не дійти :).

Поговоримо детальніше про таблиці MethodDef. Для перехоплення методів .NET-додатків це вкрай потрібна річ. Кожен запис в таблиці методів містить RVA (relative virtual address) методу, прапори методу, ім'я методу, зміщення в купі #Blob на сигнатуру методу і індекс в таблиці Param, яка містить перший параметр, який передається функції. RVA методу вказує на тіло методу (який містить IL-код) в секції .TEXT.

Сигнатура методу визначає порядок передачі параметрів (calling convention), який тип буде на повернення з методу і т.д. Щоб ти зміг зрозуміти тему на рівні Дао, на сайті rsdn.ru викладені аж цілих три статті на тему метаданих в .NET, які рекомендуються до обов'язкового вивчення ( http://www.rsdn.ru/article/dotnet/refl.xml , ... / phmetadata.xml, і ... / dne.xml).

Як далеко можна зайти в ліс?

До його середини - далі ліс уже закінчується. Поспішаю порадувати: половину лісу ми вже пройшли і поступово просуваємося до нашої головної мети - навчитися перехоплювати .NET-виклики.

Розглянемо питання виконання .NET-додатків чисто практично:

  • Mscoree.dll (виконавчий движок середовища .NET)
  • Mscorwks.dll (where most of the stuff happens)
  • Mscorjit.dll (та сама JIT)
  • Mscorsn.dll (обробляє верифікацію «суворих» імен)
  • Mscorlib.dll (Base Class Library - бібліотека базових класів)
  • Fushion.dll (assembly binding)

Будь-яке .NET-додаток на точці входу має всього одну інструкцію. Ця інструкція реалізує джамп на функцію _CorExeMain, яка розташовується в таблиці імпорту. _CorExeMain, в свою чергу, посилається на mscoree.dll, яка і починає процес завантаження і виконання .NET-додатки.

Mscoree.dll викликає _CorExeMain з mscorwks.dll. Mscorwks.dll - це досить велика бібліотека, яка контролює і обробляє процес завантаження. Вона завантажує бібліотеки базових класів (BCL) і тільки потім викликає точку входу Main () твого .NET-додатки. Так як Main () в цьому моменті все ще не декомпілювати, код Main () буде перекинуто назад в mscorwks.dll для компіляції. Mscorwks.dll викличе JITFunction, яка завантажить середу JIT з mscorjit.dll.

Як тільки згенерований IL-код буде скомпільовано в native-код, контроль буде переданий назад в Main (), яка і розпочне безпосереднє виконання.

Перехоплення? Легко!

Ну нарешті то! Поговоримо безпосередньо про перехоплення викликів в .NET. Ми звикли сприймати перехоплення класично, тобто таким чином, коли з метою його реалізації або пишуться проксі-обгортки, або ж функція просто сплайсов. У випадку з .NET все відбувається по-іншому.

Перше, що треба усвідомити для здійснення перехоплення - це те, що методи, які ми хочемо перехопити, зберігаються в кінці секції .TEXT. Це зроблено, тому що секція .TEXT .NET'овской збірки досить компактна - там недостатньо місця для зберігання всіх перехоплених функцій.

Хтось може запитати, чому не можна просто змінити стандартним загальновідомими способом (інструкції «CALL» і «JUMP» RVA-методу) на перехоплює код, а потім просто перехопити код всіх оригінальних функцій? Причина проста - інструкції «CALL» і «JUMP» в MSIL-коді використовують токени (сигнатури) методів, а не зсуву на них. Таким чином, якщо я хочу отримати посилання на код, який потрібно перехопити, це потрібно буде зробити шляхом пошуку токена методу. Отже, для вирішення нашої задачі перехоплення нам потрібно буде розсунути секцію .TEXT коду.

Звісно ж, що єдиний спосіб викликати оригінальний код - створити інший метод. Є дві причини, по яких такий підхід важко, але все ж здійснимо. По-перше, це вимагає включення нового запису в таблицю методів. І по-друге, в таблиці методів додаткового місця для цього просто немає.

Друге - нам потрібно знайти RVA методу в таблиці MethodDef і перезаписати його так, щоб він вказував на місце розташування нового перехопленого методу. Для здійснення цієї операції потрібно збільшити розмір секції .TEXT для того, щоб вона змогла вмістити все це господарство. При цьому повинні бути враховані і віртуальний, і raw-розмір секції. Віртуальний розмір - це актуальний, дійсний розмір секції, rawразмер - це розмір, округлений до вирівнювання секції.

Віртуальні адреси і розміри потрібні для того, щоб знати, як виконуваний файл завантажується в пам'ять. Якщо, наприклад, секція .TEXT має віртуальний адресу 0x1000, то з цього зміщення в пам'яті запущеного процесу ми і знайдемо цю саму секцію .TEXT, яка туди була спроектована. Разом з тим, raw-адреса секції може бути 0x200, а це значить, що секція .TEXT в самому файлі розташована по зсуву 0x200.

Ті секції, які йдуть за .TEXT (секції даних і релокі), теж потрібно буде вирівняти, бо розширення секції .TEXT «наїде» на початок наступної секції, в результаті чого файл просто не запуститься. В кінці всього цього дійства оновлюються PE-заголовки. Все, тепер наш перехоплений код вшитий прямо в збірку, а інші методи залишилися недоторканими.

ковбаса код

Як ти, напевно, вже здогадався, один з основних моментів, який дозволить нам взяти під контроль .NET-программуліна, полягає в отриманні покажчика на заголовок CLIHeader, який, в свою чергу, містить таке поле, як Metadata. Воно-то нам і потрібно:

Отримаємо покажчик на Cliheader C #

FileReader Input = new FileReader (AssemblyPath);
byte [] Buffer = Input.Read ();
[Skip ...]
ImageBase = Marshal.AllocHGlobal (Buffer.Length * 2);
HeaderOffset = * ((UInt32 *) (ImageBase + 60));
PE = (PEHeader *) (ImageBase + HeaderOffset);
HeaderOffset + = (UInt32) sizeof (PEHeader);
StandardHeader = (PEStandardHeader *) (ImageBase + HeaderOffset);
RVA * CLIHeaderRVA = (RVA *) ((byte *) StandardHeader + 208);
SectionOffset = GetSectionOffset (CLIHeaderRVA-> Address);
CLI = (CLIHeader *) (ImageBase + CLIHeaderRVA-> Address - SectionOffset);
MetaDataHeader = (MetaDataHeader *) (ImageBase + CLI-> MetaData.Address - SectionOffset);
metadata = new MetaData (Function, ImageBase, (Int32) CLI-> MetaData.Address, MetaDataHeader, CLI-> MetaData.Size);

Далі буде ненабагато складніше, особливо для тих, хто знайомий зі способом впровадження в PE-файли шляхом розширення його секцій.

Нам потрібно буде записати в кінець секції .TEXT перехоплені функції і перерахувати всі необхідні поля, пов'язані з секцією, щоб дати їй можливість нормально виконатися, після чого поновити необхідні PE-заголовки:

VirtualSize = TextSectionHeader-> VirtualSize + HookSize;
RawDataSize = VirtualSize;
if ((RawDataSize% FileAlignment)! = 0)
RawDataSize + = (FileAlignment (RawDataSize% FileAlignment));
StandardHeader-> CodeSize = RawDataSize;
HookAddress = TextSectionHeader-> VirtualAddress + TextSectionHeader-> VirtualSize;
TextSectionHeader-> VirtualSize = VirtualSize;
TextSectionHeader-> RawDataSize = RawDataSize;
[Skip ...]
StandardHeader-> DataBase = DataSectionHeader->
VirtualAddress;
StandardHeader-> ImageSize = SectionHeader->
VirtualAddress + SectionHeader-> VirtualSize;
if ((StandardHeader-> ImageSize% SectionAlignment)! = 0)
StandardHeader-> ImageSize + = (SectionAlignment (StandardHeader-> ImageSize% SectionAlignment));

От і все. На жаль, ті 75 тисяч рядків коду, які мені хотілося б викласти на сторінки журналу, в нього просто не влізуть. Жарт :). Повністю робочий код ти, як і завжди, зможеш знайти на диску.

висновок

Так вже вийшло, що більша частина статті - це опис принципів дії .NET'овской середовища, про які ти напевно чув. Але, як то кажуть, RTFM - і буде тобі щастя. Для того, щоб повністю оволодіти цією технікою і уславитися шаманом, тобі доведеться добряче попрацювати. Але це не страшно, адже перехоплення .NET-додатків в твоєму виконанні того варто.

Links

http://reflector.red-gate.com - сайт програми .NET Reflector, який дозволяє відновити вихідний код .NETпрограмм, навіть якщо їх сорци недоступні. Рекомендується до користування!

Перехоплення?
«До чого ця Нудота?
Як далеко можна зайти в ліс?
Перехоплення?

Новости

Banwar.org
Наша совместная команда Banwar.org. Сайт казино "Пари Матч" теперь доступен для всех желающих, жаждущих волнения и азартных приключений.