expert

аспект мнения мыщъха по некоторым вещам

В дизассемблировании намного больше искусства, чем науки. И хотя машинный анализ делает огромные успехи (достаточно взглянуть на последние версии IDA Pro), «сердцем» любого декомпилятора по-прежнему является человек. Отличие констант от смещений до сих пор остается фундаментальной проблемой дизассемблирования (так называемая «проблема offset'а»). В двоичном виде их представление идентично, но интерпретируются они по-разному. Нераспознанные смещения в свою очередь не позволяют отличать данные от команд и это вторая фундаментальная проблема дизассемблирования. Наконец, дизассемблеры не справляются (точнее практически не справляются) с анализом виртуальных функций, зашифрованного и самомодифицирующегося кода, поэтому приходится запрягать дизассемблер и отладчик в однуупряжку — дизассемблер реконструирует «скелет» алгоритма, а отладчик расшифровывает запакованные фрагменты кода и уточняет значение регистров ЦП и ячеек памяти в данной точке.

Дизассемблирование решает по меньшей мере две взаимоисключающие задачи: реконструирует алгоритм или создает ассемблерный листинг, пригодный к последующей трансляции. Пакетные дизассемблеры (примером которых является легендарный SOURCER) нацелены именно на генерацию листинга, правда, качество его генерации оставляет желать лучшего и прежде чем исследуемая программа, наконец, заработает над ним придется изрядно попыхтеть, исправляя константы, ошибочно принятые за указатели или наоборот. К тому же значительная часть кода остается нераспознанной вообще и оформленной в виде массива данных «знаменитой» директивой DB. Самомодифицирующийся код обнаруживает третью фундаментальную проблему — одна и та же ассемблерная команда зачастую соответствует целому ансамблю машинных инструкций (например, INC EAX может быть представлена либо как 40h, либо как FFh С0h), выбор которых ложится на плечи транслятора. Следовательно, ассемблирование даже идеального дизассемблерного листинга далеко не всегда дает идентичный двоичный файл.

Интерактивные дизассемблеры (к числу которых относится IDA Pro) нацелены именно на анализ алгоритма, для чего они содержат мощную систему навигации, распознаватель библиотечных функций, трассировщик потока управления и многие другие незаменимые инструменты. Приложения и драйвера дизассемблируются очень легко. Фактически все упирается во время. Прошивки — другое дело. Сначала требуется опознать процессор. IDA Pro с этим уже не справляется и приходится писать собственные статистические анализаторы (ведь частота использования различных сочетаний байт у всех процессоров неодинакова), впрочем, опытный кодокопатель опознает тип процессора и самостоятельно. Проблема в другом — код прошивки представляет собой бессмысленный набор машинных команд, перемешенный с обращениями к портам. За что отвечает тот или иной порт — не понятно. Один и тот же набор байт может управлять двигателем, перемещать головку или выводить изображение на экран. Если описание чипсета отсутствует (а обычно все происходит именно так) прошивку приходится разбирать буквально по кусочкам. Это примерно тоже самое, что разгадывать кроссворд на китайском языке, не имея под рукой даже словаря. В этом лабиринте своеобразной нитью Ариниды становятся отнюдь не машинные команды, а… структуры данных. Например, при исследовании прошивки пишущего привода нам встретятся ATAPI-команды, заголовки секторов, константы, отвечающие за подсчет EDC/ECC кодов и т. д.

Дальнейшее развитие техники дизассемблирования немыслимо без финансовых вложений и притока свежих умов. К сожалению, мировая общественность относится к дизассемблированию очень настороженно, считая, что это криминальная методика, пригодная только для взлома. Конечно, без взломов тут не обходится, но если запретить дизассемблирование, как бороться с вирусами? Как отлаживать программы, взаимодействующие с другими приложениями, исходных текстов которых нет?

Упаковка исполняемых файлов преследует две цели: уменьшение размеров файла на диске и затруднение исследования машинного кода. За это приходится платить замедлением загрузки и возросшими потребностями в оперативной памяти. Неупакованный файл Windows автоматически подгружает с диска по мере необходимости, а при вытеснении их памяти просто передает страницы памяти другому процессу (разумеется, предварительно их обнулив). Упакованный файл грузится в память весь целиком, распаковывается, потребляя еще больше памяти и, что самое неприятно, при его вытеснении Windows вынуждена сбрасывать страницы памяти в файл подкачки, ведь взять их непосредственно с самого файла уже не получится — он же упакован!

Остается только затруднение анализу, однако, для всех популярных упаковщиков существуют автоматические распаковщики, а если и не существуют — файл нетрудно распаковать и вручную. Главное — найти точку входа. Это несложно. В начале каждой нормальной программы присутствует библиотечный код, который легко опознать по сигнатуре или по вызову API-функции GetModuleHandleA.

Основную сложность представляет восстановление таблицы импорта (многие упаковщики ее затирают после распаковки), PE-заголовка с таблицей секций (упаковщики издевается над ней тоже) и, возможно, атрибутов страниц памяти, которые упаковщики модифицируют как хотят. К счастью, все эти операции нетрудно автоматизировать и уже существует множество утилит, освобождающих хакеров от бремени ручного труда. К тому же, для исследования программы иметь работоспособный дамп совершенно необязательно! Достаточно просто снять полный дамп процесса и загрузить его в дизассемблер. Чтобы IDA Pro смогла распознать имена функций, соответствующая библиотека сигнатур должна быть загружена вручную. А для распознания API-функций следует подключить map-файл каждой из DLL. В остальном же дизассемблирование дампов памяти ничем не отличается от анализа обычных приложений. Конечно, упакованный файл не может быть модифицирован и заменить 7x на EB у нас навряд ли получится, поэтому, приходится либо писать генераторы регистрационных номеров/ключевых файлов, либо прибегать к помощи он-лайновых патчеров, правящих образ файла в памяти на лету.

Некоторые упаковщики внедряют в файл различные анти-отладочные механизмы, перекомпилируют защитные процедуры в p-код, используют динамическую шифровку и делают множество других нехороших вещей, в результате чего размер файла только возрастает, а упаковщики превращаются в протекторы. Затрудняет ли это анализ? В какой-то мере да. Во всяком случае лобовая атака становится невозможной и хакеру приходится искать обходные пути, однако, редкий протектор обходится без ошибок и у легальных пользователей появляются проблемы. Программа отказывает в регистрации или конфликтует с другими приложениями. А вот этого допускать ни в коем случае нельзя! Конфликтные издержки зачастую превышают убытки от хакерства. Любую популярную программу все равно взломают и выложат крэк. Защита лишь чуть-чуть увеличивает это время, но редко подстегивает продажи.

Короче, к упаковке файлов следует подходить с головой, то лучше всего файл не упаковывать. Тоже самое можно сказать и про распаковку. Некорректная распаковка проявляет себя глюками, выскакивающих в самых неожиданных местах. Правильные хакеры пользуются только генераторами или правкой в памяти.

В чистом виде антиотладочные приемы никому не интересны, ведь помимо отладчика в распоряжении хакера еще имеется и дизассемблер, поэтому, для достижения желаемого результата приемы против отладчика приходится комбинировать с приемами против дизассемблера. Круче всех срубает дизассемблер динамическая упаковка/распаковка и засорение листинга «мусорными» командами.

На прикладном уровне реализуются простейшие антиотладочные приемы: замер времени выполнения групп команд инструкцией RDTSC, «размазывание» защитного кода по нескольким сложновзаимодействующих друг с другом потоков, анализ содержимого PEB, различия в обработке некоторых типов исключений и т. д. При этом, реагировать на сам факт наличия отладчика в системе категорически недопустимо. Каждый пользователь в праве держать на своей машине soft-ice и работать с ним! Программа должна блокировать только активную отладку!

На уровне нулевого кольца возможностей намного больше, но вместе с тем выше риск угробить операционную систему, помешав работе посторонних приложений, чего опять-таки допускать нельзя. Недокументированные возможности должны использоваться только тогда, когда по другому просто не получается, но даже тогда следует предусмотреть «резервный» вариант, на тот случай если в конкретной операционной системе что-то окажется не так. Ведь протестировать программу на всех конфигурациях практически нереально и конфликты могут вылезти откуда угодно! Западный же пользователь (в особенности корпоративный) ошибок не прощает и один-единственный прокол может закрыть выход на рынок навсегда.

К тому же, антиотладочные приемы серьезно затрудняют пуско-наладку. Вот, например, в готовой программе происходит сбой по таким-то адресам, но в отладочной версии все работает нормально. Что делать? Правильно, запускать отладчик и отлаживать программу в двоичном виде. Вот тут-то программист и «поблагодарит» себя за мощные антиоладочные приемы! Горит сдача, клиенты ругаются, шеф рвет на себе волосы… а хакеры тем временем пьют пиво и хачат программу в свободное время, радуясь каждому новому приему, как золотому самородку.

Разумеется, никто не призывает отказаться от анти-отладки вообще! Использовать ее можно, даже нужно, но очень осторожно, взвешивая все возможные последствия и побочные эффекты, в противном случае она превратится в оружие, направленное на своего создателя.