Главная » Статьи » Крэкинг |
ВведениеТехнология .NET готовится отпраздновать свой юбилей. За это время было написано множество коммерческих программ (и малвари в том числе), но как только дело доходит до того, чтобы заглянуть внутрь p-кода на предмет "отломать" пару ненужных байт, выясняется, что достойных хакерских инструментов нет, и судя по всему, не появится, поэтому приходится использовать то, что есть, хакерствуя в весьма стесненных обстоятельствах, словно шахтеры в забое! Отказ от ответственностиСтандартное отречение: вся информация, предоставленная ниже, преследует исключительно благие цели (анализ вредоносного программного обеспечения, например) и мыщъх не несет никакой ответственности (ни явной, ни предполагаемой) за любой возможный ущерб или потерянную выгоду от ее использования. Что нам понадобится
Рисунок 1. Отсюда можно бесплатно скачать последнюю версию Microsoft Visual Studio.
Рисунок 2. Единственная (на данный момент) реализация платформы .NET вне Microsoft.
Пишем свой первый crackmeГлубоководное погружение в байт-код .NET-программ начинается! Пристегиваем ремни, проверяя - все ли на месте и... запускаем Microsoft Visual Studio или... FAR + Colorer. Работать в IDE, конечно, удобнее (особенно начинающим, когда среда автоматически отображает список методов для каждого класса - не надо постоянно лазить в справочники), однако это порочный путь, абстрагирующий нас от машины и мы - хакеры старого поколения - предпочитаем консольные текстовые редакторы с подсветкой синтаксиса или... даже без таковой. Текст нашего первого crackme, написанного на C#, (который мы будем ломать всеми силами) в простейшем случае выглядит так (см. листинг 1). Это консольная программа, вручную набранная в текстовом редакторе. IDE пихает сюда много лишнего, затрудняющего понимание, однако на скомпилированном коде это никак не отражается. class nezumi // имя класса - произвольно и может быть любым { static void Main() { string s; // объявляем переменную s типа строка // запрашиваем у пользователя пароль System.Console.Write("enter password:"); s = System.Console.ReadLine(); if (s == "nezumi") // сравниваем введенный пароль с эталонным System.Console.WriteLine("hello, master!"); else System.Console.WriteLine("fuck off, hacker!"); } } Листинг 1. Исходный текст программы n2k_crackme_01h.cs. В среде IDE сборка .NET-программ осуществляется клавишей <F6>, а из командой строки (при этом csc.exe должен находится в путях, чего не происходит при установке по умолчанию): $csc.exe n2k_crackme_01h.cs Листинг 2. Компиляция C#-программы из командной строки. В Mono вместо csc.exe используется файл mcs/mcs.bat, но независимо от способа сборки, мы получаем n2k_crackme_01h.exe, готовый к непосредственному запуску, после которого нас спросят пароль и если мы введем его неверно - пошлют на хрен. .NET-программы на Си++Компилятор Cи++, входящий в состав Microsoft Visual Studio 2008, умеет транслировать программы не только в машинный, но и в байт-код, позволяя нам использовать все прелести .NET платформы из привычных плюсов (трансляция "чистых" Си программ в байт-код все еще не поддерживается). #include <string.h> using namespace System; // использовать классы основной системной библиотеки void main() { char buf[0x666]; printf("enter password:"); gets(buf); if (strcmp(buf, "nezumi")) printf("fuck off, hacker!\n"); else printf("hello, master!\n"); } Листинг 3. Пример простейшей Cи++ программы, написанной с учетом специфики .NET. Компиляция осуществляется путем указания ключа /CLR в командной строке компилятора CL.EXE (рядом с которым можно указать ключ /Ox для форсирования максимальной оптимизации): $cl.exe /clr hello-clr.cpp Листинг 4. Трансляция Си++ программы в .NET сборку из командной строки. Полученный файл hello-clr.exe представляет собой смесь управляемого байт-кода с большим количеством вызовов неуправляемого машинного кода из различных библиотек (плюс тянет за собой RTL, написанную на байт-коде), что позволяет сочетать достаточно высокую скорость выполнения с управляемостью и безопасностью, однако "чистые" C# сборки все-таки выигрывают как в размерах, так и в скорости. Чуть позже мы убедимся в этом самостоятельно, а пока же поверим мыщъх'у на слово. Первые экспериментыЗагружаем подопытный n2k_crackme_01h.exe в HIEW, дважды давим на <ENTER> для перевода редактора в дизассемблерный режим, жмем <F5> и попадаем в точку входа, где красуется команда jmp _CorExeMain ; mscoree.dll (см. рис. 3). Это и есть весь машинный код, который только есть (простите за каламбур).
Рисунок 3. Так выглядит "честная" .NET сборка в hex-редакторе. Дальнейшее расследование показывает, что jmp находится в самом конце секции .text, за которой располагаются секции ресурсов и перемещаемых элементов, а выше - байт-код виртуальной машины, просматривая который в hex-mode, мы обнаружим все текстовые строки (и пароль в том числе!) записанные в формате Unicode, причем перед строкой находится байт, определяющий длину строки. Узнав оригинальный пароль, мы, конечно, без труда смогли бы "взломать" crackme, однако редкая программа хранит пароли открытым текстом, да и неинтересно это. Лучше загрузим в HIEW другой исполняемый файл, написанный на Си++ - hello-clr.exe. Он также вызывает CorExeMain (см. рис. 4), но в отличие от "чистой" .NET сборки, написанной на C#, здесь присутствует большое количество "переходников" к машинному коду - функциям strcmp, gets, printf, напрямую вызываемых из библиотеки MSVCR90.DLL, за что отвечает механизм P/Invoke, позволяющий создавать "гибридные" программы, часть из которых транслируется в "живой" машинный код, а часть - в интерпретируемый байт-код, что серьезно затрудняет взлом, однако никакой P/Invoke нас не остановит! Но это будет потом, а сейчас мы покурим и загрузим .NET-сборку в нормальный дизассемблер.
Рисунок 4. Внешний вид .NET-сборки, полученной путем трансляции Си++ программы. Техника дизассемблированияЗагружаем n2k_crackme_01h.exe в IDA Pro и видим (см. рис. 5), что ничего ужасного в CIL-коде нет. Напоминает байт-код виртуальной Java-машины. IDA Pro не только создает перекрестные ссылки, но даже показывает опкоды и расставляет комментарии к командам, чтобы не было нужды каждый раз заглядывать в справочник (ECMA-335/Partition III/CIL Instruction Set).
Рисунок 5. Консольная версия IDA Pro дизассемблирует .NET сборку. Впрочем, чтобы заставить дизассемблер быть более дружелюбным к хакеру, необходимо выполнить следующие действия: в меню "Options" выбрать пункт "Text representation", там указать количество байт для отображения опкода ("Number of opcode bytes") - шести хватит вполне, а в разделе "Line prefixes" сбросить все галочки, кроме "Function offsets", после чего вновь возвратиться в меню "Options", зайти в "Comments" и взвести "Display auto comments" для автоматического отображения комментариев ко всем инструкциям (впрочем, при наличии некоторого опыта работы с CIL-кодом этого можно и не делать). Поклонники графической версии IDA Pro могут задействовать графы (см. рис. 6), упрощающие (на самом деле - усложняющие) понимание структуры программы, но тут уж как говорится - на вкус и цвет все фломастеры разные. Лично мыщъх никогда не пользовался графами и другим не советует.
Рисунок 6. Графическая версия IDA Pro дизассемблирует .NET сборку. А теперь посмотрим, на что способен штатный дизассемблер от Microsoft, по умолчанию расположенный в каталоге C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ и зовущийся ilasm.exe. Загружаем в него n2k_crackme_01h.exe и... хм, в общем-то, довольно неплохая картина получилась (см. рис. 7), а навигация по классам выполнена даже лучше, чем в IDA Pro, причем намного лучше (примечание: чтобы ildasm.exe отображал опкоды инструкций, необходимо в меню View взвести галочку "Show bytes")
Рисунок 7. Штатный дизассемблер ildasm.exe за работой. Теперь самое время исследовать дизассемблерный текст нашей программы (см. листинг 5). Ну, тут все ясно без травы и даже без комментариев (особенно тем, кто знаком с виртуальными машинами со стековой организацией). Если опустить детали, то получается следующее: программа вызывает System.Console::Write("enter password"), после чего считывает строку в переменную V_0 и вызывает функцию System.String::Equality(V_0, "nezumi") для провеки строк на соответствие и в случае их несовпадения на экран выводится строка "fuck off, hacker!", управление на которую передается машинной командой brtrue.s IL_0031 (с опкодом 2Dh 0Dh). .method private hidebysig static void Main(string[] args) cil managed Листинг 5. Результат работы штатного дизассемблера ildasm.exe. Логично - чтобы заставить программу воспринимать все пароли как правильные, двухбайтовый условный переход brtrue.s IL_0031 необходимо заменить на пару однобайтовых команд nop (опкод - 00h, а вовсе не 90h как на x86). Или же... заменить brtrue.s IL_0031 на brFALSE.s IL_0031, тогда любой неправильный пароль будет восприниматься как правильный и, соответственно, наоборот. Открыв ECMA-335, мы узнаем, что инструкция brfalse.s имеет опкод 2Ch - и это все, что нам необходимо знать для взлома программы. Дизассемблирование Cи++ сборкиА теперь загрузим в ildasm.exe бинарную сборку, выданную Си++ компилятором с ключом /CLR, и посмотрим, чем она отличается от "нормальной" .NET-сборки. Если забыть о том, что Си++ сборка тащит за собой весьма тяжеловесный RTL (в котором для нас нет ровным счетом ничего интересного) и сосредоточиться исключительно на функции main, то можно обнаружить, что все не так уж и страшно (см. листинг 6). Длинные имена методов класса (сокращенные для экономии бумаги), конечно, на первых порах вызывают шевеление волос на голове, но потом к ним быстро привыкаешь, автоматически "вычленяя" привычные "позывные" типа printf, gets, etc, однако структура кода далека от совершенства и на его анализ уходит намного больше времени, что, кстати говоря, представляет собой не такой уж "тупой" защитный прием от начинающих хакеров. Просто компилируем свои Си++ программы с ключом /CLR и хрен кто их взломает. .method assembly static int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) Листинг 6. Дизассемблирование .NET-сборки, полученной путем трансляции Си++ программы. Техника патчаТак, где там наш HIEW?! Готов ко взлому или... еще не готов? Как нам определить местоположение байта, который мы собрались захачить? Ведь виртуальные адреса в контексте CIL-кода вообще неуместны! Воспользуемся дедовским способом и поищем последовательность байт (сигнатуру), обитающую в окрестностях целевой команды. В данном случае это может быть 2Dh 0Dh 72h 2F 00h 00h 70h 28h (об обратном порядке байт не забываем, да? ildasm автоматически "нормализует" аргументы команд, IDA Pro - нет, показывая их такими, какие они есть - наименее значимый байт располагается по младшему адресу). Короче, вбиваем заданную последовательность в поиск и убедившись в том, что данное вхождение - единственное, переводим HIEW в режим записи по <F3>, заменяем 2Dh на 2Ch (см. рис. 8), сохраняем изменения в файле по <F9> и выходим.
Рисунок 8. Поиск сигнатуры в HIEW'е и bit-hack (исправление "неправильного" байта на "правильный"). Запускаем хакнутый файл и... о чудо!!! Он работает!!! Теперь любой, наугад взятый пароль - например, "123456" воспринимается как правильный (см. рис. 9). Конечно, если программа снабжена цифровым сертификатом подлинности или использует механизм контроля целостности собственного кода, этот номер уже не пройдет, но... ведь надо же с чего-то начинать ломать!
Рисунок 9. Хакнутая программа любой пароль воспринимает как правильный. Техника отладкиСпору нет, дизассемблер - весьма популярный инструмент для исследования программ. Популярный, но не единственный и во многих случаях отладчик оказывается намного более предпочтительным. Вместо того, чтобы гадать - какое значение имеет переменная в данной точке (в дизассемблере), гораздо практичнее заглянуть в нее отладчиком. И вот тут выясняется довольно любопытная вещь. На уровне исходных текстов Microsoft Visual Studio справляется с отладкой на ура, но готовые бинарные сборки, увы, не поддерживает и для работы с ними необходимо использовать ICorDebug-интерфейс, встроенный в ядро платформы .NET и реализующий базовые отладочные возможности (установка точек останова, пошаговое исполнение и т.д.), предоставляя их в виде набора API-функций. Все .NET-отладчики, которые только видел мыщъх, являются достаточно тонкими обертками вокруг ICorDebug Interface, наследуя его худшие черты, а именно - невозможность отлаживать программы без символьной (отладочной) информации, автоматически удаляемой из всех Release-проектов. Выходит, что мы можем отлаживать только свои собственные программы?! Нехорошо!!! Расследование показало, что штатному .NET отладчику (зовущемуся mdbg.exe, где "m" - сокращение от managed, т.е. управляемый код) для нормальной работы вполне достаточно pdb-файла, вот только как этот файл получить? IDA Pro может подготовить map-файл, но готовых конвертеров map2pdb в Сети что-то не наблюдается, а писать самому - лениво и непродуктивно. К счастью, существует весьма простой и элегантный путь. Дизассемблируем бинарную сборку штатной утилитой ildasm.exe, после чего ассемблируем ее заново штатным же транслятором ilasm.exe, не забыв указать "волшебный" ключик /pdb для генерации отладочной информации. Поскольку ildasm.exe поддерживает ресурсы и корректно их дампит, то предложенный способ работает в подавляющем большинстве случаев, что мы сейчас и продемонстрируем. Запускаем ildasm.exe с настройками по умолчанию, загружаем в него n2k_crackme_01h.exe (есно, оригинальный, а не хакнутый), в меню File находим пункт Dump (или нажимаем <CTRL-D>), в появившемся окне "Dump options" (см. рис. 10) оставляем все галочки в состоянии по умолчанию. Главное, чтобы была взведена галочка "Dump IL Code", после чего нажимаем <OK> и вводим имя файла для дампа - например, "cracked".
Рисунок 10. Дамп двоичной .NET-сборки в ассемблерный файл штатным дизассемблером ildasm.exe. По окончании дизассемблирования на диске образуются два файла - cracked.il с ассемблерным текстом программы и cracked.res - с ресурсами. Cracked.il представляет собой обыкновенный текстовой файл, который можно править в любом текстовом редакторе, при необходимости заменяя "brtrue.s IL_0031" на "brfalse.s IL_0031", но сейчас нас в первую очередь интересует не патч, а отладка. Берем штатный ассемблер и собираем файл следующим образом: $ilasm.exe cracked.il /pdb Листинг 7. Ассемблирование сдампленного файла штатным ассемблером. На диске образуются файлы cracked.exe и cracked.pdb, готовые к загрузке в отладчик (что примечательно - отладочная информация непосредственно в сам исполняемый файл не записывается, что очень и очень хорошо, иначе нам пришлось бы потом убирать ее оттуда или мириться с увеличением размера поломанного exe, что вряд ли входит в наши планы). Ок, набираем в командной строке "$mdbg.exe cracked.exe" и... оказываемся в консольном окне отладчика, автоматически останавливающегося на первой команде функции Main, передавая нам бразды правления. А что такого крутого и хорошего мы можем сделать?! Начнем с просмотра окрестностей, за что отвечает команда "show" или ее более короткий алиас "sh", результат работы которого выглядит так (см. листинг 8): run cracked.exe # запущена бинарная сборка cracked.exe Листинг 8. Результат работы команды "sh", показывающей IL-код. Остальные команды отладчика можно найти во встроенной справке (вызываемой командой help) или же в одноименной врезке. Сейчас нас интересует не это. Нас интересует техника работы с отладчиком. Ну, техника как техника. Никаких принципиальных отличий от x86 не появилось. Просматривая ассемблерный файл cracked.il, находим команду "IL_0020: stloc.1", стягивающую со стека результат сравнения двух строк, возвращенный функцией System.String::op_Equality, за которой следует команда "IL_0021: ldloc.1", загружающая полученное значение в локальную переменную V_1, в зависимости от содержимого которой команда "IL_0022: brtrue.s IL_0031" прыгает на метку IL_0031 (неверный пароль) или... не прыгает. Все ясно! Нам нужно установить точку останова на команде "IL_0020: stloc.1", расположенной в 55-й строке файла cracked.il, ну а дальше мы уже сориентируемся (см. листинг 9). $D:\KPNC\C#>mdbg cracked.exe # грузим бинарную сборку в отладчик Листинг 9. Сеанс работы с отладчиком mdbg.exe (команды, вводимые хакером, выделены полужирным шрифтом). Ниже, для наглядности тот же самый сеанс работы с отладчиком продемонстрирован в графическом виде (см. рис. 11):
Рисунок 11. Сеанс работы с отладчиком mdbg.exe "как он есть". Если кому-то религия запрещает использовать консоль, что ж - к его услугам Dotnet IL Editor - бесплатный IL-отладчик с GUI-интерфейсом (см. рис. 12), однако mdbg.exe мыщъх'у как-то больше по душе, да к тому же под него расширения всякие можно писать.
Рисунок 12. Dotnet IL Editor - IL-отладчик с GUI-интерфейсом. Впрочем, выбор отладчика не принципиален. Важна сама суть - техника исследования .NET-программ, которую мы только что и продемонстрировали. ЗаключениеРазумеется, в рамках одной-единственной статьи просто невозможно охватить все аспекты взлома .NET-программ. В частности, совершенно нетронутой осталась тема упаковщиков бинарных сборок и протекторов, распаковывать которые приходится руками, но это по любому тема отдельного большого разговора. А пока же имеет смысл потренироваться на простых несильно защищенных коммерческих программах (которые можно найти в Сети), малвари (взятой оттуда же) и crackme, залежи которых находятся на сайте www.crackmes.de (см. рис. 13) и где даже есть специальный раздел, посвященный исключительно платформе .NET. Мыщъх надеется, что данная статья обеспечит хороший старт, ну а остальное - дело времени, техники и бесчисленных экспериментов!
Рисунок 13. Коллекция .NET crackmes на одноименном сайте. Основные команды отладчика mdbg.exe
Терминологическое болото
| |
Просмотров: 19412 | Рейтинг: 2.5/2 |
Всего комментариев: 0 | |