Во времена рассвета DOS программист имел полную власть над системой. Находясь в реальном режиме работы CPU, у нас не было никаких ограничений и мы могли использовать в программах буквально все инструкции и регистры процессора, общее число которых на Р6 стремилось преодолеть барьер в ~200 единиц. Это была отличная школа программирования, где вся ответственность за то или иное действие возлагалась полностью на наши плечи. Всё приходилось делать вручную, в том числе и обрабатывать аппаратные исключения с ошибками. Одним словом, царила атмосфера свободы и демократии, столь редкая в наши дни.
А что мы имеем сейчас в защищённом режиме? Microsoft отобрала у нас все права и вместо них вручила более 5000 системных API, но забыла толком объяснить, для чего они и как ими пользоваться. При малейщем чихе гигантская система валится в BSOD подымая в небеса тонны пыли, не забыв прихватить с собой наши не сохранённые данные. Ладно если был-бы реальный повод, так ведь может и без веских на то причин.
В данной статье предлагаю рассмотреть способы перехвата системных исключений в защищённом режиме. Мы постараемся отобрать у системы то, что принадлежит нам по праву – это управление при возникновении различного рода эксепшенов. В системе, заправляет ими диспетчер исключений, который имеет своего секретаря "Windows Error Reporting" – именно WER при исключениях стреляет в нас модальными окнами со-знакомой всем посмертной надписью "Программа допустила ошибку и будет закрыта":
Что мы можем сделать в этой ситуации, когда большой Билли зажал нас между двумя кнопками ОК и Отмена? Выбрав "ОК" отправимся прямиком в царство Аида, иначе всплывёт отладчик (если таковой привязан), который никак не спасёт уже наше приложение – судьба его предрешена в любом случае. Но не это сейчас главное..
Если система "тасует мечеными", значит мы тоже имеем право мухлевать. В наших планах умышленное генерирование ошибок, на основе которых можно выстроить защитный механизм программ. Например все отладчики взводят TRAP-флаг, что заставляет проц после каждой инструкции генерить аппаратное исключение #DB. В реальном режиме, этот эксепшен обрабатывает прерывание биос INT_01h – отладчик перехватывает его и выводит на экран текущее состояние регистров и прочую информацию о своём пассажире.
В защищённом режиме ничего не меняется – Оля и её братия так-же взводят флаг TF, только юзер остаётся здесь в пролёте, т.к. отладчик маскирует сей факт. Как-бы мы не пытались из своего приложения взвести или сбросить TF, Оля не даст нам этого сделать, а значит мы должны забрать его силой. Тут и пригодится движок фиктивных исключений, которые мы будем сами генерить, и сами-же их отлавливать. Нужно сказать, что это не мы такие, а жизнь заставляет.. Приходится писать защитную малвару, грабить караваны, и только тогда наша советь будет чиста перед самим собой.
Устройство системного диспетчера исключений
Чтобы обойти защиту, нужно знать как она работает. Индусы (а именно их мы должны "поблагодарить" за большую часть мастдая) постарались здесь на славу. Детали реализации нам ни к чему, но если в общих чертах, то дела с обработкой исключений системой обстоят так..
Диспетчером пользовательских эксепшенов является неэкспортируемая из Ntdll.dll ядерная функциия KiUserExceptionDispatcher(). Она лежит в окопах и ждёт, когда юзер сделает то, на что программист никак не рассчитывал, и заранее не предугадал его действий. Как только наступает этот радужный для диспетчера момент, он вытаскивает из своих/широких штанин список обрабатываемых им исключений и по номерам сверяет его с возникшим. Если текущее исключение диспетчеру знакомо, он пофиксит его, иначе просто прихлопнет глючное приложение по ExitProcess() и всё. Вот это алгоритм.. всем-бы так работать.
Обработкой известных диспетчеру исключений, по-умолчанию занимается функция UnhandledExceptionFilter() из kernel32.dll. В стеке исполняемого потока имеется 8-байтный фрейм под названием SEH. Он состоит из двух 4-байтных слов, одно из которых указывает на функцию UnhandledExceptionFilter() где-то в нёдрах kernel32, а второе слово – это маркер окончания цепочки со-значением 0xFFFFFFFF. В отладчике это выглядит так:
Здесь, главное слово – цепочка "chain". Она даёт нам право добавлять к системному SEH свои обработчики, предлагая системному потесниться. В результате, управление будет получать уже не системный, а наш обработчик исключений. Эту рульную фишку должна иметь любая уважающая себя программа. Когда мы получим управление, в нашем распоряжении окажется код исключения, состояние регистров на этот момент, адрес глючной инструкции и прочая техническая инфа. Всё это предоставит нам диспетчер KiUserExceptionDispatcher() по следующему алгоритму..
Таким образом, когда наша прожка примет от диспетчера проанализированное управление, в стеке окажется фрейм "EXCEPTION_POINTER" из 4-х указателей. Потянув за них мы можем получить голограмму данной ошибки, через указанные структуры диспетчера. Прототип фрейма вполне легальный и описывается так:
Этой информации нам будет достаточно, чтобы описав пару кругов над краем пропасти, вытянуть приложение с того света.
Формат обработчика пользовательского SEH
SEH означает "Structured Exception Handling", или структурный обработчик исключений. Есть ещё VEH (векторный), но он пока не нужен. По своей природе, seh-обработчик представляет собой Callback-функцию, т.е. должен возвращать кому-то некое значение (callback в дословном переводе – перезвони). В данном случае, абонентом на линии является диспетчер исключений, который в регистре EAX ждёт от нас один из двух возможных флагов:
• EAX=0 (Execute Handler) – предписывает диспетчеру ребутнуть контекст регистров, и продолжить исполнение нашей программы. Если мы возвращаем этот флаг, то должны предварительно подправить в структуре Context нужные нам регистры и сказать диспетчеру фас! Тогда он прервёт SEH-цепочку и сразу передаст управление опять нашей программе, вдохнув в неё новую жизнь.
• EAX=1 (Continue Search) – мы отказываемся от обработки данного эксепшена (фильтруем по коду исключения в структуре Record), и просим диспетчера передать управление следующему SEH-фрейму в цепочке. Как-правило, этим следующим является системный SEH-handler, который сфинализирует нашу прожку по известному алго – R.I.P. и к праотцам.
Осталось рассмотреть содержимое структур CONTEXT и EXCEPTION_RECORD.
Чтобы подключить их к своему проекту, мы напишем специальный инклуд и будем звать его по мере необходимости.
• ExceptionCode – системный код исключения. Ознакомиться с по истине огромной коллекцией этих кодов можно в сишном заголовке ntstatus.h. На всякий-пожарный, я прикрепил его в скрепке. Смысловую нагрузку кода можно разделить на 4-части – непосредственно код, источник ошибки, флаг клиента, и важность:
• ExceptionFlag – передаёт диспетчер и указывает на то, какие значения может возвращать наш обработчик: EAX=0 или EAX=1. При значении(0), диспетчер разрешает нам обработать данное исключение, после чего можно будет перезагрузить контекст регистров с EAX=0. Если флаг(1), значит дело труба и случилось что-то непоправимое. В этом случае ребут контекста не возможен и мы должны вернуть коллбэком EAX=1.
• ExceptionInfo – дополнительная информация, непостоянна и зависит от конкретного кода исключения. К примеру если код = 0хC0000005 (ошибка доступа), здесь будет лежать следующая информация - в первом слове 0= ошибка при чтении, 1= при записи. Во-втором слове будет прописан адрес, при попытке доступа к которому произошёл сбой.
Установка пользовательского SEH
Добавить свой обработчик исключений в системную цепочку можно тремя инструкциями ассемблера. В структуре ТЕВ потока по-смещению нуль имеется поле ExceptionList, которое хранит указатель на первый SEH-фрейм в цепочке. На начало ТЕВ всегда указывает регистр FS. Значит чтобы вклиниться в цепочку обработчиков нам нужно оформить свой фрейм так, чтобы текущее значение ExceptionList стало следующим, а вместо него подставить указатель на свой обработчик. Код ниже демонстрирует сказанное:
С этого момента наш обработчик "mySeh" будет отлавливать буквально все системные исключения, а дальше мы должны их фильтровать. Например если хотим парсить только исключения типа "Ошибка доступа к памяти" с кодом 0хС0000005 (см.хидер ntstatus.h), значит проверяем код-ошибки в структуре Exception_Record на это значение. После установки пользовательского обработчика, стек потока примет такой вид:
Здесь видно, что в стеке появился опознанный Олей ещё один SEH-фрейм, после которого следует системный по-умолчанию с маркером окончания цепочки -1. Поскольку в TEB.ExceptionList прописан верхний фрейм, то при исключениях он получит управление первым. Системному может что-то перепасть только в том случае, если мы из своего коллбэка вернём диспетчеру единицу.
Практика – перехват исключений
Чтобы подкрепить вышесказанное на практике, напишем небольшое консольное приложение, которое будет отлавливать исключения любого рода и ориентации. Можно было замутить и гуй, только с выводом на экран там проблемы и затмит весь полезный код. В консоль-же будем выводить одним выстрелом дробью, через printf().
Значит ловим исключение, и провожаем его на задний двор. Дальше парсим структуры диспетчера CONTEXT и EXCEPTION_RECORD, по ходу сбрасывая нарытую информацию на консоль. Можно было вывести на экран все регистры, только их много и сути дела это не меняет.
Если посмотреть на значение регистра EIP, то оно совпадает с адресом глючной инструкции верхнего блока. Значит диспетчер сдампил регистры правильно. Диспетчер вернул нам флаг(0), тем-самым давая добро на обработку исключения. Если-бы там лежала единица, то мы должны были через callback вернуть тоже единицу, что означает необрабатываемое исключение. Для наглядности, здесь я этой проверкой принебрёг.
Заключение
В исходнике кода есть вариант установки флага трассировки TF, без которого не могут жить отладчики. Что будет, если мы взведём этот флаг? Тогда без отладчика мастдай сгенерит исключение 0х80000004, а мы его перехватим. А вот под отладчиком никакого исключения не будет, т.к. он маскирует этот флаг. Такой поворот событий может определить факт трассировки нашего приложения. Вообще этот флаг открывает перед нами большие возможности, что будет темой для следующего разговора.
А что мы имеем сейчас в защищённом режиме? Microsoft отобрала у нас все права и вместо них вручила более 5000 системных API, но забыла толком объяснить, для чего они и как ими пользоваться. При малейщем чихе гигантская система валится в BSOD подымая в небеса тонны пыли, не забыв прихватить с собой наши не сохранённые данные. Ладно если был-бы реальный повод, так ведь может и без веских на то причин.
В данной статье предлагаю рассмотреть способы перехвата системных исключений в защищённом режиме. Мы постараемся отобрать у системы то, что принадлежит нам по праву – это управление при возникновении различного рода эксепшенов. В системе, заправляет ими диспетчер исключений, который имеет своего секретаря "Windows Error Reporting" – именно WER при исключениях стреляет в нас модальными окнами со-знакомой всем посмертной надписью "Программа допустила ошибку и будет закрыта":
Что мы можем сделать в этой ситуации, когда большой Билли зажал нас между двумя кнопками ОК и Отмена? Выбрав "ОК" отправимся прямиком в царство Аида, иначе всплывёт отладчик (если таковой привязан), который никак не спасёт уже наше приложение – судьба его предрешена в любом случае. Но не это сейчас главное..
Если система "тасует мечеными", значит мы тоже имеем право мухлевать. В наших планах умышленное генерирование ошибок, на основе которых можно выстроить защитный механизм программ. Например все отладчики взводят TRAP-флаг, что заставляет проц после каждой инструкции генерить аппаратное исключение #DB. В реальном режиме, этот эксепшен обрабатывает прерывание биос INT_01h – отладчик перехватывает его и выводит на экран текущее состояние регистров и прочую информацию о своём пассажире.
В защищённом режиме ничего не меняется – Оля и её братия так-же взводят флаг TF, только юзер остаётся здесь в пролёте, т.к. отладчик маскирует сей факт. Как-бы мы не пытались из своего приложения взвести или сбросить TF, Оля не даст нам этого сделать, а значит мы должны забрать его силой. Тут и пригодится движок фиктивных исключений, которые мы будем сами генерить, и сами-же их отлавливать. Нужно сказать, что это не мы такие, а жизнь заставляет.. Приходится писать защитную малвару, грабить караваны, и только тогда наша советь будет чиста перед самим собой.
Устройство системного диспетчера исключений
Чтобы обойти защиту, нужно знать как она работает. Индусы (а именно их мы должны "поблагодарить" за большую часть мастдая) постарались здесь на славу. Детали реализации нам ни к чему, но если в общих чертах, то дела с обработкой исключений системой обстоят так..
Диспетчером пользовательских эксепшенов является неэкспортируемая из Ntdll.dll ядерная функциия KiUserExceptionDispatcher(). Она лежит в окопах и ждёт, когда юзер сделает то, на что программист никак не рассчитывал, и заранее не предугадал его действий. Как только наступает этот радужный для диспетчера момент, он вытаскивает из своих/широких штанин список обрабатываемых им исключений и по номерам сверяет его с возникшим. Если текущее исключение диспетчеру знакомо, он пофиксит его, иначе просто прихлопнет глючное приложение по ExitProcess() и всё. Вот это алгоритм.. всем-бы так работать.
Обработкой известных диспетчеру исключений, по-умолчанию занимается функция UnhandledExceptionFilter() из kernel32.dll. В стеке исполняемого потока имеется 8-байтный фрейм под названием SEH. Он состоит из двух 4-байтных слов, одно из которых указывает на функцию UnhandledExceptionFilter() где-то в нёдрах kernel32, а второе слово – это маркер окончания цепочки со-значением 0xFFFFFFFF. В отладчике это выглядит так:
Здесь, главное слово – цепочка "chain". Она даёт нам право добавлять к системному SEH свои обработчики, предлагая системному потесниться. В результате, управление будет получать уже не системный, а наш обработчик исключений. Эту рульную фишку должна иметь любая уважающая себя программа. Когда мы получим управление, в нашем распоряжении окажется код исключения, состояние регистров на этот момент, адрес глючной инструкции и прочая техническая инфа. Всё это предоставит нам диспетчер KiUserExceptionDispatcher() по следующему алгоритму..
1. Перехватив ошибку, диспетчер сбрасывает в стек породившего исключение потока состояние всех его регистров CPU – этот т.н. "Context потока" и для его описания имеется специальная структура, где указывается очерёдность следования регистров в дампе. Среди этих регистров есть и доступный для модификации регистр EIP, который позволит нашему коду после исключения отправиться в будущее, настоящее или прошлое – это мы должны будем предусмотреть заранее, посредством метки в коде.
2. Теперь диспетчер засылает в стек ещё одну структуру "ExceptRecord", с подробными деталями возникшего эксепшена. Эта структура хранит код ошибки, адрес породившей исключение инструкции и некоторые флаги, которые рассматриваются ниже.
3. На заключительном этапе, поместив в стек два указатели на структуры Context и ExceptRecord, диспетчер отдаёт управление опять нашей программе, чтобы свои проблемы она попыталась решить сама, посредством установленного SEH-обработчика. Если пользовательского обработчика в стеке нет, то всю эту кухню на грудь принимает системный SEH, который представлен на рисунке выше.
Таким образом, когда наша прожка примет от диспетчера проанализированное управление, в стеке окажется фрейм "EXCEPTION_POINTER" из 4-х указателей. Потянув за них мы можем получить голограмму данной ошибки, через указанные структуры диспетчера. Прототип фрейма вполне легальный и описывается так:
C-подобный:
struct EXCEPTION_POINTERS ;// Стековый фрейм диспетчера исключений
pExceptRecord dd 0 ; линк на тех-информацию об исключении
pExceptFrame dd 0 ; линк на установленный нами SEH-фрейм
pContext dd 0 ; линк на контекст регистров процессора
pParam dd 0 ; резерв..
ends
Этой информации нам будет достаточно, чтобы описав пару кругов над краем пропасти, вытянуть приложение с того света.
Формат обработчика пользовательского SEH
SEH означает "Structured Exception Handling", или структурный обработчик исключений. Есть ещё VEH (векторный), но он пока не нужен. По своей природе, seh-обработчик представляет собой Callback-функцию, т.е. должен возвращать кому-то некое значение (callback в дословном переводе – перезвони). В данном случае, абонентом на линии является диспетчер исключений, который в регистре EAX ждёт от нас один из двух возможных флагов:
• EAX=0 (Execute Handler) – предписывает диспетчеру ребутнуть контекст регистров, и продолжить исполнение нашей программы. Если мы возвращаем этот флаг, то должны предварительно подправить в структуре Context нужные нам регистры и сказать диспетчеру фас! Тогда он прервёт SEH-цепочку и сразу передаст управление опять нашей программе, вдохнув в неё новую жизнь.
• EAX=1 (Continue Search) – мы отказываемся от обработки данного эксепшена (фильтруем по коду исключения в структуре Record), и просим диспетчера передать управление следующему SEH-фрейму в цепочке. Как-правило, этим следующим является системный SEH-handler, который сфинализирует нашу прожку по известному алго – R.I.P. и к праотцам.
Осталось рассмотреть содержимое структур CONTEXT и EXCEPTION_RECORD.
Чтобы подключить их к своему проекту, мы напишем специальный инклуд и будем звать его по мере необходимости.
C-подобный:
struct EXCEPTION_RECORD ;// Техническая информация об исключении
ExceptionCode dd ? ; код ошибки - см.сишный хидер "ntstatus.h"
ExceptionFlags dd ? ; флаг управления данным обработчиком
NestedRecord dd ? ; указатель на предыдущий SEH-фрейма
ExceptionAddress dd ? ; адрес инструкции вызвавшей исключение
NumberParameters dd ? ; число доп.параметров в сл.поле "ExceptionInfo"
ExceptionInfo rd 15 ; дополнительные параметры.
ends
• ExceptionCode – системный код исключения. Ознакомиться с по истине огромной коллекцией этих кодов можно в сишном заголовке ntstatus.h. На всякий-пожарный, я прикрепил его в скрепке. Смысловую нагрузку кода можно разделить на 4-части – непосредственно код, источник ошибки, флаг клиента, и важность:
• ExceptionFlag – передаёт диспетчер и указывает на то, какие значения может возвращать наш обработчик: EAX=0 или EAX=1. При значении(0), диспетчер разрешает нам обработать данное исключение, после чего можно будет перезагрузить контекст регистров с EAX=0. Если флаг(1), значит дело труба и случилось что-то непоправимое. В этом случае ребут контекста не возможен и мы должны вернуть коллбэком EAX=1.
• ExceptionInfo – дополнительная информация, непостоянна и зависит от конкретного кода исключения. К примеру если код = 0хC0000005 (ошибка доступа), здесь будет лежать следующая информация - в первом слове 0= ошибка при чтении, 1= при записи. Во-втором слове будет прописан адрес, при попытке доступа к которому произошёл сбой.
Установка пользовательского SEH
Добавить свой обработчик исключений в системную цепочку можно тремя инструкциями ассемблера. В структуре ТЕВ потока по-смещению нуль имеется поле ExceptionList, которое хранит указатель на первый SEH-фрейм в цепочке. На начало ТЕВ всегда указывает регистр FS. Значит чтобы вклиниться в цепочку обработчиков нам нужно оформить свой фрейм так, чтобы текущее значение ExceptionList стало следующим, а вместо него подставить указатель на свой обработчик. Код ниже демонстрирует сказанное:
C-подобный:
push mySeh ;// первый dword нашего SEH-фрейма = указатель на обработчик
push dword[fs:0] ;// второй dword – текущее значение ExceptionList
mov dword[fs:0],esp ;// подменяем ExceptionList на свой фрейм!
С этого момента наш обработчик "mySeh" будет отлавливать буквально все системные исключения, а дальше мы должны их фильтровать. Например если хотим парсить только исключения типа "Ошибка доступа к памяти" с кодом 0хС0000005 (см.хидер ntstatus.h), значит проверяем код-ошибки в структуре Exception_Record на это значение. После установки пользовательского обработчика, стек потока примет такой вид:
Здесь видно, что в стеке появился опознанный Олей ещё один SEH-фрейм, после которого следует системный по-умолчанию с маркером окончания цепочки -1. Поскольку в TEB.ExceptionList прописан верхний фрейм, то при исключениях он получит управление первым. Системному может что-то перепасть только в том случае, если мы из своего коллбэка вернём диспетчеру единицу.
Практика – перехват исключений
Чтобы подкрепить вышесказанное на практике, напишем небольшое консольное приложение, которое будет отлавливать исключения любого рода и ориентации. Можно было замутить и гуй, только с выводом на экран там проблемы и затмит весь полезный код. В консоль-же будем выводить одним выстрелом дробью, через printf().
Значит ловим исключение, и провожаем его на задний двор. Дальше парсим структуры диспетчера CONTEXT и EXCEPTION_RECORD, по ходу сбрасывая нарытую информацию на консоль. Можно было вывести на экран все регистры, только их много и сути дела это не меняет.
C-подобный:
;// EXCEPT.INC - инклуд fasm'a,
;// для пользовательской SEH-обработки внутрипоточных исключений
;// ------------------------------------------------------------
EXCEPTION_MAXIMUM_PARAMETERS = 15
MAXIMUM_SUPPORTED_EXTENSION = 512
SIZE_OF_80387_REGISTERS = 80
struct FLOATING_SAVE_AREA
ControlWord dd ?
StatusWord dd ?
TagWord dd ?
ErrorOffset dd ?
ErrorSelector dd ?
DataOffset dd ?
DataSelector dd ?
RegisterArea rb SIZE_OF_80387_REGISTERS
Cr0NpxState dd ?
ends
struct CONTEXT
ContextFlags dd ?
iDr0 dd ?
iDr1 dd ?
iDr2 dd ?
iDr3 dd ?
iDr6 dd ?
iDr7 dd ?
FloatSave FLOATING_SAVE_AREA
regGs dd ?
regFs dd ?
regEs dd ?
regDs dd ?
regEdi dd ?
regEsi dd ?
regEbx dd ?
regEdx dd ?
regEcx dd ?
regEax dd ?
regEbp dd ?
regEip dd ?
regCs dd ?
regFlag dd ?
regEsp dd ?
regSs dd ?
ExtendedRegisters rb MAXIMUM_SUPPORTED_EXTENSION
ends
struct EXCEPTION_RECORD
ExceptionCode dd ?
ExceptionFlags dd ?
pExceptionRecord dd ?
ExceptionAddress dd ?
NumberParameters dd ?
ExceptionInformation rd EXCEPTION_MAXIMUM_PARAMETERS
ends
struct EXCEPTION_POINTERS
pExceptionRecord dd ?
pExceptionFrame dd ?
pExceptionContext dd ?
pParam dd ?
ends
C-подобный:
format pe console
include 'win32ax.inc'
include 'except.inc' ;// Подключаем инклуд обработки эксепшенов
entry start
;//------
.data
capt db 13,10,' R.I.P. example v0.1'
db 13,10,' *******************************',0
report db 13,10,' Exception code...: %08X'
db 13,10,' Exception address: %08X'
db 13,10,' Exception flag...: %08X'
db 13,10,10,' ======= Registers dump ========'
db 13,10,' CS....: %04X'
db 13,10,' EIP...: %08X'
db 13,10,' ESP...: %08X'
db 13,10,' EFLAGS: %08X'
db 13,10,' EAX...: %08X EBX: %08X'
db 13,10,' ECX...: %08X EDX: %08X',0
frmt db '%s',0
;//------
.code
start: invoke printf,capt
;//-- Устанавливаем своей обработчик исключений
push mySeh ; указатель на обработчик
push dword[fs:0] ; указатель на следующй SEH-фрейм
mov dword[fs:0],esp ; подмена в TEB.ExceptionList
;//-- Вызываем исключение!
xor ebx,ebx ; EBX:=0
div ebx ; попытка деления EAX на нуль!
; ..получим исключение типа 0хС0000094.
;//======================
;// mov eax,[ebx] ; можно прочитать с нулевого указателя!
;// ; ..получим исключение 0хС0000005.
;//======================
;// pushf ; можно взвести флаг трассировки TF
;// pop eax ; ..берём EFLAGS в EAX
;// or ah,1 ; ..взводим бит(8) TRAP-flag
;// push eax ; ..обновляем EFLAGS новым значением
;// popf ; ..получим исключение 0x80000004.
;//======================
next: invoke scanf,frmt,capt ;// на метку "NEXT" мы натравим EIP в структуре CONTEXT
invoke exit,0 ;
;//=====================================
;//=== Пользовательский SEH-обработчик
;//==== в качестве аргументов передаём структуру "EXCEPTION_POINTERS" диспетчера
proc mySeh pRecord, pFrame, pContext, pParam
mov esi,[pRecord]
mov eax,[esi+EXCEPTION_RECORD.ExceptionCode]
mov ebx,[esi+EXCEPTION_RECORD.ExceptionAddress]
mov ecx,[esi+EXCEPTION_RECORD.ExceptionFlags]
mov esi,[pContext]
invoke printf,report,eax,ebx,ecx,\
[esi+CONTEXT.regCs],[esi+CONTEXT.regEip],\
[esi+CONTEXT.regEsp],[esi+CONTEXT.regFlag],\
[esi+CONTEXT.regEax],[esi+CONTEXT.regEbx],\
[esi+CONTEXT.regEcx],[esi+CONTEXT.regEdx]
;//=== ВНИМАНИЕ! Правим регистр EIP, чтобы код продолжился с метки NEXT
mov esi,[pContext] ; указатель на структуру диспетчера
mov eax,next ; указатель на метку перехода
mov [esi+CONTEXT.regEip],eax ; фиксим EIP в структуре "CONTEXT"
;//=== Callback диспетчеру EAX = 0 (перезагрузить и продолжить)
xor eax,eax ;
ret ;
endp ;
;//==========================================
;//=== Секция импорта =======================
data import
library msvcrt, 'msvcrt.dll'
import msvcrt, printf,'printf',scanf,'scanf',exit,'exit'
end data
Если посмотреть на значение регистра EIP, то оно совпадает с адресом глючной инструкции верхнего блока. Значит диспетчер сдампил регистры правильно. Диспетчер вернул нам флаг(0), тем-самым давая добро на обработку исключения. Если-бы там лежала единица, то мы должны были через callback вернуть тоже единицу, что означает необрабатываемое исключение. Для наглядности, здесь я этой проверкой принебрёг.
Заключение
В исходнике кода есть вариант установки флага трассировки TF, без которого не могут жить отладчики. Что будет, если мы взведём этот флаг? Тогда без отладчика мастдай сгенерит исключение 0х80000004, а мы его перехватим. А вот под отладчиком никакого исключения не будет, т.к. он маскирует этот флаг. Такой поворот событий может определить факт трассировки нашего приложения. Вообще этот флаг открывает перед нами большие возможности, что будет темой для следующего разговора.
Вложения
Последнее редактирование: