Статья FASM. Обработка исключений в Win-64

На этапе проектирования софта, как-правило предусматривают в нём модуль обработки ошибок и исключений. На жаргоне программистов его называют «защитой от дурака», хотя не исключён вариант, что дураком является здесь как-раз сам разработчик, поскольку плохо продумал пользовательский интерфейс. В любом случае «Exception Handler» должен присутствовать в коде, но с переходом на Win-64 мы сталкиваемся с проблемой. Дело в том, что основанный на стековых фреймах привычный механизм SEH больше не работает – на х64 он подвергся полной «прокачке» и требует теперь абсолютно иного подхода. В данной статье попробуем освятить достоинства и недостатки усовершенствованного механизма SEH-х64.

Оглавление:

1. Введение;
2. Проблемы отладчика WinDbg на 64-бит системах Win7;
3. Программная реализация обработки исключений;
4. Структуры обработчиков;
5. Секция исключений «.pdata»;
6. Практика – пример программы;
7. Заключение.



1. Введение и терминология

Интеллект центрального процессора заключается в способности отлавливать им допущенные как пользователем, так и программистом ошибки. Если глюк глобальный и может отрицательно повлиять на дальнейший ход событий, при помощи ловушек Trap система пытается сама устранить его, иначе проц забивается в угол и от безысходности генерит исключение Еxception. В сишном хидере Ntstatus.h можно найти коды 37 эксепшенов, которые предоставляет диспетчер в надежде на то, что в системе найдётся заинтересованное лицо и решит всё-таки возникшую проблему. В противном случае ОС выстрелит в нас своим окном «Ошибка приложения», с бесполезным предложением найти ответ в интернете.

Чтобы получить от диспетчера код-исключения и повесить на него свой обработчик, все системы класса Win предлагают нам два механизма – это векторный и структурный, причём в силу своей простоты последний используется чаше собрата, и звучит как «Structured Exception Handler» SEH. В классическом виде, он держится всего на двух указателях – первый хранит адрес сл.фрейма в цепочке, второй – адрес обработчика. Связывание SEH-фреймов в цепочки Chain дают возможность обрабатывать исключения разного типа, в зависимости от их кодов.

Такая схема верой и правдой прослужила нам без малого пол-века, но за такой период нажила себе и врагов. Как и следовало ожидать, уязвимым местом стало использование ею системного стека. Переполняющая буфера малварь могла запросто затереть стековый SEH-фрейм, в результате чего вся конструкция по обработке исключений с треском падала, в очередной раз доказывая свою несостоятельность.

Учитывая сей факт, на системах Win64 инженеры выбрали другой подход. Теперь в SEH-фреймах прописываются не указатели, а сразу несколько связанных между собой структур. В базовом варианте обязательны лишь две из них – это основная RUNTIME_FUNCTION размером в 12-байт, и привязанная к ней UNWIND_INFO такого-же размера. Итого 24-байт вместо восьми. Чтобы не было путаницы, блок из этих\двух структур условимся называть «SEH-кадром», хотя кадр это и есть frame на англ.манер.

Как и в случае цепочки фреймов Win32, SEH-кадров может быть несколько – каждый из них отслеживает исключения в своей зоне программного кода. На рис.ниже представлены отличительные особенности обработки исключений на различных системах:


SEH-x64.png


Из этой схемы видно, что инженеры не просто обновили старый механизм обработки исключений (как это было, например, с ключом /safeseh в Win32), а полностью переработали его до неузнаваемости. Как показала практика, старая схема изжила себя, безотлагательно требуя новых решений. Отметим, идея не нова и тестировалась ещё на 64-битной ХРюше, и видимо теперь пришло её время – как говорится: «Наши топоры, лежали до поры..»

• Во-первых, стек приложения освободили от всего хлама, и перенесли новые структуры в специально предназначенную для этих целей, секцию РЕ-файла «.pdata». Благодаря такому подходу, затирающая стек малварь теперь не сможет нарушить работу механизма, пока в корень не сменит стратегию.
• Во-вторых, обработчики исключений и вся их поддержка устанавливаются уже не динамически (после загрузки образа в память), а статически, на этапе компиляции кода. Ясно, что это требует от нас некоторой квалификации и «правильных» телодвижений, но взамен получаем невероятно устойчивый к сбоям механизм.
• Если в приложении много контролируемых участков кода (в сишке они оборачиваются в блоки _try\_except), нам нужно организовать массив кадров в секции .pdata (на рис.выше их всего 4). В своём коде мы можем располагать кадры последовательно в произвольном порядке, однако на этапе загрузки образа в память, системный загрузчик сортирует их по возрастанию адресов подконтрольных им блоков, предоставляя диспетчеру более удобную схему для поиска.

Алгоритм работы диспетчера такой, что в момент исключения он получает от ядра адрес глючной инструкции (в виде текущего значения регистра RIP), и должен найти соответствие этому значению в структурах каждого из четырёх представленных кадров. В литературе этот процесс назвали «раскруткой кадров в стеке», чем собственно и занимается функция диспетчера RtlVirtualUnwind(). Обнаружив нужный кадр, диспетчер сможет изъять из его структуры UNWIND_INFO адрес обработчика возникшего исключения.


2. Проблемы отладчика WinDbg на 64-бит системах Win7

Всем, кто увлекается кодесами, часто требуются описания различных структур. Искать их каждый раз на MSDN не удобно от слова совсем, а если учесть, что представленные там прототипы в большинстве случаях для х32, имеет смысл установить на своём узле отладчик WinDbg, и командой dt запрашивать экстерны у него. Так мы убиваем сразу двух зайцев – получаем самые актуальные версии структур, и всё-это не отходя от кассы.

Однако на 64-бит системах Win7, без танцев с бубном локальная отладка ядра в WinDbg не поддерживается. Решать проблему энтузиасты предлагают при помощи ключа конфигуратора загрузки: bcdedit /debug on. По личному опыту могу сказать, что этот совет не только бесполезный для этих систем, но после него в лучшем случае мастдай вообще перестанет грузиться (на старте виснет, ожидая отлаживаемую ОС), а в худшем – роняет систему в BSOD.

Cвязано это с тем, что с переходом семёрки на 64-бит, в поддерживающих дебаг ядра библиотеках WinDbg появились ещё две дополнительные проверки. Смысл их в том, чтобы полностью отключить обработку исключений пользовательского режима, при отладке ядра. Обойти данный тест можно подкрутив бут-менеджер следующим образом (см.справку: bcdedit /? /dbgsettings):

1. bcdedit /debug on 2. bcdedit /dbgsettings local /start disable /noumex

Если повезёт, с выставленными в такую позицию шестерёнками мы сможем активировать режим «Local-Kernel-Debug» на системе Win7-64. Но здесь нужно учитывать, что с включенной опцией /debug воспроизведение DVD/MPEG2 не будет работать в системе – это не ошибка, а изначально так задумано разработчиком. Мда.. картина создаёт удручающее впечатление. Чтобы подобраться к структурам ядра, мы должны пожертвовать юзермодными исключениями, и кодеками MPEG2. Имхо обмен не рентабельный..

Можно-ли обойтись малой кровью и найти другое решение? Оказалось, что да и заключается оно в следующем..
Скачиваем с репозитория Microsoft веб-установщик WinDbg (для ленивых я положил его в скрепку), в окне установщика выбираем SDK и DebugingTools, после чего ОК и ждём окончания процесса (установщик должен скачать порядка 250 МБ). По завершению, конфигуратор bcdedit вообще не трогаем, а скачиваем пакет Руссиновича , в составе которого имеется консольная утилита «LiveKD».

Запустив её на исполнение обнаруживаем, что она магическим образом открыла все замки в Kernel, и нам стала доступна локальная отладка ядра. Ради интереса я проверил настройки загрузчика ОС (bcdedit без параметров), и ключа /debug в нём не нашёл. Можно-же оказывается не доставать бубен, но видимо Microsoft это не устраивает, и нужно обязательно создать проблемы юзеру. В результате получаем окно ниже, которое отзывается на все стандартные команды WinDbg:


LiveKD.png



3. Программная реализация SEH64

Выше упоминалось, что в базовой форме, каждый SEH-кадр включает в себя две структуры.
Обязательная IMAGE_RUNTIME_FUNCTION_ENTRY должна присутствовать во-всех кадрах, и описывает один программный блок кода, внутри которого диспетчер будет отлавливать исключения. Если в отладчике выше ввести команду dt и аргументом передать имя этой структуры, он вернёт нам следующее:

C-подобный:
struct IMAGE_RUNTIME_FUNCTION_ENTRY
  BeginAddress        dd  0  ;// RVA-адрес начала блока
  EndAddress          dd  0  ;// RVA-адрес конца блока
  UnwindInfoAddress   dd  0  ;// RVA-линк на дочернюю структуру "UNWIND_INFO"
ends

Обратите внимание, что в этой структуре нужно указывать адреса относительно базы загрузки образа в памяти, или «Relative-Virtual-Address» (относительный). Причиной тому системный механизм ASLR (Advanced-Space-Layot-Randomization), который призван при каждой перезагрузке системы рандомно менять базу исполняемых файлов в вирт.памяти (в дефолте она выставляется компиляторами на значение 0х00400000). Поэтому в структуре выше адреса указываются без базы, а диспетчер потом добавляет к ним текущую, от ASLR.

Последнее поле «UnwindInfoAddress» хранит указатель на вторую структуру кадра UNWIND_INFO, и она требует некоторых пояснений. Основное условие – это варавнивание её в памяти на 16-байтную границу (хотя в доках указано 8), иначе диспетчер посчитает содержимое не валидным. Структура имеет битовые поля, а потому асматикам придётся заполнять их инструкцией сдвига влево SHL, ну или использовать сразу предопределённые константы 9 и 19h.

Код:
0: kd> dt _unwind_info
WDFLDR!_UNWIND_INFO
   +0x000 Version        : Pos 0. 3-Bits  ;// 3-бита под версию (1 или 2)
   +0x000 Flags          : Pos 3. 5-Bits  ;// 5-бит  под флаги (см.ниже)
   +0x001 SizeOfProlog   : UChar          ;// байт с размером прогола функции
   +0x002 CountOfCodes   : UChar          ;// байт с кол-вом вложенных структур UNWIND_CODE
   +0x003 FrameRegister  : Pos 0. 4-Bits  ;// 4-бита под регистр-кадра (0=RAX и т.д)
   +0x003 FrameOffset    : Pos 4. 4-Bits  ;// 4-бита под смещение кадра
   +0x004 UnwindCode     : _UNWIND_CODE   ;// массив кодов раскрутки (зависит от флагов)
0: kd>

Не пугайтесь такому зоопарку опций – байты по смещению 1,2,3 мы просто выставим в нуль, что означает дефолт. Версию и флаги ставим в 1. Двоичная маска флагов может принимать значения ниже, и нашим требованиям удовлетворяет «Execute» (обрабатывать исключения). Флаг со-значением нуль (Next) даёт постановку диспетчеру пропустить данный кадр при раскрутке стека-исключений, т.е. делает привязанный к кадру обработчик полностью недееспособным. Этим поспешила воспользоваться малварь и прочая нечисть.


C-подобный:
#define UNW_FLAG_NHANDLER  = 00000 = 0  ;// Next    (пропустить)
#define UNW_FLAG_EHANDLER  = 00001 = 1  ;// Execute (обработать)
#define UNW_FLAG_UHANDLER  = 00010 = 2  ;// Unwind  (раскрутить)
#define UNW_FLAG_CHAININFO = 00100 = 4  ;// Chain   (это цепочка)

Теперь рассмотрим формат вложенной структуры UNWIND_CODE, которая непостоянна, а содержимое её напрямую зависит от флагов E,U,C. Поскольку расположившись в хвосте она фактически является продолжением UNWIND_INFO, то объединив все битовые поля, последнюю можно представить так:


C-подобный:
struct UNWIND_INFO
  VersionFlags       db  9  ;// флаг и версия = по 1
  SizeOfProlog       db  0  ;// дефолт 16-байт
  CountOfCodes       db  0  ;// отсчёт с нуля = 1
  FrameRegOffset     db  0  ;// 0=RAX регистр фрейма
;//---
;//--- if Flags = 001b (eHandler, обработка)
  ExceptHandlerAddr  dd  0  ;// RVA-адрес обработчика исключения

;//--- if Flags = 010b (uHandler, раскрутить кадры в стеке)
  UnwindCode         UNWIND_CODE  ;// вложенная структура

;//--- if Flags = 100b (cHandler, это цепочка, так-что опять вставляем первую структуру кадра)
  FunctionEntry      IMAGE_RUNTIME_FUNCTION_ENTRY

  LangSpecificData   dd  0  ;// нет специфичных для языка-программирования данных
ends

Глядя на неё можно сделать вывод, что содержимое этой структуры диспетчер интерпретирует в одном из трёх вариантов, а мы будем использовать только первый ExceptionHandler, передав диспетчеру адрес своего обработчика исключений. Второй варик предусмотрен на случай, когда исключение возникает не в основной функции программного кода, а во-вложенной в неё. Тогда диспетчеру придётся раскручивать стек, двигаясь назад к параметрам основной функции, при этом черпая информацию из структуры UNWIND_CODE. Третий вариант – это схема обработки исключений с цепочкой структур IMAGE_RUNTIME_FUNCTION_ENTRY, и соответственно первая связывается со-следующей своей ксерокопией. Поле с языком заполняют компиляторы си, поэтому в ассме оно нам не интересно и ставим его в нуль.

А вот как выглядит вся эта болтология в графическом виде.
На этой схеме имеем один обработчик на два подконтрольных блока в приложении. По сути можно было к каждому из них привязать свой кадр с отдельными парами структур RUNTIME_FUNCTION + UNWIND_INFO, но в своей демке я внутри обработчика проверяю код-исключения, и по его значению возвращаюсь обратно на нужный участок кода. В реальных программах нужно избегать таких\ленивых алгоритмов, а в качестве демонстрации и так сойдёт:


Handler_Scheme.png


Ассемблер FASM выделяется на фоне остальных своими макросами. Так, чтобы вычислить относительный адрес RVA, нам не нужно знать базу образа в памяти (тем более, что ASLR меняет её по своему усмотрению) – достаточно вставить в нужное поле одноимённый макрос «RVA», и транслятор сам рассчитает относительный адрес. Не знаю, есть-ли подобная фишка в других ассемблерах типа MASM, NASM и в зоопарке остальных, но данный макрос несомненно избавляет нас от скучной рутины.


4. Формат обработчиков исключений

В момент, когда наш обработчик исключения получит штурвал, диспетчер должен передать ему техническую инфу о возникшем исключении. На системах х32 это был фрейм в стеке приложения, где лежали 4-указателя на служебные структуры. Но в системах Win64 аргументы передаются функциям уже через регистры RCX,RDX,R8,R9, а потому нужно искать информацию именно в них. Таким образом, на входе в обработчик EXCEPTION_ROUTINE, в перечисленных регистрах можно будет найти следующие указатели:


Except_Routine.png


Как видно из этой справки, актуальная для нас информация передаётся диспетчером только в регистрах RCX и R8. Для удобного доступа к структурам RECORD64 и CONTEXT, имеет смысл виртуализировать их по адресу. Воплотить эту идею в реальность способен оператор virtual ассемблера FASM. Вот как данная фишка будет выглядеть в коде исходника:


C-подобный:
section '.xdata' code readable executable
proc  Handler   ;//<----- обработчик исключения!
;//-------------
  virtual at rcx
          record   EXCEPTION_RECORD64
  end virtual
;//-------------
  virtual at r8
          context  CONTEXT64
  end virtual
. . . . . .

Теперь внутри обработчика, мы можем в любой момент обращаться к этим структурам, например: mov rax,[record.ExceptionCode]. По названию структуры CONTEXT легко догадаться, что диспетчер сбрасывает в неё содержимое всех регистров процессора на момент исключения, включая MMX и XMM. На 64-бит системах она имеет размер 1224-байт, поэтому я зарыл её в спойлер ниже.

Когда мы обработаем исключение, нужно будет вернуть управление диспетчеру с флагом ContinueExecute=0, на что диспетчер отреагирует восстановлением контекста регистров. Если в этот момент мы не обновим регистр-указателя на сл.инструкцию RIP, то получим мёртвый цикл, т.к. процессор будет генерить предыдущее исключение снова и снова. Чтобы не попасть в такую ловушку, на выходе из обработчика нам нужно перезаписать RIP меткой в коде, чтобы пропустить глючную инструкцию.


C-подобный:
......
           mov     [context.Rip],@next  ;// обновить RIP в структуре “CONTEXT”
           xor      eax,eax             ;// флаг “ContinueExecution=0”
           ret
endp

Обработчики исключений (а в целом и все пользовательские функции) не должны изменять содержимое регистров RBX,RBP,RSI,RDI,R12-R15,XMM6-XMM15. В оригинальных доках их так и называют «Non-Volatile Registers». Не соблюдение этого правила приведёт к большим неприятностям, особенно если речь идёт об обработчиках исключений. Зациклившись, система может арестовать нас внутри своего-же обработчика, что будет выглядеть весьма глупо. Из режима Long-mode была напрочь удалена инструкция pushaq, при помощи которой можно было запихать в стек сразу все регистры. Поэтому придётся перечислять их в ручную, ну или написать соответствующий макрос.


Код:
0: kd> dt _context
nt!_CONTEXT
   +0x000 P1Home           : Uint8B
   +0x008 P2Home           : Uint8B
   +0x010 P3Home           : Uint8B
   +0x018 P4Home           : Uint8B
   +0x020 P5Home           : Uint8B
   +0x028 P6Home           : Uint8B
   +0x030 ContextFlags     : Uint4B
   +0x034 MxCsr            : Uint4B
   +0x038 SegCs            : Uint2B
   +0x03a SegDs            : Uint2B
   +0x03c SegEs            : Uint2B
   +0x03e SegFs            : Uint2B
   +0x040 SegGs            : Uint2B
   +0x042 SegSs            : Uint2B
   +0x044 EFlags           : Uint4B
   +0x048 Dr0              : Uint8B
   +0x050 Dr1              : Uint8B
   +0x058 Dr2              : Uint8B
   +0x060 Dr3              : Uint8B
   +0x068 Dr6              : Uint8B
   +0x070 Dr7              : Uint8B
   +0x078 Rax              : Uint8B
   +0x080 Rcx              : Uint8B
   +0x088 Rdx              : Uint8B
   +0x090 Rbx              : Uint8B
   +0x098 Rsp              : Uint8B
   +0x0a0 Rbp              : Uint8B
   +0x0a8 Rsi              : Uint8B
   +0x0b0 Rdi              : Uint8B
   +0x0b8 R8               : Uint8B
   +0x0c0 R9               : Uint8B
   +0x0c8 R10              : Uint8B
   +0x0d0 R11              : Uint8B
   +0x0d8 R12              : Uint8B
   +0x0e0 R13              : Uint8B
   +0x0e8 R14              : Uint8B
   +0x0f0 R15              : Uint8B
   +0x0f8 Rip              : Uint8B
   +0x100 FltSave          : _XSAVE_FORMAT
   +0x100 Header           : [2] _M128A
   +0x120 Legacy           : [8] _M128A
   +0x1a0 Xmm0             : _M128A
   +0x1b0 Xmm1             : _M128A
   +0x1c0 Xmm2             : _M128A
   +0x1d0 Xmm3             : _M128A
   +0x1e0 Xmm4             : _M128A
   +0x1f0 Xmm5             : _M128A
   +0x200 Xmm6             : _M128A
   +0x210 Xmm7             : _M128A
   +0x220 Xmm8             : _M128A
   +0x230 Xmm9             : _M128A
   +0x240 Xmm10            : _M128A
   +0x250 Xmm11            : _M128A
   +0x260 Xmm12            : _M128A
   +0x270 Xmm13            : _M128A
   +0x280 Xmm14            : _M128A
   +0x290 Xmm15            : _M128A
   +0x300 VectorRegister   : [26] _M128A
   +0x4a0 VectorControl    : Uint8B
   +0x4a8 DebugControl     : Uint8B
   +0x4b0 LastBranchToRip  : Uint8B
   +0x4b8 LastBranchFromRip : Uint8B
   +0x4c0 LastExceptionToRip : Uint8B
   +0x4c8 LastExceptionFromRip : Uint8B
0: kd>

5. Секция исключений «.pdata»

Microsoft утверждает, что структуры механизма исключений RUNTIME_FUNCTION и UNWIND_INFO должны находиться в специальной секции РЕ-файла, которая носит название «.pdata». Однако при тестах оказалось, что имя секции может быть произвольным, но зарегистрировать её в каталоге РЕ-файла, нужно обязательно под номером(3). Когда система загружает образ приложения в память, лоадер просматривает записи в этом каталоге, и передаёт информацию соответствующим инстанциям, в том числе и диспетчеру исключений. Вот как отображает записи данного каталога двоичный редактор Hiew (жми комбинацию: Enter-->F8-->F10):


ExceptDir.png


С именем секции «.pdata» обнаружился неприятный один нюанс..
Оказывается весь софт по сбору информации о РЕ-файлах, ищет в файлах исключительно секцию «.pdata», и если она называется как-то иначе, то просто не отображает инфу об исключениях в своём окне. Это никак не добавляет скила разработчикам софта. Если ты видишь запись в каталоге секций, значит секция активна, и нужно просто по какой-нибудь сигнатуре найти её содержимое в дампе. На скрине ниже одна из таких программ «PE File Browser x64». Тулза конечно мощная и способна дать фору всем остальным, но и она игнорирует все имена, кроме «.pdata»:


PeB_x64.png


Посмотрите на заголовок окна - я загрузил в неё «Autoruns64.ехе» Марка Руссиновича. Удивляет то, что автор не поленился установить аж 1127 обработчиков исключений, детальную инфу о которых и показывает софт. Ясно, что перед нами программа на плюсах, лексикон которой поддерживает _try\_except, а остальное оформляет сам компилятор без участия программиста. Но чтобы кол-во обработчиков перевалило за тыщу, это вообще туши свет. По всей вероятности автор юзает для всех\своих приложений один и тот-же шаблон, т.к. у большинства обработчиков флаги выставлены в нуль, что означает UNW_FLAG_NHANDLER, или пропустить. В любом случае «PE-Browser» круто исполняет своё дело, а это уже гуд.


6. Пример программы обработки исключений

Под занавес закрепим теорию практикой, где я контролирую два потенциально опасных участка кода.

• Первый блок будет запрашивать у юзера адрес для чтения памяти. Если область не доступна, то система должна оповестить нас исключением «AccessViolation» с кодом 0xC0000005. При обычных обстоятельствах, после этого прожка отправилась-бы к праотцам, но я перехватываю экспепшен, и обработав возвращаю управление опять коду.

• Второй блок будет вести контроль над целочисленным делением. Любому младенцу известно, что при операциях с целыми числами делить на нуль нельзя, но у нас получится «льзя». Здесь я так-же ставлю свой обработчик, и вытягиваю приложение буквально с того света.


C-подобный:
format  pe64 console
entry   start
include 'win64ax.inc'
include 'equates\except64.inc'
;//-----------

section '.data' data readable writeable
a          dq  0
b          dq  0
c          dq  0
aa         dd  0
bb         dd  0
rdAddr     dq  0

szAccess   db  'Access Violation!',0
szDivide   db  'Divide by Zero!',0
buff       dd  0
;//-----------

section '.code' code readable executable
start:
frame
             invoke   SetConsoleTitle,<'x64 SEH example',0>
             invoke   GetModuleHandle,0
             xchg     rdx,rax

;//----- Пытаемся прочитать адрес юзера ------------
            cinvoke   printf, <10,' Image base...: 0x%016I64x',\
                               10,' Read address.: 0x',0>,rdx
            cinvoke   scanf,<'%I64x',0>,rdAddr
             or       eax,eax
             jnz      @startSeh1
@err:       cinvoke   printf, <' Error!',0>
             jmp      @exit

;//**********************************************************************
;//********* БЛОК КОНТРОЛЯ ИСКЛЮЧЕНИЙ ***********************************
@startSeh1:  mov      rax,[rdAddr]
             mov      rdx,[rax]
@endSeh1:   cinvoke   printf, <' Value........: 0x%016I64x',10,0>,rdx
;//**********************************************************************
;//**********************************************************************

;//----- Умножение чисел с плавающей точкой -----------------------------
@next:      cinvoke   printf, <10,10,' MUL float value...',\
                                  10,' ---------------------',\
                                  10,' Input A float: ',0>
            cinvoke   scanf,<'%lf',0>,a
             or       eax,eax
             jz       @err

            cinvoke   printf, <' Input B float: ',0>
            cinvoke   scanf,<'%lf',0>,b
             or       eax,eax
             jz       @err
            cinvoke   printf, <' Result.......: ',0>

             movsd    xmm0,[a]
             mulsd    xmm0,[b]
             movq     rax,xmm0
            cinvoke   printf,<'%lf',0>,rax

;//----- Операция деления целых чисел -----------------------------------
            cinvoke   printf, <10,10,' DIV integer value...',\
                                  10,' ---------------------',\
                                  10,' Input A int..: ',0>
            cinvoke   scanf,<'%u',0>,aa
             or       eax,eax
             jz       @err

            cinvoke   printf, <' Input B int..: ',0>
            cinvoke   scanf,<'%u',0>,bb
             or       eax,eax
             jz       @err
            cinvoke   printf, <' Result.......: ',0>

;//**********************************************************************
;//********* БЛОК КОНТРОЛЯ ИСКЛЮЧЕНИЙ ***********************************
@startSeh2:  xor      rdx,rdx
             mov      eax,[aa]
             div      [bb]
             xchg     rbx,rdx
@endSeh2:   cinvoke   printf, <'%u.%u',0>,rax,rbx
;//**********************************************************************
;//**********************************************************************

@exit:    cinvoke   _getch
          cinvoke   exit,0
endf

;//--------------
section  '.idata' import data readable
library  kernel32,'kernel32.dll',msvcrt,'msvcrt.dll'
include  'api\kernel32.inc'
include  'api\msvcrt.inc'

;//----- Секция исключений --------------------------------
;//----- Регистрируем её в каталоге РЕ под номером(3) -----
section '.pdata' data readable
data 3
;//---- RUNTIME_FUNCTION(1)
    dd  RVA @startSeh1
    dd  RVA @endSeh1
    dd  RVA @UnwindInfo

;//---- RUNTIME_FUNCTION(2)
    dd  RVA @startSeh2
    dd  RVA @endSeh2
    dd  RVA @UnwindInfo

align 16     ;//<--- Обязательное выравнивание структуры “UNWIND_INFO”
@UnwindInfo:                  ;//v------------ Флаг
    db  9,0,0,0      ;// 9 = 00001.001b <----- Версия
    dd  RVA Handler
    dd  0
end data

;//----- Секция под обработчик исключений -----------------------
;//----- Microsoft советует помещать код обработчика именно сюда,
;//----- хотя это вовсе не принципиально ------------------------
section '.xdata' code readable writeable executable
proc  Handler
;// RCX = адрес структуры "EXCEPTION_RECORD64"
;// R8  = адрес структуры "CONTEXT64"
virtual at rcx
       record   EXCEPTION_RECORD64
end virtual

virtual at r8
       context  CONTEXT64
end virtual
align 16
           PushNonVolReg
           mov     [context.Rip],@next  ;// обновить RIP
           mov      r15,szAccess

           mov      r10d,[record.ExceptionCode]
           cmp      r10d,EXCEPTION_ACCESS_VIOLATION  ;// проверить код-исключения
           jz       @f

           mov     [context.Rip],@exit  ;// меняем RIP в зависимости от кода
           mov      r15,szDivide

@@:        mov      r11d,[record.ExceptionFlags]   ;// собираем инфу для лога
           mov      r12, [record.ExceptionAddress]
           mov      r13, [context.Rcx]
           mov      r14, [context.Rdx]
           mov      rsi, [context.R8]
           mov      rdi, [context.R9]

          cinvoke   printf,<10,10,' ************** Attention! Exception Handler **************',\
                            10,10,'       Except code...: 0x%08X --> %s',\
                               10,'       Except flag...: 0x%08x',\
                               10,'       Except addr...: 0x%016I64x',10,\
                               10,'       RCX: 0x%016I64x',\
                                  '       R8: 0x%016I64x',\
                               10,'       RDX: 0x%016I64x',\
                                  '       R9: 0x%016I64x',10,\
                               10,' **********************************************************',0>,\
                                    r10,r15,r11,r12,r13,rsi,r14,rdi
           PopNonVolReg
           mov      eax,EXCEPTION_CONTINUE_EXECUTION  ;// команда диспетчеру «Всё ОК! Продолжить»
           ret
endp

Log_x64.png



7. Заключение

Очередное словоблудие подошло к концу..
В скрепке лежат 4 файла:

• Веб-установщик отладчика WinDbg для Win7-x64 (win7sdk_web.exe);
• Исполняемый файл для тестов отлова исключений (SEH64.exe);
• Инклуд с константами и форматом структур Except64.inc (положить в папку: fasm\include\equates);
• Инклуд с перечислением функций из библиотеки msvcrt.dll (положить в папку: fasm\include\api). Он избавит вас от ручного перечисления функций в секции-импорта.

Всем удачи, пока!
 

Вложения

  • SEH_x64.zip
    479,7 КБ · Просмотры: 283

Pavel Shuhray

Green Team
14.12.2016
19
5
BIT
65
Дайте пример, пожалуйста, как пользоваться livekd. Вот в этой статье
мы запускали windbg в локальном режиме и что-то искали командой dt nt! *thread*
Я запускаю livekd64, она выдаёт приглашение kd>
Она уже в локальном режиме или надо как-то перейти в локальный режим?
 

Marylin

Mod.Assembler
Red Team
05.06.2019
326
1 451
BIT
695
Она уже в локальном режиме или надо как-то перейти в локальный режим?
Да, это локальный режим ядра. Вводите команды и всё.
При первом запуске, "LiveKd" должен запросить у вас расположение папки с отладочными символами. Вы указали на неё? Символы нужны обязательно, иначе отладчик не выводит название функций и структур.

Если хотите отлаживать не ядро, а пользовательские приложения, то запускайте оригинальный WinDbg.
 

Pavel Shuhray

Green Team
14.12.2016
19
5
BIT
65
Да, символы указал. На пробную команду
dt nt! *thread*
выдаёт сообщение

Unable to load image \??\C:\WINDOWS\system32\Drivers\LiveKdD.SYS, Win32 error 0n2
Ambiguous matches found for nt! (dumping largest sized):
nt!MxPageAlwaysHot
nt!MiSyncSystemPdes {0x06c bytes}
nt!MiSyncSystemPdes
MiSyncSystemPdes
Cannot find specified field members.

Это так и должно быть? (то мы отлаживали 32, а у меня 64)
 

Marylin

Mod.Assembler
Red Team
05.06.2019
326
1 451
BIT
695
Это так и должно быть?
Нет, так не должно быть. Вы видимо копируете, а в команде притаилась русская буква.
Если я ввожу с клавиатуры, то получаю такой результат (система Win7-x64):

Loading Kernel Symbols ............................................................... ................................................................ ........................ Loading User Symbols ............ Loading unloaded module list .................. 0: kd> dt nt!*thread* ntkrnlmp!_KTHREAD ntkrnlmp!_KTHREAD_COUNTERS ntkrnlmp!_ETHREAD ntkrnlmp!_VI_DEADLOCK_THREAD ntkrnlmp!_THREAD_PERFORMANCE_DATA 0: kd>

То-есть отладчик вернул все структуры, где встречается указанное нами слово "thread".
В данном случае 0: kd - это ядро(0) процессора,
но можно попробовать тест на другом ядре, для чего вводи ~1 и т.д.
 

Pavel Shuhray

Green Team
14.12.2016
19
5
BIT
65
А, я просто ставил лишний пробел (после nt!). Теперь получилось, спасибо.
 
  • Нравится
Реакции: Marylin
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!