Защищенные подсистемы

Серверы Windows называются защищенными подсистемами, так как каждый из них - это отдельный процесс, память которого защищена от других процессов системой виртуальной памяти исполнительной системы Windows.

Каждая защищенная под средством DLL система обеспечивает интерфейс прикладным программам (API) посредством клиентской стороны. Когда приложение или другой сервер вызывает некоторую процедуру API, соответствующая DLL упаковывает параметры функции API в сообщение и с помощью средства локального вызова процедур (Local Procedure Call, LPC) посылает его серверу, реализующему данную процедуру. Сервер же, выполнив вызов, посылает ответное сообщение вызывающей программе. Передача сообщений остается невидимой для прикладного программиста. Используя такую процедуру вызывающая программа никогда не получает прямого доступа к адресному пространству подсистемы.

Надо отметить, что далеко не все функции API реализуются сервером, например, большая часть функций API Win32 оптимизирована в DLL клиентской стороны, и в действительности не обращается к подсистеме Win32.

Защищенные подсистемы подразделяются на:

- подсистемы среды (environment subsystems)

- неотъемлемые подсистемы (integral subsystems).

4.1.1. Подсистема среды- это сервер пользовательского режима, реализующий API не которой ОС.Самая важная подсистема среды в Windows – это подсистема среды Win32, которая предоставляет прикладным программам интерфейс API 32-разрядной Windows. В Windows также реализованы подсистемы среды:

- POSIX,

- OS/2

- виртуальная DOS машина (virtual DOS machine, VDM), эмулирующая 16-разрядную Windows и MS-DOS.

Данные подсистемы предоставляют свои API, но используют для получения пользовательского ввода и отображения результатов подсистему Win32, т.е. перенаправляют видеовывод своих приложений подсистеме Win32 для отображения.

Говоря о подсистемах окружения, необходимо отметить также следующее. Каждая прикладная программа (и даже более того - каждый модуль, будь то .exe, .dll, .sys или что-то другое) может относиться только к какой-то одной подсистеме окружения, либо не относиться ни к одной из них. Эта информация прописывается в любом исполняемом модуле на этапе его компиляции и может быть получена через утилиту) "Быстрый просмотр" (Quick View) в пункте "Subsystem" (варианты: The image does not require subsystem, Win32 GUI, Win32 Console, ...).

Подсистема среды Win32 делится на следующие части (рис.20):

a) серверный процесс (csrss.exe - Client/Server Runtime Subsystem)

b) клиентские DLLs (user32.dll, gdi32.dll, kernel32.dll), которые связаны с программой, использующей Win32 API.

Стрелка, указывающая от модуля А к модулю В, означает зависимость А от В, т.е. модуль А вызывает функции из модуля В. модули, показанные двойными стрелками на рис.11, зависят друг от друга.

сюда Win32 API разделен на три категории:

Управление окнами (windowing) и передача сообщений (messaging). Эти интерфейсы оконных процедур и процедур сообщений включают, например, такие функции, как CreateWindow(), SendMessage(), и предоставляются прикладной программе через библиотеку user32.dll.

Рисование (drawing). Например, функции Rectangle() и LineTo() являются Win32-функциями рисования и предоставляются библиотекой gdi32.dll.

Базовые сервисы. Базовые сервисы включают весь ввод/вывод Win32, управление процессами и потоками, управление памятью, синхронизацию и предоставляются библиотекой kernel32.dll.

 

Когда Win32-приложение вызывает функцию API Win32, управление передается одной из клиентских DLLs подсистемы Win32. Эта DLL может (рис.21):

1. Выполнить функцию самостоятельно без обращения к системным сервисам ОС и вернуть управление вызывающей программе.

2. Послать сообщение Win32-серверу для обработки запроса в том случае, если сервер должен участвовать в выполнении заданной функции. Так, функция CreateProcess(), экспортируемая библиотекой kernel32.dll, требует взаимодействия с Win32-сервером, который в свою очередь, вызывает функции "родного" API.

3. Вовлечь "родной" интерфейс API для выполнения заданной функции. Последний вариант встречается наиболее часто.


В версиях Windows NT ниже 4.0 функции окон (windowing) и рисования (drawing) были расположены в Win32-сервере (csrss.exe). Это означало, что, когда приложение использовало такие функции, посылались сообщения указанному серверу. В версии 4.0 эти функции были перенесены в компонент режима ядра, называемый win32k.sys (рис.22).

Теперь вместо того, чтобы посылать сообщение серверу, клиентская DLL обращается к этому компоненту ядра, уменьшая затраты на создание сообщений и переключение контекстов потоков различных процессов. Это увеличило производительность графического ввода/вывода. Библиотеки gdi32.dll и user32.dll стали вторым "родным" API, но оно менее загадочно, чем первое, так как хорошо документировано.

 
 

Функциями библиотеки kernel32.dll, вызывающими "родной" интерфейс API напрямую, являются функции ввода/вывода, синхронизации и управления памятью. Фактически, большинство экспортируемых библиотекой kemel32.dll функций используют "родной" API напрямую.

На рис. 23 иллюстрируется пример передачи управления от Win32-приложения, выполнившего вызов Win32-функции CreateFile(), библиотеке kernel32.dll, затем функции NtCreateFile() и Ntdll.dll и далее режиму ядра, где управление передается системному сервису, реализующему создание/открытие файла.

"Родной" API для ОС Windows NT (Native Windows NT API)

Native ("Истинный") API для Windows является средством, которое реализует контролируемый вызов системных сервисов, исполняемых в режиме ядра. Так, например, если программа, исполняющаяся в пользовательском режиме, захочет выполнить операцию ввода/вывода, зарезервировать или освободить регион в виртуальном адресном пространстве, запустить поток или создать процесс, - она должна запросить (естественно, не напрямую) один или несколько системных сервисов, расположенных и режиме ядра.

Этот интерфейс API является интерфейсом системных вызовов и не предназначается для непосредственного использования пользовательскими программами, кроме того, его документация ограничена. В Windows "родной" интерфейс спрятан от прикладных программистов под интерфейсами API более высокого уровня, таких как Win32, OS/2, POSIX, DOS/Winl6.

"Родной" API предоставляется коду пользовательского режима библиотекой ntdll.dll. Более точно, ntdll – это внешняя часть Native API пользовательского режима. Настоящий же интерфейс реализован в ntoskernel.exe.

 
 

Библиотека ntdll.dll, имеющая точки входа в "родной" API для кода пользовательского режима, содержит также код загрузки модуля и запуска потока процесса. Однако большинство входов в "родной" API являются заглушками, которые просто передают управление режиму ядра. Это осуществляется за счет использования шлюза прерываний для переключения уровня привилегий процессора из режима пользователя в режим ядра.

Например, ассемблерный код функции NtCreateFile() в библиотеке ntdll.dll, выглядит следующим образом:

mov eax, 20h

lea edx, [esp+04]

int. 0x2E

ret 0x2C

Другие вызовы выглядят почти также.

Перваяинструкция загружает регистр процессора индексным номером идентификатора вызова, соответствующего одной из функций "родного" API (каждая функция "родного" API имеет уникальный индексный номер).

Вторая инструкция загружает в регистр указатель на параметры вызова.

Третья инструкция - команда генерации программного прерывания. ОС регистрирует обработчик ловушки для перехвата управления, переключения из пользовательского режима в режим ядра и передачи управления в фиксированную точку ОС при возникновении прерывания или исключения. В случае вызова системного сервиса (на процессорах х86 программное исключение для вызова системных сервисов генерируется кодом 0х2Е), этот обработчик ловушки передает управление диспетчеру системных сервисов.

Четвертая инструкция забирает параметры из стека вызывающего потока.

Как работает WtiteFile? WriteFile--->NtWriteFile--->DeviceIoControl(почти все API так делают)--->NtDeviceIoControlFile--->команда SysEnter. В NtDeviceIoControlFile передается тот же хэндл, что в WriteFile. Т.е. все идет к драйверу. Он и заведует хэндлами.

Диспетчер системных сервисов определяет, является ли корректным индексный номер функции "родного" API. Индексный номер, переданный из пользовательского режима, используется для входа в таблицу дескрипторов системных сервисов (KeServiceDescriptorTable).

Каждый элемент этой таблицы включает указатель на соответствующий системный сервис и число параметров. Диспетчер системных сервисов берет параметры, переданные в стеке пользовательского режима (указатель стека находится в регистре edx) и помещает их в стек ядра, а затем передает управление для обработки запроса соответствующему системному сервису, который исполняется в режиме ядра и находится в ntoskrnl.exe.

Введенные в Windows NT версии 4.0 интерфейсы API Win32 управления окнами и рисованием управляются тем же диспетчером системных сервисов, но индексные номера Win32-функций указывают на то, что должен использоваться второй массив указателей системных сервисов. Указатели во втором массиве ссылаются на функции в win32k.sys.

Большинство системных сервисов должно выполнять проверку параметров, переданных им из пользовательского режима. Некоторые параметры являются указателями, а передачи неверного указателя в режим ядра без предварительной проверки может привести к краху системы.

Проверка параметров обязательна, но оказалось, что некоторые функции "родного" API (13 системных сервисов из win32k.sys) не выполняют обстоятельную проверку, что приводит в некоторых случаях к падению системы. Microsoft закрыла эти дыры в Service Pack 1. В дальнейшем оказалось, что при тестировании параметров, соответствующих граничным условиям, вызовы еще 40 функции "родного" API, из которых 25 из win32k.sys, вызвали падение системы. Эти дыры были закрыты в SP4.

После проверки параметров, системные сервисы обычно вызывают функции, реализуемые компонентами исполнительной системы:

– диспетчером процессов,

– диспетчером памяти,

– диспетчером ввода/вывода

– средством локального вызова процедур.

Все функции прикладного уровня, вызывающие это прерывание, сосредоточены в модуле ntdll.dll и имеют в своем названии префикс Nt либо Zw, например, NtCreateFile()/ZwCreateFile(). Точка входа для двух таких имен одна. Вызов многий функций различных подсистем рано или поздно приведет к вызову соответствующей функции из ntdll.dll. При этом не все, что есть в ntdll.dll вызывается из подсистемы Win32.

Вызов системных сервисов возможен не только из прикладной программы, но и из ядра ОС, то есть из драйверов. Имена соответствующих функций ядра имеют префикс либо Zw, либо Nt (ZwCreateFile(), NtCreateFile()).

Функции с префиксом Zw обращаются к сервисам посредством шлюза (прерывание 2Еh), тогда как функции с префиксом Nt являются собственно точками входа стандартных системных сервисов. Другими словами, идентификаторы Nt* указывают на настоящий код, а их разновидности Zw* ссылаются на заглушки int 2Eh.

Из этого следует, что число функций с префиксом Nt неизменно, а множество этих функций является подмножеством функций с префиксом Zw.

He путайте функции с префиксами Nt и Zw режима ядра и пользовательского режима. В режиме ядра они находятся в модуле ntoskrnl.exe (микроядро), в пользовательском режиме - в модуле ntdll.dll ("родной" API, вызывают int 2E).

4.1.2. Неотъемлемые подсистемы.Другой тип защищенных подсистем - неотъемлемые подсистемы - это серверы, выполняющие важные функции ОС. Примером неотъемлемой подсистемы является подсистема безопасности, исполняющаяся в пользовательском режиме и реализующая правила контроля доступа, определенные для локального компьютера. Некоторые компоненты сетевого обеспечения Windows также реализованы как защищенные подсистемы, например, сервис рабочей станции реализует API для доступа и управления сетевым редиректором.

4.1.3. Особенности реализации защищенных подсистем в .Net Framework.Технология .NET Framework состоит из:

- библиотеки классов, называемой Frame­work Class Library (FCL),

- общеязыковой исполняющей среды (Com­mon Language Runtime, CLR).

CLR предоставляет среду для выпол­нения управляемого кода с такими возможностями, как компиляция по требованию (just-in-time compilation, JIT compilation), верификация типов, сбор мусора и защита по правам доступа кода (code access secu­rity). Благодаря этому CLR создает среду разработки, которая повыша­ет продуктивность труда программистов и уменьшает вероятность появления распространенных ошибок программирования.

CLR реализована как классический СОМ-сервер, код которой хра­нится в стандартной Windows DLL пользовательского режима. Факти­чески все компоненты .NET Framework реализованы как стандартные Windows DLL пользовательского режима, занимающие уровень поверх неуправляемых функций Windows API. Взаимо­связи этих компонентов показаны на рис.24.

 
 

WinFX — "новый Windows API". Это результат раз­вития .NET Framework, которая будет поставляться с версией Windows Longhorn, следующим выпуском Windows. WinFX также можно установить в Windows XP и Windows Server 2003. WinFX образует фундамент для приложений следующего поколения, создаваемых для операционной системы Windows.