Автор: (c)Крис Касперски ака мыщъх
Этот необычный во всех отношениях обзор посвящен... ошибкам отладчиков, приводящим к утрате контроля над отлаживаемым приложением еще на стадии загрузки exe-файла в память, что для исследователей малвари зачастую заканчивается катастрофой - только хотел взглянуть на очередного зверька под отладчиком на своей основной машине, как отладчик проскакивает точку входа и живчик поселяется на компьютере - ищи его потом!
Работая с отладчиком Syser версии 1.95.1900.0894, выпущенной в начале 2008 года, мыщъх обратил внимание, что при загрузке программ с сетевых дисков и сменных носителей (типа дискет) отладчик проскакивает точку входа в файл (она же Entry Point или, сокращенно, EP), передавая подопытной программе бразды правления и утрачивая над ней всякий контроль (впрочем, глобальные точки останова на API-функции срабатывают нормально, если, конечно, не забыть заблаговременно их поставить). Мыщъх отослал разработчикам баг-репорт, получив подтверждение об ошибке вкупе с обещанием ее исправить, но... версии Syser'а продолжают выходить одна за другой, а ошибка как была, так и осталась. Почему же она вообще возникает? Откуда берется и почему никуда не девается? Дело в том, что механизм загрузки файлов с жестких дисков и сменных носителей принципиально различен. Исполняемые файлы (и динамические библиотеки), расположенные на винчестере, операционная система просто проецирует в память, что (грубо говоря) превращает их в "файл подкачки, доступный только на чтение". Подкачка страниц с диска в оперативную память происходит только по мере обращения к ним, а при недостатке памяти немодифицированные страницы не вытесняются в настоящий файл подкачки, а просто высвобождаются под новые данные. Действительно - какой смысл записывать их в своп? Проще вновь обратиться к исполняемому файлу - он же никуда не денется за это время. Ну, это с жесткого диска он не денется (и потому система блокирует к нему доступ на время выполнения), а вот с дискеты или сетевого диска... Там, во-первых, скорость намного ниже, чем у винчестера, а во-вторых, сеть в любой момент может лечь, а диск - быть вынутым. Разработчики Windows учли такую возможность развития событий и решили проблему путем предварительной загрузки файла в оперативную память, за что отвечает совсем другой компонент загрузчика, игнорируемый Syser'ом со всеми отсюда вытекающими...
Все существующие в данный момент версии Syser'а.
Не требуется, достаточно попробовать загрузить любой исполняемый файл с сетевого диска, дискеты или CD/DVD, как результат все скажет сам за себя.
Всегда копируйте файлы с сетевых дисков (сменных носителей) на жесткий диск перед их загрузкой в Syser.
Рисунок 1. Попытка загрузки файла с сетевого диска в Syser ведет к "проскоку" точки входа в файл и утрате контроля за отлаживаемым приложением.
Экспериментируя со штатным линкером от Microsoft на предмет создания предельно компактных исполняемых файлов, мыщъх получил от одного из бета-тестеров баг-репорт, что на его машине мыщъх'иные файлы при активном Syser'е (активном - просто запущенном, загрузки файла в отладчик не требуется) роняют XP SP2 в BSOD с указанием на драйвер Syser.sys, принадлежащий, как и следует из его названия, сабжевому отладчику. Мыщъх попробовал воспроизвести данный эффект на своей горячо любимой W2K и... получил тот же самый BSOD. Вот тебе, бабушка, и оптимизация! Зато какой шикарный способ борьбы с активным Syser'ом! На Soft-Ice данный эффект не распространяется, однако Soft-Ice на хакерских машинах встречается все реже и реже, особенно на новых системах, которые Soft-Ice вообще не поддерживает. Разработчикам Syser'а был отправлен очередной баг-репорт, но никакого ответа от них не последовало и когда они исправят дефект в отладчике - неизвестно. Подробнее об этой ошибке можно прочитать на мыщъх'ном блоге: http://souriz.wordpress.com/2008/05/09/syser-causes-bsod/.
Все существующие версии Syser'а.
Готовую бинарную сборку файла, вызывающего BSOD (вместе с исходными текстами и командными файлами для сборки в среде MS VC), мыщъх выложил на свой сервер: http://nezumi.org.ru/souriz/TF-bug.zip. Впрочем, сам исполняемый файл тут не при чем (он может быть любым), главное - это опции линкера для его сборки, которые выглядят следующим образом:
$link.exe %NIK%.obj /FIXED /ENTRY:nezumi /SUBSYSTEM:CONSOLE /ALIGN:16 /MERGE:.rdata=.text /STUB:stub KERNEL32.LIB
Листинг 1. Сборка компактного исполняемого файла, высаживающего Syser'а на полный BSOD.
Здесь: /ALIGN:16 - установить выравнивание секций в файле и в памяти по границе 10h, что намного меньше "официально" разрешенного значения (1000h - для выравнивания секций в памяти, что соответствует размеру одной страницы и 200h для выравнивания секций на диске, что соответствует размеру одного сектора), однако для драйверов такого ограничения нет, а грузит их один и тот же системный загрузчик, поэтому обозначенный трюк вполне законен для всех операционных систем из линейки NT, вплоть по Висту/Server 2008 включительно.
/MERGE:.rdata=.text - говорит линкеру объединить секцию .rdata (секция данных, доступная только на чтение) с секцией .text (содержащей код), в результате чего у нас образуется всего одна секция и мы экономим до 10h байт, которые в противном случае ушли бы на выравнивание второй секции.
/STUB:stub - приказывает линкеру использовать пользовательскую MS-DOS "затычку", вместо той, что по умолчанию вставляется в начало всякого PE-файла и выводит на экран известное сообщение, что программа жить не может без Windows. В целях оптимизации мыщъх использовал "голый" MS-DOS old-exe заголовок с отрезанным телом файла.
В результате всех этих ухищрений размер файла (с полезным кодом, намного более функциональным чем "hello, world") составил 624 байта и это при том, что файл написан на языке Си (пускай и не без ассемблерных вставок) и собран штатными средствами! Какой именно из этих параметров привел к падению Syser'а - мыщъх не знает, а экспериментировать, роняя свою систему в BSOD - этим пусть разработчики Syser'а занимаются. Кстати, если загрузить такой файл в Syser, а не просто запустить его при активном Syser'е, то все будет нормально и BSOD не появится.
Выгружать Syser перед запуском потенциально небезопасных файлов (благо, Syser, в отличие от Soft-Ice, поддерживает возможность выгрузки из памяти "на лету").
Рисунок 2. BSOD, вызываемый Syser'ом при запуске "оптимизированного" файла.
Как известно, IDA-Pro с некоторых времен не только дизассемблер, но еще и отладчик. Отладчик не то, чтобы сильно мощный (намного слабее Ольги), но все-таки намного более удобный, чем постоянное переключение между дизассемблером и внешним отладчиком, а потому активно используемый хакерами наряду с исследователями малвари. И все было бы хорошо, но если "скормить" дизассемблеру файл с нулевым базовым адресом, он нормально загрузит его по этому самому нулевому адресу, но вот при попытке запуска отладчика мы получим следующее ругательство: "IDA Pro couldn't automatically determine if the program should be rebased in the database because the database format is too old and doesn't contain enough information. Create a new database if you want automated rebasing to work properly. Notice you can always manually rebase the program by using the Edit, Segments, Rebase program command" ("IDA-Pro не может автоматически определить: должна ли программа быть перемещена в базе, поскольку формат базы очень старый и не содержит достаточно информации. Создайте новую базу, если вы хотите автоматизировать перемещение. Примечание: вы можете переместить базу и самостоятельно: Edit -> Segment -> Rebase program"), после чего нам предлагают нажать на "ОК", чтобы согласиться. Но что бы мы не нажали - "ОК" или "Escape", IDA-Pro запускает процесс, полностью утрачивая контроль и мерзко игнорируя все ранее установленные точки останова. Вот такой замечательный анализ малвари! К слову сказать, файл с нулевым базовым адресом загрузки при наличии в нем таблицы перемещаемых элементов совершенно законен и операционная система переместит его в памяти автоматически. А вот IDA-Pro - нет. Мыщъх послал баг-рапорт Ильфаку и тот сказал, что “будем посмотреть”, так что следите за новостями: http://souriz.wordpress.com/2008/05/14/773-bug-in-ida-pro-fails-to-debug-zero-base-pe/.
Все существующие на данный момент версии IDA-Pro (проверялось на 4.7 и 5.2).
Исходный текст программы, демонстрирующей уязвимость (вместе с ключами сборки), приведен ниже:
Листинг 2. "hello-world.c" - вполне типичная программа на Си.
$cl /c hello-world.c $link hello-world.obj /FIXED:NO /BASE:0
Листинг 3. Сборка программы с нулевым базовым адресом загрузки.
Перед запуском отладчика удостовериться, что программа находится по "правильным" адресам, в противном случае переместить ее на новое место (например, по адресу 400000h) через Edit -> Segment -> Rebase program, или же с помощью утилиты ms editbin.exe (в последнем случае потребуется перезагрузка файла в IDA-Pro с потерей всех предыдущих результатов анализа).
Рисунок 3. Реакция IDA-Pro на попытку отладки файла с нулевым базовым адресом загрузки.
В процессе реализации проекта по переносу Soft-Ice на Висту и Server 2008 (при финансовой поддержке фирмы K7), мыщъх исследовал Soft-Ice вместе с кучей других отладчиков и с удивлением обнаружил, что все они спроектированы неправильно и отлаживаемая программа может вырваться из-под контроля еще до начала трассировки - непосредственно в процессе загрузки файла в отладчик. Чтобы выяснить - почему так происходит, нам необходимо разобраться - как вообще отладчики "стопорят" программу, а делают они это приблизительно так - сначала процесс загружается в память, отладчик отслеживает этот момент и, считывая из PE-заголовка точу входа в файл, устанавливает по этому адресу программную или (реже) аппаратную точку остановка, после чего возвращает бразды правления операционной системе, которая посредством функции KiUserApcDispatcher создает первичный поток. Прототип функции приведен на рис. 4, откуда видно, что стартовый адрес потока передается как аргумент по NTAPI-соглашению, то есть через пользовательский стек, после чего система начинает подгружать статически прилинкованные динамические библиотеки, вызывая функцию DllMain (если, конечно, DLL имеет точку входа) и каждой DLL.
Рисунок 4. Прототип функции KiUserApcDispatcher, с которой начинает жизнь любой поток.
Если DllMain возвращает ноль, система сообщает об ошибке загрузки приложения (см. рис. 5) и завершает процесс. В противном же случае (когда все динамические библиотеки рапортуют, что инициализация прошла успешно) управление передается первичному потоку процесса, где находится точка останова, установленная отладчиком. При попытке ее выполнения процессор генерирует исключение типа breakpoint exception, отлавливаемое операционной системой и передающей отладчику бразды правления.
Рисунок 5. Сообщение операционной системе о неудачной инициализации динамической библиотеки, ведущей к завершению процесса.
Таким образом, вырисовывается следующая (не очень-то приятная для отладчиков и исследователей малвари) картина:
Рисунок 6. Блог мыщъх'а.
MS VS, WinDbg, OllyDbg, ImmDbg, Syser, Soft-Ice, IDA-Pro и многие другие...
Демонстрационный пример (вместе с полными исходными текстами, правда, без комментариев) выложен на OpenRCE в репозиторий мыщъх'а: https://www.openrce.org/repositories/users/nezumi/quux-crackme.zip, занимающий в упакованном виде меньше 2-х килобайт, совместимый с Вистой/Server 2008 и не конфликтующий с Syser'ом (в смысле - не "выбивающий" из него BSOD), но ускользающий из-под всех вышеперечисленных отладчиков.
Рисунок 7. Файловый репозиторий мыщъх'а на OpenRCE.
Отсутствует. Ну, не то, чтобы совсем отсутствует, но общих решений нет и помимо DllMain существует туча всего такого, исполняющегося до "всплытия" отладчика, взять хотя бы те же TLS-callback'и, например, а потому загрузка программы в отладчик равносильна ее запуску и потенциально опасные файлы можно использовать только на специальной (виртуальной) машине, на которой нет ничего такого, что было бы жалко потерять (примечание: кстати, в Olly Advanced - популярном plug-in'е для Ольги - есть опция "Flexible Breakpoints", убирающая программную точку останова, что предотвращает обнаружение отладчика, но не в силах противостоять подмене стартового адреса первичного потока. Аналогичным образом дела обстоят и с другим популярным plug-in'ом - PhantOm, имеющим опцию "Remove EP Break", назначение которой говорит само за себя).
Для облегчения понимая принципов работы отладчиков и способов "побега" из-под них, мыщъх приводит комментированные исходные тексты quux-crackme, однако прежде, чем их читать, рекомендуется попробовать разобраться с crackme самостоятельно, благо он очень простой, даже start-up убит для облегчения понимания и коду там - всего несколько сотен машинных команд, без каких бы то ни было трюков или выкрутасов.
Листинг 4. Исходный код quux-crackme.c (головной исполняемый файл).
Листинг 5. Исходный текст динамической библиотеки quux-dll.c.
Разумеется, на самом деле это никакой не crackme (в обычном смысле этого слова), а настоящий exploit типа proof-of-concept, предназначенный для тестирования отладчиков на "вшивость" и написанный так, чтобы предельно облегчить его понимание, благодаря чему сломать его - плевое дело, но вот если наворотить здесь хитрый код, то шансы на его понимание резко снижаются, а шансы на "взлом" отладчика, соответственно, резко возрастают. Однако борьба с отладчиками на данном этапе не входила в задачу мыщх'а - этому посвящена отдельная колонка в "Хакере", а в этой мы говорим исключительно о дырах (прорыв сквозь отладчик - в первую очередь дефект проектирования отладчика, т.е. дыра, и только потом антиотладочный прием).