BSOD (Blue Screen Of Death) – это последний вздох системы, после которого душа отделяется от тела (в смысле, дамп памяти сбрасывается на диск), и машина уходит в ребут, унося с собой все несохранённые данные. Здесь мы исследуем природу этого явления, а так-же попытаемся модифицировать само окно. Поскольку на системах х64 требуется подпись драйверов, а так-же предусмотрена защита "Patch Guard", эксперименты буду проводить на ядре Win-XP, хотя это не принципиально и вы можете выбрать любую ОС, т.к. всё сказанное ниже действительно для любой системы класса NT, от Win2k до Win10.
1. Источник угрозы
2. Устройство BSOD
3. Модификация окна
4. Практическое руководство
5. Заключение
1. Источник угрозы
Коварные BSOD'ы нам досаждают, когда в ядре происходят события, которые ОС не может решить самостоятельно. Практика показывает, что 90% ошибок отнюдь не фатальны - они вполне совместимы с жизнью, но ядро мастдая Ntoskrnl.exe не спрашивает нас, хотим мы продолжить работу, или предпочитаем внезапно умереть. BSOD - это драматический признак того, что в каком-либо драйвере что-то не так. Из всего списка ошибок можно выделить обращения драйверов к занятой или недоступной памяти ОЗУ, ровно как и освобождение уже свободной. По большому счёту, вся эта конструкция держится на честном слове, и в любой момент может рухнуть как карточный дом.
Начиная с Vista-x64 майки ввели цифровую подпись для драйверов - теперь левый дров можно загрузить только в дебаг-режиме работы ОС. Но выдача этой подписи процедура чисто формальная и наивно полагать, что после неё все глюки в драйверах как ветром сдует - ошибки были, есть и будут. Инженеры пытались решить проблему переносом части драйверов с ядра на прикладной уровень (например USB-девайсов), тогда при падении драйвера мы получим обычную мессагу о критической ошибке, после чего система просто перезапустит дров. Однако значительная их часть как и прежде живут в ядре, и это самые глючные драйвера, например звуковых, сетевых, и видео-устройств.
На самом деле, большая часть Win-драйверов вовсе не драйвера, а не управляющие никакими девайсами обычные программы, которые просто нуждаются в доступе к тем или иным структурам ядра, до которых невозможно дотянуться через юзер Win32-API. ОС полностью доверяет находящимся на своей территории подопечным и не следит за тем, как они используют её ресурсы. К примеру при закрытии юзер-приложения все его открытые дескрипторы на автомате закрываются, а выделенная память освобождается. Проблема в том, что на драйверы ядра этот механизм не распространяется - процедура
2. Устройство BSOD
Когда в ядре возникает ошибка, система останавливает все потоки, переводит видео-адаптер в текстовый режим (раньше 640х480, сейчас 1024х768), после чего пытается на худой палитре отобразить макс.объём информации на момент краха. Если предварительно была включена опция в свойствах системы, то создаётся дамп ядерной памяти, который позже можно будет распотрошить в поисках проблемы, например командой
Роль киллера играет экспортируемая ядром функция
По идее в ядре имеются две родственные функции:
Поскольку
3. Модификация окна BSOD
Теперь, посмотрим на код основной функции
Как говорилось выше, эта внутренняя процедура играет ключевую роль в создании экрана BSOD. Если навести курсор на функцию предыдущего скрина и нажать Enter, мы попадём в святая-святых, пролистав которую можно найти много интересного, например сброс дампа, перевод кода ошибки в текстовый эквивалент, вывод первичных сообщений функцией
Но в контексте данной темы нас больше интересуют функции с префиксами
Значит сначала идёт проверка, установлен-ли драйвер дисплея и если нет, то все последующие функции игнорируются. Если-же ок, то
Таким образом, манипулируя аргументами
Немного иную картину наблюдаем на 64-битных системах Виста+. По сути здесь всё то-же самое, только набор функций
4. Практическое руководство
Условимся, что никаких зверских деяний мы совершать не будем, ведь это не тест на выносливость, а обычная жизнь со всеми её пагубными факторами. В процессе тестов нам понадобится несколько раз умышленно сгенерить BSOD, чтоб посмотреть, сработает-ли наш план. Как вариант, можно варварски выдёргивать любой работающий PCI-девайс, или написать читающий нуль-указатель кривой драйвер. Но это всё не наши методы - есть способ попроще.
На этапе тестирования системы, инженеры специально оставили один служебный люк, который открывается ключом в определённой ветке реестра. Мы пойдём по пути наименьшего сопротивления, и ограничимся правкой этого ключа. Тогда чтобы сгенерить BSOD, достаточно будет удерживая правую клавишу
Для правки аргумента
Теперь бросаем пропатченное ядро NtHack.exe обратно в папку system32 (благо на ХР нет сторожа PatchGuard), и чтобы система его подцепила, внесём изменения в файл конфигурации загрузки boot.ini. Здесь просто добавляем вторую строку, в результате чего при загрузке ОС появится возможность выбора ядер:
После всех телодвижений загружаемся с кастомного ядра, и как появится рабочий стол жмём комбинацию клавиш
5. Заключение
Ядро Win-XP представляет собой отличный полигон для тестов, особенно в контексте программирования драйверов. Отсутствие всевозможного рода сторожей типа PatchGuard даёт нам полную свободу мыслей, и остаётся лишь реализовать их на практике. По большому счёту это инсайдер ко всем ныне актуальным ОС класса Win, т.к. современные ядра не далеко ушли от классического ядра Win-NT. Если какая-нибудь идея витает в воздухе, можно без последствий протестить её на вирт.машине, и уже потом перенести на физическую с Win-10/11. Как показал данный эксперимент, установив шунт на
1. Источник угрозы
2. Устройство BSOD
3. Модификация окна
4. Практическое руководство
5. Заключение
1. Источник угрозы
Коварные BSOD'ы нам досаждают, когда в ядре происходят события, которые ОС не может решить самостоятельно. Практика показывает, что 90% ошибок отнюдь не фатальны - они вполне совместимы с жизнью, но ядро мастдая Ntoskrnl.exe не спрашивает нас, хотим мы продолжить работу, или предпочитаем внезапно умереть. BSOD - это драматический признак того, что в каком-либо драйвере что-то не так. Из всего списка ошибок можно выделить обращения драйверов к занятой или недоступной памяти ОЗУ, ровно как и освобождение уже свободной. По большому счёту, вся эта конструкция держится на честном слове, и в любой момент может рухнуть как карточный дом.
Начиная с Vista-x64 майки ввели цифровую подпись для драйверов - теперь левый дров можно загрузить только в дебаг-режиме работы ОС. Но выдача этой подписи процедура чисто формальная и наивно полагать, что после неё все глюки в драйверах как ветром сдует - ошибки были, есть и будут. Инженеры пытались решить проблему переносом части драйверов с ядра на прикладной уровень (например USB-девайсов), тогда при падении драйвера мы получим обычную мессагу о критической ошибке, после чего система просто перезапустит дров. Однако значительная их часть как и прежде живут в ядре, и это самые глючные драйвера, например звуковых, сетевых, и видео-устройств.
На самом деле, большая часть Win-драйверов вовсе не драйвера, а не управляющие никакими девайсами обычные программы, которые просто нуждаются в доступе к тем или иным структурам ядра, до которых невозможно дотянуться через юзер Win32-API. ОС полностью доверяет находящимся на своей территории подопечным и не следит за тем, как они используют её ресурсы. К примеру при закрытии юзер-приложения все его открытые дескрипторы на автомате закрываются, а выделенная память освобождается. Проблема в том, что на драйверы ядра этот механизм не распространяется - процедура
DriverUnload() должна сама подчистить за собой все хвосты, о чём может забыть разработчик. Как результат, пул ядерной памяти медленно но верно будет приближаться к краю пропасти, и ОС может выплюнуть бсод в самом неподходящем месте.2. Устройство BSOD
Когда в ядре возникает ошибка, система останавливает все потоки, переводит видео-адаптер в текстовый режим (раньше 640х480, сейчас 1024х768), после чего пытается на худой палитре отобразить макс.объём информации на момент краха. Если предварительно была включена опция в свойствах системы, то создаётся дамп ядерной памяти, который позже можно будет распотрошить в поисках проблемы, например командой
!analyze отладчика WinDbg.Роль киллера играет экспортируемая ядром функция
KeBugCheckEx() - она вызывается из сотен мест, о чём свидетельствует лог дизассемблера IDA-Pro. Скопируем файл ядра Ntoskrnlp.exe из папки system32, и отправив его в IDA перейдём на вкладку "Exports". Теперь в окне экспорта жмём комбинацию поиска [Ctrl+F], вводим маску "KeBugCheckEx", и наконец Enter для перехода к функции. Остаётся установить курсор на имя функции (при этом оно выделяется красным), и нажать [Ctrl+Х] для сбора перекрёстных ссылок "XREF" на указанную функцию. У себя я получил 260 адресов, от куда вызывается KeBugCheckEx(). Как видим, API принимает на грудь 5 аргументов(х), первый из которых хранит код ошибки, а остальные четыре - место/время/обстоятельства её возникновения. Эти аргументы потом сбрасываются в окно BSOD:По идее в ядре имеются две родственные функции:
KeBugCheck() и расширенная KeBugCheckEx(). Первая досталась нам в наследство от Win2k, где она представляла собой трамплин к KeBugCheckEx(), которая и делала всю полезную работу. Начиная с Win-ХР обёрткой является уже сама KeBugCheckEx(), а непосредственно сбором инфы и выводом её в окно BSOD занимается теперь внутренняя (неэкспортируемая) функция ядра KeBugCheck2(). Если вскрыть первую, то становится очевидно, что она просто передаёт 5 указанных выше параметра основной функции KeBugCheck2():Поскольку
KeBugCheck2() вызывается посредством инструкции CALL, она возвращает управление обратно этому коду, и далее выход RETN 14h. Во времена Win2k был ядерный отладчик "Soft-Ice", который в реальном времени мог перехватывать обращения к трамплину KeBugCheck(). Как результат, в момент критической ошибки отладчик всплывал на входе в функцию, и модифицировав пролог MOV EDI,EDI мы могли джампом прыгнуть сразу в её конец RETN 14h, что позволяло сохранить данные в обход окна BSOD. Такой трюк был возможен даже на Win-XP с отладчиком "Syser" (Айс на хрюше уже не работал). К сожалению начиная с Висты мы потеряли это достояние, и на данный момент нет ни одного дебагера, способного вклиниться в ядро в реальном времени (WinDbg не в счёт, т.к. требует две соединённые шнурком машины).3. Модификация окна BSOD
Теперь, посмотрим на код основной функции
KeBugCheck2().Как говорилось выше, эта внутренняя процедура играет ключевую роль в создании экрана BSOD. Если навести курсор на функцию предыдущего скрина и нажать Enter, мы попадём в святая-святых, пролистав которую можно найти много интересного, например сброс дампа, перевод кода ошибки в текстовый эквивалент, вывод первичных сообщений функцией
DbgPrint() и многое другое - вот фрагмент. Обратите внимание, что функции с префиксом Ki_xx() не экспортируются ядром наружу (внутренние) от слова "Kernel Internals", в отличии от внешних "Externals" Ke_xx():Но в контексте данной темы нас больше интересуют функции с префиксами
Inbv_xx(), которые подразумевают "Init Boot Video component" и отображены на сл.скрине. Они так-же вызываются из тушки KeBugCheck2(), а детальную информацию по их аргументам можно найти здесь:Значит сначала идёт проверка, установлен-ли драйвер дисплея и если нет, то все последующие функции игнорируются. Если-же ок, то
AcquireDisplayOwnership() захватывает экран в своё распоряжение, а следующая сбрасывает его в дефолт ResetDisplay(). Последущие две функции для нас в приоритете (выделены в красные блоки) - они задают цвет фона и текста голубого экрана смерти BSOD. Вот их аргументы:
C-подобный:
InbvSolidColorFill()
--------------------
PUSH 4 = синий цвет фона
PUSH 1DFh = 480px (координата Y)
PUSH 27Fh = 640px (координата X)
PUSH ESI = 0x0 (левый/верхний угол)
InbvSetTextColor()
--------------------
PUSH 0Fh = белый цвет текста
Таким образом, манипулируя аргументами
PUSH 4/0Fh мы можем изменить вид окна, например сделать фон зелёным(2) или красным(1), а текст чёрным(0). Разрешение экрана 640х480 трогать нельзя, т.к. оно задаётся в другом месте, а здесь определяется лишь периметр для заливки цветом.Немного иную картину наблюдаем на 64-битных системах Виста+. По сути здесь всё то-же самое, только набор функций
INBV_xx() завёрнут в фантик KiDisplayBlueScreen(), о чём прямым текстом сообщает нам отладчик WinDbg. Обратите внимание на кол-во инструкций в теле KeBugCheck2() = 781, и это не считая размеров вызываемых изнутри функций. Это говорит о том, что система проделывает огромный объём работы, прежде чем склеить ласты с отображением окна BSOD:
Код:
0: kd> uf /i /c KeBugCheck2
nt!KeBugCheck2 (fffff800`0215a2f0), 781 instructions
call to nt!KeQueryCurrentStackInfo (fffff800`02066df4)
call to nt!memmove (fffff800`0208ce80)
call to hal!HalpPrepareForBugcheck (fffff800`025ff6b4)
call to nt!VfNotifyVerifierOfEvent (fffff800`02511a30)
call to nt!KiSaveCurrentEtwTraceBuffer (fffff800`0210f900)
call to nt!KiScanBugCheckCallbackList (fffff800`0210b6d0)
call to hal!HalReturnToFirmware (fffff800`025f1dc4)
call to nt!KiPcToFileHeader (fffff800`0210b7a0)
call to nt!MmIsSpecialPoolAddress (fffff800`0205698c)
call to nt!MmIsSpecialPoolAddressFree (fffff800`020fa2d0)
call to nt!MmIsSessionAddress (fffff800`0201d9a4)
call to nt!MmLocateUnloadedDriver (fffff800`020e47a0)
call to nt!KeBugCheckUnicodeToAnsi (fffff800`020e5350)
call to nt!KiDumpParameterImages (fffff800`02159de0)
call to nt!DbgPrintEx (fffff800`02076348)
call to nt!KiBugCheckDebugBreak (fffff800`02159c10)
call to hal!HalSendNMI (fffff800`025fb5a0)
call to hal!KeStallExecutionProcessor (fffff800`025f78e4)
call to nt!KiDisplayBlueScreen (fffff800`0215a070) <--------//
call to nt!KdEnableDebuggerWithLock (fffff800`0212ac60)
call to nt!InbvDisplayString (fffff800`02159cc0)
call to nt!IoIsTriageDumpEnabled (fffff800`020e73e0)
call to nt!IoAddTriageDumpDataBlock (fffff800`0210f370)
call to nt!KdCopyDataBlock (fffff800`020e6d50)
call to nt!IoWriteCrashDump (fffff800`021596c0)
call to nt!KiYieldWaitForDebugger (fffff800`02112c20)
call to nt!memset (fffff800`0208e280)
call to nt!DebugService2 (fffff800`020a5770)
call to hal!HaltSystem (fffff800`025f1ecc)
0: kd>
4. Практическое руководство
Условимся, что никаких зверских деяний мы совершать не будем, ведь это не тест на выносливость, а обычная жизнь со всеми её пагубными факторами. В процессе тестов нам понадобится несколько раз умышленно сгенерить BSOD, чтоб посмотреть, сработает-ли наш план. Как вариант, можно варварски выдёргивать любой работающий PCI-девайс, или написать читающий нуль-указатель кривой драйвер. Но это всё не наши методы - есть способ попроще.
На этапе тестирования системы, инженеры специально оставили один служебный люк, который открывается ключом в определённой ветке реестра. Мы пойдём по пути наименьшего сопротивления, и ограничимся правкой этого ключа. Тогда чтобы сгенерить BSOD, достаточно будет удерживая правую клавишу
[Ctrl] 2 раза жмякнуть [Scroll Lock] и всё.. система уйдёт в анабиоз. Ошибка известна как "MANUALLY_INITIATED_CRASH = 0xE2", или вручную сгенерированный глюк. Никаких последствий он нам не сулит, и предусмотрен мягкими специально для тестов. Значит открываем сл.ветку реестра, и создаём в ней ключ-дворд "CrashOnCtrlScroll" - значение(1) активирует фишку, а нуль отключает:
Код:
Для клавиатуры PS/2: HKLM\SYSTEM\CurrentControlSet\Services\i8042prt\Parameters
Для USB-клавиатуры : HKLM\SYSTEM\CurrentControlSet\Services\kbdhid\Parameters
Для правки аргумента
PUSH 4 функции InbvSolidColorFill() как нельзя лучше подходит двоичный редактор HIEW, хотя вы можете использовать любой по своему усмотрению. Значит открываем скопированный из папки system32 файл ядра Ntoskrnlp.exe, переименуем его например в NtHack.exe, и вскормим HIEW'у. Далее жмём в HIEW цепочку клавиш [Enter+Enter+F8+F9] для просмотра экспорта, и прокручиваем список до функции KeBugCheckEx(). Теперь зайдя в функцию по Enter жмём(1) для входа в KeBugCheck2(), и мотаем листинг пока не упрёмся в InbvSolidColorFill(). Здесь установив курсор на PUSH 4 активируем режим правки клавишей F3=Edit, и вводим для чёрного фона значение(0), для зелёного(2), или для красного(1). Аналогичным образом можно изменить и цвет текста PUSH 00F в аргументе InbvSetTextColor(). Клавиша F9 сохраняет изменения. Лично я выставил красный цвет фона(1) по такой схеме (напомню, что в дефолте было 4=синий):Теперь бросаем пропатченное ядро NtHack.exe обратно в папку system32 (благо на ХР нет сторожа PatchGuard), и чтобы система его подцепила, внесём изменения в файл конфигурации загрузки boot.ini. Здесь просто добавляем вторую строку, в результате чего при загрузке ОС появится возможность выбора ядер:
Код:
[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional RU" /execute /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Hacked Windows XP" /execute /fastdetect /kernel=NtHack.exe
После всех телодвижений загружаемся с кастомного ядра, и как появится рабочий стол жмём комбинацию клавиш
[Правый Ctrl + 2 ScrollLock]. Упс, как и положено система падает в BSOD, однако нашему взору предстаёт не голубой, а "Красный экран смерти", что может послужить пруфом к теории выше:5. Заключение
Ядро Win-XP представляет собой отличный полигон для тестов, особенно в контексте программирования драйверов. Отсутствие всевозможного рода сторожей типа PatchGuard даёт нам полную свободу мыслей, и остаётся лишь реализовать их на практике. По большому счёту это инсайдер ко всем ныне актуальным ОС класса Win, т.к. современные ядра не далеко ушли от классического ядра Win-NT. Если какая-нибудь идея витает в воздухе, можно без последствий протестить её на вирт.машине, и уже потом перенести на физическую с Win-10/11. Как показал данный эксперимент, установив шунт на
KeBugCheckEx(), мы можем полностью избавиться от BSOD'ов как от системного механизма, и это далеко не единственный пример.