Дизассемблирование
В этой статье мы с вами поговорим о дизассемблерах, о том, что они из себя представляют и рассмотрим принципы их работы.
Что такое дизассемблер?
Дизассемблер - транслятор, преобразующий машинный код, объектный файл или библиотечные модули в текст программы на языке ассемблера.
Дизассемблирование – это операция, обратная ассемблированию, т.е. восстановление текста программы на ассемблере из исполняемой программы в машинных кодах.
Сразу забегу немножко вперёд и скажу, что при повторном ассемблировании восстановленного текста, нет никакой гарантии получить тот же самый код, а значит, есть шанс, что программа откажется работать.
Любая попытка модификации дизассемблированного текста может окончательно развалить программу.
Дело в том, что ассемблер заменяет все метки на константы. При внесении изменений в программу необходимо скорректировать все ссылки на метки.
Виды дизассемблеров
По режиму работы дизассемблеры делятся на автоматические и интерактивные.
Хорошим примером автоматического дизассемблера является Sourcer.
Такие дизассемблеры генерируют готовый листинг, который можно затем править в текстовом редакторе.
Пример интерактивного дизассемблера - IDA.
Он позволяет изменять правила дизассемблирования и является весьма удобным инструментом для исследования программ.
Основная трудность при работе дизассемблера - отличить данные от машинного кода, поэтому на первых проходах автоматически или интерактивно собирается информация о границах процедур и функций, а на последнем проходе формируется итоговый листинг. Интерактивность позволяет улучшить этот процесс, так как, просматривая дамп дизассемблируемой области памяти, пользователь может сразу выделить строковые константы, дать содержательные имена известным точкам входа, прокомментировать разобранные им фрагменты программы.
По числу просмотров объектного кода дизассемблеры делятся на одно-, двух- и многопроходные.
Однопроходные, называемые также «дизассемблерами без меток», используются в основном в отладчиках и программных мониторах и разрабатываются, если необходимо быстро получить псевдоассемблерный текст.
Второе название отражает их существенную особенность - в псевдоассемблерном листинге отсутствуют метки в командах переходов и вызовов подпрограмм (стоят абсолютные шестнадцатеричные адреса). Поэтому после ассемблирования такого восстановленного текста мы получаем в общем случае абсолютную программу. За один проход при ограниченном объёме ОЗУ нельзя собрать всю информацию о метках, так как теряются «ссылки назад».
Метки, создаваемые дизассемблерами, обычно имеют вид Lxxxx , где первая буква английского слова Label(метка), a xxxx - адрес перехода или подпрограммы.
Метки данных имеют ту же структуру, но начинаются с буквы D . Такой вид меток наиболее удобен при разборе псевдолистинга. Программист с помощью редактора текстов может заменить эти метки на более информативные.
Наиболее распространены двухпроходные дизассемблеры, которые позволяют получать метки в листинге дизассемблирования, но, не решающие полностью проблемы разделения команд и данных. После них программист «вручную» подправляет сомнительные места.
Назначение дизассемблера
Чаще всего дизассемблер используют для анализа программы (или её части), исходный текст которой неизвестен - с целью модификации, копирования или взлома. Реже - для поиска ошибок/багов в программах и компиляторах, а также для анализа и оптимизации создаваемого компилятором машинного кода.
При работе с исполняемым кодом или байт-кодом, созданным на некоторых языках высокого уровня (например, java) имеется возможность восстановить не только текст на языке ассемблера, но даже и структуру классов программы, а если при компиляции исполняемого файла не была отключена отладочная информация, то и исходный текст программы. Для исключения таких возможностей используется обфускация.
Примеры работы
После того как вы установили необходимые опции и выбрали команду Gо, SOURCER загружает программу в память и определяет размеры сегментов. Во время первого прохода определяется большинство ссылок на подпрограммы и области данных. Далее SOURCER подразумевает, что области кода и данных являются кодом, пока не будет подтверждено обратное. В начале каждого следующего прохода SOURCER анализирует ссылки на код и данные для более точного определения областей кода и данных. На последнем проходе определяются необходимые директивы ассемблера, формат каждой строки и комментарии.
Внутренний имитатор следит за изменениями содержимого всех регистров и поддерживает отдельный стек для программы. Имитатор также следит за тем, чтобы в случае использования нескольких сегментов данных использовался верный сегмент. В задачу имитатора входит слежение за комментариями, обращениями к портам ввода/вывода и разрешение индексных вызовов и переходов. Имитатор повторяет действия программы. Не имитируются большинство инструкций, изменяющих содержимое памяти, хотя поддерживаются инструкции, считывающие данные из памяти. Специальная поддержка для регистра CS обеспечивает полную имитацию работы с RОМ и RAM.
В комплект поставки входят несколько утилит, среди которых утилита LST2ASM, позволяющая преобразовывать листинги в ассемблерный текст, и утилита PATCHER, позволяющая вносить изменения в двоичные файлы.
BIOS Pre-Processor позволяет (совместно с SOURCER) получить прокомментированный исходный текст базовой системы ввода/вывода (BIOS), установленной на компьютере. Зачем это нужно? Для изучения работы и организации BIOS, для внесения изменений и дополнений в BIOS, для исправления ошибок, а также в ряде других случаев. Создание листинга для BIOS может занять от 10 минут до 2 часов, но результат стоит того.
BIOSPre-Processor работает следующим образом. Сначала анализируется таблица векторов прерываний и находятся точки входа обработчиков прерываний. Потом определяются ключевые области данных и их размер. Затем определяется размер BIOS и вся необходимая информация заносится в файл BIOS.DEF, который и обрабатывается SOURCER.
Следует знать, что зачастую текст, выданный дизассемблером, либо вообще не поддается реассемблированию, либо получаемая программа ведет себя совсем не так, как хочется, но об этом мы уже говорили в начале.
Защита программ от дизассемблирования
Для защиты от дизассемблеров используются различные методы, большинство из которых базируется на использовании "принципа фон Неймана", который заключается в том, что программы и данные выглядят и хранятся одинаково, в результате чего программа может модифицировать саму себя. Использования таких методов чаще всего достаточно для защиты от дизассемблеров.
Ниже приведены некоторые приёмы, которые следует использовать для противодействия дизассемблерам.
1. Шифрование критичного кода программы и дешифрация его самой системой защиты перед передачей управления на него. Таким образом, дешифрация программы происходит не сразу, а частями и защита от дизассемблера оказывается распределённой во времени. При этом никогда не осуществляйте дешифрацию одной подпрограммой, т.к. её будет легко вычислить и отключить. Также следует затирать те участки программы, которые уже не понадобятся. Шифрование исполняемого кода программы с целью защиты от дизассемблера является наиболее простым средством как в смысле его реализации, так и в смысле снятия. Шифрование может быть использовано лишь как часть защиты от дизассемблера и поэтому необязательно должно быть сложным.
2. Скрытие команд передачи управления приводит к тому, что дизассемблер не может построить граф передачи управления.
2.1. Косвенная передача управления.
2.2. Модификация адреса перехода в коде программы (таблица 1).
2.3. Использование нестандартных способов передачи управления (jmp через ret, ret и call через jmp) (таблица 2).
3. Перекрывающийся код. Рассмотрим следующий пример:
Первая инструкция заносит "незначимое" значение в AX. Вторая делает переход на значение операнда команды MOV AX. '02EB' переводится как 'jmp$+2'. Этот переход перепрыгивает первый JMP и продолжает дальше по коду. Вот ещё один пример:
Этот код более полезен. Смоделируем трассировку, показывая HEX-дамп каждый шаг, чтобы прояснить ситуацию.
B8 05 FE EB FC 80 C4 3B mov ax,0FE05h ; ax=FE05h ^^ ^^ ^^ B8 05 FE EB FC 80 C4 3B jmp $-2 ; jmp into '05 FE' ^^ ^^ B8 05 FE EB FC 80 C4 3B add ax,0EBFEh ; 05 is 'add ax' ^^ ^^ ^^ B8 05 FE EB FC 80 C4 3B cld ; a dummy instruction ^^ B8 05 FE EB FC 80 C4 3B add ah,3Bh ; ax=2503h ^^ ^^ ^^
Инструкция ADD AH,03Bh здесь означает просто занесение 2503h в AX. Добавив 5 байт (вместо простого использования 'mov ax,2503h'), этот код очень хорошо затруднит работу дизассемблеру. Даже если инструкции дизассемблированы верно, значение AX будет неизвестно до тех пор, пока не будет помещено в AX. Вы можете скрывать значение от дизассемблера, используя 'ADD AX' или 'SUB AX' везде, где это только возможно. Если Вы хорошо проверите это, Вы сможете увидеть, что любое значение может быть занесено в AX. Два этих значения могут быть изменены на 0FEh в первой строке и 03Bh - в последней.
4. Использование возможностей установки префикса сегментного регистра перед некоторыми командами (pushf, pushfd, cld и др.). Дизассемблер не в состоянии правильно распознать программу (db 3Eh, 2Eh, 90h = ds: cs: nop).
5. Дизассемблер сбивается на нестандартном формате загружаемого модуля (например, перекрыть весь сегмент кода exe-файла DOS стеком).
Итоги
Приведённый список методов противодействия дизассемблерам является неполным, но вполне достаточным для противодействия попыткам получить дизассемблированный листинг программы.
Примеры программ дизассемблеров
Дизассемблеров на самом деле существует много, но мы кратко рассмотрим самые популярные.
IDA Pro
IDA Pro - это одновременно интерактивный дизассемблер и отладчик.
IDA позволяет превратить бинарный код программы в ассемблерный текст, который может быть применён для анализа работы программы.
Правда, стоит сказать, что встроенный ring-3 отладчик довольно примитивен.
Он работает через MS Debugging API (в NT) и через библиотеку ptrace (в UNIX), что делает его лёгкой добычей для защитных механизмов.
Для IDA существует огромное количество полезных плагинов, в том числе
поддерживающих разные скриптовые языки для написания сценариев в дополнение к встроенному IDC.
W32DASM
Отличный дизассемблер, удобный и понятный. Набор функций с точки зрения
профессионала довольно ограничен, да и вообще его пора отнести к инструментам из прошлого века, но нет... W32DASM выдаёт хороший листинг и для новичков является отличным вариантом понять и разобраться что к чему. К тому же, именно на него опираются в многочисленных мануалах для новичков.
PE Explorer
Программа для просмотра и редактирования PE-файлов, начиная с EXE, DLL и
ActiveX контролов и заканчивая скринсейверами(Screensavers), апплетами
панели управления CPL, SYS и бинарниками для платформы Windows Mobile.
По сути, это не одна утилита, а целый набор тулз для того, чтобы посмотреть изнутри, как работает программа или библиотека. Включает в себя просмотрщик заголовков, экспорт вызовов API-функций, редактор ресурсов и конечно же дизассемблер.
Заключение
Спасибо всем тем, кто дочитал.
Статья получилась длинной, но если вас заинтересовала эта тема, то более подробно про дизассемблирование вы можете прочитать в книге "Искусство дизассемблирования" Криса Касперского и Рокко Евы.
Книга посвящена вопросам и методам дизассемблирования, знание которых позволит эффективно защитить свои программы и создать более оптимизированные программные коды. Объяснены способы идентификации конструкций языков высокого уровня таких, как C/C++ и Pascal, показаны различные подходы к реконструкции алгоритмов. Приводится обзор популярных хакерских инструментов для Windows, UNIX и Linux - отладчиков, дизассемблеров, шестнадцатеричных редакторов, API- и RPC-шпионов, эмуляторов. Рассматривается исследование дампов памяти, защитных механизмов, вредоносного программного кода - вирусов и эксплоитов. Уделено внимание противодействию антиотладочным приемам.