life-after-bsod

жизнь после BDOS\\ или night of the living undead II

крис касперски ака мыщъх, no-email

голубой экран смерти — это последний вздох системы, после которого душа отделяется от тела (дамп памяти падает на диск) и компьютер уходит в перезагрузку, унося с собой все несохраненные данные… есть ли жизнь после смерти доподлинно неизвестно, но установка термоядерного отладчика типа soft-ice или syser'а позволяет взять ситуацию под свой контроль, вытаскивая систему из мира мертвых, в мир живых. мыщъх об этом уже писал ранее, но то было давно и неправда ;) сейчас другое время, иные операционные системы — XP SP2, Висла, Server 2003/2008, соответственно, древние ритуалы «воскрешения» требуют адоптации, в общем, тут есть куда махнуть хвостом!

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

Голубые экраны смерти, вспыхивающие время от времени, отлавливаются soft-ice, который мыщъх держит постоянно загруженным и в подавляющем большинстве случаев возвращает систему к жизни, продолжая работать как ни в чем ни бывало. Это вопрос чести и хакерской этики. Перезагрузки — тривиальный, но порочный путь. Каждый сбой компьютера, каждый глюк системы мыщъх воспринимает чуть ли не как физическую боль и борется за здоровье машины как за свое собственное!

И пускай меня сочтут ненормальным, назовут чокнутым… главное — что методики реанимации системы, разработанные и обкатанные мыщъхем, полезны не только ему одному. Так что, но пасаран!!! Баги не пройдут!!!

Для экспериментов, описанных в статье, нам понадобятся следующие вещи, программы и инструменты:

  1. Windows Driver Kit (WDK) для всех систем по Вислу включительно:
    1. http://www.microsoft.com/whdc/DevTools/default.mspx__ (требует регистрации); - Windows Server 2003 SP1 DDK: - http://www.microsoft.com/whdc/devtools/ddk/default.mspx__;
  2. IA-32 Architecture Software Developer's Manual Vol. 3: System Programming Guide:
  3. Soft-Ice:
    1. в настоящее время поддержка soft-ice прекращена, и хотя старые версии все еще можно найти в Сети, они не дружат с Вислой и Server 2008, однако, мыщъх (при финансировании компании K7 Computing) вплотную занялся переносом soft-ice под новые системы, так что следите за новостями! первая пре-альфа уже на подходе!

Голубые экраны вспыхивают всякий раз, когда ядро сталкивается с ситуацией, которую не может разрулить самостоятельно и которая способна пустить систему в разнос если не остановить некорректно работающий код, завершив работу всех механизмов оси в аварийном режиме, что кардинально отличает NT-подобные системы от мира UNIX, впадающие в BSOD (kernel panic – в их терминологии) только в реально хардкорных обстоятельствах, а все остальное время просто выгружают порочный драйвер (примерно так же, как NT завершает работу некорректно работающего приложения, не трогая остальных).

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

Но, прежде, чем бросаться в бой, нужно отделить программные ошибки от аппаратных отказов железа (как разогнанного, так и нет). Если голубые экраны вспыхивают в случайное время, каждый раз отображая разные данные (да только кто эти данные читает?!), то с высокой степенью вероятности мы имеем дело с глюками железа. Пытаться реанимировать компьютер при этом чрезвычайно опасно. Если содержимое оперативной памяти разрушено из-за разгона или некачественного блока питания, то после выхода из BSOD'а операционная система попытается скинуть дисковые буфера, а там у нас что? Правильно — мусор. И дисковый том отправится к праотцам, что _намного_ хуже, чем потеря оперативный данных.

Впрочем, дефекты программного обеспечения так же могут приводить к генерации «рандомных» экранов голубой смерти и без полного анализа ситуации здесь не обойтись. Однако, не будем падать духом!!! Рано или поздно мы «объездим» ядро и разберемся во всех тонкостях его организации, ну а пока ограничимся лишь общей схемой.

Роль палача в NT-системах играет функция KeBugCheckEx, экспортируемая ядром и вызываемая из сотен (если не тысяч!) мест с теми или иными параметрами. Что это за параметры? Обратившись к NTDDK, мы узнаем, что функция KeBugCheckEx принимает пять аргументов, первый из которых (BugCheckCode) содержит код ошибки, а четыре следующих параметра — места/время/обстоятельства ее возникновения.

Перечень BugCheck-кодов можно найти в том же NTDDK, там же содержится описание и четырех аргументов, специфичных для каждого BugCheck-кода, количество которых чуть меньше сотни и потому, чтобы не держать кучу ненужной информации в голове, рекомендуется распечатать документацию и всегда держать ее под рукой.

BugCheck-коды можно разделить на две большие категории: первая содержит адрес инструкции, вызвавшей исключение (например, 1Eh: KMODE_EXCEPTION_NOT_HANDLED, 0Ah: IRQL_NOT_LESS_OR_EQUAL, 24h: NTFS_FILE_SYSTEM), что позволяет «заглянуть» отладчиком непосредственно на место аварии, исправить пробоину и, выйдя из отладчика, продолжить плавание (естественно, для этого нужно не только знать ассемблер, но и разбираться в тонкостях драйверостроения, но это — в идеале).

Другая категория BugCheck-кодов не содержит адреса дефективной инструкции, поскольку ядро диагностирует аварийную ситуацию на поздней стадии. Найти виновника в этих случаях довольно затруднительно. Взять хотя бы такой BugCheck-код как C2h: BAD_POOL_CALLER, вызываемый из функции распределения памяти, обнаружившей, что память на конкретной измене, но кто ее разрушил и когда — этого система сказать не может.

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

Для борьбы с голубыми экранами смерти нам понадобится любой достойный термоядерный отладчик, загруженный _до_ их возникновения (надеюсь, не нужно объяснять почему?). Достойных отладчиков ядра всего три: soft-ice, syser и Microsoft Kernel Debugger, но soft-ice не работает на Висле и Server'е 2008, а Microsoft Kernel Debugger — это не вариант вообще. Остается syser, который мы и будем использовать.

Установка обычно проходит гладко и нареканий не вызывает. Выбираем ручной режим загрузки (boot — manual), но чтобы не грузить его вручную (это же напряг какой!) перетягиваем иконку «Syser Loader», созданную инсталлятором в папку «Автозагрузка». В принципе, можно не извращается и выбрать автоматический режим загрузки, но в этом случае, если возникнет конфликт отладчика с операционной системой его будет довольно трудно выгрузить.

ОК, будет считать что syser загружен и готов работе, что подтверждается наличием соответствующей управляющей консоли на экране, которую можно свернуть или совсем закрыть. Отладчику от этого хуже не станет, но мы ничего закрывать не будем, поскольку планируем немного поэкспериментировать с заведомо дефективным драйвером, запуск которого как раз и осуществляется через эту консоль, но это будет потом. А сейчас нажимаем <CTRL-F12> и вводим магическую команду «bpm KeBugCheckEx x<ENTER>x<ENTER>», заставляющую syser перехватывать вызов функции KeBugCheckEx _до_ возникновения голубого экрана смерти. Набирать ее придется вручную при _каждом_ запуске syser'а, поскольку текущие версии отладчика макросов автозапуска не поддерживают, увы (в soft-ice вообще делать ничего не надо, поскольку он перехватывает KeBugCheckEx по умолчанию).

На этом нашу миссию можно считать законченной. Теперь ни один голубой экран смерти не пробежит мимо нас незамеченным!

Напишем простой драйвер, обращающийся к памяти по нулевому указателю (что категорически недопустимо) и, как следствие, вызывающий BSOD, с которым мы и будем сражаться.

Исходный ассемблерный текст простейшего драйвера-убийцы приведен ниже:

.686

.model flat, stdcall

extern DbgPrint:PROC

.code

DriverEntry proc

push offset to_die; вывод предупредительного сообщения

CALL DbgPrint

pop eax

XOR EAX,EAX; обнуляем регистр EAX

MOV EAX,[EAX]; ←- здесь выскакивает BSOD

push offset happy; если вы читаете этот текст,

CALL DbgPrint; значит, вы еще живы ;-)

pop eax

mov eax, 0C0000182h; STATUS_DEVICE_CONFIGURATION_ERROR

;RET; Four-F says

RETN 8; ← haron says

DriverEntry endp

.data

to_die DB «*] prepare to die! [*»,0Dh,0Ah,0

happy DB «*] welcome to life [*»,0Dh,0Ah,0

end DriverEntry

Листинг 1 исходный текст драйвера убийцы CALL-the-BSOD.asm

Для его сборки нам понадобиться NTDDK (который можно бесплатно скачать с серверов Microsoft), а так же командный файл следующего содержания, в котором переменная окружения ntoskrnl содержит полный путь к библиотеке ntoskrnl.lib, зависящий от того, куда инсталлятор установил NTDDK. Как легко видеть, мыщъх использует путь отличный от пути по умолчанию («C:\Prpgram Files\…) и потому нуждающийся в коррекции, в противном случае собрать драйвер не получится.

На всякий случай, готовый драйвер CALL-the-BSOD.sys прилагается к статье.

@ECHO OFF

REM устанавливаем необходимые переменные окружения

SET FILE_NAME=CALL-the-BSOD

SET ntoskrnl=D:\NTDDK\libchk\i386\ntoskrnl.lib

REM удаляем результаты предыдущей сборки

IF EXIST %FILE_NAME%.obj DEL %FILE_NAME%.obj

REM транслируем ассемблерный листинг

ml /nologo /c /coff %FILE_NAME%.asm

IF NOT EXIST %FILE_NAME%.obj GOTO err

REM линкуем сгенерированный .obj файл

link /nologo /driver /base:0x10000 /align:32 /out:%FILE_NAME%.sys /subsystem:native %FILE_NAME%.obj %ntoskrnl%

GOTO end

:err

ECHO -ERR!

:end

Листинг 2 командный файл для сборки драйвера-убийцы CALL-the-BSOD.sys

В консоли syser'а находим пункт «Tools», а в нем — «Quick Driver Loader». В появившимся диалоговом окне указываем путь к драйверу CALL-the-BSOD.sys (Driver File Name). Имя сервиса (Service Name) загрузчик подставит самостоятельно. Нажимаем «Install» (установка) и «Start» (внимание: установку драйвера достаточно выполнить всего один раз и затем просто давить Start, а когда нам надоест с ним экспериментировать — сказать «Uninstall» для удаления сервиса из системы, впрочем, можно и не говорить, т.к. это всего лишь запись в реестре, которая никому не мешает).

Рисунок 1 загрузка драйвера-убийцы через Quick Driver Loader отладчика Syser

Но мы сильно забегаем вперед. После нажатия кнопки «Start» отладчик появляется на экране, послушно остановившись на функции KeBugCheckEx, как мы и ожидали (см. рис. 2):

Рисунок 2 syser перехватил вызов KeBugCheckEx, предотвращая появление голубого экрана смерти

Если теперь нажать «x<ENTER>» для выхода из отладчика, передавая управления функции KeBugCheckEx, система немедленно рухнет, отображая следующий BSOD (см. рис. 3), то есть, свершится то, что произошло бы если отладчик не был бы установлен и сконфигурирован.

Рисунок 3 голубой экран смерти, вызванный нашим драйвером-убийцей CALL-the-BSOD.sys

Обратившись к NTDDK, мы узнаем, что номер 1Eh принадлежит BugCheck-коду KMODE_EXCEPTION_NOT_HANDLED, сигнализирующем об ошибке доступа к памяти. Первый аргумент функции KeBugCheckEx содержит код исключения, в данном случае равный C0000005h (STATUS_ACCESS_VIOLATION – нарушение доступа), второй аргумент (равный F75DF2AFh) – адрес дефективной машинной инструкции, до которой можно «дотянуться» командой «u *(esp+(4*3))« – дизассемблировать содержимое указателя, лежащего в третьем двойном слове относительно регистра-указателя вершины стека.

Если команда введена правильно, мы увидим код драйвера-убийцы, который мы только что компилировали, линковали и загружали через «Quick Driver Loader» (см. рис. 4):

Рисунок 4 вот она — машинная инструкция, вызвавшая исключение, обрушившее систему в экран голубой смерти

Все ясно! Машинная команда MOV EAX, [EAX] (где EAX, как мы помним, равен нулю) обращается к нулевой ячейке памяти, процессор генерирует исключение, подхватываемое ядром и после непродолжительных мытарств попадающее под трибунал KeBugCheckEx.

На регистры, отображаемые отладчиком в левом верхнем окне, лучше не смотреть. EAX там равен не нулю, а черт знает вообще чему, а все потому, что с момента вызова исключения прошло слишком много времени и регистровый контекст был изменен. А потому, возвращается назад в драйвер нам нельзя. Точнее — можно, но для этого потребуется совершить слишком большое количество телодвижений, а мы тут не акробатикой занимаемся, а хакерством. Не будем крутить попой!!! Будем думать головой!!!

Начинаем мозговой штурм. Какие будут предложения? Э, нет, суицид не предлагать. И покурить мы еще успеем. А тем временем syser, забывающий о том, что процессор надо охлаждать, разогревает его так, что вентиляторы просто взлетают и для борьбы с посторонним шумом мешающим нам думать надеваем наушники и включаем Transilvanian Beat Club, срывающий крышу и высаживающий нас на нереальный креатив.

Сознание очищается и в голове появляется множество умный мыслей. Что мы вообще делаем на ядерном уровне, когда можем просто совершить нуль-транспортировку на прикладной, на котором никакие BSOD'ы вообще не возникают и самое худшее, что может случится — это критическая ошибка, вызывающая аварийное завершение _текущего_ приложения, но никак не падение всей системы целиком.

А что же ядро? Как там со стеком и прочими структурами данных? В каком состоянии мы их оставим? Ну, что касается ядра, то при вызове ядерных функций с прикладного уровня оно заново подготавливает регистровый контекст и все будет ОК. Тоже самое происходит и при генерации аппаратных прерываний, механизм диспетчеризации которых заслуживает отельной статьи, но мы не будем так глубоко углубляться в дебри теоретических изысканий. Самая большая опасность, которая нам грозит — это прерывание функции драйвера, оставляющий свои собственные данные в хаотичном состоянии и при последующем обращении к ним, BSOD с высокой степенью вероятности вспыхнет вновь. Хм, а может быть и не вспыхнет. Это уж как повезет или не повезет.

Ладно, рискнем (а что нам еще остается делать?) и воспользуемся легальной функций возвращения на прикладной уровень (которая, между прочим, недокументированна и варьируется от системы к системы). А других вариантов нет? Почему же?! Ядро работает с кодовым селектором 08h, прикладной уровень — 1Bh, следовательно, для нуль транспортировки нам достаточно изменить регистр CS с 08h на 1Bh, но syser отказывается воспринимать команду «r CS 1B», ругаясь на ошибку синтаксиса, хотя с синтаксисом все нормально, а вот syser определенно еще недоделан и чтобы изменить CS приходится щелкать мышью по окну с регистрами и модифицировать CS вручную, посредством графического интерфейса (хвост бы его побрал). После чего можно со спокойной совестью выйти из отладчика по <CTRL-F12> и… тут же попасть под артобстрел голубых экранов смерти, падающих один за другим, но если не сдаваться и мужественно возвращаться каждый раз на прикладной режим путем модификации CS, то (при определенной степени везения) дождаться относительного затишья и продолжить работать на прикладном уровне как ни в чем небывало.

А вот другое решение. Вместо того, чтобы нуль транспортироваться на прикладной режим, оставляя ядро в аварийном состоянии, попробуем модифицировать функцию KeBugCheckEx, воткнув в ее начало машинную команду «RETN 14h», соответствующую машинному коду: C2h 14h 00h. Находясь в начале KeBugCheckEx просто дадим команду «d eip» (отобразить в дампе памяти содержимое по адресу, на который указывает регистр EIP), и, щелкнув по мышью по верхнему окну, заменим три первых байта на «C2h 14h 00h». Поскольку, syser – сырой продукт, синхронизация дампа памяти с окном кода отсутствует, и чтобы увидеть проделанные изменения, щелкнем мышью по кодовому окну, нажав <PageUp>/<PageDown>, ага, теперь, команда «RETN 14h» появилась в самом начале функции KeBugCheckEx (см. рис. 5):

Рисунок 5 устраиваем «короткое замыкание» в функции KeBugCheckEx

Ну, и чего мы добились?! Некоторые (между прочим, достаточно многие) виды исключений, при попытке их игнорирования таким варварским путем, будут вызывать BSOD вновь и вновь, пускай он уже никогда не появится на экране (ведь своим RETN 14h мы фактически устроили короткое замыкание внутри функции-палача).

Однако, на многопроцессорных системах (включая HT- и многоядерные процессоры) все будет работать, хоть и сильно тормозить, поскольку «зацикливание» одного ядерного «потока» практически никак не повлияет на все остальные. Поток взят в кавычки потому, что в ядре NT никаких потоков нет, но для объяснения происходящего на пальцах, такая трактовка ситуации вполне сойдет.

А вот другой вариант. Вместо возврата из KeBugCheckEx, просто зациклим ее, воткнув в ее начало команду JMP SHORT $-2, которой соответствует следующий машинный код: EBh FEh, внедряемый по прежней схеме: «d eip» и дальше запись EBh FEh поверх существующего кода (см. рис. 6).

Рисунок 6 циклим функцию KeBugCheckEx

Зациклившая KeBugCheckEx на однопроцессорной машине мы сильно рискуем получить глухой завис, однако, двухпроцессорные тачки еще какое-то время успеют проработать, прежде чем оба процессора вызовут KeBugCheckEx и войдут в бесконечный цикл, из которого их вывести может только аппаратное прерывание, сгенерированное таймером или иными внешними устройствами, правда, при этом существует реальная угроза переполнения стека. Если KeBugCheckEx многократно вызывается, выпадая в бесконечный цикл и оставляя переданные аргументы на вершине стека, то стеку рано или поздно наступит конец, а ловить исключение уже некому и система уйдет в перезагрузку безо всяких голубых экранов. Впрочем, это можно исправить путем изменения JMP SHORT $-2 на ADD ESP,14h/JMP SHORT $-2, что соответствует машинному коду: 83h C4h 14h/EBh FEh.

Вероятность выживания системы существенно повышается, впрочем, все универсальные приемы преодоления BSOD далеки от совершенства, ведь если бы хоть одно надежное универсальное решение существовало, то его уже давно бы реализовали: если не сама Microsoft, то сторонние разработчики.

Так что без изучения ассемблера и анатомических особенностей NT-подобных систем нам все равно не уйти.

Хакерские навыки не приобретаются в одночасье. Начиная с простых экспериментов, и употребления различных «рецептурных справочников» мы постепенно въезжаем в суть вещей, постигая устройство мира, а мир устроен одновременно и просто, и сложно. Любая решенная задача кажется простой и вызывает удивление, что же нам мешало решить ее раньше?

Термоядерные отладчики _действительно_ позволяют разрулить огромное количество мелких и крупных проблем, однако, технику их использования нельзя передать словами. Тут необходима практика, интуиция… но надо же с чего-то начинать? Мыщъх искренне надеяться, что эта статья и будет тем пинком (гм, толчком) который подвигнет читателей на эксперименты и исследования.