Windows намного надежнее, чем это принято считать в народе. Моя основная машина (на базе W2K) перезагружается не чаще двух раз в месяц, а файловый сервер (и по совместительству – рабочая станция для цифрового монтажа, также вращающаяся под W2K) бесперебойно проработал полгода и упал лишь из-за броска по питанию, с которым не смог справиться UPS.
Голубые экраны смерти, вспыхивающие время от времени, отлавливаются SoftICE, который мыщъх держит постоянно загруженным. В большинстве случаев он возвращает систему к жизни. Это вопрос чести и хакерской этики. Перезагрузки – тривиальный, но порочный путь. Каждый сбой компьютера, каждый глюк системы мыщъх воспринимает чуть ли не как физическую боль и борется за здоровье машины, как за свое собственное! И пускай меня сочтут ненормальным… главное – методики реанимации системы, разработанные и обкатанные мной, могут принести пользу не только мне. Итак, что нам понадобится? Windows Driver Kit (WDK) для всех систем по Vista включительно (www.microsoft.com/whdc/DevTools/) Windows Server 2003 SP1 DDK (www.microsoft.com/whdc/devtools/ddk/default.mspx) IA-32 Architecture Software Developer's Manual Vol. 3: System Programming Guide (www.intel.com/products/processor/manuals/) Syser 1.95.19000.0894 (www.sysersoft.com/download/download.php) По ту сторону BSOD'ов
Голубые экраны вспыхивают всякий раз, когда ядро сталкивается с ситуацией, которую не может разрулить самостоятельно. Если не остановить некорректно работающий код, завершив работу всех механизмов оси в аварийном режиме, ситуация способна пустить систему в разнос. Это, кстати, кардинально отличает NT-подобные системы от мира UNIX, впадающего в BSOD (kernel panic – в их терминологии) только в хардкорных обстоятельствах (все остальное время они просто выгружают порочный драйвер примерно так же, как NT завершает работу некорректно работающего приложения).
Конечно, если ошибка возникнет в драйвере файловой системы, то далеко на такой машине не уедешь. Подавляющее большинство сбоев приходится на драйвера, установленные вирусами, антивирусами, брандмауэрами, звуковыми и видеокартами. Причем, как показывает практика, 90% ошибок отнюдь не фатальны. Они вполне совместимы с жизнью, но ядро не спрашивает нас, хотим ли мы продолжить работу или предпочитаем внезапно умереть (в тот самый момент, когда открыта масса приложений с тучей не сохраненных файлов).
Прежде, чем бросаться в бой, нужно отделить программные ошибки от аппаратных отказов железа (как разогнанного, так и нет). Если голубые экраны вспыхивают в случайное время, каждый раз отображая разные данные (да кто эти данные читает?), то с большой вероятностью мы имеем дело с глюками железа. Пытаться реанимировать компьютер при этом чрезвычайно опасно. Если содержимое оперативной памяти разрушено из-за разгона или некачественного блока питания, то после выхода из BSOD'а операционная система попытается скинуть дисковые буфера. А там у нас что? Правильно, – мусор. И дисковый том отправится к праотцам, что намного хуже, чем потеря оперативных данных.
Впрочем, дефекты программного обеспечения тоже могут приводить к генерации «рандомных» экранов голубой смерти. Следовательно, без полного анализа ситуации здесь не обойтись. Однако не будем падать духом! Рано или поздно мы «объездим» ядро и разберемся во всех тонкостях его организации, а пока ограничимся лишь общей схемой. Как устроен 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. Он вызывается из функции распределения памяти, обнаружившей, что память на конкретной измене, но кто ее разрушил и когда – этого система сказать не может.
Поиск диверсанта зачастую отнимает несколько дней кропотливого ручного труда и, что самое неприятное, – исправить разрушенные структуры данных практически невозможно, а, значит, перезагрузки все равно не избежать. Хотя с риском для жизни еще можно вернуться на уровень прикладного режима, попробовав сохранить хотя бы часть данных. Если нам повезет, то с разрушенным пулом (специальной областью ядерной памяти) можно проработать несколько минут, а иногда и дней. В исключительных ситуациях система держится на плаву целую неделю, однако никакого смысла в таком экстриме нет. Риск разрушения дисковых томов очень велик и потому, сохранив все несохраненные данные, лучше все-таки перезагрузиться.
Подготовка к работе
Для борьбы с голубыми экранами смерти нам понадобится любой достойный термоядерный отладчик, загруженный до их возникновения (надеюсь, не нужно объяснять почему?). Достойных отладчиков ядра всего три: SoftICE, Syser и Microsoft Kernel Debugger, но SoftICE не работает на Висле и Server'е 2008, а Microsoft Kernel Debugger – это вообще не вариант. Остается Syser, который мы и будем использовать.
Установка обычно проходит гладко и без нареканий. Выбираем ручной режим загрузки (boot – manual) и, чтобы не грузить его вручную (это ж напряг какой!), перетягиваем иконку «Syser Loader», созданную инсталлятором в папку «Автозагрузка». В принципе, можно не извращаться и выбрать автоматический режим загрузки. Но в этом случае, если возникнет конфликт отладчика с операционной системой, его будет трудно выгрузить.
Окей, будет считать, что Syser загружен и готов работе, что подтверждается наличием соответствующей управляющей консоли на экране. Ее можно свернуть или совсем закрыть – отладчику от этого хуже не станет. Однако мы ничего закрывать не будем, поскольку чуть позже планируем немного поэкспериментировать с дефективным драйвером, запуск которого как раз и осуществляется через эту консоль. А сейчас нажимаем <CTRL-F12> и вводим магическую команду «bpm KeBugCheckEx x<ENTER>x<ENTER>», заставляющую Syser перехватывать вызов функции KeBugCheckEx до возникновения голубого экрана смерти. Набирать ее придется вручную при каждом запуске Syser'а, поскольку текущие версии отладчика, увы, макросов автозапуска не поддерживают (в SoftICE делать вообще ничего не надо, так как он перехватывает 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
Для его сборки нам понадобится NTDDK (который можно бесплатно скачать с серверов Microsoft), а также командный файл, в котором переменная окружения ntoskrnl содержит полный путь к библиотеке ntoskrnl.lib (зависящий от того, куда инсталлятор установил NTDDK). Как видно, мыщъх использует путь, отличный от пути по умолчанию (C:\Program 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 Первый бой – он трудный самый!
В консоли Syser'а находим пункт «Tools», а в нем – «Quick Driver Loader». В появившемся диалоговом окне указываем путь к драйверу CALL-the-BSOD.sys (Driver File Name). Имя сервиса (Service Name) загрузчик подставит самостоятельно. Нажимаем «Install» (установка) и «Start» (внимание: установку драйвера достаточно выполнить всего один раз и затем просто давить «Start», а когда нам надоест с ним экспериментировать – сказать «Uninstall» для удаления сервиса из системы, но впрочем, можно и не говорить, это всего лишь запись в реестре, которая никому не мешает).
Но мы сильно забегаем вперед. После нажатия кнопки «Start» отладчик появляется на экране, послушно остановившись на функции KeBugCheckEx. Если теперь нажать «x<ENTER>» для выхода из отладчика, передавая управления функции KeBugCheckEx, система немедленно рухнет, отображая следующий BSOD (смотри рисунок), то есть свершится то, что произошло бы, если бы отладчик не был установлен и сконфигурирован.
Обратившись к NTDDK, мы узнаем, что номер 1Eh принадлежит BugCheck-коду KMODE_EXCEPTION_NOT_HANDLED, сигнализирующему об ошибке доступа к памяти. Первый аргумент функции KeBugCheckEx содержит код исключения, в данном случае равный C0000005h (STATUS_ACCESS_VIOLATION – нарушение доступа). Второй аргумент (равный F75DF2AFh) – адрес дефективной машинной инструкции, до которой можно «дотянуться» командой «u *(esp+(4*3))» – дизассемблировать содержимое указателя, лежащего в третьем двойном слове относительно регистра-указателя вершины стека.
Если команда введена правильно, мы увидим код драйвера-убийцы, который мы только что компилировали, линковали и загружали через «Quick Driver Loader». Все ясно! Машинная команда MOV EAX, [EAX] (где EAX, как мы помним, равен нулю) обращается к нулевой ячейке памяти. Процессор генерирует исключение, подхватываемое ядром и после непродолжительных мытарств попадающее под трибунал KeBugCheckEx.
На регистры, отображаемые отладчиком в левом верхнем окне, лучше не смотреть. EAX там равен не нулю, а черт знает чему, а все потому, что с момента вызова исключения прошло слишком много времени, и регистровый контекст был изменен. А потому, возвращаться назад в драйвер нам нельзя. Точнее – можно, но для этого потребуется совершить слишком большое количество телодвижений, а мы тут не акробатикой занимаемся, а хакерством. Не будем крутить попой! Будем думать головой!
Универсальные способы реанимации системы
Начинаем мозговой штурм. Какие будут предложения? Думаем…
Что мы вообще делаем на ядерном уровне, когда можно просто совершить нуль-транспортировку на прикладном, на котором никакие 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!
Ну и, чего мы добились? Достаточно многие виды исключений, при попытке игнорирования их таким варварским путем, будут вызывать BSOD вновь и вновь, пусть он уже не появится на экране (ведь своим RETN 14h мы фактически устроили короткое замыкание внутри функции-палача). Однако на многопроцессорных системах (включая HT- и многоядерные процессоры) все будет работать, хоть и сильно тормозить, поскольку «зацикливание» одного ядерного «потока» практически никак не повлияет на все остальные. «Поток» взят в кавычки потому, что в ядре NT никаких потоков нет, но для объяснения происходящего такая трактовка вполне сойдет.
А вот еще один вариант. Вместо возврата из KeBugCheckEx, просто зациклим ее, воткнув в ее начало команду JMP SHORT $-2, которой соответствует следующий машинный код: EBh FEh, внедряемый по прежней схеме: «d eip», и дальше запись EBh FEh поверх существующего кода.
Зацикливая KeBugCheckEx на однопроцессорной машине, мы сильно рискуем получить глухой завис. Двухпроцессорные тачки какое-то время успеют проработать, прежде чем оба процессора вызовут KeBugCheckEx и войдут в бесконечный цикл, из которого их вывести может только аппаратное прерывание, сгенерированное таймером или иными внешними устройствами (правда, при этом существует реальная угроза переполнения стека). Если KeBugCheckEx многократно вызывается, выпадая в бесконечный цикл и оставляя переданные аргументы на вершине стека, то стеку рано или поздно наступит конец. Ловить исключение уже некому и система уйдет в перезагрузку безо всяких голубых экранов. Впрочем, это можно исправить путем изменения JMP SHORT $-2 на ADD ESP,14h/JMP SHORT $-2, что соответствует машинному коду: 83h C4h 14h/EBh FEh.
Вероятность выживания системы существенно повышается. Впрочем, все универсальные приемы преодоления BSOD далеки от совершенства, ведь если бы хоть одно надежное универсальное решение существовало, то его уже давно бы реализовали: если не сама Microsoft, то сторонние разработчики. Так что без изучения ассемблера и анатомических особенностей NT-подобных систем нам все равно далеко не уйти. Как я раньше этого не сделал?
Хакерские навыки не приобретаются в одночасье. Начиная с простых экспериментов и употребления различных «рецептурных справочников», мы постепенно въезжаем в суть вещей, постигая устройство мира. Мир устроен одновременно и просто, и сложно. Любая задача зачастую после решения кажется простой.
Термоядерные отладчики позволяют разрулить огромное количество мелких и крупных проблем. Для грамотного использования отладчиков необходимы практика и интуиция. Мыщъх искренне надеется, что эта статья и будет тем пинком (гм, толчком), который подвигнет тебя на эксперименты и исследования. WARNING
Работа с операционкой в аварийном режиме может привести к краху системы и потере данных. INFO
Ранее мы описывали методы борьбы с BSOD с помощью SoftICE. В настоящее время поддержка SoftICE прекращена, и хотя старые версии все еще можно найти в Сети, они не дружат с Windows Vista и Server 2008. Мыщъх (при финансировании компании K7 Computing) вплотную занялся переносом SoftICE под новые системы, так что следи за новостями! Первая пре-альфа уже на подходе!
|