Основы .NET Реверс-инжиниринга
Введение
Постараюсь восполнить недостаток работ по данному направлению. Данный вводный пост должен стать частью предстоящей серии, которая осветит часть концепций и архитектуру .NET по мере становления некоторых положений более прозрачными для реверс инженеров.
Перед тем как начать, я настоятельно рекомендую Вам выделить несколько часов для самообучения, по меньшей мере одному из .NET языков (Visual Basic .NET или C#). Может показаться, что реверсирование .NET программ происходит легче, нежели реверсирование «традиционных» программ, что не всегда правильно с одной точки зрения.
Саму концепцию .NET можно с легкостью сравнить с сущностью JAVA и Виртуальной Машины Java, хотя бы в том случае, когда говорим о компиляции. В отличие от большинства традиционных языков программирования, таких как C/C++, приложения, разработанные с использование .NET фреймворков и скомпилированные в Common Intermediate Language (CIL или Microsoft Intermediate Language MSIL), сравнимого с байткодом (bytecode) в Java программах, вместо того, чтобы быть преобразованными напрямую в нативный машинный исполняемый код, .NET Common Language Runtime (CLR) будет транслировать CIL в машинный код во время выполнения.
Это безусловно увеличит скорость исполнения, а также будет иметь некие преимущества, так как каждая программа .NET будет хранить все имена классов, переменных функций и подпрограмм в скомпилированной программе, а это, с точки зрения программиста, такая же великая вещь с тех пор как появилась возможность производить сборку разных частей программы, используя различные языки программирования, доступные и поддерживаемые фреймворками.
Рисунок 1. Обзор Common Language Infrastructure (CLI)
Что это означает для реверс инженера?
В принципе любое скомпилированное приложение .NET есть не более чем его представление в Common Intermediate Language, всё еще имеющее все pre-coded идентификаторы в том виде, в котором ввел программист.
Понимание Common Intermediate Language просто приведет к идентификации инструкций языков высокого уровня и структур. Это означает, что из скомпилированной программы .NET мы сможем восстановить обратно исходный код, даже с возможностью выбора предпочтельного языка программирования .NET.
Когда говорим о приложениях .NET, мы говорим о рефлексии («reflection»), а не о декомпиляции («decompilation»). Эта техника позволяет нам исследовать информацию о классах или сборке во время выполнения, таким образом, мы можем получить все свойства, методы, функции и т.д. со всеми параметрами и аргументами, а также все интерфейсы, структуры и прочее.
В настоящее время существует множество инструментов, которые позволяют “рефлексировать” исходный код готового приложения .NET. Широкое распространение получила утилита «Reflector», позволяющая просматривать классы, декомпилировать и анализировать программы и компоненты .NET. Она позволяет выполнять просмотр и поиск CIL инструкций, ресурсов и XML документации, хранимой в сборке .NET. Но это не единственная утилита, которая нам понадобится для реверса .NET приложений. Постараюсь представить остальные утилиты по мере публикации новых постов.
Что Вы изучите в данной статье?!
Эта статья покажет Вам как обращаться с Reflector для реверса лабораторного приложения Crack-ME. В данном случае очень простой пример и не претендует на то, чтобы объяснить реальную защиту программного обеспечения.
Немного практики
Наша программа Crack-ME представляет собой простое однооконное приложение .NET, запрашивающее пользователя ввести пароль. Написано оно для показа основ реверс инжиниринга .NET. Обычно исследователи начинают с анализа объекта и его поведения. Давайте взглянем с чем имеем дело!
Далее показано окно с ошибкой при вводе неправильного пароля:
Давайте посмотрим, почему скомпилированный код выдает сообщение “Invalid password”. Открываем Reflector, теперь нам надо настроить Reflector через выпадающий список на главной панели путем выбора любого языка программирования, с которым Вы знакомы. В качестве примера я выберу Visual Basic.
Рисунок 2. Выбор основного языка
Загрузите Crack-ME (File > Open menu) и поищите что-нибудь, что сможет привлечь наше внимание. Crack-ME проанализирован и помещен в древовидную структуру. Мы продолжим исследовать узлы, которые нас интересуют:
Рисунок 3. Наш Crack-ME загружен
Вы можете развернуть объект путем нажатия значка “+”:
Продолжите раскрывать дерево и увидите, что находится внутри Crack-ME:
Теперь мы можем увидить, что наш Crack-ME состоит из References, Code и Resources.
- Code: данная часть содержит интересные вещи и всё, что нам понадобится, находится внутри InfoSecInstitute_dotNET_Reversing (являющийся Namespace).
- References: схоже с секциями “imports”, “includes” для PE файлов.
- Resources: на данный момент нас мало волнуют, но похожи на остальные ресурсы, что используются в других программах Windows.
Разворачивая исходный узел мы увидим следующее дерево:
Reflector определяет единственную форму Form1 со всеми переменными, процедурами, функциями и элементами графического интерфейса пользователя. Также, как сказано выше, он выводит исходные имена, что облегчает нам работу и позволяет предугадать, что каждый элемент предположительно делает. Для примера, функция btn_Chk_Click(Object, EventArgs), по-видимому, должна срабатывать, когда кнопка “btn_Chk” нажата, а btn_About_Click(Object, EventArgs) вызывается, когда кнопка “btn_About” нажата…
Поскольку это приложение для лабораторной работы, оно не содержит множества форм и функций, дающих понимание о работе программы. В данном случае нас может заинтересовать функция btn_Chk_Click (). Мы хотим узнать, что наша программа Crack-ME действительно делает при нажатии btn_Chk. Всё это может быть оттранслировано в выбранный ранее язык программирования (см. Рисунок 2).
Чтобы увидеть текущий исходный код, дважды щелкните по имени функции. Reflector показывает нам декомпилированный исходный код на выбранном языке, в нашем случае мы получим:
Никакой магии, мы действительно видим, что данная функция делает. Любой человек с разным уровнем подготовки в произвольном языке программирования сможет понять, что эта функция\процедура проверяет, если набран пароль “p@55w0rd!”. После нажатия мы получим:
Все очень легко! Развивая тему, мы сможем исследовать больше возможностей, например, создание патча для Crack-ME, позволяющего принимать любые пароли на ввод (рассмотрим в следующих постах).
В данном посте сознательно избегается подача сложных объяснений относительно приложений .NET, дабы не оттолкнуть потенциального исследователя. Я попытался показать Вам наглядный способ как справиться с очень простой защитой, надеюсь это научило Вас нескольким вещам, которые имеют значение при реверсировании .NET программ. В следующий раз, я попытаюсь показать более глубокие знания о том, как действительно работают .NET программы.
Ссылки
Спасибо за проделанную работу.