Трудности перевода. Учимся ломать .NET-сборкиm0r0 m0r0@inbox.ru Первая версия .NET впервые была представлена Microsoft в 2002 году. С тех пор утекло много воды, и на подходе уже 4-я версия фреймворка. Число софтин растет быстрее, чем размножаются хомячки, а ты боишься к ним даже приблизиться. Пора разобраться, что к чему, и восстановить справедливость. Загадочный фреймворкГоворя о .NET, следует признать, что в Microsoft вовсе не дураки работают, и иногда в недрах корпорации рождаются поистине интересные решения. Я не буду вдаваться в подробности относительно противостояния .NET и Java, проводить детальные сравнения и меряться всем, чем можно. Факт налицо - .NET развивается и уже включен в официальную поставку новых ОС (Vista/2008/7) от Microsoft.
Microsoft предоставляет не только среды разработки и исполнения, но и богатые библиотеки классов, позволяющие реализовать практически любой функционал пользовательского приложения. Совместно с Hewlett-Packard и Intel были стандартизованы CLI, C# и C++/CLI (ECMA-335, ECMA-334, ECMA-372, соответственно). Это открыло дорогу для Novell с их проектом Mono, реализующим данные стандарты и позволяющим запускать приложения .NET на других, безоконных операционных системах. Microsoft делает ставку на .NET как на лидирующую среду для создания приложения под ОС Windows, а, значит, и нам следует отнестись к ней с уважением и научиться справляться с потенциальными трудностями. Одним из несомненных преимуществ .NET является поддержка нескольких языков программирования. В первую очередь, это заостренный C (C#) и, конечно же, Basic в формате VB.NET. В рамках одного проекта можно комбинировать сборки, разработанные на различных языках. Среда сама обеспечит корректность исполнения конечного решения. Каким же образом это достигается? Реализованы концепции песочницы, CLI и JIT. Ольга, прощай!На самом деле, все достаточно просто (здесь опять можно вспомнить о Java, но мы этого делать не будем). Независимость от языка программирования и от конечной платформы достигается за счет компиляции приложения не в нативный, а в промежуточный байт-код. В терминологии Microsoft он носит название MSIL (Microsoft Intermediate Language), а после стандартизации все называют его просто CIL (Common Intermediate Language) или, еще круче, - IL. CLR (Common Language Runtime) отвечает за компиляцию байт-кода в нативный, обеспечивая его выполнение на целевой системе. При этом используется подход JIT (Just-In-Time), согласно которому компиляция производится оперативно по мере необходимости. Посмотрим, что представляет собой .NET-приложение глазами Ольги. Для этого создадим простенькое приложение Windows.Forms, содержащее одну кнопку и вызывающее MessageBox с сообщением «Hello, World» при ее нажатии. Не правда ли, очень оригинально? Создал? Тогда грузи сборку в Ольгу и наслаждайся результатом. Приложение загрузилось, а в окне дизассемблера – пусто. Обидно до соплей. Точку входа не поймали и чего дальше делать – непонятно. Жмем паузу и переходим к отладке программы. Ставим условную точку останова на TranslateMessage для перехвата нажатий левой кнопки мыши (WM_LBUTTONUP: MSG == 202). Кликаем и погружаемся в транс – казалось бы, элементарный вызов MessageBox заставляет Ольгу совершать кучу действий и не дает никакого понимания логики работы программы. Наверное, не все так уж и ужасно, но сей факт заставляет задуматься о целесообразности использования классического отладчика для работы с .NET-сборками. Итак, продолжим поиск новых друзей-отладчиков. Братья-близнецы: ILASM и ILDASMДинамическое компилирование делает процедуру отладки практически невозможной для сложных приложений. Но зачем пытаться отлаживать нативный код, когда можно подняться выше и работать на уровне IL? Первая проблема – как этот самый код получить, имея под рукой только бинарник. Для этого сама Microsoft предлагает инструмент под названием ILDASM. Название выбрано неспроста (надеюсь, ты уже догадался, что к чему). ILDASM устанавливается автоматически при установке Visual Studio, так что, поверь, он у тебя уже есть. Запускаем консоль студии (или просто подгружаем переменные среды с помощью скрипта vsvars32.bat) и запускаем ildasm. Открываем наше приветливое приложение и утыкаемся в нечто похожее на диаграмму классов. Развертывая метод любого из классов, получаем его IL-код. С кодом мы разберемся немного позже, а сейчас проведем эксперимент. Выбираем меню File -> Dump и сохраняем код всего приложения в текстовый файл Sample1.il. После этого запускаем ILASM (угадай для чего): ilasm Sample1.il. Получаем файл с непонятным расширением exe, запускаем и видим до боли знакомую форму. Перед нами простейший алгоритм патчинга приложений:
Учим матчастьИтак, IL. Неплохо хотя бы немного представлять, как писать на IL, скажешь ты, и будешь абсолютно прав. Чтобы разбираться в логике работы, а тем более, иметь возможность ее изменять, нам нужно изучить азы языка. Этим сейчас и займемся. Для начала определимся с инструментом. «Блокнот» – великая вещь, но хотелось бы иметь под рукой среду, обеспечивающую комфортную работу на всем цикле разработки: подсветку синтаксиса, автоматическую компиляцию и линковку, отладку. Напрашивается Visual Studio, ан нет, Microsoft не реализует поддержку IL. Возможно, есть какие-нибудь плагины, но мне их найти не удалось. По большому счету, можно программировать прямо в IL, поэтому будем искать среду программирования и отладки. Проектов таких два: DILE и ILIDE#. Первый – редактор, позволяющий отлаживать исполняемый код. Самое смешное, что как раз редактировать он и не умеет (классно назвали). Второй проект представляет собой полноценную среду программирования. Однако попытка импортировать код, полученный из ILDASM, приводит к невозможности компиляции (классная IDE). В конечном итоге выбор пал на среду SharpDevelop, в которой можно создавать проекты с поддержкой IL. Такую же поддержку предоставляет MonoDevelop, однако у меня он работать отказался, а то, что не работает, идет в топку. Недостатки SharpDevelop: отсутствует поддержка автодополнения и невозможна работа с диаграммами классов. Напишем небольшое консольное приложение, на примере которого рассмотрим основные языковые конструкции. Программа будет складывать два числа и выводить их сумму. Код MainClass.il Вся программа состоит из набора директив и команд. Директивы начинаются с символа точки и реализуют декларативные функции:
Описание всех директив ты найдешь в официальном документе Common Language Infrastructure (CLI), Partition II: Metadata Definition and Semantics на нашем диске. После объявления переменных (конструкция init() предназначена для инициализации переменных значениями по умолчанию) производится вызов функции Write. Для этого в стек кладется строка с сообщением, которое будет выведено на экран. Далее используется команда call, вызывающая статический метод Write из класса System.Console сборки mscorlib. Обрати внимание: при вызове указывается прототип функции, – чтобы компилятор мог определить, какую из перегрузок использовать. При вызове нестатических методов перед параметрами в стек нужно положить ссылку на экземпляр класса, а при вызове после call нужно поставить ключевое слово instance. Для вызова виртуальных методов применяй callvirt. Далее вызывается функция ReadLine, и строка парсится в число, которое сохраняется в первой переменной. Перед вызовом функции вывода на экран результатов переменные в стеке подвергаются преобразованию в ссылочные типы (обертки) с помощью команды box32. Остальное должно быть ясно без пояснений. Еще один важный момент для реверсера – это отлов ветвлений. В IL ветвления создаются с использованием семейства команд b… (от branch). За детализацией беги на MSDN. Попробуй сломай меняУже чувствуешь себя крутым? Тогда пора что-нибудь да поломать. Идем на любой сайт с челенджами и скачиваем задание из раздела cracking в стиле .NET. Я тебе покажу на одном из них, как все просто и красиво. Из этических соображений раскрывать ресурс я не буду, но если интересно, можешь выяснить и сам, посмотрев внимательно внутрь бинарника. Приложение app14 содержит на форме текстбокс и кнопку. В текстбокс нужно ввести правильную фразу, тогда обработчик кнопки выведет кодовое слово, которое и является решением. При неправильной фразе выводится какая-то лабуда. Забыл сказать: для редактирования IL очень удобно использовать утилу Red Gate’s .NET Reflector с установленным плагином Reflexil. Утила хороша тем, что на основе IL, восстанавливает код на некоторых языках высокого уровня, включая шарп. Реверсинг превращается в увлекательное занятие анализа исходных кодов. Итак, загружаем в Reflector нашего подопытного и смотрим саммари по классам и методам. Видим два класса – Encrypt и, собственно, класс формы под названием goes. Можно вкуривать в реализацию Encrypt – она несложная, но я предпочел посмотреть, что же происходит при нажатии заветной кнопки. Обработчик ищется элементарно, по прототипу функции. Она должна принимать два параметра: первый типа object и второй типа EventArgs. Таких функций всего две и по названию очевидно, что нам интересна функция vla_Click. Выбираем по правой кнопке меню Disassemble и видим чистый c#! Код обработчика нажатия кнопки в CrackMe Выполняется куча каких-то проверок и сравнений, которым надо удовлетворить. Мое же внимание привлекла последняя инструкция Ну да, при правильном раскладе в текстбокс попадает значение, представляющее собой расшифрованную строку “fm`{f}kpwrn”. Собственно алгоритм расшифровывания реализуется функцией ParseandDecrypt из класса Encrypt. Дальше можно либо разбираться в алгоритме (зачем оно нам надо?), либо скопировать код функции и скомпилировать собственный проект, либо просто сделать один верный прыжок. Запускаем reflexil из меню Tools и видим IL-код. Нужно идентифицировать место последнего if и прыгнуть с него на выявленную команду. В IL нет операторов ветвления, поэтому они реализуются в виде последовательности простых команд сравнения и прыжков. Здесь все как в ассемблере. Итак, ищем последний переход перед выводом MessageBox с сообщением nope. Локализация перехода
Меняем brfalse на brtrue, чтобы прыжок совершить, ну а чтобы избежать проверки else if, меняем адрес IL_0110 на IL_013d. Вывод решения в текстбоксе формы В левой панели кликаем на название бинарника, затем нажимаем кнопку Save as и сохраняем его под именем app14_patched. Запускаем, вводим любую фразу и в текстбоксе получаем кодовое слово ihatethereg!!! Нам не страшен серый волкНеужели все действительно так просто? На самом деле и да, и нет. Если мы (и еще туева хуча людей) знаем о возможности восстановления кода, об этом должны были позаботиться в Microsoft. Да и другие конторы не прочь предоставить решения по защите от анализа и реверса. В целом так и есть, только решения эти далеко не всегда обеспечивают реальную защиту. Начнем с обфускации. В поставку Visual Studio входит утила под названием Dotfuscator. Ее задача – усложнить работу реверсера за счет изменения имен классов, методов и переменных, а также шифрования строк. Она так и делает, но анализ кода от этого не усложняется. Ну да, теперь методы называются типа a, b… Тот же обработчик мы легко идентифицируем по определению делегата, а шифрованные строки все равно будут расшифрованы. Так что, ни хрена это не защита. Второй метод связан со статическим шифрованием всей сборки. В бинарник вставляется нативный код, который расшифровывает сборку при загрузке приложения и защищает ее от статического анализа. Решений таких – ну просто очень много. Одно из самых известных – CodeVeil от XHEO. Реально крутая тулза! Попробуем зашифровать сборку и открыть ее в Reflector. Оба-на, ошибка: «Module … does not contain CLI header». Reflector, ILDASM и другие утилы даже не идентифицируют ее как .NET-сборку. Между прочим, в Professional-версии утила стоит 1200 зеленых рублей, а лозунг ее – «Don’t just confuse hackers. Stop them». Впечатляет и вдохновляет! Ломается такая защита в два счета. Давай подумаем. Если сборка расшифровывается в памяти перед передачей управления CLR, значит, нужно просто снять дамп памяти. Загружаем «защищенную» по самые никуда сборку в память и цепляемся к ней WinHex’ом (Tools -> Open Ram; далее выбираем процесс и модуль в нем). Копируем в файл содержимое памяти: CTRL+A, затем Edit -> Copy All -> Into New File и сохраняем под именем app14_unveiled.exe. Пытаемся запустить и ловим ошибку инициализации. Видимо, где-то поползли секции. Открываем в Reflector и, о чудо, код как на ладони; правда, не работает Reflexil, вылетая с исключением. Запускаем ILDASM, делаем дамп IL и компилируем… – получаем новую полностью работоспособную сборку со снятой защитой. Во как! Справедливости ради стоит отметить, что в текущей версии CodeVeil 3.2, по заявлению разработчиков, реализована динамическая защита во время исполнения, что сильно затрудняет задачу. Необходимо детально исследовать нативный код шифровщика. Мне кажется, что создание unpacker’а – лишь вопрос времени. Пока никто эту задачу не решил, так что дерзай, возможно, получится у тебя. Таким образом, программисту приходится полагаться только на самого себя. Можно шифровать сборки, динамически расшифровывать их во время исполнения и подгружать с помощью Application.Load. В общем, все как в старом добром Assembler’е. А можно воспользоваться виртуальными контейнерами типа ThinApp или Xenocode. Ну а лучше всего и вовсе реализовывать критические участки с помощью неуправляемого кода. Из России с любовьюПомни, что только практика способствует твоему развитию. Как видишь, все не так уж сложно, но, заходя на сайт глобального рейтинга www.wechall.net, я с грустью обнаруживаю Россию на 25-м месте. В рейтинге зарегистрировано всего 5 российских хакеров-участников! Давай поддержим нашу страну: регистрируйся на сайтах-челленджах и бросай баллы в копилку Родины. Все описанные действия совершены под музыку Джо Кокера. Слушай блюз и будь счастлив! DVDНа диске ты найдешь все упомянутые в статье тулзы, официальное описание CIL от Microsoft, а также препарируемый CrackMe и его пропатченную версию. WWWДля описания команд IL бегом на MSDN: http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes_fields.aspx Интересуют средства защиты .NET-приложений? Тогда тебе сюда:
INFOНаилучшую защиту своих сборок от анализа ты получишь в случае использования Xenocode PostBuild (http://www.xenocode.com/products/postbuild-for-net), позволяющего компилировать приложение в нативный код со статическим включением сторонних сборок. |