..-----------------------------------------..
.. inside BSOD ..
.. ..
.. (опыт дизассемблирования) ..
.._________________________________________..
Многие хотя бы раз в жизни видели BSOD (Blue Screen Of Death). Если
кому-то повезло работать в win95, тот видел его каждый день и довольно
часто. По-моему - это самое замечательное что вообще может быть во всей
линейке виндов :) Синий экранчик радует глаз ровными строчками
замысловатых символов, а палец так и тянется нажать на reset. Ведь
больше уже ничто не поможет...
Со временем наблюдается даже некая эволюция этого самого действа. Если
раньше был стандартный текст стандартного размера с неким подобием
псевдографики, то сейчас - модный дизайн, шрифт, бегущие процентики
дампа памяти. Красота!
В системных API уровня ядра существует единственная функция, которая
отвечает за регистрацию исключительных ситуаций и остановку системы в
критических случаях (тот самый BSOD). Вообще на данный момент есть три
способа обрабатывать как системные, так и свои собственные ошибки. Это
т.н. "статусные коды" (status codes), SEH (Structured Exception
Handling) и ... даже не знаю как это будет звучать по-русски :) И т.н.
"bug checks".
Первый тип - наиболее простой из всех. Большинство системных функций
возвращают как раз эти самые статусные коды, сигнализирующие о том,
произошла ли какая-либо ошибка внутри нее или нет. Это всем известные
STATUS_SUCCESS и т.п.
Второй тип - более продвинутый, который поддерживается самой системой.
Не будем на нем подробно останавливаться,т.к. существует много хорошей
документации. Для примера, статья "A Crash Course on the Depths of
Win32 Structured Exception Handling" Мэта Питрека или "Win32 Exception
handling for assembler programmers" Джереми Гордона.
Последний тип - тема сегодняшнего рассказа. Как я уже и говорил, в
недрах ядра есть единственная функция, которая отвечает за генерацию
BSOD и остановку системы. По правде говоря,вызывается она двумя разными
способами, описанными в DDK так:
VOID KeBugCheck( IN ULONG BugCheckCode );
VOID KeBugCheckEx(IN ULONG BugCheckCode,IN ULONG_PTR BugCheckParameter1,\
IN ULONG_PTR BugCheckParameter2, IN ULONG_PTR BugCheckParameter3, \
IN ULONG_PTR BugCheckParameter4 );
В документации рекомендуется использовать последнюю как более новую и
продвинутую. Эти функции экспортируются модулем ntoskrnl.exe,являющимся
исполнительной системой ядра.
Да, хочу заметить, что все исследования проводились в системе XP Pro
5.1.2600 SP1. Microsoft очень сильно любит менять внутреннюю реализацию
своих api от версии к версии. Например,KeBugCheckEx в XP и KeBugCheckEx
в w2k server - две почти различные функции, хотя и отвечают они за одно
и то же.
Итак, заглянем внутрь BSOD. Для работы нам потребуются следующие вещи:
- PE Tools (http://www.uinc.ru)
- IDA Pro (где достать - без понятия, я брал с диска)
- SoftICE или всю DriverStudio, что не принципиально (можно
поискать на http://reversing.kulichki.ru)
Пока ida дизассемблирует ntoskrnl, с помощью PE Tools можно посмотреть
на те api, которые экспортирует этот модуль и найти нужные нам.
KeBugCheck отличается от KeBugCheckEx только тем, что в первой из них
просто игнорируются четыре параметра.
KeBugCheck:
xor eax, eax
push eax
push eax
push eax
push eax
push eax
push [esp+14h+arg_0]
call sub_420A66 ;Основная функция
KeBugCheckEx:
push ebp
mov ebp, esp
push 0
push [ebp+arg_10]
push [ebp+arg_C]
push [ebp+arg_8]
push [ebp+arg_4]
push [ebp+arg_0]
call sub_420A66 ;Основная функция
pop ebp
Всю логику работы BSOD можно условно разделить на три части:
1) Определяется код исключения (BugCheckCode)
2) Для некоторых из кодов выполняется различная дополнительная
работа (с использованием последних четырех параметров
KeBugCheckEx)
3) Собственно формирование текста BSOD и вывод его на экран
Начнем, как ни странно, с самого начала. Всего в DDK определено 0E4h
всевозможных кодов (смотреть в bugcodes.h). И первое, что делает
функция - это определяет какой условный тип кода к ней поступил -
системный или же код стороннего драйвера. Да, драйверы тоже могут
аварийно завершать работу системы и использовать свои собственные
номера исключений (0E5h). Проверка осуществляется довольно просто:
mov ebx, [ebp+arg_0] ;BUG code
[...]
cmp ebx, 0E5h
[...]
jnz short loc_420AA8 ;if system code
call sub_420778 ;if NOT system code --
;process user code
push 3
call ds:HalReturnToFirmware
Перед тем как определять bug code, система сохраняет регистры cr0-cr4,
dr0-dr3, dr6, dr7, gdt, idt, tr, ldt по адресу fs:20h + 1ch.
Дальнейшие действия мне немного непонятны. Зачем проверять группы кодов
различными способами? Первым из всех обрабатывается 7fh
(UNEXPECTED_KERNEL_MODE_TRAP). Затем алгоритм разветвляется и следует
проверка ошибок с номерами большими и меньшими 7fh соответственно.
Возможно это сделано из-за того, что некоторые коды возникают в системе
чаще всего. Такие как:
0Ah (IRQL_NOT_LESS_OR_EQUAL)
1Eh (KMODE_EXCEPTION_NOT_HANDLED)
23h (FAT_FILE_SYSTEM)
24h (NTFS_FILE_SYSTEM)
7Fh (UNEXPECTED_KERNEL_MODE_TRAP)
Ошибки с номерами, меньшими 7fh проверяются вычитанием, т.е. :
mov eax, ebx ;BUG code < 7fh
sub eax, ecx ;eax = BUG code - 1eh
jz short loc_420B34 ;if BUG code == 1eh
sub eax, 5
jz loc_420C07 ;if BUG code == 23h
dec eax
jz loc_420BA6 ;if BUG code == 24h
[...]
Ошибки с номерами, большими 7fh имеют непосредственные значения в коде
(константы), т.е. :
сmp ebx, 8Eh ;BUG code > 7fh
jz loc_420B34 ;if BUG code == 8eh
cmp ebx, 0A5h
jz short loc_420C07 ;if BUG code == 0a5h
cmp ebx, eax ;eax == 0c5h
jz short loc_420C07 ;if BUG code == 0c5h
[...]
Остальные же значения конкретно не обрабатываются (для подробностей
смотрите дамп функции в архиве, или дизассемблируйте и смотрите
непосредственно в системе). И еще один момент - в этом месте
осуществляется проверка непонятного кода 0C00002D1h,объяснение которому
мне не удалось нигде найти. Возможно какой-то тестовый вариант, который
забыли удалить? :)
Следующий шаг - это выполнение некоторых дополнительных действий для
определенных кодов и работа с параметрами KeBugCheckEx(последние четыре
штуки). Параметры сохраняются в структуре, определенной следующим
образом:
.data:00475C60 KiBugCheckData dd ? ;BUG code
dword_475C64 dd ? ;[ebp+arg_4]
dword_475C68 dd ? ;[ebp+arg_8]
dword_475C6C dd ? ;[ebp+arg_C]
dword_475C70 dd ? ;[ebp+arg_10]
Обрабатываются не все коды. Непосредственно идет работа с номерами:
0Ah (IRQL_NOT_LESS_OR_EQUAL)
4Ch (FATAL_UNHANDLED_HARD_ERROR)
50h (PAGE_FAULT_IN_NONPAGED_AREA)
0BEh (ATTEMPTED_WRITE_TO_READONLY_MEMORY)
0D8h (DRIVER_USED_EXCESSIVE_PTES)
Остальные не обрабатываются конкретно, другие же просто игнорируются.
И заключительный кусок кода - это формирование текста и вывод самого
BSOD. Но перед этим идет некая работа с дебаггером (если он включен
конечно). Очень странно, но в коде явно видно, что программисты
микрософта работают с Kd. Никогда не поверю, что они в глаза не видели
SoftICE и используют только свою кривую вещь, работающую к тому же с
удаленным, а не локальным компьютером. Но как бы то ни было:
cmp KdDebuggerEnabled, 0
jz short loc_420ED3 ;If no debuggers enabled
push dword_475C70
push dword_475C6C
push dword_475C68
push dword_475C64
push KiBugCheckData
push offset aFatalSystemErr ;"\n*** Fatal System " ...
call DbgPrint
Затем непосредственно вызывается KfRaiseIrql. Вся работа по
формированию строк и отображению их на экране идет с помощью функций
Inbv* (InbvSetTextColor,InbvEnableDisplayString,InbvDisplayString,...),
которые также определены в ntoskrnl.exe.
Последние строки функции - это, собственно, аварийное завершение работы
системы и (возможно, если включен) передача управления дебаггеру.
Вот такой вот он, BSOD... Немного запутанный, но отнюдь не сложный код.
Самая важная часть во всем ядре винды =) В архиве вы найдете листинг
от ida (без вложенных процедур). Хотя для лучшего понимания все же
лучше самим дизассемблить и пробежаться пару раз дебаггером :)
ЗЫ. Спасибо командам MoskovSKAya, Ska-P, сборникам SKA Chartbusters,
SKA attack и всему диску "SKA Punk" (Mad Sound Collection) за
благоприятный фон,создаваемый во время исследований и написания данного
текста.