Вызов функций Windows в режиме ядра

в 12:21, , рубрики: windows, Программирование, метки:

Многие из вас, уважаемыее, пользуются функциями ядра Windows. Однако далеко не все представляют себе, как же эти функции вызываются. А знание некоторых механизмов ядра может быть весьма полезным.

Этот текст не предназначается драйверописателям, которые (по идее) знают это даже на более глубоком уровне.

Начнем мы с того, что в режиме ядра гораздо больше функций, чем во всем известном WinAPI. Связано это с тем, что в ядро заложено множество недокументированных функций(правда, ситуация улучшилась с выходом Win7 и Win8, многие такие функции попадают на MSDN). Список таких функций можно посмотреть, например, здесь.

Эти функции можно разделить на несколько групп:

  • Kernel API — функции находятся в ntoskrnl.exe (на самом деле обычная dll), основные функции ядра.
  • Windowing API — функции находятся в gdi32.dll, функции для работы с окнами и графикой.
  • Messaging API — функции находятся в user32.dll, функции обработки сообщений.

Как же все это вызывается?

Начнем с вызова из пользовательского режима.

— Первым делом мы обратимся к NtDll.dll — «прослойке» между режимом пользователя и режимом ядра. В ней находится нужная нам функция ядра. Эта функция помещает в регистр EAX свой номер. Он является индексом в двух таблицах, известных ядру: ServiceTable и ArgumentTable. Характерно то, что номер этот специфичен для каждой версии ОС. Нередко он меняется не просто при переходе от, скажем, WinXP к Vista, а даже при переходе от Vista к Vista SP1.
— Далее, в EDX помещается адрес вершины стека параметров, в большинстве случаев это значение регистра ESP. Казалось бы, зачем копировать уже существующее значение? Дело в том, что после выполнения прерывания на стек сбросится все состояние процессора, и ESP уже будет не тем, что нужен нам.
— Наконец происходит переключение в режим ядра. На старых процессорах и системах это выполняется с помощью прерывания 0x2e, на современных же для этого существует специальная инструкция процессора: SysEnter для Intel, SysCall для AMD.
— Результатом вызова является выполнение процессором кода, адрес которого находится в глобальной таблице прерываний в определенной ячейке. Этот код вызывает либо KiSystemService(), в случае прерывания, либо KiFastCallEntry(), в случае специальной инструкции процессору. Кстати говоря, вторая зарегистрирована в скрутом регистре MSR, что обеспечивает быстрый доступ к ней.
— Далее код, работающий в ядре, выполняется немного по-разному, в зависимости от того, откуда пришел вызов функции, из режима ядра или из пользовательского режима. Задача кода ядра: по внутренней таблице функций выполнить вызов функции из EAX. Эта таблица называется KiServiceDescriptorTable. Это структура с тремя полями:

  • адресом на таблицу ServiceTable (nt!KiServiceTable) — массив функций
  • адресом на ArgumentTable (nt!KiArgumentTable) — кол-во байтов, которое занимают параметры
  • количество элементов в этих таблицах

Когда вызываются функции SysEnter and SysCall, им нужно выполнить диспетчеризацию к функциям ОС. Каждый поток имеет стек пользовательского режима и режима ядра. ОС берет в таблице ArgumentTable значение ячейки, которое показывает, сколько байтов занимают параметры на стеке(для этого мы запоминали EDX). Потом она копирует это количество байтов на стек ядра. И после этого выполняет вызов функций через ServiceTable. Вся эта морока с двумя стеками нужна, разумеется, для уверенности, что никакие изменения в пользовательском режиме не навредят выполнению функции в ядре.

— После того, как функция вызвана и выполнена, и ее работа завершается, возврат из режима ядра происходит с помощью инструкии iret или SysExit/SysRet.

Вызов функций ядра в режиме ядра.

В принципе, имеем все то же самое, но…
Если внимательно просмотреть список функций ядра, то можно заметить, что существуют функции, полностью аналогичные nt- функциям, но с префиксом zw-. Все они однозначно отображены на nt функции. Однако в режиме ядра они работают по-разному.

Разница же состоит в том, что в функциях zw- не проводится валидация параметров. Иными словами, при вызове из пользовательского режима система тщательно следит за параметрами, передаваемыми в функцию — не дай бог программист что-то упустит, и привет, синий экран. В режиме ядра же примущественно работают с драйверами, и там система уже полагает, что вы все параметры сами проверите, и гарантируете их валидность, ведь дополнительные проверки съедят драгоценное время. Работает это так: если вызывается zw, то он загружает в EAX номер функции, в EDX ложит указатель на стек ядра, а потом вызывает nt- вариант, который не проводит валидацию.

В связи с чем возникает вопрос — а что, если я выхову zw- функцию из режима пользователя? Система все простит? Не совсем. Если в usermode вызывается zw, то он просто вызовет своего nt- близнеца.

Вот, в общем-то и вся базовая информация о вызовах функций ядра. Надеюсь, что вам было интересно.

Автор: Angelore

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js