Доктор Ватсон
"Доктор Ватсон" является штатным обработчиком критических ошибок, входящим в базовый пакет поставки всех операционных систем семейства Windows. По своей природе он представляет собой статическое средство сбора релевантной информации. Предоставляя исчерпывающий отчет о причинах сбоя, "Доктор Ватсон" в тоже самое время лишен активных средств воздействия на некорректно работающее программы. Утихомирить разбушевавшееся приложение, заставив его продолжить свою работу с помощью одного "Доктора Ватсона", вы не сможете и для этого вам придется прибегать к интерактивным отладчикам, одним из которых является MicrosoftVisual Studio Debugger, входящий в состав одноименной среды разработки и рассматриваемый несколькими страницами далеениже.
Считается, что "Доктор Ватсон" предпочтительнее использовать на рабочих станциях (точнее –— на автоматизированных рабочих местах), а интерактивные средства отладки –— на серверах. Дескать, во всех премудростях ассемблера пользователи все равно не разбираются, а вот на сервере "продвинутый" отладчик будет как нельзя кстати. Отчасти это действительно так, но не стоит игнорировать то обстоятельство, что далеко не все источники ошибок обнаруживаются статическими средствами анализа, к тому же интерактивные инструменты значительно упрощают процедуру анализа. С другой стороны, "Доктор Ватсон" достается нам даром, а все остальные программные пакеты приходится приобретать за дополнительную плату. Так что предпочтительный обработчик критических ошибок вы должны выбирать сами.
Для установки "Доктора Ватсона" отладчиком по умолчанию добавьте в реестр следующую запись (листинг 3.1) или запустите файл Drwtsn32.exe c ключом "–i" (для выполнения обоих действий вы должны иметь права администратора).:
Листинг 3.1. Установка "Доктора Ватсона" отладчиком по умолчанию
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug]
"Auto"="1"
"Debugger"="drwtsn32 -p %ld -e %ld -g"
"UserDebuggerHotKey"=dword:00000000
Листинг 1 установка "Доктора Ватсона" отладчиком по умолчанию
Теперь возникновение критических ошибок программы станет сопровождаться генерацией отчета, составляемого "Доктором Ватсоном" и содержащим более или менее подробные сведения о характере ее происхождения (рис. 3.3).
Рис.унок 3.4 3. drw.gif Реакция "Доктора Ватсона" на критическую ошибку
Образец дампа (dump), созданный "Доктором Ватсоном", приведен в листинге 3.2ниже. Комментарии, добавленные автором, выделены серым цветом.:
Листинг 3.2. Образец отчета "Доктора Ватсона" с комментариями автора
Исключение в приложении:
Прил.: (pid=612)
; pid процесса, в котором произошло исключение
Время: 14.11.2003 @ 22:51:40.674
; время, когда произошло исключение
Номер: c0000005 (нарушение прав доступа)
; код категории исключения
; расшифровку кодов исключений можно найти в WINNT.H
; входящим в состав SDK, прилагаемом к любому Windows-компилятору
; подробное описание всех исключений содержится в документации
; по процессорам Intel и AMD, бесплатно распространяемой их производителями
; (внимание: для перевода кода исключения операционной системы в
; вектор прерывания ЦП, вы должны обнулить старшее слово)
; в данном случае это 0x5 – попытка доступа к памяти по запрещенному адресу
*----> Сведения о системе <----*
Имя компьютера: KPNC
Имя пользователя: Kris Kaspersky
Число процессоров: 1
Тип процессора: x86 Family 6 Model 8 Stepping 6
Версия Windows 2000: 5.0
Текущая сборка: 2195
Пакет обновления: None
Текущий тип: Uniprocessor Free
Зарегистрированная организация:
Зарегистрированный пользователь: Kris Kaspersky
; краткие сведения о системе
*----> Список задач <----*
0 Idle.exe
8 System.exe
232 smss.exe
…
1244 os2srv.exe
1164 os2ss.exe
1284 windbg.exe
1180 MSDEV.exe
1312 cmd.exe
612 test.exe
1404 drwtsn32.exe
0 _Total.exe
(00400000 - 00406000)
(77F80000 - 77FFA000)
(77E80000 - 77F37000)
; перечень загруженных DLL
; согласно документации, справа от адресов должны быть перечислены имена
; соответствующих модулей, однако практически все они так хорошо "замаскировались",
; что стали совершенно не видны. вытащить их имена из файла протокола все-таки можно,
; но придется немного пошаманить (см. ниже "таблицу символов")
Копия памяти для потока 0x188
; ниже идет копия памяти потока, вызывавшего исключение
eax=00000064 ebx=7ffdf000 ecx=00000000 edx=00000064 esi=00000000 edi=00000000
eip=00401014 esp=0012ff70 ebp=0012ffc0 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000202
; содержимое регистров и флагов
функция: <nosymbols>
; распечатка окрестной точки cбоя
00400ffc 0000 add [eax],al ds:00000064=??
; записываем в ячейку на которую ссылает EAX значение AL
; значение адреса ячейки, вычисленной Доктором Ватсоном, равно 64h
; что, очевидно, не соответствует действительности;
; Доктор Ватсон подставляет в выражение значение регистра EAX
; на момент возникновения сбоя, и это совсем не то значение, которое
; было в момент исполнения! к сожалению, чему был равен EAX в момент
; исполнения ни нам, ни Доктору Ватсону не известен.
00400ffe 0000 add [eax],al ds:00000064=??
; записываем в ячейку, на которую ссылает EAX значение AL
; как? опять? что это за бред?! вообще-то так кодируется
; последовательность 00 00 00 00, по всей видимости являющаяся
; осколком некоторой машинной команды, неправильно интерпретированной
; дизассемблерным движком Доктора Ватсона;
00401000 8b542408 mov edx,[esp+0x8] ss:00f8d547=????????
; загружаем в EDX аргумент функции
; какой именно аргумент – сказать невозможно, т.к. мы не знаем адрес
; стекового фрейма;
00401004 33c9 xor ecx,ecx
; обнуляем ECX
00401006 85d2 test edx,edx
00401008 7e18 jle 00409b22
; если EDX == 0, прыгаем на адрес 409B22h
0040100a 8b442408 mov eax,[esp+0x8] ss:00f8d547=????????
; загружаем уже упомянутый аргумент в регистр EAX
0040100e 56 push esi
; сохраняем ESI в стеке, перемещая тем самым указатель вершины стека
; на 4 байта вверх (в область младших адресов)
0040100f 8b742408 mov esi, [esp+0x8] ss:00f8d547=????????
; загружаем в ESI очередной аргумент
; поскольку ESP был только что изменен, это совсем не тот аргумент,
; с которым мы имели дело ранее
00401013 57 push edi
; сохраняем регистр EDI в стеке
СБОЙ -> 00401014 0fbe3c31 movsx edi,byte ptr [ecx+esi] ds:00000000=??
; вот мы и добрались до инструкции, возбудившей исключение доступа
; она обращается к ячейке памяти, на которую указывает сумма
; регистров ECX и ESI
; а чему равно их значение? прокручиваем экран немного вверх и находим, что
; что ECX и ESI равны 0, о чем Доктор Ватсон нам и сообщает: "ds:000000"
; отметим, что этой информации можно верить, поскольку подстановка
; эффективного адреса осуществлялась непосредственно в момент исполнения
; теперь вспомним, что ESI содержит копию переданного функции аргумента
; и что ECX был обнулен явно, следовательно в выражении [ECX+ESI]
; регистр ESI – указатель, а ECX – индекс.
; раз ESI равен нулю, то нашей функции передали указатель на невыделенную
; область памяти. обычно это происходит либо вследствие алгоритмической
; ошибки в программе, либо вследствие исчерпания виртуальной памяти
; к сожалению, Доктор Ватсон не осуществляет дизассемблирование
; материнской функции, и какой из двух предполагаемых вариантов правильный
; нам остается лишь гадать… правда, можно дизассемблировать дамп памяти
; процесса (если, конечно, он был сохранен), но это уже не то…
00401018 03c7 add eax, edi
; сложить содержимое регистра EAX с регистром EDI и записать результат в EAX
0040101a 41 inc ecx
; увеличить ECX на единицу
0040101b 3bca cmp ecx,edx
0040101d 7cf5 jl 00407014
; до тех пор пока ECX < EDX, прыгать на адрес 407014
; (очевидно, мы имеем дело с циклом, управляемым счетчиком ECX)
; при интерактивной отладке мы могли бы принудительно выйти
; из функции, возвратив флаг ошибки, чтобы материнская функция
; (а с ней и вся программа целиком) могла продолжить свое выполнение
; и в этом случае потерянной окажется лишь последняя операция, но все
; остальные данные окажутся неискаженными;
0040101f 5f pop edi
00401020 5e pop esi
00401021 c3 ret
; выходим из функции
*----> Обратная трассировка стека <----*
; содержимое стека на момент возникновения сбоя
; распечатывает адреса и параметры предыдущих выполняемых функций,
; при интерактивной отладке мы могли бы просто передать управление
; на одну из вышележащих функций, что эквивалентно возращению в прошлое
; это только в реальной жизни разбитую чашку восстановить нельзя,
; в компьютерной вселенной возможно все!
FramePtr ReturnAd Param#1 Param#2 Param#3 Param#4 Function Name
; FramePtr: указывает на значение фрейма стека,
; выше (т.е. в более младших адресах) содержатся аргументы функции
; ниже – ее локальные переменные
;
; ReturnAd: бережно хранит адрес возврата в материнскую функцию
; если здесь содержится мусор и обратная трассировка стека[Y87]
; начинает характерно шуметь, с высокой степенью вероятности
; можно предположить, что мы имеем дело с ошибкой "срыва стека"
; а возможно, и с попыткой атаки вашего компьютера
;
; Param#: четыре первых параметра функции – именно столько параметров
; Доктор Ватсон отображает на экране; это достаточно жесткое
; ограничение – многие функции имеют десятки параметров и
; четыре параметра еще ни о чем не говорят; однако недостающие
; параметры легко вытащить из копии необработанного стека вручную
; достаточно лишь перейти по указанному в поле FramePtr адресу
;
; Func Name: имя функции (если только его возможно определить); реально
; отображает лишь имена функций, импортируемые из других DLL,
; поскольку встретить коммерческую программу, откомпилированную
; вместе с отладочной информацией практически нереально
;
0012FFC0 77E87903 00000000 00000000 7FFDF000 C0000005 !<nosymbols>
0012FFF0 00000000 00401040 00000000 000000C8 00000100 kernel32!SetUnhandledExceptionFilter
; функции перечисляются в порядке их исполнения; самой последней исполнялась
; kernel32!SetUnhandledExceptionFilter функция, обрабатывающая данное исключение
*----> Копия необработанного стека <----*
; копия необработанного стека содержит стек таким, какой он есть
; очень помогает при обнаружении buffer overfull атак – весь shell-код,
; переданный злоумышленником, будет распечатан Доктором Ватсоном, и вам
; останется всего лишь опознать его (подробнее об этом рассказывается
; в моей книге "Техника сетевых атак")
0012ff70 00 00 00 00 00 00 00 00 - 39 10 40 00 00 00 00 00 ........9.@.....
0012ff80 64 00 00 00 f4 10 40 00 - 01 00 00 00 d0 0e 30 00 d.....@.......0.
…
00130090 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
001300a0 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
*----> Таблица символов <----*
; таблица символов содержит имена всех загруженных DLL вместе с именами
; импортируемых функций. используя эти адреса в качестве отправной точки,
; мы без труда сможем восстановить «перечень загруженных DLL»
ntdll.dll
77F81106 00000000 ZwAccessCheckByType
…
77FCEFB0 00000000 fltused
kernel32.dll
77E81765 0000003d IsDebuggerPresent
…
77EDBF7A 00000000 VerSetConditionMask
;
; итак, возвращаемся к таблице загруженных DLL
; (00400000 - 00406000) - это, очевидно, область памяти, занятая самой программой
; (77F80000 - 77FFA000) – это KERNEL32.DLL
; (77E80000 - 77F37000) - это NTDDL.DLL