Реверс-инжиниринг NET-приложений. Часть третья: Продвинутые методы Byte Patching

Реверс-инжиниринг NET-приложений. Часть третья: Продвинутые методы Byte Patching

В двух первых статьях мы изучили некоторые основы, которые помогут вам при исследовании NET-приложений; мы рассмотрели механизм компиляции, немного узнали о языке Microsoft Common Intermediate Language, проанализировали низкоуровневый IL-код, используя «отражение» кода (code reflection), узнали назначение некоторых IL-инструкций и их байтовое представление.

Автор: Суфиан Тахири (Soufiane Tahiri)

Введение

В двух первых статьях мы изучили некоторые основы, которые помогут вам при исследовании NET-приложений; мы рассмотрели механизм компиляции, немного узнали о языке Microsoft Common Intermediate Language, проанализировали низкоуровневый IL-код, используя «отражение» кода (code reflection), узнали назначение некоторых IL-инструкций и их байтовое представление.

Также мы рассмотрели базовые техники поиска и замены последовательности байтов в шестнадцатеричном редакторе (byte patching).

В этой статье мы рассмотрим более продвинутые техники byte patching, базовые концепции защит, которые основаны на проверке лицензии, методы исследования подобных защит (по просьбам читателей) и познакомимся с новыми инструментами из арсенала реверс-инженера.

Пример

В этом примере я попытался учесть некоторые вопросы и пожелания читателей, касающиеся первых двух статей. Во втором Crack Me я смоделировал «реальную» защиту с неактивными кнопками и функциями, а также с проверкой лицензии; Crack Me выглядит так:

Для начала нам нужно активировать первую кнопку (с надписью «Enable me»), кликнув на которую, станет доступна кнопка «Save as…», которая позволит нам симулировать сохранение файла. Позже мы рассмотрим, где происходит проверка лицензии.

Дизассемблировать приложение будем утилитой ILSpy. Она очень похожа на Reflector, поэтому нет необходимости дополнительно рассказывать об этом инструменте (ссылка для загрузки находится в разделе «Ссылки»). После анализа наш Crack Me помещается в дерево (очень похожее на дерево в Reflector). Развернув узлы, мы увидим следующую картину:

Глядя на имена методов (которые, как вы помните, сохраняются), можно легко догадаться об их назначении. В нашем Crack Me только одна форма с именем MainForm. Рассмотрим логику работы метода «Form1_Load(object, EventArgs) : void». Кликнув на имя метода, мы увидим его код:

Если у вас есть базовые представления о программировании, вы можете легко догадаться, что строка «this.btnEnableMe.Enabled = false;» деактивирует компонент «btnEnableMe», который в нашем случае является кнопкой. Теперь посмотрим, что представляет собой IL-код и байтовое представление составляющих его инструкций.

.method private
instance void Form1_Load (
object sender,
class [mscorlib]System.EventArgs e
) cil managed
{
// Method begins at RVA 0x1b44c
// Code size 29 (0x1d)
.maxstack 2
.locals init (
[0] valuetype [System.Drawing]System.Drawing.Color
)

IL_0000: ldarg.0
IL_0001: callvirt instance class [System.Windows.Forms]System.Windows.Forms.Button CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_btnEnableMe()
IL_0006: ldc.i4.0
IL_0007: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Enabled(bool)
IL_000c: ldarg.0
IL_000d: callvirt instance class [System.Windows.Forms]System.Windows.Forms.Label CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_LblStat()
IL_0012: call valuetype [System.Drawing]System.Drawing.Color [System.Drawing]System.Drawing.Color::get_Red()
IL_0017: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_ForeColor(valuetype [System.Drawing]System.Drawing.Color)
IL_001c: ret
} // end of method MainForm::Form1_Load

Теперь рассмотрим подробнее каждую IL-инструкцию вышеупомянутого кода (в порядке их следования):

  1. ldarg.0 Помещает значение 0 в стек вычисления.
  2. callvirt Вызывает метод get(), связанные с объектом btnEnableMe.
  3. ldc.i4.0 Помещает 0 в стек как 32-битное целое число.
  4. callvirt Вызывает метод set(), связанные с объектом btnEnableMe.

Мы видим, что перед вызовом метода set_Enabled(bool) в стек помещается значение 0; Обычно в программировании значение 0 ассоциируется с булевым значением «False». Таким образом, чтобы активировать кнопку, нам нужно вместо нуля передать единицу (что означает «True») как параметр в метод set_Enabled(bool). IL-инструкция, которая помещает в стек значение 1 - ldc.i4.1.

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

IL-инструкция

Назначение

Байтовое представление

Ldc.I4.0

Помещает целочисленное значение 0 в стек вычислений как int32.

16

Ldc.I4.1

Помещает целочисленное значение 1 в стек вычислений как int32.

17

Callvirt

Вызывает метод, связанный с объектом.

6F

Ldarg.0

Загружает в стек аргумент 0.

02

Для поиска IL-инструкции, которую необходимо изменить, нам нужно преобразовать последовательность IL-инструкций (ldc.i4.0, callvirt, ldarg.0, callvirt) в их байтовое представление и поискать эту последовательность в файле при помощи шестнадцатеричного редактора.

Комбинация байтов вышеупомянутой последовательности: 166F??026F??; «??» означает, что мы не знаем байтовое представление instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Enabled(bool) (строка IL_0007) и байтовое представление instance class [System.Windows.Forms]System.Windows.Forms.Label CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_LblStat() (строка IL_000d).

Примеры становятся все более сложными, и мы будем использовать дополнительные утилиты. Первая из них – ILDasm, которая входит в состав .NET Framework SDK. Если у вас установлен Microsoft Visual Studio, вы можете найти ILDasm в папке SDK; на моем компьютере ILDasm находится в папке C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin

ILDasm может служить альтернативой Reflector и ILSpy, за исключением того, что у него менее дружественный интерфейс и нет возможности трансляции в высокоуровневый код. Однако вы можете поэкспериментировать с ним. Давайте загрузим наш Crack ME (File -> Open) и развернем деревья:

По умолчания ILDasm не показывает байтовое представление. Для того чтобы увидеть байты, вам нужно выполнить команду View -> Show Bytes:

Далее кликните по нужному методу (в нашем случае Form1_Load…) для получения IL-кода и соответствующих байтов:

Рисунок 1. ILDasm IL и байтовое представление инструкций метода Form1_Load()

Теперь у нас есть более полная информация об IL-инструкциях и их байтовом представлении. Для использования новой информации вы должны знать, что после символа «|» находится младший байт числа, хранимый в PE-файле по младшему адресу. Старший же байт хранится по старшему адресу. Такой порядок хранения называется «От младшего к старшему» (Little Endian).

Так что же все это означает?

Когда мы смотрим внутрь метода Form1_Load( ) в ILDasm, то видим следующее:

IL_0006: /* 16 |
IL_0007: /* 6F | (0A)000040
IL_000c: /* 02 |
IL_000d: /* 6F | (06)000022

Эти байты хранятся в файле так: 166F4000000A026F22000006.

Возвращаемся к нашей задаче

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

Термин «смещение» по-другому называемый «относительным адресом», который используется для получения определенного абсолютного адреса. Мы может рассчитать смещение для инструкций, которые мы хотим изменить. Глядя на рисунки отображения IL-кода для метода Form1_Load (в ILDasm и ILSpy), мы видим Относительный Виртуальный Адрес (Relative Virtual Address, RVA) в строке // Method begins at RVA 0x1b44c. Чтобы преобразовать виртуальный адрес в смещение (или местонахождение в файле), мы должны определить топологию нашего файла, чтобы увидеть различные секции и смещения / размеры. Можно использовать PEiD или любую другую утилиту для работы с PE-файлами, однако я расскажу вам об утилите dumpbin, которая идет в комплекте с Microsoft Visual C++. Dumpbin можно использовать для просмотра PE-секций (если у вас ее нет, обратитесь к разделу «Ссылки»).

Dumpbin – утилита, которая работает в режиме командной строки. Для просмотра информации о файле вводим следующую команду: dumpbin -headers target_name.exe.

Прокрутив вниз, находим интересную информацию:

SECTION HEADER #1
.text name
1C024 virtual size
2000 virtual address
1C200 size of raw data
400 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
60000020 flags
Code
Execute Read

Заметим, что метод Form1_Load() начинается с RVA-адреса 0x1b44c (см. Рисунок 1), а у секции .text виртуальный размер (virtual size) 0x1c024, а виртуальный адрес 0×2000. Таким образом, наш метод должен находиться внутри этой секции. Секция, в которой находится наш метод, начинается с адреса 0X400 (File pointer to raw data) в исполняемом файле. Используя эти адреса и размеры, мы можем вычислить смещение для нашего метода.

(Виртуальный адрес метода – Виртуальный адрес секции) + File pointer to raw data (все значения являются шестнадцатеричными).

Используя калькулятор в Windows (или любой другой, позволяющий производить шестнадцатеричные операции), получаем искомое смещение: (1B44C – 2000) + 400 = 1984C.

Число 0x1984C – смещение нашего метода в исполняемом файле. Теперь мы можем перейти к местонахождению нашего метода, используя любой шестнадцатеричный редактор. Сейчас наша задача – поменять несколько байт, находящихся после смещения.

Вспоминая ранее полученную последовательность байтов 166F4000000A026F22000006 и переходя по рассчитанному смещению, получаем:

Мы хотим поменять ldc.i4.0, что эквивалентно 16, на ldc.i4.1, что эквивалентно 17. Внесем изменения и посмотрим на результат (повторюсь, не забывайте делать резервные копии файлов).

Итак, первая задача решена, однако все еще присутствует надпись «Unregistered Crack Me» и не протестирована функция сохранения файлов (кнопка «Save as…»). Как только мы нажмем на кнопку «Enable Me», разблокируется вторая кнопка, что означает получение доступа к главной функции программы.

Однако затем происходит следующее:

Перед сохранением программа проверяет наличие лицензии; если лицензия не найдена, процесс сохранения останавливается и происходит блокировка кнопок.

Механизм защиты программы всегда зависит от разработчика. Существует как множество защит, так и способов их обхода. Тем не менее, мы можем отнести защиту к определенному «типу» или «виду». Один из таких видов – проверка лицензии. В зависимости от механизма работы и алгоритма проверки лицензии варьируется трудоемкость обхода защиты.

Вернемся к нашему Crack ME:

Метод btn_EnableMe_Click _1() вызывается при нажатии на кнопку «Enable Me»; метод btn_About_Click() показывает информацию при нажатии на кнопку «About». Также у нас есть еще два метода, заслуживающих внимания: btn_EnableMe_Click () и checkLicence().

Заглянем внутрь метода btn_EnableMe_Click():

При нажатии на кнопку «Save» вместо сохранения Crack Me проверяет «статус регистрации» программы, что можно рассматривать как дополнительную защиту; Crack Me проверяет корректность регистрации перед сохранением даже в случае доступности кнопки «Save as…». Рассмотрим поближе метод checkRegStat():

Рисунок 2. Исходный код метода checkReStat().

Понятно, что переменная isRegistered типа Boolean, и она меняется. Однако при нажатии на кнопку «Save as…» мы ее не меняли. Если isRegistered = false (if (!this.isRegistered)…) в Crack Me происходит вызов метода checkLicense(). Если мы кликнем на метод .ctor, то увидим место инициализации переменной isRegistered:

Рисунок 3. Метод .ctor().

Метод .ctor() – обычный конструктор, в котором происходит инициализация переменных.

Теперь рассмотрим логику работы метода checkLicense():

Рисунок 4. Метод checkLicense().

Естественно, это самый простой механизм защиты, которая проверяет лицензию. Crack Me проверяет присутствие файла «lic.dat» в той же директории, где находится исполняемый файл;

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

Если мы во время инициализации принудительно выставим у переменной isRegistered значение True (см. рисунок 3), тогда не будет вызван метод checkLicense(), внутри которого происходит сохранение файла. Из рисунка 2 видно, что checkLicense() запускается только если isRegistered = false:

public void checkRegStat()
{
this.LblStat.ForeColor = Color.Green;
this.LblStat.Text = “Saving…”;
if (!this.isRegistered)
{
this.checkLicence();
}
}

Мы можем изменить проверочное выражение в операторе ветвления (if… else… endif, рисунок 4) и, таким образом, сможем сохранять файлы, если лицензия не найдена.

Прежде мы рассматривали «классический» метод изменения байтов, используя смещение и шестнадцатеричный редактор. Сейчас я покажу вам более простой и способ, который может существенно сэкономить ваше время.

Переходим обратно в Reflector (за дополнительной информацией об этой утилите обращайтесь к предыдущим статьям). Функционал этой программы можно расширять при помощи плагинов. Мы будем использовать плагин Reflexil (дополнение к Reflector), который позволяет отредактировать IL-код, а затем сохранить изменения на диск. После загрузки Reflexil вам нужно его установить. Откройте Reflector и выполните команду Tools -> Add-ins (в некоторых версиях View -> Add-ins):

Далее кликните на «Add…» и выберите файл «Reflexil.Reflector.dll»:

После этого вы увидите добавленный плагин в окне Add-ins, которое теперь можно закрыть:

Загрузим наш измененный Crack-Me в Reflector и перейдем к методу checkLicence():

Рисунок 5

Наша задача – изменить Crack Me так, чтобы мы могли сохранять файлы. Рассмотрим IL-код метода checkLicence():

.method
public
instance
void
checkLicence() cil managed
{
.maxstack 3
.locals
init (
[0] string
str,
[1] valuetype [System.Drawing]System.Drawing.Color
color)
L_0000: call string [System.Windows.Forms]System.Windows.Forms.Application::get_StartupPath()
L_0005: ldstr “\\lic.dat”
L_000a: call string [mscorlib]System.String::Concat(string, string)
L_000f: stloc.0
L_0010: ldloc.0
L_0011: call bool [mscorlib]System.IO.File::Exists(string)
L_0016: brtrue.s L_006b
L_0018: ldstr “license file missing. Cannot save file.”
L_001d: ldc.i4.s 0×10
L_001f: ldstr “License not found”
L_0024: call valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxResult [Microsoft.VisualBasic]Microsoft.VisualBasic.Interaction::MsgBox(object, valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxStyle, object)
L_0029: pop
L_002a: ldarg.0
L_002b: ldc.i4.0
L_002c: stfld bool
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::isRegistered
L_0031: ldarg.0
L_0032: callvirt instance
class [System.Windows.Forms]System.Windows.Forms.Label
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_LblStat()
L_0037: call valuetype [System.Drawing]System.Drawing.Color [System.Drawing]System.Drawing.Color::get_Red()
L_003c: callvirt instance
void [System.Windows.Forms]System.Windows.Forms.Control::set_ForeColor(valuetype [System.Drawing]System.Drawing.Color)
L_0041: ldarg.0
L_0042: callvirt instance
class [System.Windows.Forms]System.Windows.Forms.Label
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_LblStat()
L_0047: ldstr “Unregistered Crack Me”
L_004c: callvirt instance
void [System.Windows.Forms]System.Windows.Forms.Label::set_Text(string)
L_0051: ldarg.0
L_0052: callvirt instance
class [System.Windows.Forms]System.Windows.Forms.Button
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_btnEnableMe()
L_0057: ldc.i4.0
L_0058: callvirt instance
void [System.Windows.Forms]System.Windows.Forms.Control::set_Enabled(bool)
L_005d: ldarg.0
L_005e: callvirt instance
class [System.Windows.Forms]System.Windows.Forms.Button
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_btnSaveAs()
L_0063: ldc.i4.0
L_0064: callvirt instance
void [System.Windows.Forms]System.Windows.Forms.Control::set_Enabled(bool)
L_0069: br.s L_0092
L_006b: ldarg.0
L_006c: callvirt instance
class [System.Windows.Forms]System.Windows.Forms.Label
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_LblStat()
L_0071: call valuetype [System.Drawing]System.Drawing.Color 

[System.Drawing]System.Drawing.Color::get_Green()
L_0076: callvirt instance
void [System.Windows.Forms]System.Windows.Forms.Control::set_ForeColor(valuetype [System.Drawing]System.Drawing.Color)
L_007b: ldarg.0
L_007c: callvirt instance
class [System.Windows.Forms]System.Windows.Forms.Label
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::get_LblStat()
L_0081: ldstr “File saved !”
L_0086: callvirt instance
void [System.Windows.Forms]System.Windows.Forms.Label::set_Text(string)
L_008b: ldarg.0
L_008c: ldc.i4.1
L_008d: stfld bool
CrackMe2_InfoSecInstitute_dotNET_Reversing.MainForm::isRegistered
L_0092: ret
}

В справочнике находим описание нужных инструкций:

IL-инструкция

Назначение

Байтовое представление

Call

Вызывает метод, указанный в передаваемом дескрипторе.

28

Brtrue.s

Передает управление целевой инструкции (короткая форма), если значение равно true, не равно null, или не равно нулю.

2D

Br.s

Безусловно передает управления целевой инструкции (короткая форма).

2B

Ret

Возвращается в текущий метод, помещая возвращаемое значение (если оно есть) в стек вычислений.

2A

Таблица 1. IL-инструкции.

Crack Me проверяет наличие файла лицензии (см. рисунок 4); если файл найден, тогда возвращается True, инструкция brtrue.s переходит к строке L_006b, и Crack Me выводит сообщение «File saved!». Иначе выполняется безусловная передача управления br.s, а затем посредством инструкции ret выполняется возврат из текущего метода.

Помните, наша задача заключается в том, чтобы Crack Me проверял отсутствие файла лицензии, то есть возвращал True и выводил сообщение «File saved!» в том случае, если файл лицензии не найден.

Возвращаемся к Reflector. Теперь мы нашли участок кода, который мы хотим изменить (рисунок 5); здесь нам как раз поможет плагин Reflexil. Вначале активируем плагин Tool -> Reflexil v1.x:

После этого под исходным кодом (или IL-кодом) появляется панель Reflexil.

На рисунке выше показана панель инструкций IL-кода. Существует два способа изменений посредством этого плагина, но в этой статье я расскажу только об одном. Мы научимся редактировать инструкции, используя IL-код.

После анализа IL-кода, показанного выше, мы знаем, что нужно изменить конструкцию «если не найден» на «если найден», что означает изменение инструкции brtrue.s (Таблица 1) на противоположную. Возвращаясь к справочнику IL-инструкций, находим нужную инструкцию

brfalse.s: передает управление целевой инструкции (короткая форма), если значение равно false, null (Nothing в Visual Basic), или ноль.

Возвращаемся к панели инструкций Reflexil и находим инструкцию, которую будем менять:

Щелкните правой кнопкой на выделенной линии, далее Edit… Появляется следующее окно:

Удалите строку «brtrue.s», вместо нее введите новую инструкцию «brfalse.s», а затем кликните на «Update». После закрытия окна, вы увидите внесенные изменения:

Чтобы сохранить данные на диск щелкните правой кнопкой мыши на корневом узле дизассемблированного Crack Me, выберите Reflexilv1.x и далее Save as …

Теперь, когда Crack Me изменен, проверим его работу. При запуске кнопка «Enable Me» разблокирована; при нажатии на нее, становится доступной кнопка «Save as…», кликнув на которую получаем сообщение «File Saved!»:

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

Ссылки

Не ждите, пока хакеры вас взломают - подпишитесь на наш канал и станьте неприступной крепостью!

Подписаться