Разделы презентаций


Основы ядерной архитектуры ОС Windows

Содержание

Применение знаний ядра ОССредства защиты информации – системы шифрования сетевого трафика, защита данных на диске (виртуальные диски, фильтр-драйверы, защищенные файловые системы)Вредоносное ПОСредства анализа в самом широком смысле (антивирусы, отладчики, системные утилиты)Знание

Слайды и текст этой презентации

Слайд 1Основы ядерной архитектуры ОС Windows

Основы ядерной архитектуры ОС Windows

Слайд 2Применение знаний ядра ОС
Средства защиты информации – системы шифрования сетевого

трафика, защита данных на диске (виртуальные диски, фильтр-драйверы, защищенные файловые

системы)
Вредоносное ПО
Средства анализа в самом широком смысле (антивирусы, отладчики, системные утилиты)
Знание ядерной архитектуры важно для аналитика программного обеспечения
Применение знаний ядра ОССредства защиты информации – системы шифрования сетевого трафика, защита данных на диске (виртуальные диски,

Слайд 3Применение знаний при разработке инфраструктуры анализа бинарного кода
Драйвер получения информации:

список процессов, модулей и копии адресных пространств процессов
Алгоритмы в составе

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

Слайд 4Основные понятия
Процессы, потоки и адресные пространства в ОС Windows: взаимосвязь

с процессорной архитектурой

Архитектура процессора в защищенном режиме предоставляет для использования

разработчикам ОС некоторые механизмы:
Защита памяти («кольца защиты»)
Сегментация
Cтраничная организация памяти
Многозадачность (task switching)

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

Слайд 5Защита памяти по привилегиям («кольца защиты»)
0
1
2
3
User mode
Kernelmode
Передача управления
Доступ к данным
Важной

особенностью реализации механизма колец защиты является то, что для каждого

кольца защиты хранится своя копия стековых регистров (ESP), и при переключении кольца защиты аппаратура процессора осуществляет замену текущего значения ESP на сохраненную копию, соответствующую новому кольцу защиты.
Защита памяти по привилегиям («кольца защиты»)0123User modeKernelmodeПередача управленияДоступ к даннымВажной особенностью реализации механизма колец защиты является то,

Слайд 6Особенности организации памяти в Windows на примере 32-разрядной адресации
Вирт. адрес

Особенности организации памяти в Windows на примере 32-разрядной адресацииВирт. адрес

Слайд 7ОС NT, хотя и использует селекторы, но использует их в

минимальной степени.
NT реализует плоскую 32-разрядную модель памяти с размером линейного

адресного пространства 4 Гб (232 байт).
В NT определено 11 селекторов, из которых нас будут интересовать всего 4:

Эти 4 селектора позволяют адресовать все 4Гб линейного адресного пространства, разница только в режиме доступа.
Первые 2 селектора имеют DPL=0 и используются драйверами и системными компонентами для доступа к системному коду, данным и стеку.
Вторые 2 селектора используются кодом пользовательского режима для доступа к коду, данным и стеку пользовательского режима. Эти селекторы являются константами для ОС NT.
Сегментное преобразование пары селектор:смещение дает 32-битный линейный адрес, для всех рассматриваемых селекторов совпадающее со значением смещения виртуального адреса.

ОС NT, хотя и использует селекторы, но использует их в минимальной степени.NT реализует плоскую 32-разрядную модель памяти

Слайд 8Реальная защита памяти по привилегиям, как и реализация понятия адресное

пространство, осуществляется посредством механизма страничной организации памяти:
Каждый контекст памяти (адресное

пространство процесса) описывается своим каталогом страниц. Физический адрес текущего каталога страниц содержится в регистре CR3
элемент таблицы страниц содержит бит, указывающий на возможность доступа к странице из пользовательского режима (0 - доступ только ядру, 1 - доступ пользовательскому режиму). При этом все страницы доступны из режима ядра. Таким образом, механизм защиты страниц использует всего 2 уровня привилегий - 0 (ядро) и 3 (пользователь). С помощью этого механизма адресное пространство делится на 2 части: системную и пользовательскую.
элемент таблицы страниц содержит бит, указывающий на возможность записи в соответствующую страницу памяти (0 - доступ только на чтение, 1 - чтение/запись). Чтение возможно всегда, когда разрешен доступ по привилегиям.
В некоторых режимах элемент таблицы страниц содержит бит, определяющий соответствующую страницу как данные (запрет на выполнение кода с данной страницы)

Реальная защита памяти по привилегиям, как и реализация понятия адресное пространство, осуществляется посредством механизма страничной организации памяти:Каждый

Слайд 9Виртуальное адресное пространство процесса
0
2Гб
4Гб
user
kernel
0
3Гб
4Гб
user
kernel
Код и данные прикладных программ
Код и данные

ядра, память устройств
user
kernel
Модули и объекты ядра
Модули и объекты прикладной программы
Адресное

пространство в 32 разрядном режиме

Границу между пользовательским и системным диапазоном можно сдвинуть

Взаимодействие между кодом пользовательского режима и ядром часто изображают так:

Виртуальное адресное пространство процесса02Гб4Гбuserkernel03Гб4ГбuserkernelКод и данные прикладных программКод и данные ядра, память устройствuserkernelМодули и объекты ядраМодули и

Слайд 10Каталоги страниц всех процессов организованы так, что преобразование линейного в

физический адрес для системного диапазона любого адресного пространства совпадают –

диапазон ядра «общий» для всех адресных пространств

0

4Гб

user

kernel

Код и данные процесса 1

ядро

Код и данные процесса N


Адресное пространство 1

Адресное пространство N

Каталоги страниц всех процессов организованы так, что преобразование линейного в физический адрес для системного диапазона любого адресного

Слайд 12Каталоги страниц позволяют создавать «совместно используемые» как внутри одного, так

и между несколькими адресными пространствами области памяти путем трансляции разных

виртуальных адресов в одинаковые физические.

Физическая память

Адресное пространство 1

Адресное пространство 2

Каталоги страниц позволяют создавать «совместно используемые» как внутри одного, так и между несколькими адресными пространствами области памяти

Слайд 13Разновидность такого механизма – copy-on-write – используется для реализации механизма

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

в адресное пространство все исполняемые модули - .exe, .dll, .sys и т.п.

Физическая память

Адресное пространство 1

Адресное пространство 2

Изначально все страницы адресных пространств, соответствующие исполняемому модулю, отображены в общий набор страниц физической памяти

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

Физическая память

Адресное пространство 1

Адресное пространство 2

модификация

Исходная копия страницы

Модиф. копия страницы

Разновидность такого механизма – copy-on-write – используется для реализации механизма отображения файлов в память, через который в

Слайд 14Загрузка модулей в адресное пространство
Исполняемые файлы в ОС Windows имеют

формат PE (Portable Executable).
При запуске программы создается адресное пространство

процесса, причем системный диапазон адр. простр. запускаемого процесса создается простым копированием части каталога страниц запускающего процесса.
Затем в пользовательский диапазон памяти отображается исполняемый модуль (обычно .exe) в соответствии с предпочтительным адресом загрузки, прописанном в PE-заголовке (задается компилятором, для MS VisualStudio обычно 0x400000).
Далее в соответствии с таблицей импорта модуля начинается подгрузка (также через механизм отображения в память) всех динамических библиотек, с которыми связан текущий загружаемый модуль. Каждая такая библиотека (обычно .dll) также имеет формат PE, и для нее весь процесс повторяется заново.
Если предпочтительный адрес загрузки уже занят, загрузчик перебазирует модуль на свободный участок виртуальной памяти. Кроме того, компилятор может создавать неперебазируемые модули, которые обязаны грузиться строго по указанному в заголовке адресу (обычно используется для некоторых модулей ядра). Сложность перебазирования определяется необходимостью загрузчику модифицировать инструкции, осуществляющие обращения по глобальным адресам (например при доступе к глобальным переменным) – компилятор формирует адреса таких переменных исходя из назначенного им же базового адреса загрузки, при перебазировании все такие адреса должны быть модифицированы. Список адресов кода, в которые требуется внести поправки при перебазировании, хранится в секции поправок (relocation table).
Загрузка модулей в адресное пространствоИсполняемые файлы в ОС Windows имеют формат PE (Portable Executable). При запуске программы

Слайд 15При завершении загрузки модуля со всеми его зависимостями управление передается

на функцию – точку входа в модуль, прописанную в его

PE-заголовке (это касается всех загружаемых модулей).
После отображения в память содержимое файла все еще не прочитано с диска. Такой файл начинает выступать в роли файла подкачки, а реальное чтение страниц произойдет при первом обращении к соответствующему адресу виртуальной памяти.
Даже после выгрузки модуля ОС по возможности сохраняет информацию о выполненном ранее отображении файла и его физические страницы, для ускорения возможной повторной загрузки модуля.
Перебазирование модуля – почти катастрофическая ситуация для ОС, поскольку вносимые поправки приводят к увеличению страниц физической памяти за счет срабатывания механизма copy-on-write. Кроме того, для перебазированного модуля становится затруднительным использовать оставшиеся от предыдущей загрузки страницы памяти.
В ОС начиная с Vista появился механизм Address Space Layout Randomization (ASLR), предназначенный для усложнения атак со стороны вредоносного кода, направленных на прямой доступ к коду и данным (в т.ч. стеку) системных библиотек. В соответствии с этим механизмом, модули размещаются по «случайно» выбираемым адресам.
В силу указанных выше проблем с перебазированием ОС не может допустить случайное размещение модуля в разных адресных пространствах. Вместо этого «случайные» адреса загрузки всех модулей вычисляются разово для всех модулей всех адресных пространств, и в ходе текущего сеанса работы ОС не пересчитываются.
При завершении загрузки модуля со всеми его зависимостями управление передается на функцию – точку входа в модуль,

Слайд 17Поддержка многозадачности
Механизм аппаратной поддержки многозадачности, предоставляемый архитектурой x86 (task switching)

– в Windows не используется при штатном функционировании ОС. Единственный

случай его применения – при обработке BSOD (“синий экран”), т.е. при фатальном сбое в работе ядра ОС, для обработки которого на время вывода диагностических сообщений и формирования crashdump требуется работоспособная программно-аппаратная среда. В момент штатной работы ОС все происходит в рамках одного task’а, для обработки BSOD происходит переключение в другую задачу, выхода из нее уже нет.

Собственно многозадачность в терминах ОС Windows реализуется программно и будет рассмотрена позже.
Поддержка многозадачностиМеханизм аппаратной поддержки многозадачности, предоставляемый архитектурой x86 (task switching) – в Windows не используется при штатном

Слайд 18Основные понятия: подведение итогов
Процесс – соответствует некоторому запущенному в ОС

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

работы.
Одним из важнейших ресурсов является адресное пространство. Каждому процессу выделяется свое собственное, единственное, адресное пространство.
Код выполняется в рамках потоков
Основные понятия: подведение итоговПроцесс – соответствует некоторому запущенному в ОС приложению. Процесс можно рассматривать как контейнер ресурсов,

Слайд 19Адресное пространство – диапазон виртуальных адресов, доступный процессу.
Для некоторой

части этих адресов средствами ОС при поддержке аппаратуры осуществляется трансляция

в физические адреса, т.е. обращение к таким адресам будет либо немедленно перенаправлено в ОЗУ или память устройств, или приведет к задействованию механизма подкачки страниц с диска в ОЗУ с последующим доступом к ним в ОЗУ.
Если аппаратные механизмы контроля доступа к памяти не позволяют осуществить доступ, либо трансляция памяти для адресуемой виртуальной ячейке отсутствует, процессор генерирует исключение.
Адресное пространство разбито на 2 диапазона: по младшим адресам – диапазон памяти, доступный и коду пользовательского режима (user mode = 3 кольцо защиты), и коду ядра (kernel mode = 4 к.з.; по старшим адресам – диапазон памяти ядра (доступен только коду ядре).
Диапазон памяти ядра с точки трансляции адресов – «общий» для всех адресных пространств
Вся память устройств, отображаемая на адресуемую физическую память, в виртуальном адресном пространстве отображается в диапазон памяти ядра. Доступ к портам в/в коду user mode также закрыт, в результате прикладной уровень не имеет возможности прямой работы с оборудованием.

Адресное пространство – диапазон виртуальных адресов, доступный процессу. Для некоторой части этих адресов средствами ОС при поддержке

Слайд 20TSS – Task State Segment
Процессор определяет 5 структур для организации

переключения задач (multitasking):
TSS - хранит состояние процессора
Дескриптор TSS (только в

GDT, в LDT быть не может) – задает расположение TSS в памяти. Используется для вызова задачи с помощью специальных call/jmp
Task-gate descriptor/шлюз задачи (может находиться в GDT, LDT или IDT). Содержит значение селектора TSS. Используется для вызова задачи с помощью специальных call/jmp или int.
Task register TR
Флаг NT в регистре флагов EFLAGS
В защищенном режиме TSS и Дескриптор TSS должны быть созданы хотя бы для одной задачи; селектор, определяющий Дескриптор TSS , д.б. загружен в регистр задач TR (инструкцией LTR)
TSS – Task State SegmentПроцессор определяет 5 структур для организации переключения задач (multitasking):TSS - хранит состояние процессораДескриптор

Слайд 22Переключение на другую задачу осуществляется в одном из 4 случаев:
выполняется

инструкция JMP или CALL на дескриптор TSS в GDT.
выполняется инструкция

JMP или CALL на шлюз задачи в GDT или LDT.
происходит прерывание через шлюз задачи в IDT.
выполняется инструкция IRET при установленном флаге NT (Nested Task).
Переключение на другую задачу осуществляется в одном из 4 случаев:выполняется инструкция JMP или CALL на дескриптор TSS

Слайд 23Обработка прерываний и исключений
Два механизма прерывания работы программы
interrupt – асинхронное

событие, как правило генерируемое устройством в/в.
exception – синхронное событие, генерируемое

при попытке выполнения инструкции, в случае если состояние процессора соответствует одному или нескольким предопределенным условиям.
Три класса исключений:
Faults –сохраняемый cs:eip указывает на адрес вызвавшей исключение инструкции. Позволяет рестартовать инструкцию, вызвавшую исключение. Пример – деление на 0
Traps – исключение генерируется после выполнения вызвавшей его инструкции. сохраняемый cs:eip указывает на адрес следующей инструкции. Пример – отладочные прерывания.
Aborts – инструкция, вызвавшая исключение, не всегда определена и не м.б. рестартована.
Три вида дескрипторов прерываний (хранятся в IDT):
Task Gate – переключает задачу
Interrupt Gate
Trap Gate
Разница между Interrupt Gate и Trap Gate – при использовании Interrupt Gate очищается флаг IF (запрет маскируемых прерываний). При использовании Trap Gate флаг IF остается неизменным.


Обработка прерываний и исключенийДва механизма прерывания работы программыinterrupt – асинхронное событие, как правило генерируемое устройством в/в.exception –

Слайд 24DPL задает ограничение на уровень привилегий кода, который пытается вызвать

прерывание (код 3-го кольца защиты не сможет вызвать прерывание с

DPL=0, т.е. 0 кольца защиты).
селектор в Interrupt и Trap Gate определяет, на каком кольце защиты будет работать обработчик прерывания. В ОС Windows все обработчики прерываний работают в 0 кольце защиты.
Если при входе/выходе в обработчик прерывания меняется кольцо защиты работающего кода – меняется и стек (значение вершины стека берется из текущего TSS из поля, соответствующего новому номеру кольца защиты).
В противном случае стек не меняется.

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

DPL задает ограничение на уровень привилегий кода, который пытается вызвать прерывание (код 3-го кольца защиты не сможет

Слайд 25EFLAGS
EIP
CS
Error code
Свободная часть стека
Занятая часть стека
ESP перед вызовом прерывания
ESP после

вызова прерывания
Рост адресов
EFLAGS
EIP
CS
Error code
Свободная часть стека 0
Занятая часть стека 0
SS:ESP

в процессе вызова прерывания после переключения кольца защиты – взяты из TSS

ESP после вызова прерывания

Рост адресов

Свободная часть стека 3

Занятая часть стека 3

SS:ESP перед вызовом прерывания

Рост адресов

Вызов прерывания: переключение 3->0 кольцо защиты (смена стека)

Вызов прерывания: без переключения колец защиты (стек не меняется)

ESP

SS

EFLAGSEIPCSError codeСвободная часть стекаЗанятая часть стекаESP перед вызовом прерыванияESP после вызова прерыванияРост адресовEFLAGSEIPCSError codeСвободная часть стека 0Занятая

Слайд 26Поток – единица исполнения в ОС Windows. Механизм вытесняющей многозадачности,

реализуемый ОС, применяется именно к потокам.
Программный код исполняется в

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


Контекст потока (thread context) – термин, часто используемый в ядерной документации. Состояние работы потока определяется принадлежащей ему копией процессорных регистров, в том числе отдельной копией регистра вершины стека для каждого кольца защиты. Для ядерных функций различают два вида контекстов:
Arbitrary thread context – контекст случайного потока – как правило для функций, вызов которых произошел из-за возникновения асинхронного аппаратного события, прервав работу произвольного работающего потока (на рис. стрелка вверх)
Non-arbitrary thread context – контекст определенного потока – как правило – контекст потока, в котором произошла передача управления в код ядра (на рис. стрелка вниз)

user

kernel

Модули и объекты ядра

Модули и объекты прикладной программы

аппаратура

Поток – единица исполнения в ОС Windows. Механизм вытесняющей многозадачности, реализуемый ОС, применяется именно к потокам. Программный

Слайд 27Система приоритетов
Windows NT имеет двухуровневую модель приоритетов:
Приоритеты высшего уровня (уровни

запросов прерываний - Interrupt ReQuest Level - IRQL) управляют аппаратными

и программными прерываниями
приоритеты низшего уровня (приоритеты планирования) управляются планировщиком и управляют исполнением потоков
Система приоритетовWindows NT имеет двухуровневую модель приоритетов:Приоритеты высшего уровня (уровни запросов прерываний - Interrupt ReQuest Level -

Слайд 28Классы приоритетов в Win32 API
0
31 для 32-разрядных ОС
15 для 64-разрядных

ОС
Приоритет из этого диапазона можно назначить, только обладая полномочиями администратора
Ядро

возвращает управление коду user mode только при уровне IRQL PASSIVE_LEVEL
Классы приоритетов в Win32 API031 для 32-разрядных ОС15 для 64-разрядных ОСПриоритет из этого диапазона можно назначить, только

Слайд 29Приоритеты планирования
Схема приоритетов, реализуемая ядром ОС:
При запуске потоку назначается базовый

приоритет (б.п.).
В процессе работы – у потока текущий приоритет (т.п.)
Если

базовый приоритет в динамическом диапазоне, исходно т.п. = б.п., затем в процессе работы ОС может менять т.п. по правилу: б.п. ≤ т.п. ≤ 15
Если базовый приоритет диапазоне realtime, то всегда т.п. = б.п.

Win32 API скрывает эту схему: процессу назначается класс приоритета, а его потокам – относительный приоритет – значение ±2 относительно класса приоритета потока.
Вся эта схема все равно сводится к системе приоритетов, реализуемой ядром ОС.
Приоритеты планированияСхема приоритетов, реализуемая ядром ОС:При запуске потоку назначается базовый приоритет (б.п.).В процессе работы – у потока

Слайд 30Планировщик Windows реализует «карусельную» (round-robbin) схему переключения потоков по правилу:
Если

есть потоки из диапазона realtime – переключение по карусельной схеме

будет только для потоков с максимальным значением realtime-приоритета. Любые потоки с более низким приоритетом никогда не исполнятся.
Если потоков с realtime-приоритетом нет – карусельная схема для всех потоков с динамическим диапазоном приоритетов. Для исполнения выбираются потоки с максимальным т.п., но у потоков с более низким т.п. он иногда инкрементируется, и в конце концов поток даже с самым низким базовым приоритетом в конце концов получит квант времени, после чего его т.п. сбрасывается.
Планировщик Windows реализует «карусельную» (round-robbin) схему переключения потоков по правилу:Если есть потоки из диапазона realtime – переключение

Слайд 31Уровни запросов прерываний (IRQL)
IRQL – способ управления маскированием прерываний на

конкретном процессоре. По сути – это некоторое число, назначаемое конкретному

процессору и обозначающее факт маскирования на нем некоторых прерываний. Конкретное значение IRQL из диапазона DIRQL (Device IRQL) также связывается с каждым обработчиком аппаратных прерываний. Если такое прерывание в данный момент не маскировано и оно доставлено процессору, ОС производит смену текущего IRQL процессора на IRQL прерывания, маскируя при этом все прерывания с IRQL ≤ нового IRQL процессора. При завершении обработчика прерывания значение IRQL процессора восстанавливается на старое.
На уровне IRQL_PASSIVE_LEVEL разрешены все прерывания и работает механизм многозадачности. Код пользовательского режима работает только на PASSIVE_LEVEL (возможно за исключением функций, вызываемых механизмом APC на уровне APC_LEVEL – надо проверить)
На уровне APC_LEVEL реализуется механизм асинхронного вызова процедур Asynchronous Procedure Call (APC), через который в частности ядро ОС реализует механизм обратного вызова функций (callback functions) прикладного уровня для уведомления прикладной программы ядром о возникновении некоторой ожидаемой ситуации. На этом уровне маскированы прерывания с IRQL= APC_LEVEL. Доступ к выгружаемой (paged) памяти разрешен.
Уровни запросов прерываний (IRQL)IRQL – способ управления маскированием прерываний на конкретном процессоре. По сути – это некоторое

Слайд 32На уровне IRQL DISPATCH_LEVEL работают так называемые «отложенные процедуры» (Deferred

Procedure Calls - DPC), поэтому этот уровень иногда называют DPC_LEVEL.

DPC создаются обработчиками прерываний, и туда выносится весь «долгоиграющий» код обработки прерывания. Задачей обработчика прерывания является как можно быстрее зафиксировать факт возникновения прерывания и данные, необходимые для дальнейшей обработки в DPC. Вызовы DPC помещаются в очередь (своя для каждого процессора), обработка которых ведется в соответствии с собственной системой приоритетов (low-medium-high). Возврата к IRQLНа этом же уровне работают прерывания, отвечающие за подгрузку страниц в ОЗУ из файлов подкачки, а также за диспетчеризацию потоков (проверить). Отсюда самое главное ограничение на код ядра:
На уровне IRQL DISPATCH_LEVEL работают так называемые «отложенные процедуры» (Deferred Procedure Calls - DPC), поэтому этот уровень

Слайд 33при работе процессора на уровне IRQL≥DISPATCH_LEVEL запрещено обращение к выгружаемой

памяти – оно немедленно приведет к BSOD с ошибкой IRQL_NOT_LESS_OR_EQUAL.


На уровне IRQL>PASSIVE_LEVEL нельзя предпринимать никакие действия, которые могут привести к необходимости переключения потока (например использовать механизмы синхронизации на основе диспетчерских объектов (Dispatcher Objects) с помощью функции WaitFor(Single/Multiple)Objects с ненулевым временем ожидания) – замаскированы обслуживающие такое переключение прерывания уровня APC и DISPATCH_LEVEL
Уровни выше DISPATCH_LEVEL в основном соответствуют обработчикам аппаратных прерываний (DIRQLs). Маскирована часть прерываний, к возникновению которых могут приводить в том числе вызовы многих служебных ядерных функций, поэтому множество разрешенных к вызову служебных функций сильно ограничено. Серьезная обработка должна быть вынесена в DPC, а иногда – и в рабочие потоки с IRQL PASSIVE_LEVEL.

при работе процессора на уровне IRQL≥DISPATCH_LEVEL запрещено обращение к выгружаемой памяти – оно немедленно приведет к BSOD

Слайд 34Пространство имен диспетчера объектов

Пространство имен диспетчера объектов

Слайд 35Имена ядерных объектов размещаются в едином пространстве имен. Подсистема Win32

скрывает его наличие, транслируя обращения к именам объектов различных типов

в обращения к конкретным директориям единого пространства имен, иногда полностью видоизменяя имя объекта. Например, обращение к файлу с именем c:\foo.txt будет последовательно трансформировано следующим образом:
\??\c:\foo.txt
В директории \?? будет найден элемент с именем С:, имеющий тип SymbolicLink и значение \Device\HarddiskVolume1, в результате подстановки имя примет вид
\Device\HarddiskVolume1\foo.txt
В директории \Device будет найден элемент с типом Device, на этом разбор имени будет закончен, а соответствующему устройству будет отправлен запрос, частью которого будет оставшаяся неразобранной часть имени \foo.txt – дальше с ней будет разбираться драйвер устройства.
Имена ядерных объектов размещаются в едином пространстве имен. Подсистема Win32 скрывает его наличие, транслируя обращения к именам

Слайд 36Структура драйвера
Работа драйвера начинается с вызова функции DriverEntry, соответствующей начальной

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

PE-заголовке.
Задача DriverEntry – инициализация работы драйвера. Если она завершается успешно – функция завершается с кодом STATUS_SUCCESS (=0), после чего драйвер остается в памяти ядра. При любом другом коде завершения драйвер выгружается из памяти.
Работа драйвера после загрузки по сути определяется тем, какие callback-функции зарегистрированы драйвером при инициализации:
Диспетчерские функции: массив точек входа DriverObject->MajorFunction[], вызываемых для обработки запросов, направляемых устройствам драйвера.
Обработчики прерываний (ISR), отложенных вызовов (DPC).
Подмена адресов функций обработки вызовов системных сервисов.
Функции, определяемые типом программного интерфейса задействованного при инициализации (например NDIS).
В ОС начиная с Vista – драйверы должны быть подписаны. Механизм проверки подписи может быть отключен при запуске ОС.
Структура драйвераРабота драйвера начинается с вызова функции DriverEntry, соответствующей начальной точке входа в исполняемый модуль драйвера, которая

Слайд 37ограничения
Проблема при попытке написания кода драйвера на C++: функция main()

– не первая функция с которой начинает работать код (инициализация

памяти, вызов конструкторов глобальных экземпляров классов).
В коде драйвера – жесткие ограничения на библиотечные функции. Разрешенные к вызову – можно вызывать только функции ядра, некоторые типы драйверов имеют ограниченный набор функций, разрешенных к вызову, чтобы соответствовать некоторой спецификации – например NDIS.
Для всех доступных для вызова ядерных функций документация фиксирует жесткие условия, в которых они могут быть вызваны (контекст потока – случайный или нет, уровень IRQL, с которого может быть осуществлен вызов). Кроме того – ограничения на доступ к памяти (не всегда можно работать с paged-памятью) в зависимости от условий работы.
В ранних версиях ОС было ограничение (запрет) на работу с инструкциями FPU, возникающее вследствие несохранения контекста FPU при переключении user->kernel mode
ограниченияПроблема при попытке написания кода драйвера на C++: функция main() – не первая функция с которой начинает

Слайд 38Среда разработки
Пакет DDK/WDK, поддерживает все ОС начиная с WinXP
VisualStudio
Для интеграции

WDK с VisualStudio – сторонний продукт VisualDDK, без него –

утилита build, работающая в режиме командной строки
В пакете WDK для Windows 8 впервые официально заявлена возможность использования IDE (Integrated Development Environment) VisualStudio, но с рядом ограничений (в частности не поддерживается разработка драйверов для XP, предположительно поддерживает только WDF/KMDF-драйверы, полнофункциональная поддержка всех типов драйверов – по прежнему официально только через build)
Среда разработкиПакет DDK/WDK, поддерживает все ОС начиная с WinXPVisualStudioДля интеграции WDK с VisualStudio – сторонний продукт VisualDDK,

Слайд 39Многоуровневая модель сокрытия знаний о функционировании ядра ОС
WDF/KMDF – WDM

– Legacy Drivers

Legacy Drivers – по сути способ написания ядерного

драйвера «с нуля», опираясь только на знания архитектуры ОС. По сути архитектура таких драйверов не претерпела изменений с момента появления ОС Windows NT 3.51. Такой драйвер будет работать на всех современных Windows
WDM – Windows Driver Model – модель драйверов, изначально разработанная как способ реализации универсальных драйверов для кардинально различающихся линеек ОС: Win9x и WinNT, путем предоставления драйверу для работы некоторого программного окружения, по сути соответствующего поведению ядра ОС линейки Windows NT . Данная архитектура стала стандартом для драйверов всех последующих ОС данной линейки
WDF/KMDF – фреймворк, программная оболочка, скрывающая реальное устройство ядра ОС для облегчения разработки драйверов, но может стать «тормозом» при разработке «продвинутых» драйверов
Многоуровневая модель сокрытия знаний о функционировании ядра ОСWDF/KMDF – WDM – Legacy DriversLegacy Drivers – по сути

Слайд 42Интеграции WDK с VisualStudio –VisualDDK

Интеграции WDK с VisualStudio –VisualDDK

Слайд 44#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif

#include

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN

PUNICODE_STRING RegistryPath)
{
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
return STATUS_SUCCESS;
}
Код простейшего драйвера: ничего делать

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

Регистрация драйвера в реестре. Обратить внимание на формат пути в ImagePath

Зарегистрированный драйвер может быть запущен и остановлен с помощью консольных команд
net start имя_сервиса / net stop имя_сервиса
Программно драйвер управляется с помощью WinAPI-функций диспетчера сервисов (SCM - ServiceControlManager): CreateService/DeleteService/StartService/ControlService


Слайд 45#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif

#include

void OnUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("simple: OnUnload\n");
}

NTSTATUS

DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
DriverObject->DriverUnload =

OnUnload;
return STATUS_SUCCESS;
}

Для обеспечения возможности выгрузки драйвера он должен реализовывать обработчик функции выгрузки DriverObject->DriverUnload
Задача этой функции – очистка всех ресурсов, выделенных при работе драйвера


Слайд 46#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include
void OnUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("simple: OnUnload\n");
IoDeleteDevice(DriverObject->DeviceObject);
}
NTSTATUS OnCreate(IN

PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnCreate\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,

IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS OnClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnClose\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNICODE_STRING DeviceName;
PDEVICE_OBJECT DeviceObject = NULL;
NTSTATUS status;
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
RtlInitUnicodeString(&DeviceName,L"\\Device\\simple0");
DriverObject->MajorFunction[IRP_MJ_CREATE] = OnCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = OnClose;
DriverObject->DriverUnload = OnUnload;
status = IoCreateDevice(DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject);
if (!NT_SUCCESS(status))
return status;
if (!DeviceObject)
return STATUS_UNEXPECTED_IO_ERROR;
DeviceObject->Flags |= DO_DIRECT_IO;
DeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;

return STATUS_SUCCESS;
}

Взаимодействие с драйвером обычно осуществляется через создаваемые им устройства, которым направляются запросы в/в в формате IRP-пакетов

DeviceObject);}NTSTATUS OnCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ DbgPrint("simple: OnCreate\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS;}NTSTATUS OnClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ DbgPrint("simple: OnClose\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS;}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath){ UNICODE_STRING DeviceName; PDEVICE_OBJECT DeviceObject = NULL; NTSTATUS status; DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer); RtlInitUnicodeString(&DeviceName,L"\\Device\\simple0"); DriverObject->MajorFunction[IRP_MJ_CREATE] = OnCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = OnClose; DriverObject->DriverUnload = OnUnload; status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (!NT_SUCCESS(status)) return status; if (!DeviceObject) return STATUS_UNEXPECTED_IO_ERROR; DeviceObject->Flags |= DO_DIRECT_IO; DeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT; return STATUS_SUCCESS;}Взаимодействие с драйвером обычно осуществляется через создаваемые им устройства, которым направляются запросы в/в в формате IRP-пакетов">DeviceObject);}NTSTATUS OnCreate(IN PDEVICE_OBJECT" alt="#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #include void OnUnload(IN PDRIVER_OBJECT DriverObject){ DbgPrint("simple: OnUnload\n"); IoDeleteDevice(DriverObject->DeviceObject);}NTSTATUS OnCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ DbgPrint("simple: OnCreate\n"); Irp->IoStatus.Status =">

Слайд 47#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include
void OnUnload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING Win32Device;
DbgPrint("simple: OnUnload\n");
RtlInitUnicodeString(&Win32Device,L"\\DosDevices\\simple0");
IoDeleteSymbolicLink(&Win32Device);
IoDeleteDevice(DriverObject->DeviceObject);
}
NTSTATUS

OnCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnCreate\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information =

0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS OnClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
DbgPrint("simple: OnClose\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
UNICODE_STRING DeviceName,Win32Device;
PDEVICE_OBJECT DeviceObject = NULL;
NTSTATUS status;
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);
RtlInitUnicodeString(&DeviceName,L"\\Device\\simple0");
RtlInitUnicodeString(&Win32Device,L"\\DosDevices\\simple0");
DriverObject->MajorFunction[IRP_MJ_CREATE] = OnCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = OnClose;
DriverObject->DriverUnload = OnUnload;
status = IoCreateDevice(DriverObject,
0,
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject);
if (!NT_SUCCESS(status))
return status;
if (!DeviceObject)
return STATUS_UNEXPECTED_IO_ERROR;
DeviceObject->Flags |= DO_DIRECT_IO;
DeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
IoCreateSymbolicLink(&Win32Device, &DeviceName);
return STATUS_SUCCESS;
}

Чтобы к устройству мог обратиться код пользовательского режима, имя этого устройства должно стать видимым в специальной части пространства имен диспетчера объектов. Это достигается путем создания символической ссылки.

DeviceObject);}NTSTATUS OnCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ DbgPrint("simple: OnCreate\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS;}NTSTATUS OnClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ DbgPrint("simple: OnClose\n"); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS;}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath){ UNICODE_STRING DeviceName,Win32Device; PDEVICE_OBJECT DeviceObject = NULL; NTSTATUS status; DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer); RtlInitUnicodeString(&DeviceName,L"\\Device\\simple0"); RtlInitUnicodeString(&Win32Device,L"\\DosDevices\\simple0"); DriverObject->MajorFunction[IRP_MJ_CREATE] = OnCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = OnClose; DriverObject->DriverUnload = OnUnload; status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (!NT_SUCCESS(status)) return status; if (!DeviceObject) return STATUS_UNEXPECTED_IO_ERROR; DeviceObject->Flags |= DO_DIRECT_IO; DeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT; IoCreateSymbolicLink(&Win32Device, &DeviceName); return STATUS_SUCCESS;}Чтобы к устройству мог обратиться код пользовательского режима, имя этого устройства должно стать видимым в специальной части пространства имен диспетчера объектов. Это достигается путем создания символической ссылки.">DeviceObject);}NTSTATUS OnCreate(IN" alt="#ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #include void OnUnload(IN PDRIVER_OBJECT DriverObject){ UNICODE_STRING Win32Device; DbgPrint("simple: OnUnload\n"); RtlInitUnicodeString(&Win32Device,L"\\DosDevices\\simple0"); IoDeleteSymbolicLink(&Win32Device); IoDeleteDevice(DriverObject->DeviceObject);}NTSTATUS OnCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ DbgPrint("simple: OnCreate\n"); Irp->IoStatus.Status">

Слайд 48Взаимосвязь основных ядерных объектов при прохождении запроса в/в

Взаимосвязь основных ядерных объектов при прохождении запроса в/в

Слайд 49DRIVER_OBJECT
Найти устройство по имени
int 2E
SYSCALL
SYSENTER
DeviceObject
MajorFunction[]
DriverUnload()
CreateFile(filename, …)
NtCreateFile(filename, …)
Диспетчер в/в
Найти адрес функции

в таблице системных сервисов
NtCreateFile(filename, …)
DEVICE_OBJECT
DriverObject
NextDevice
AttachedDevice
DeviceExtension
Найти драйвер этого устройства
Сформировать IRP-запрос на

основе параметров CreateFile()

Передать IRP-запрос для устройства через IoCallDriver

DrvObj->MajorFunction[IRP_MJ_CREATE](pDevObj, pIrp )

FILE_OBJECT

DeviceObject

Создать файловый объект, описывающий сеанс связи с устройством

IRP

FileObject
DeviceObject
MajorFunction

= IRP_MJ_CREATE

pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, 0);
return STATUS_SUCCESS;

Если завершение с ошибкой – уничтожить файловый объект

Если успешно – поместить его адрес в таблицу описателей и вернуть индекс как HANDLE

FileObject

Таблица описателей

HANDLE – индекс в таблице описателей

Вернуть управление в CreateFile

Native API: ntdll.dll

user

kernel

DRIVER_OBJECTНайти устройство по имениint 2ESYSCALLSYSENTERDeviceObjectMajorFunction[]DriverUnload()CreateFile(filename, …)NtCreateFile(filename, …)Диспетчер в/вНайти адрес функции в таблице системных сервисовNtCreateFile(filename, …)DEVICE_OBJECTDriverObjectNextDeviceAttachedDeviceDeviceExtensionНайти драйвер этого

Слайд 50DRIVER_OBJECT
Найти файловый объект по описателю
int 2E
SYSCALL
SYSENTER
DeviceObject
MajorFunction[]
DriverUnload()
ReadFile(hfile, …)
NtReadFile(hfile, …)
Диспетчер в/в
Найти адрес

функции в таблице системных сервисов
NtReadFile(hfile, …)
DEVICE_OBJECT
DriverObject
NextDevice
AttachedDevice
DeviceExtension
Найти устройство
Сформировать IRP-запрос на основе

параметров ReadFile()

Передать IRP-запрос для устройства через IoCallDriver

DrvObj->MajorFunction[IRP_MJ_READ](pDevObj, pIrp )

FILE_OBJECT

DeviceObject

Найти драйвер этого устройства

IRP

FileObject
DeviceObject
MajorFunction

= IRP_MJ_READ

pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, 0);
return STATUS_SUCCESS;

//Отложить обработку: сохранить pIrp
IoMarkIrpPending(pIrp);
return STATUS_PENDING;

//где-то в случайном контексте
IoCompleteRequest(pIrp, 0);

FileObject

Таблица описателей

HANDLE – индекс в таблице описателей

Если завершено или вызов асинхронный – вернуть управление в ReadFile

Если не завершено и вызов синхронный – не возвращать управление до завершения запроса

Native API: ntdll.dll

user

kernel

Используется APC для доставки уведомления о завершении запроса в/в

DRIVER_OBJECTНайти файловый объект по описателюint 2ESYSCALLSYSENTERDeviceObjectMajorFunction[]DriverUnload()ReadFile(hfile, …)NtReadFile(hfile, …)Диспетчер в/вНайти адрес функции в таблице системных сервисовNtReadFile(hfile, …)DEVICE_OBJECTDriverObjectNextDeviceAttachedDeviceDeviceExtensionНайти устройствоСформировать

Слайд 51Примеры анализа драйвера
Использование дизассемблера IDA Pro
Использование среды анализа TREX

Примеры анализа драйвераИспользование дизассемблера IDA ProИспользование среды анализа TREX

Слайд 52Отладочные возможности процессора, особенности в Windows, средства отладки
Пошаговая отладка: TraceFlag

в регистре флагов

Точки прерывания:
Модификация отлаживаемого кода – запись инструкции int

3 (код 0xCC)
Отладочные регистры DR0-DR3 (не более 4 точек прерывания на доступ к памяти r/w/e)

И при пошаговой отладке, и при использовании отладочных регистров генерируется прерывание int 1
Отладочные возможности процессора, особенности в Windows, средства отладкиПошаговая отладка: TraceFlag в регистре флаговТочки прерывания:Модификация отлаживаемого кода –

Слайд 53Проблемы программных отладчиков
Конкуренция с отлаживаемой программой за общие ресурсы: программные

отладчики могут быть выявлены путем контроля флагового регистра, отладочных регистров,

контроля целостности кода (выявление программных точек останова)

При отладке ядерным отладчиком ядерного кода – использование общего стека (можно было бы запускать обработку отладочных прерываний в отдельной задаче, но ни один доступный отладчик этого не делает) – отлаживаемый код может нарушить работу отладчика. Пример.

Доставлять или нет отлаживаемой программе контролируемые отладчиком прерывания? Все существующие отладчики могут быть выявлены при специальной организации в отлаживаемой программе обработки исключений (механизм SEH - Structured Exception Handling или VEH – Vectored Exception Handling). Пример.

Катастрофическое замедление программы при пошаговой отладке - все существующие отладчики могут быть выявлены путем контроля времени исполнения фрагментов программного кода

//выполните любым системным отладчиком (не эмулятором) пошаговую отладку данного кода драйвера:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
static ULONG tmpesp;
DbgPrint("simple: DriverEntry, regpath=%ws\n", RegistryPath->Buffer);

__asm
{
mov tmpesp, esp
mov esp, 0
mov eax, 0
mov esp, tmpesp
}
return STATUS_SUCCESS;
}

Задача: найти ошибку в этом коде (исправить одну инструкцию)


Слайд 54«Классические» отладчики уровня ОС фактически вытеснены отладочными возможностями симуляторов:

backend: VmWare
frontend:

интерфейс GDB (IDA, VisualStudio, …)

QEMU, VirtualPC, BOCHS, AMD SimNow
Сам

по себе дизассемблер или отладчик не автоматизирует решение задачи анализа потока данных, если под анализом понимать отслеживание зависимостей между инструкциями (по управлению и данным). Автоматизацией занимаются средства-«надстройки», например:
автоматизация поверх IDA:
Фирма zynamics (www.zynamics.com), средство BinNavi
Автоматизация поверх отладчиков - трассировка:
BitBlaze/TEMU
Valgrind + Avalanche
TREX
«Классические» отладчики уровня ОС фактически вытеснены отладочными возможностями симуляторов:backend: VmWarefrontend: интерфейс GDB (IDA, VisualStudio, …) QEMU, VirtualPC,

Слайд 55Взаимодействие с устройствами
ReadFile – ZwReadFile – IRP_MJ_READ
WriteFile – ZwWriteFile –

IRP_MJ_WRITE
DeviceIoControl – ZwDeviceIoControl – IRP_MJ_DEVICE_CONTROL

Взаимодействие с устройствамиReadFile – ZwReadFile – IRP_MJ_READWriteFile – ZwWriteFile – IRP_MJ_WRITEDeviceIoControl – ZwDeviceIoControl – IRP_MJ_DEVICE_CONTROL

Слайд 56Flags
StackSize = 3
AttachedDevice
Flags
StackSize = 1
AttachedDevice

Пересылка запросов между устройствами - IoCallDriver()

Получение

адреса устройства по имени – IoGetDeviceObjectPointer()

Подключение фильтра – IoAttachDeviceToDeviceStack()
DeviceObjectN
DeviceObject1
Flags
StackSize =

2
AttachedDevice

FltDeviceObject

IoCallDriver()

FlagsStackSize = 3AttachedDeviceFlagsStackSize = 1AttachedDevice…Пересылка запросов между устройствами - IoCallDriver()Получение адреса устройства по имени – IoGetDeviceObjectPointer()Подключение фильтра

Слайд 57Драйвера подразделяются на 3 класса по их положению в стеке

драйверов: драйвера высшего уровня, драйвера промежуточного уровня и драйвера низшего

уровня.
Драйвер высшего уровня –верхний в стеке драйверов, получает запросы через Диспетчер в/в от компонентов прикладного уровня.
Драйвер высшего уровня (или, что более правильно, устройство высшего уровня) имеет один или несколько стеков размещения в/в.

Драйвера подразделяются на 3 класса по их положению в стеке драйверов: драйвера высшего уровня, драйвера промежуточного уровня

Слайд 58Число стеков размещения в/в устанавливается Диспетчером в/в в поле StackSize

объекта-устройство. По умолчанию это значение равно 1. Присваивание происходит при

создании устройства функцией IoCreateDevice(). Если создаётся многоуровневый драйвер, необходимо установить StackSize на 1 больше, чем StackSize нижележащего объекта-устройство. В случае, если устройство будет использовать больше одного устройства уровнем ниже, его поле StackSize должно быть на 1 больше максимального значения StackSize всех устройств уровнем ниже.
Число стеков размещения в/в устанавливается Диспетчером в/в в поле StackSize объекта-устройство. По умолчанию это значение равно 1.

Слайд 59Код запроса в/в сохранен в поле MajorFunction (есть еще второстепенный

код MinorFunction) текущего стека размещения ввода - вывода в IRP

PIO_STACK_LOCATION

IoStack;
IoStack = IoGetCurrentIrpStackLocation(Irp);
switch (IoStack->MajorFunction)
{
case IRP_MJ_READ:

break;

}

Доступ к стеку нижележащего устройства:
IoStack = IoGetNextIrpStackLocation(Irp);

Код запроса в/в сохранен в поле MajorFunction (есть еще второстепенный код MinorFunction) текущего стека размещения ввода -

Слайд 60BOOL WINAPI ReadFile(
__in HANDLE hFile,

__out LPVOID lpBuffer,
__in DWORD

nNumberOfBytesToRead,
__out_opt LPDWORD lpNumberOfBytesRead,
__inout_opt LPOVERLAPPED lpOverlapped );
BOOL WINAPI ReadFile(  __in HANDLE   hFile,  __out LPVOID   lpBuffer,  __in

Слайд 61BOOL WINAPI WriteFile(
__in HANDLE hFile,


__in LPCVOID lpBuffer,
__in DWORD

nNumberOfBytesToWrite,
__out_opt LPDWORD lpNumberOfBytesWritten,
__inout_opt LPOVERLAPPED lpOverlapped );
BOOL WINAPI WriteFile(  __in HANDLE    hFile,  __in LPCVOID   lpBuffer,

Слайд 62По адресу lpNumberOfBytesRead/ lpNumberOfBytesWritten в результате успешного завершения функции помещается

содержимое поля Irp->IoStatus.Information диспетчерской функции драйвера, обработавшего

запрос
По адресу lpNumberOfBytesRead/ lpNumberOfBytesWritten в результате успешного завершения функции помещается содержимое поля    Irp->IoStatus.Information диспетчерской

Слайд 63Метод передачи буфера, используемый в запросах чтения и записи, контролируется

полем Flags объекта-устройство (DeviceObject->Flags).
После создания объекта-устройство с помощью функции

IoCreateDevice() необходимо выставить в нем нужные флаги.
Можно устанавливать несколько флагов, при этом применяются следующие правила:
Если установлены флаги DO_BUFFERED_IO или DO_DIRECT_IO, метод передачи буфера будет соответственно буферизованным или прямым
Если поле флагов не инициализировано (никакие флаги не установлены), используется метод передачи буфера Neither.
Одновременная установка флагов DO_BUFFERED_IO и DO_DIRECT_IO запрещена и будет являться ошибкой.
Метод передачи буфера, используемый в запросах чтения и записи, контролируется полем Flags объекта-устройство (DeviceObject->Flags). После создания объекта-устройство

Слайд 65Размер буфера для операций чтения/записи расположен в стеке размещения в/в:
Stack->Parameters.Read.Length
Stack->Parameters.Write.Length

С

точки зрения языка СИ поле Parameters – это объединение (union)

структур для всех видов запросов
т.е. одна и та же область памяти (поле Parameters) может интерпретироваться различно в зависимости от вида запроса (код главной функции Stack->MajorFunction)

Размер буфера для операций чтения/записи расположен в стеке размещения в/в:Stack->Parameters.Read.LengthStack->Parameters.Write.LengthС точки зрения языка СИ поле Parameters –

Слайд 66Neither I/O
buf_1
user
kernel
virtual address
Адр. простр.1
Адр. простр.2
buf_2
физ. память
чтение/запись

Neither I/Obuf_1userkernelvirtual addressАдр. простр.1Адр. простр.2buf_2физ. памятьчтение/запись

Слайд 67Direct I/O
buf_1
user
kernel
virtual address
Адр. простр.1
Адр. простр.2
buf_2
физ. память
buf_3
MDL
MmGetSystemAddressForMdl()
чтение/запись
MDL – структура, описывающая буфер

с сохранением информации об адресном пространстве, по сути описываются страницы

физической памяти
Буфер в пользовательском диапазоне адресов, описанный через MDL, можно использовать вне зависимости от текущего адресного пространства
Преобразование MDL в буфер в виртуальной памяти в системном диапазоне адресов:
OutBuffer = MmGetSystemAddressForMdl( Irp->MdlAddress );
Direct I/Obuf_1userkernelvirtual addressАдр. простр.1Адр. простр.2buf_2физ. памятьbuf_3MDLMmGetSystemAddressForMdl()чтение/записьMDL – структура, описывающая буфер с сохранением информации об адресном пространстве, по

Слайд 68Buffered I/O
buf_1
user
kernel
virtual address
Адр. простр.1
Адр. простр.2
buf_2
физ. память
buf_3
Буфер в невыгружаемой памяти
чтение/запись

Buffered I/Obuf_1userkernelvirtual addressАдр. простр.1Адр. простр.2buf_2физ. памятьbuf_3Буфер в невыгружаемой памятичтение/запись

Слайд 69BOOL WINAPI DeviceIoControl(
__in HANDLE hDevice,


__in DWORD dwIoControlCode,
__in_opt LPVOID

lpInBuffer,
__in DWORD nInBufferSize,
__out_opt LPVOID lpOutBuffer,
__in DWORD nOutBufferSize,
__out_opt LPDWORD lpBytesReturned,
__inout_opt LPOVERLAPPED lpOverlapped );
BOOL WINAPI DeviceIoControl(  __in HANDLE    hDevice,  __in DWORD    dwIoControlCode,

Слайд 70По адресу lpBytesReturned в результате успешного завершения функции помещается содержимое

поля Irp->IoStatus.Information диспетчерской функции драйвера, обработавшего запрос

Способ

передачи буфера в отличие от запросов чтения/записи управляется полем Method в значении контрольного кода dwIoControlCode
По адресу lpBytesReturned в результате успешного завершения функции помещается содержимое поля    Irp->IoStatus.Information диспетчерской функции

Слайд 71CTL_CODE( DeviceType, Function, Method, Access )
DeviceType определяет тип объекта-устройство, которому

предназначен запрос. Это тот самый тип устройства, который передается функции

IoCreateDevice() при создании устройства.
Существует два диапазона значений типов устройств:
0-32767 – зарезервированные значения для стандартных типов устройств,
32768-65535 – диапазон значений типов устройств для выбора разработчиком.

Function идентифицирует конкретные действия, которые должно предпринять устройство при получении запроса. Значение должны быть уникальным внутри устройства. Два диапазона значений:
0-2047 – зарезервированный диапазон значений,
2048-4095 – диапазон значений, доступный разработчикам устройств.

FILE_ANY_ACCESS 0
FILE_READ_ACCESS 0x01
FILE_WRITE_ACCESS 0x02

METHOD_BUFFERED 0
METHOD_IN_DIRECT 1
METHOD_OUT_DIRECT 2
METHOD_NEITHER 3

CTL_CODE( DeviceType, Function, Method, Access )DeviceType определяет тип объекта-устройство, которому предназначен запрос. Это тот самый тип устройства,

Слайд 73METHOD_BUFFERED
InBuf[InBufferSize ]
OutBuf[OutBufferSize ]
Промежуточный буфер
[max(InBufferSize, OutBufferSize)]
Irp->AssociatedIrp.SystemBuffer
1. Перед вызовом IRP_MJ_DEVICE_CONTROL копируется InBufferSize

байт
2. После успешного завершения IRP_MJ_DEVICE_CONTROL копируется Irp->IoStatus.Information байт

METHOD_BUFFEREDInBuf[InBufferSize ]OutBuf[OutBufferSize ]Промежуточный буфер[max(InBufferSize, OutBufferSize)]Irp->AssociatedIrp.SystemBuffer1. Перед вызовом IRP_MJ_DEVICE_CONTROL копируется InBufferSize байт2. После успешного завершения IRP_MJ_DEVICE_CONTROL  копируется

Слайд 74Механизмы синхронизации
Спин-блокировки – для межпроцессорной синхронизации, синхронизации на уровне IRQL=DISPATCH_LEVEL

(обычные блокировки) или >DISPATCH_LEVEL (DIRQL)(блокировки обработчиков прерываний)
Для синхронизации на уровне

либо Ресурсы Исполнительной системы (позволяют управлять синхронизацией вида «писатель - читатели»)
Механизмы синхронизацииСпин-блокировки – для межпроцессорной синхронизации, синхронизации на уровне IRQL=DISPATCH_LEVEL (обычные блокировки) или >DISPATCH_LEVEL (DIRQL)(блокировки обработчиков прерываний)Для

Слайд 75Спин-блокировки (spin-lock)
Спинлоки служат для обеспечения монопольного доступа потока к защищаемой

структуре данных.
Физически спинлок представляет собой переменную в памяти и реализуется

на атомарных операциях, которые должны присутствовать в системе команд процессора. Каждый процессор, желающий получить доступ к разделяемому ресурсу, атомарно записывает условное значение «занято» в эту переменную, используя аналог операции swap (в архитектуре x86 — xchg). Если предыдущее значение переменной (возвращаемое командой) было «свободно» то считается, что данный процессор получил доступ к ресурсу, в противном случае, процессор возвращается к операции swap и крутится в цикле ожидая, пока спинлок будет освобождён. После работы с разделяемым ресурсом процессор-владелец спинлока должен записать в него условное значение «свободно».
Спин-блокировки (spin-lock)Спинлоки служат для обеспечения монопольного доступа потока к защищаемой структуре данных.Физически спинлок представляет собой переменную в

Слайд 76Пример реализации спин-блокировки (для ядра Windows неверен)
mov eax, spinlock_address
mov

ebx, SPINLOCK_BUSY
wait_cycle:
lock xchg [eax], ebx
cmp ebx, SPINLOCK_FREE


jnz wait_cycle

Поток 1

Поток 2

SP

1.

2.

3,4...

Пример реализации спин-блокировки (для ядра Windows неверен)	mov eax, spinlock_address 	mov ebx, SPINLOCK_BUSY wait_cycle: 	lock xchg [eax], ebx

Слайд 77Поток 1
Поток 2
SP
1.
2.
4,5...
CPU
3.
IRQL=PASSIVE_LEVEL
IRQL=PASSIVE_LEVEL
6.
7.
9.
8.

Поток 1Поток 2SP1.2.4,5...CPU3.IRQL=PASSIVE_LEVELIRQL=PASSIVE_LEVEL6.7.9.8.

Слайд 78Поток 1
Поток 2
SP
Квантование времени выключено,
переключение на поток1 невозможно
deadlock
2.
4,5...
CPU
3.
IRQL=PASSIVE_LEVEL
IRQL=PASSIVE_LEVEL
6.
IRQL=DISPATCH_LEVEL
1.

Поток 1Поток 2SPКвантование времени выключено,переключение на поток1 невозможноdeadlock2.4,5...CPU3.IRQL=PASSIVE_LEVELIRQL=PASSIVE_LEVEL6.IRQL=DISPATCH_LEVEL1.

Слайд 79Поток 1
Поток 2
SP
1.
2.
3,4...
CPU1
IRQL=PASSIVE_LEVEL
IRQL=DISPATCH_LEVEL
5.
6.
IRQL=DISPATCH_LEVEL
Поток 1
Поток 2
SP
1.
2.
5
CPU
4.
IRQL=PASSIVE_LEVEL
IRQL=DISPATCH_LEVEL
3.
6.
IRQL=DISPATCH_LEVEL
CPU2
Правильная реализация спин-блокировок в ядре Windows:


в момент захвата спин-блокировки уровень IRQL повышается до некоторого уровня

IRQL>=DISPATCH, ассоциированного со спин-блокировкой;
в момент освобождения - восстановление старого уровня IRQL.

Однопроцессорная система: если какой-то поток уже захватил блокировку, переключение на другой поток невозможно до ее освобождения (за счет отключения механизма вытесняющей многозадачности)

Многопроцессорная система: попытка захвата уже занятой блокировки может последовать только со стороны другого процессора, его работа блокируется до освобождения блокировки первым процессором

Поток 1Поток 2SP1.2.3,4...CPU1IRQL=PASSIVE_LEVELIRQL=DISPATCH_LEVEL5.6.IRQL=DISPATCH_LEVELПоток 1Поток 2SP1.2.5CPU4.IRQL=PASSIVE_LEVELIRQL=DISPATCH_LEVEL3.6.IRQL=DISPATCH_LEVELCPU2Правильная реализация спин-блокировок в ядре Windows: в момент захвата спин-блокировки уровень IRQL повышается

Слайд 80С каждой спин-блокировкой связан конкретный уровень IRQL, на который перейдет

процессор после захвата блокировки.

В соответствии с правилом, нельзя использовать

блокировку из кода, работающего на IRQL>IRQL блокировки.

В Windows – 2 вида спин-блоктровок:
Обычные – с ними связан IRQL=DISPATCH_LEVEL
Спин-блокировки синхронизации прерываний – с ними связан один из DIRL

Правило использования спин-блокировок – ограничение на IRQL до и после захвата:

IRQLдо <= IRQLпосле

С каждой спин-блокировкой связан конкретный уровень IRQL, на который перейдет процессор после захвата блокировки. В соответствии с

Слайд 81Функции для работы с обычными спин-блокировками
VOID KeInitializeSpinLock(IN PKSPIN_LOCK SpinLock);
VOID KeAcquireSpinLock(IN

PKSPIN_LOCK SpinLock, OUT PKIRQL OldIrql);
VOID KeReleaseSpinLock(IN PKSPIN_LOCK SpinLock, IN KIRQL

NewIrql);
VOID KeAcquireLockAtDpcLevel(IN PKSPIN_LOCK SpinLock);
VOID KeReleaseLockFromDpcLevel(IN PKSPIN_LOCK SpinLock);

typedef struct _DEVICE_EXTENSION
{
...
KSPIN_LOCK spinlock
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;

NTSTATUS DriverEntry(....)
{
KeInitializeSpinLock(&extension->spinlock);
}
NTSTATUS DispatchReadWrite( .... )
{
KIRQL OldIrql;
...
KeAcquireSpinLock(&extension->spinlock, &0ldIrql);
// произвести обработку данных, защищенных спин-блокировкой
KeReleaseSpinLock(&extension->spinlock, OldIrql);
}

Функции для работы с обычными спин-блокировкамиVOID KeInitializeSpinLock(IN PKSPIN_LOCK SpinLock);VOID KeAcquireSpinLock(IN PKSPIN_LOCK SpinLock, OUT PKIRQL OldIrql);VOID KeReleaseSpinLock(IN PKSPIN_LOCK

Слайд 82Взаимоблокировки (deadlocks)
Решение: блокировки должны захватываться всеми потоками в одном порядке
Поток

1
Поток 2
SP1
1
2
4,...
SP2
3,...
Проблема:
Поток 1
Поток 2
SP1
1.
2.
5,...
SP2
4
3.
6.

Взаимоблокировки (deadlocks)Решение: блокировки должны захватываться всеми потоками в одном порядкеПоток 1Поток 2SP1124,...SP23,...Проблема:Поток 1Поток 2SP11.2.5,...SP243.6.

Слайд 83Диспетчерские объекты Dispatcher Objects
набор механизмов синхронизации, рассчитанных на применение в основном

для уровня IRQL PASSIVE_LEVEL.
В начале каждого объекта – структура DISPATCHER_HEADER
Два

состояния: сигнальное и несигнальное
Типы диспетчерских объектов различаются правилом изменения состояния (правило перехода в сигнальное или несигнальное состояние
поток, ожидающий захвата диспетчерского объекта, блокирован и помещен в список ожидания в структуре DISPATCHER_HEADER.
Диспетчерские объекты Dispatcher Objectsнабор механизмов синхронизации, рассчитанных на применение в основном для уровня IRQL PASSIVE_LEVEL.В начале каждого

Слайд 84Блокирование потока – состояние потока, при котором он не занимает

время процессора.
Блокированный поток не будет поставлен планировщиком в очередь

на исполнение до тех пор, пока не будет выведен из состояние блокирования.
Блокирование потока – состояние потока, при котором он не занимает время процессора. Блокированный поток не будет поставлен

Слайд 86Диспетчерские объекты могут иметь имена в пространстве имен диспетчера объектов

(обычно директория \BaseNamedObjects)
Через эти имена существующий объект можно открывать из

приложений пользовательского режима или ядра ОС.
Кроме того, код ядра может получить доступ к объекту по его описателю (HANDLE): ObReferenceObjectByHandle()
Для окончания использования объекта, полученного через ObReferenceObjectByHandle() – функция ObDereferenceObject()
Диспетчерские объекты могут иметь имена в пространстве имен диспетчера объектов (обычно директория \BaseNamedObjects)Через эти имена существующий объект

Слайд 87Ожидание (захват) диспетчерских объектов
Для ожидания момента перехода объекта из несигнального

в сигнальное состояние служат специальные функции ожидания: KeWaitForSingleObject() и KeWaitForMultipleObjects()
в

качестве одного из их параметров указывается интервал времени ожидания.
Функции вернут управление либо при захвате объекта, либо при истечении времени ожидания
!!! либо если были вызваны с ненулевым временем ожидания на IRQL=DISPATCH_LEVEL (вытесняющая многозадачность отключена, заменить текущий поток нечем) – так делать нельзя!!!
С ненулевым временем ожидания можно вызывать при IRQLС нулевым временем ожидания можно вызывать на IRQL<=DISPATCH_LEVEL
Нулевое время ожидания: параметр Timeout == NULL или *Timeout == 0
Нулевое время ожидания используется для проверки состояния объекта без блокирования потока
На DISPATCH_LEVEL можно использовать потому, что поток не блокируется, т.е. нет необходимости переключаться на другой поток
Ожидание (захват) диспетчерских объектовДля ожидания момента перехода объекта из несигнального в сигнальное состояние служат специальные функции ожидания:

Слайд 88Мьютексы ядра
Мьютекс (mutex = Mutually EXclusive) означает взаимоисключение, т.е. мьютекс

обеспечивает нескольким потокам взаимоисключающий доступ к совместно используемому ресурсу. В

отличие от спин-блокировки, ожидающий поток не блокирует процессор.
захват мьютекса является уникальным в рамках конкретного контекста потока. Поток, в контексте которого произошел захват мьютекса, является его владельцем, и может впоследствии рекурсивно захватывать его. Драйвер, захвативший мьютекс в конкретном контексте потока, обязан освободить его в том же контексте потока, нарушение этого правила приведет к появлению “синего экрана”.
Для мьютексов предусмотрен механизм исключения взаимоблокировок
Мьютексы ядраМьютекс (mutex = Mutually EXclusive) означает взаимоисключение, т.е. мьютекс обеспечивает нескольким потокам взаимоисключающий доступ к совместно

Слайд 89VOID KeInitializeMutex(IN PKMUTEX Mutex, IN ULONG Level);
LONG KeReleaseMutex(IN PKMUTEX Mutex,

IN BOOLEAN Wait);
Если параметр Wait равен TRUE, сразу за вызовом

KeReleaseMutex() должен следовать вызов одной из функций ожидания KeWaitXxx(). В этом случае гарантируется, что пара функций – освобождение мьютекса и ожидание – будет выполнена как одна операция, без возможного в противном случае переключения контекста потока.
LONG KeReadStateMutex(IN PKMUTEX Mutex);


VOID KeInitializeMutex(IN PKMUTEX Mutex, IN ULONG Level);LONG KeReleaseMutex(IN PKMUTEX Mutex, IN BOOLEAN Wait);Если параметр Wait равен TRUE,

Слайд 90семафор
более гибкая форма мьютексов. В отличие от мьютексов, программа имеет

контроль над тем, сколько потоков одновременно могут разблокироваться семафором.

VOID KeInitializeSemaphore(

IN PKSEMAPHORE Semaphore,
IN LONG Count,
IN LONG Limit);
Count – начальное значение, присвоенное семафору, определяющее число свободных в данный момент ресурсов. Если Count=0, семафор находится в несигнальном состоянии (свободных ресурсов нет), если >0 – в сигнальном.
Limit – максимальное значение, которое может достигать Count (максимальное число свободных ресурсов).
семафорболее гибкая форма мьютексов. В отличие от мьютексов, программа имеет контроль над тем, сколько потоков одновременно могут

Слайд 91LONG KeReleaseSemaphore(
_Inout_ PRKSEMAPHORE Semaphore,
_In_ KPRIORITY Increment,

_In_ LONG Adjustment,
_In_ BOOLEAN Wait
);

Adjustment:
на

сколько должно увеличиться поле Count семафора (Count = Count + Adjustment ).
Не м.б. отрицательным.
Если Count + Adjustment > Limit, изменение Count не происходит, генерируется исключение STATUS_SEMAPHORE_LIMIT_EXCEEDED
LONG KeReleaseSemaphore( _Inout_ PRKSEMAPHORE Semaphore, _In_   KPRIORITY Increment, _In_   LONG Adjustment, _In_

Слайд 92Использование семафоров: задача «потребители – производители»
WaitFor…
Потоки - производители
S2
S1
WaitFor…
ReleaseSemaphore
ReleaseSemaphore
Потоки - потребители
Заполнение

задания на обработку потребителем
Обработка и возврат обработанного задания производителю
Список пустых

заданий

Очередь заданий

Начальное состояние:
очередь заданий пуста, все задания в списке пустых заданий, число заданий = MaxJobs

S1(Count=0, Limit=MaxJobs)

S2(Count=MaxJobs, Limit=MaxJobs)

Использование семафоров: задача «потребители – производители»WaitFor…Потоки - производителиS2S1WaitFor…ReleaseSemaphoreReleaseSemaphoreПотоки - потребителиЗаполнение задания на обработку потребителемОбработка и возврат обработанного

Слайд 93События (events)
Позволяют проводить синхронизацию исполнения различных потоков, т.е. один или

несколько потоков могут ожидать перевода события в сигнальное состояние другим

потоком.

два вида событий:
события синхронизации (synchronization events). при переводе в сигнальное состояние будет разблокирован один поток, после чего событие автоматически переходит в несигнальное состояние.
Оповещающие события (notification event): при переводе в сигнальное состояние будут разблокированы все ожидающие событие потоки. Перевод события в несигнальное состояние - вручную.
События (events)Позволяют проводить синхронизацию исполнения различных потоков, т.е. один или несколько потоков могут ожидать перевода события в

Слайд 94KeInitializeEvent()
IoCreateNotificationEvent()
IoCreateSynchronizationEvent()
KeClearEvent() и KeResetEvent() – сброс в несигнальное состояние
KeSetEvent() – перевод

в сигнальное состояние
KePulseEvent – пара KeSetEvent(), KeResetEvent()

Уведомляющие события: При использовании KePulseEvent

или подряд идущих KeSetEvent() + KeClearEvent() / KeResetEvent() гарантируется освобождение ВСЕХ ожидающий это событие потоков
KeInitializeEvent()IoCreateNotificationEvent()IoCreateSynchronizationEvent()KeClearEvent() и KeResetEvent() – сброс в несигнальное состояниеKeSetEvent() – перевод в сигнальное состояниеKePulseEvent – пара KeSetEvent(), KeResetEvent()Уведомляющие

Обратная связь

Если не удалось найти и скачать доклад-презентацию, Вы можете заказать его на нашем сайте. Мы постараемся найти нужный Вам материал и отправим по электронной почте. Не стесняйтесь обращаться к нам, если у вас возникли вопросы или пожелания:

Email: Нажмите что бы посмотреть 

Что такое TheSlide.ru?

Это сайт презентации, докладов, проектов в PowerPoint. Здесь удобно  хранить и делиться своими презентациями с другими пользователями.


Для правообладателей

Яндекс.Метрика