Он посинел, ему плохо?
BSOD – реакция ядра на неразрешимую исключительную ситуацию. Если вы его видите, то это значит, что случилось что-то определенно нехорошее.
Среда ядра накладывает множество ограничений на свободу действий программиста: учитывай IRQL, синхронизируй доступ к разделяемым переменным, не задерживайся в ISR, проверяй любые данные из «юзерленда»… Нарушив хотя бы одно из правил, вы получите настоящий выговор из штампованных фраз в стандартном VGA-видеорежиме с худой палитрой.
На самом деле в этом есть логика. Если в юзермоде при необработанном исключении и полном крахе логики исполнения приложение просто завершается, даже не убрав за собой следы жизнедеятельности (что любезно за него сделает ядро), оно не потянет за собой юзермодных соседей, не нарушит их целостность и тем более целостность всей системы. Ну, хорошо, может потянуть парочку, с которой оно было связано через межпроцессное взаимодействие, не более.
В кернелмоде все иначе. Во-первых, кернелмодные соседи – уже соседи не по квартире, но по комнате. Между ними нет прочных стен, которые бы защитили одних жильцов от нетрезвых выходок других. При этом взаимосвязи кернелмодных модулей тонки и хрупки. Ядро и его компоненты – гигантские часы, с огромным количеством компонентов. И в этом соль: если один винтик поврежден, останавливается вся система. Конечно, есть модули, сбои которых логически никак не влияют на работу ОС. Можно было бы изолировать сбойный модуль – кое-где так делают, ну, вы знаете. Однако логика заключается в том, что ядерный компонент активно взаимодействует с другими компонентами и ядром ОС, следовательно, сбой в одном компоненте может привести к цепочке сбоев в других, в конечном счете разрушив все ядерные структуры, или, что еще хуже, повредить пользовательские данные. Кроме того, отлаживать такие затаившиеся баги крайне тяжело.
Тем не менее, допустим, вы – пользователь уровня «чайник». Какая вероятная реакция будет у вас при виде синего экрана?
Допустим теперь, что вы – системный администратор. Какая у вас реакция на «синяк»? Ну, до того, как вы начинаете читать код ошибки и все такое.
Пусть вы кернелкодер, и вам кучу раз приходилось видеть этот изощренный отладочный вывод. Что вам приходит в голову, когда вы видите его снова? Ну, кроме междометий.
А имеем за сим, с одной стороны, невозможность продолжить нормальное функционирование системы, с другой, – бесценные невосстанавливающиеся нервные клетки. Как быть? Перехват KeBugCheck не предлагать – мы все знаем, что из этого выйдет. До релиза Windows 8 с ее душевным DirectX BSOD’ом нужно еще подождать какое-то время. А пока что…
Сам себе Руссинович
Думаю, всем знакомо это имя. Руссинович классный, хоть и хитрит. Среди кучи полезных утилит от Sysinternals есть одна интересная – NotMyFault. Она может искусственно генерировать различные ошибки в режиме ядра, что, конечно же, отобразит BSOD. Кроме того, в ней есть интересная возможность – изменить цвет фона и шрифта экрана смерти. Эта утилита настолько крута, что даже поставляется вместе с исходником! Однако, как я уже говорил, Руссинович классный…
Я какое-то время не мог понять, что происходит: в заголовочном файле ioctlcmd.h есть такой код:
#define IOCTL_BSOD_COLOR (ULONG) CTL_CODE(FILE_DEVICE_MYFAULT, 0x10, METHOD_BUFFERED, FILE_ANY_ACCESS)
Но это единственное место, где есть след кода, отвечающего за изменение цвета экрана смерти. Файл драйвера myfault.c содержит в себе кулинарную книгу ядерных извращений без главного блюда! Но! Собранный драйвер, по-видимому, нужный код все-таки имеет, поскольку отрабатывает на ура. «Окей», – подумал я.
Отвлечемся на минутку. Прежде, чем вы задумаете что-то сплайсить в ядре, не поленитесь заглянуть на MDSN, поскольку callback-фукнций (функций обратного вызова) в ядре предостаточно. Так и с синим экраном: есть callback-функция, вызов которой происходит сразу после отображения синего экрана. Регистрируется она следующей функцией:
BOOLEAN KeRegisterBugCheckReasonCallback(
__out PKBUGCHECK_REASON_CALLBACK_RECORD CallbackRecord,
__in PKBUGCHECK_REASON_CALLBACK_ROUTINE CallbackRoutine,
__in KBUGCHECK_CALLBACK_REASON Reason,
__in PUCHAR Component
);
Эта callback-функция указывает причину своей регистрации: либо ей необходимо дописать что-то в дамп, либо отследить момент, когда дамп уже записан, либо, указав в качестве причины KbCallbackReserved1, мы можем быть вызваны «просто так». Параметр KbCallbackReserved1 является приватным и вызывается раньше всех остальных callback-функций при возникновении критических ошибок.
Кроме этой callback-функции есть и другая, похожая, которая регистрируется следующей функцией:
BOOLEAN KeRegisterBugCheckCallback(
__out PKBUGCHECK_CALLBACK_RECORD CallbackRecord,
__in PKBUGCHECK_CALLBACK_ROUTINE CallbackRoutine,
__in_opt PVOID Buffer,
__in ULONG Length,
__in PUCHAR Component
);
Она оповещает зарегистрировавшийся модуль о критической ошибке после того, как все самое страшное уже случилось, и можно перезагружать компьютер.
Снова к делу. Увидев в дизассемблерном листинге автоматически назначенное имя функции «CallbackRoutine», я даже и не знал, куда еще можно отправиться в поисках волшебного кода. И вот же он! … Стойте, что это? «Mov – out, mov – out». Не знаю, как у вас, а у меня было такое чувство, что меня обманули. Я ждал чудес и сказки. А тут Марк берет VGA-порты и через них меняет палитру. Именно палитру! Т. е. делает синий цвет, например, зеленым, так что фон окрашивается в зеленый цвет:
mov edx, 3C8h ; порт, в который записывается индекс цвета в палитре ЦАП
mov al, 4 ; был синий
out dx, al
mov al, 0x00003F00 ; станет зеленый (6 бит на цвет)
lea ecx, [edx+1] ; edx = 0x3C9 – порт для записи цветовых компонент
mov edx, ecx
out dx, al ; установить Red-составляющую
mov eax, 0x00003F00
shr eax, 8
out dx, al ; Green
mov eax, 0x00003F00
shr eax, 10h
out dx, al ; Blue
Ну что ж, в принципе тоже сойдет. Но хочется большего.
Наслаждайтесь мелочами
Анимированный экран загрузки ОС, в принципе, дает хорошее представление о том, что можно выжать из VGA-видеорежима. Можно даже догадаться о том, что код для отрисовки графики уже есть готовый где-то в ядре. Не буду томить: нас интересует семейство функций Inbv*. Обратите внимание на то, что некоторые из них даже являются экспортируемыми из ядра. На примере реверса KiDisplayBlueScreen можно разобраться, как пользоваться такими функциями:
if (InbvIsBootDriverInstalled())
{
InbvAcquireDisplayOwnership(); // теперь мы командуем
InbvResetDisplay(); // очищаем экран, переинициализируем палитру
InbvSolidColorFill(0, 0, 639, 479, 4); // заливаем все синей краской
InbvSetTextColor(15); // а пишем белой
InbvInstallDisplayStringFilter(0); // сбрасываем callback-функцию на отображение текстовой строки
InbvEnableDisplayString(TRUE); // разрешаем писать строки
InbvSetScrollRegion(0, 0, 639, 475); // сужаем рамки экрана
…
InbvDisplayString(«Hello world!»); // выводим текст
…
};
Этими функциями можно смело пользоваться в коде своего драйвера. Но не забывайте о том, что переключившись в этот режим, вы не сможете из него так просто вернуться.
Но самая примечательная функция – InbvBitBlt:
VOID NTAPI InbvBitBlt(IN PUCHAR Buffer, IN ULONG X, IN ULONG Y)
Догадываетесь? Да, она непосредственно отрисовывает BMP-изображение (читай, BMP-файл с 256 цветами без файлового заголовка)! Проблема только в том, что она неэкспортируемая. К счастью, она – всего лишь обертка аналогичной функции VidBitBlt. Роль обертки заключается только в синхронизации отрисовки, что, в общем-то, нас не сильно интересует. А VidBitBlt экспортируется из модуля bootvid.dll, который, как можно догадаться по названию, развлекает пользователя загрузочной анимацией. Так что в путь, мои дорогие! Абсолютно легально ищем загруженные модули, парсим таблицу экспорта и получаем указатель на эту волшебную функцию. А дальше вы ограничены только своей фантазией.
Можно было бы похвастаться этой поделкой или поспорить на хот-дог, например. Главное, не забывайте, в чем разница между человеком и машиной.
Всем позитива!
Автор: HonoraryBoT