..-----------------------------------------..
..    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)  за
   благоприятный фон,создаваемый во время исследований и написания данного
   текста.