Реверс-инжиниринг NET-приложений. Часть четвертая: Введение в циклическую разработку

Реверс-инжиниринг NET-приложений. Часть четвертая: Введение в циклическую разработку

После изучения основ реверс-инжиниринга NET-приложений, настало время более подробно рассмотреть язык MSIL. В этой статье не ставится цель научиться вас программировать на этом языке.

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

Введение

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

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

Понятие «Циклической разработки»

Первоначально IL-ассемблер и дизассемблер были созданы как внутренние инструменты, используемые при разработке среды CLR. Когда обе утилиты стали в достаточной мере синхронизированными, на базе ILAsm начали появляться (наряду с библиотекой классов NET Frameworks) сторонние компиляторы, ориентированные на платформу NET.

По существу, циклическая разработка представляет собой возможность синхронизации нескольких (двух и более) средств разработки программного обеспечения. В случае с платформой NET имеется в виду возможность создания управляемой сборки как в высокоуровневой среде разработки (Microsoft Visual C# или Microsoft Visual Basic .NET), так и на низкоуровневом языке с последующей компиляцией (например, при помощи ILAsm). Все это означает, что мы можем дизассемблировать сборку, внести изменения в IL-код, а затем выполнить обратное реассемблирование и получить измененную рабочую сборку (или модуль).

Замечание: Управляемые NET-приложения называются «сборками», а управляемые исполняемые файлы NET называются «модулями».

Основы

В этой статье мы будем работать только с двумя инструментами, официально представленными компанией Microsoft в рамках Windows SDK: IL-ассемблер (ILASM) и IL-дизассемблер (ILDASM, который мы использовали в прошлой статье). В принципе, мы можем исследовать любую NET-сборку / модуль при помощи этих двух утилит.

ILDASM можно найти в папке C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin. ILASM находится в папке C:\WINDOWS\Microsoft.NET\Framework\vx.x (в зависимости от версии платформы, которая используется для создания сборки, которую мы хотим изменить).

Прежде чем приступить к анализу Crack ME (о нем я еще не рассказывал), я подробнее остановлюсь на некоторых аспектах платформы NET и начну со среды Common Language Runtime.

Среда CLR является промежуточным звеном между NET-сборками и операционной системой, в которой запускаются сборки; на данный момент (я надеюсь) вы знаете, что каждая NET-сборка «транслируется» в низкоуровневый промежуточный язык (Common Intermediate Language – CIL или Microsoft Intermediate Language – MSIL). Несмотря на разработку на высокоуровневом языке, сборка независима от целевой платформы. Такой вид «абстракции» позволяет вести разработку на разных языках.

Common Intermediate Language опирается на набор спецификаций, гарантирующих совместное сосуществование разных языков; этот набор спецификаций известен как Common Language Specifications – CLS, как это определено в спецификации общеязыковой структуры (Common Language Infrastructure), стандарте Ecma International, и международной организации по стандартизации (International Organization for Standardization – ISO; ссылку для загрузки Раздела I можно найти в разделе «Ссылки»).

NET-сборки и модули, спроектированные для запуска в среде Common Language Runtime (CLR), состоят в основном из метаданных и управляемого кода.

Управляемый код состоит из набора инструкций, которые являются «сердцем» сборки / модуля и представляет собой набор функций и методов закодированный в абстрактной и стандартизированной форме известной как MSIL (или CIL), что позволяет опознать управляемый исходный код, который запускается исключительно в среде CLR.

С другой стороны, термин «метаданные» является довольно неоднозначным (метаданные можно еще назвать как «данные, описывающие данные»). В нашем случае метаданные – это система дескрипторов, касающихся «содержимого» сборки. Метаданные относятся к структуре данных, находящихся внутри низкоуровневого CIL-кода, и описывают структуру высокоуровневого кода: отношения между классами, их членами, возвращаемыми типами, глобальными элементами и параметрами методов. Если обобщить все вышесказанное (всегда учитывайте контекст среды CLR), метаданные описывают все элементы, которые задекларированы (или на которые есть ссылка) внутри модуля.

Таким образом, можно сказать, что модуль состоит из двух компонентов: метаданных и IL-кода; среда CLR подразделяется на две подсистемы: «загрузчик» и JIT-компилятор.

Загрузчик разбирает метаданные и создает в памяти нечто вроде схемы / паттерна представления внутренней структуры модуля, а затем в зависимости от результата предыдущей операции, JIT-компилятор (также называемый jitter) компилирует IL-код в машинный код конкретной платформы.

Рисунок ниже описывает механизм создания и запуска управляемого модуля:

Наш третий объект исследования – управляемый модуль «CrackMe3-InfoSecInstitute-dotNET-Reversing.exe» (ссылка для загрузки представлена в разделе «Ссылки) выглядит так:

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

Шаг 1: Дизассемблирование

Первым делом дизассемблируем наш Crack Me, используя ILDASM и посмотрим встроенный IL-код управляемого модуля. Сосредоточим внимание на двух узлах:

Управляемый модуль содержит одну форму Form1 и один класс GetSerial. Рассмотрим первый узел (с формой):

Дважды щелкните по методу Form_Load, чтобы увидеть IL-код:

.method private instance void Form1_Load(object sender, class [mscorlib]System.EventArgs e) cil managed
{
// Code size 19 (0×13)
.maxstack 8
IL_0000: ldstr “I’m a nag screen, remove me.”
IL_0005: ldc.i4.s 16
IL_0007: ldstr “Nagging you!”
IL_000c: call valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxResult [Microsoft.VisualBasic]Microsoft.VisualBasic.Interaction::MsgBox(object, valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxStyle, object)
IL_0011: pop
IL_0012: ret
} // end of method Form1::Form1_Load

Для повышения читабельности кода все служебные слова ILAsm (читай инструкции) выделены жирным шрифтом. Рассмотрим детально механизм работы этого участка кода и наши дальнейшие действия.

Понимание механизма работы метода Form_Load():

.method private instance void Form1_Load(…) cil managed элемент метаданных Method Definition.

Служебные слова private и instance определяют флаги элемента Method Definition. Ключевое слово public означает, что метод Frm1_Load() доступен всем членам, для которых видим «материнский» класс (тот, который содержит этот метод). Служебное слово instance говорит нам о том, что метод связан с объектом, а не классом.

Служебное слово void в явной форме определяет тип возвращаемого значения (по умолчанию) текущего метода. Void означает, что метод ничего не возвращает.

Служебные слова cil и managed означают, что тело метода представлено IL-кодом и определяют флаги реализации Method Definition.

.maxstack 8 – директива, определяющая максимальное число элементов, которое может находиться в стеке вычислений во время выполнения метода.

IL_0000 – метка, не занимающая места в памяти. ILDASM помечает каждую строку (инструкцию) подобными метками. Метки не компилируются и используются исключительно для идентификации некоторых смещений внутри IL-кода во время компиляции программы.

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

Ldstr «I’m a nag screen, remove me». Создает объект типа строка из передаваемой строковой константы и помещает ссылку на этот объект в стек вычислений. Подобные константы хранятся в метаданных. Эта строковая константа общеязыковой среды выполнения или строковая константа метаданных всегда хранится в формате Unicode (UTF-16).

Ldc.i4.s 16 короткая форма (так как оканчивается на «.s») инструкции, которая загружает целое число 16 типа int32 в стек вычисления (в данном случае происходит загрузка стиля окна сообщения с иконкой о критической ошибке).

call valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxResult [Microsoft.VisualBasic]Microsoft.VisualBasic.Interaction::MsgBox(object, valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxStyle, object); valuetype используется перед объектом, который мы хотим создать. Указывается полное имя класса, включая имя библиотеки, так происходит вызов (не виртуального?) метода. Ключевое слово valuetype обязательно для экземпляров общего типа, поскольку они представлены в метаданных как TypeSpecs.

Инструкция pop удаляет из стека строку «Nagging you!».

После выполнения инструкции ret происходит возврат из текущего метода, и в стек вычислений помещается значение определенного типа. В нашем случае метод возвращает значение типа void, что означает отсутствие какого-либо значения в стеке вычисления во время возврата из метода.

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

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

Информацию о версии можно узнать, если посмотреть содержимое манифеста сборки:

Директива .ver указывает номер версии:

Рисунок 1. Версия сборки

Теперь выгрузим сборку. Выполните команду File->Dump->Ok (или Ctrl+D) и выберите директорию, куда сохранять файлы. В результате получится нечто подобное:

Файл с расширением .il содержит все IL-инструкции управляемого модуля. Откройте его в текстовом редакторе и найдите метод Form1_Load:

Теперь мы можем пойти несколькими путями. Например, удалить все инструкции внутри метода или удалить весь метод, поставив в начало инструкцию ret. Я предпочитаю удалить содержимое так, чтобы метод Form1_Load() не выполнял вообще никаких операций.

Удалите строки с 1192 до 1201 и сохраните изменения:

После всех манипуляций нам нужно реассемблировать измененный .il файл. Мы будем использовать утилиту ILAsm, которая поставляется вместе с Visual Studio и Windows SDK. С помощью ILAsm мы сможем собрать исполняемый файл из файла инструкций на языке Microsoft Intermediate Language.

Шаг 2: Реассемблирование

Мы помним, что версия нашей сборки - 4.0.30319 (см. рисунок 1). Соответствующая версия ILASM находится в папке C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319.

Переходим в командную строку Microsoft Windows Command (CMD) или Visual Studio Command Prompt (в любом случае результат будет один и тот же).

Выполните команду Start->Run->CMD.

CD C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319
ilasm filename.il –res=filename.res

Filename = полный путь к .il файлу

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

Если процесс компиляции прошел успешно, вы увидите следующее сообщение:

Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
Writing PE file
Operation completed successfully

И в новой версии уже не будет всплывающего окна:

Замечание: вы также можете изменить текст, появляющийся во всплывающем окне посредством изменения строки, которая загружается в стек вычисления, или поменять стиль всплывающего окна:

Имя члена

Значение

Описание

OKOnly

0

Выводится только кнопка OK.

OKCancel

1

Выводятся кнопки OK и Отмена.

AbortRetryIgnore

2

Выводятся кнопки Прервать, Повтор и Пропустить.

YesNoCancel

3

Выводятся кнопки Да, Нет и Отмена.

YesNo

4

Выводятся кнопки Да и Нет.

RetryCancel

5

Выводятся кнопки Повтор и Отмена.

Critical

16

Выводится иконка критического сообщения.

Question

32

Выводится иконка предупреждающего запроса.

Exclamation

48

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

Information

64

Выводится иконка информационного сообщения.

DefaultButton1

0

Первая кнопка является кнопкой по умолчанию.

DefaultButton2

256

Вторая кнопка является кнопкой по умолчанию.

DefaultButton3

512

Третья кнопка является кнопкой по умолчанию.

ApplicationModal

0

Модальное окно сообщения в приложении. Пользователь должен нажать кнопку в окне сообщения прежде, чем продолжить работу.

SystemModal

4096

Системное модальное окно сообщения. Работа всех приложений приостанавливается до тех пор, пока пользователь не нажмет кнопку в окне сообщения.

MsgBoxSetForeground

65536

Окно сообщения переднего плана.

MsgBoxRight

524288

Текст, выровненный по правому краю.

MsgBoxRtlReading

1048576

Текст для чтения справа налево (иврит и арабские системы).

Таблица 1. Возможные значения для Visual Basic MsgBoxStyle (источник: Microsoft MSDN)

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

Ссылки

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

Подписаться