Всем Бобра! В данной теме я приведу свой пример программы управления который закрепит знания полученные в ходе изучения первых глав книги, переводом которой занимается Virt и X-Shar. Пример будет полезен для новичков которым тяжело с чего-то начать свой грандиозный проект. Таким образом появится материал (работающий демо проект) от которого можно будет оттолкнуться и двигаться вперед. На мой взгляд некоторые моменты не были освещены в книге но найдут отражение в приведенном коде.
Драйвер собран в среде:
Microsoft Visual Studio Professional 2019
Версия 16.7.2
и вот его код:
все выполнено одним модулем и компилируется на моей машине без проблем. однако как создать пустой проект драйвера, чтобы потом вставить туда этот код, читайте материал главы 2.
в собранном виде он прикреплен в архиве к посту.
Как видно из кода, драйвер способен отправлять отладочные сообщения при старте и остановке. Эти сообщения возможно перехватить программой DBGView. Об настройке и использовании которой хорошо написано в главе 2 данной ветки форума. надо отметить DbgView это для моего примера единственное средство отображения информации о том что происходит с драйвером. поэтому не имеет смысла тестировать демо не разобравшись с запуском DbgView (по материалу главы 2). хотя работоспособность код не потеряет- однако для нас драйвер станет черным ящиком который что-то там сам в себе делает не имея видимого результата.
При создании клиента использовалась windows10 с установленной RadStudio 10.4
Итак создаем проект в дельфи с пустой формой стандартно. Размещаем на ней элементы:
1)четыре кнопки
2)таймер
3)Мемо лист(проверить свойство автоскрол=true)
никаких других свойств в инспекторе объектов менять не следует (кроме текста на кнопках и автоскрола в мемо, хотя это не повлияет на работоспособность).
все приведем к виду как показано на рисунке(можете что-то улучшить):
нажмите на картинку чтобы сохранить зрение в порядке.
Сохраните проект там где вам будет удобно.
откомпилируйте, запустите этот пустой проект чтобы создались папки с откомпилированным файлом проекта (.exe).
поместите файл подопытного драйвера с именем СDRV.sys (приложен в архиве) в папку в которой создался откомпилированный проект. таким образом .exe и СDRV.sys файл окажутся в одной папке.
и целиком замените весь код модуля (единственного) на приведенный ниже:
теперь сохраните откомпилируйте запустите и наблюдайте результат работы. Читая комментарии к коду вы сможете прояснить для себя некоторые моменты и поднять настроение. И научитесь регистрировать, запускать, останавливать, удалять драйверы а также отправлять пакет IRP от клиента к драйверу и принимать, обрабатывать его.
Не следует относиться к приведенному коду критично, так как он невкоем разе не претендует на то что именно так и надо делать. Более того для серьезных задач следует делать очень много всяких проверок на исключительные ситуации которым я уделил мало времени, однако некоторые (на мой взгляд важные) присутствуют.
Данный код следует воспринимать как ознакомительный- чтобы новичок мог понять как управляться с сервисами самоделками чтобы хоть что-то заработало и стало отправной точкой для продвижения вперед к своей цели.
P.S.> прошу не ругать меня за местами криво написанный не по шаблону код. Пользуйтесь на здоровье. Права на репост спрашивайте у владельцев этого великолепного Форума который и помог мне собрать этот пример в процессе изучения материала глав.
Драйвер собран в среде:
Microsoft Visual Studio Professional 2019
Версия 16.7.2
и вот его код:
код драйвера:
#include <ntddk.h>
//два макроса которые будут заменять в коде все места где найдут const_DEVName и
//и const_slDEVName на строку нужного формата в Юникоде стринге с указанием длинны.
//\\Device\\CDRV это путь в дереве объектов для нашего объекта Устройства он там появится
//на этапе инициализации процедурой DriverEntry. второй путь это символьная ссылка на наше
//Устройство. дело в том что ветвь \\Device\\ из ЮзерМоде недоступна, а Устройство хранится там и доступно
//только для кернел моде. а ветка \\??\\ видна для ЮзерМоде. Вот почему мы создаем ссылочку.
//в разделе \\??\\ создаем имя которое ссылается на устройство из \\Device\\. Система получив запрос
//на получение хендла объекта Устройства с использование ссылки отождествит его с объектом Устройства
//из каталога \\Device\\. все эти объекты Устройств и ссылки на них для ЮзерМоде можно посмотреть
//в программе WinObj
UNICODE_STRING const_DEVName = RTL_CONSTANT_STRING(L"\\Device\\CDRV");//путь к объекту устройства
UNICODE_STRING const_slDEVName = RTL_CONSTANT_STRING(L"\\??\\CDRV");//путь к ссылке на объект устройства
#define IOCTL_C_DEV_IO CTL_CODE(0x8000, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
//объявляем прототипы функций
//функция вызывается автоматически при остановке драйвера
void CDRVUnload(_In_ PDRIVER_OBJECT p_DRVObj);
//функция инициализации ввода вывода
NTSTATUS DEVioPrepare(
_In_ PDEVICE_OBJECT p_DevObj,//указатель на объект устройства
_In_ PIRP p_IRP);//указатель на пакет I/O(IRP)
//основная функция ввода-вывода
NTSTATUS DEVio(
_In_ PDEVICE_OBJECT p_DevObj,//указатель на объект устройства
_In_ PIRP p_IRP);//указатель на пакет I/O(IRP)
extern "C"
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT p_DRVObj, _In_ PUNICODE_STRING p_RegPath) {
KdPrint(("выполняется DriverEntry"));
UNREFERENCED_PARAMETER(p_RegPath);
//регистрируем функцию остановки драйвера в объекте драйвера
p_DRVObj->DriverUnload = CDRVUnload;
//создаем в объекте драйвера (его структуре) массив функций диспетчерезации (dispatch routines),
//предназначенные для обработки разных типов пакетов запроса ввода-вывода (IRP). Каждый элемент этого массива
//соответствует своему типу IRP. Если, например, драйверу необходимо обрабатывать запрос типа IRP_MJ_SHUTDOWN,
//уведомляющий о завершении работы системы, то он должен поместить в соответствующую позицию массива MajorFunction
//указатель на функцию, которой запросы этого типа и будут направляться. Если такая функциональность драйверу не нужна,
//как в нашем случае, то и заполнять этот элемент массива MajorFunction не требуется.
//Все процедуры диспетчеризации имеют одинаковый прототип.
p_DRVObj->MajorFunction[IRP_MJ_CREATE] = DEVioPrepare;//формируется диспетчером ввода-вывода при вызове кодом режима
//пользователя функции CreateFile;
p_DRVObj->MajorFunction[IRP_MJ_CLOSE] = DEVioPrepare;//формируется диспетчером ввода-вывода при вызове кодом режима
//пользователя функции CloseHandle.(это не я придумал это копипаст
//и меня это удивило что закрытие хендла коих мы делаем много
//раз в клиенте приводит к IRP. однако наверное это когда
//закрываем хендл Устройства тогда логично)
//основная процедура передачи информации клиент-драйвер.
p_DRVObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DEVio;//формируется диспетчером ввода-вывода при вызове кодом режима
//пользователя заклинания DeviceIoControl
//создаём объекта устройства
PDEVICE_OBJECT p_DEVObj;
NTSTATUS statusCreateDEV = IoCreateDevice(
p_DRVObj, //указатель на объект Драйвера
0,// no need for extra bytes,
&const_DEVName, //указатель на имя устройства
FILE_DEVICE_UNKNOWN, //тип Устройства
0, // characteristics flags,
FALSE, //монопольный доступ
&p_DEVObj //OUT возвращаемый указатель на созданный объект Устройства
//этот аргумент описан в прототипе как PDEVICE_OBJECT *DeviceObject
//*- означает взять значение которое хранится по адресу
);
//с ошибкой на выход
if (!NT_SUCCESS(statusCreateDEV)) {
KdPrint(("Failed to create device object (0x%08X)\n", statusCreateDEV));
return statusCreateDEV;
};
//создаём ссылку на наш Объект Устройства в доступной для ЮзерМоде ветке //??
//по этой ссылке управляющая программа из ЮзерМоде сможет получать хендл на устройство.
//напрямую хендл на объекты в ветке //Device получать из ЮзерМоде нельзя.
NTSTATUS statusCreateLink = IoCreateSymbolicLink(&const_slDEVName, &const_DEVName);
//с ошибкой на выход
if (!NT_SUCCESS(statusCreateLink)) {
KdPrint(("Failed to create symbolic link (0x%08X)\n", statusCreateLink));
IoDeleteDevice(p_DEVObj);
return statusCreateLink;
}
//функция DriverEntry возвращает успех. в противном случае система ее заблокирует.
//либо не даст запуститься, останется остановленной. либо вапще отменит регистрацию сервиса в системе.
//точно не знаю.
return STATUS_SUCCESS;
}
void CDRVUnload(_In_ PDRIVER_OBJECT p_DRVObj) {
KdPrint(("выполняется UnLoad"));
//в DriverEntry мы создали объект Устройства а также Ссылку на него. следует удалить их при остановке Драйвера.
//удаляем ссылку
IoDeleteSymbolicLink(&const_slDEVName);
//удаляем объект устройства
IoDeleteDevice(p_DRVObj->DeviceObject);
}
_Use_decl_annotations_
NTSTATUS
DEVioPrepare(_In_ PDEVICE_OBJECT p_DEVObj, _In_ PIRP p_IRP) {
KdPrint(("Инициализация ввода/вывода\n"));
UNREFERENCED_PARAMETER(p_DEVObj);
//по окончании обработки IRP он вернется отправителю. который сможет сделать вывод об успешной обработке
//посланного пакета данных. мы заполним структуру которую сможет прочесть клиент(отправитель)
p_IRP->IoStatus.Status = STATUS_SUCCESS;
p_IRP->IoStatus.Information = 0;
//это заклинание завершает обработку IRP и структура заполненная выше улетит отправителю.
IoCompleteRequest(p_IRP, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS
DEVio(_In_ PDEVICE_OBJECT p_DEVObj, _In_ PIRP p_IRP) {
KdPrint(("Получен IRP пакет I/O.\n"));
//подсмотрим точное значение "Кода Управления" и будем его использовать в дельфи у клиента
//потомуто я несмог понять как его в дельфи собрать такойже как тут.
KdPrint(("для справки ожидаемый CTRL_CODE=\n"));
KdPrint(("%.8x", IOCTL_C_DEV_IO));
UNREFERENCED_PARAMETER(p_DEVObj);
//умные люди пишут:
//при получениее IRP необходимо убедиться в доступе к области памяти с буферами.
//на микрософте для этого рекомендуют применять специальные подпрограммы (они там даны)
//проводить чтение и запись буферов внутри обработчиков исключений.
// get our IO_STACK_LOCATION
auto stack = IoGetCurrentIrpStackLocation(p_IRP); // IO_STACK_LOCATION*
auto status = STATUS_SUCCESS;
switch (stack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_C_DEV_IO: //создали этот код в начале модуля макросом
KdPrint(("Свершилось! Получен IRP пакет данных с кодом $80002003!УРА!\n"));
//опишем структуру буфера которую ожидаем получить в пакете с кодом 5
struct TCData //прототип структуры
{
int A; //широта
int B; //долгота
};
//посмотрим сколько байт мы получили. это уже элемент отладки, потомучто у меня сразу не заработало
//кол-во байт какое-то неверное пришло, не то что я ожидал. поэтому разбираемся сколько байт прилетело
KdPrint(("%02d", stack->Parameters.DeviceIoControl.InputBufferLength));
//и навсякий случай покажем сколько байт ожидаем.
KdPrint(("%02d", sizeof(TCData)));
//сравним размер полученного буфера с тем что мы ожидаем получить в пакете с кодом $80002003
if (stack->Parameters.DeviceIoControl.InputBufferLength == sizeof(TCData))
{
KdPrint(("кол-во байт верное(как ожидалось)\n"));
//если размер буфера такойже как ожидаемый то
//прочтем буфер. сопоставим его нашей структуре. и таким образом мы будем иметь
//не просто какието байты информации непонятные, а упорядоченные в структуру переменные.
auto CData = (TCData*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
//Если указатель на структуру NULL, то мы должны выйти со статусом ошибки :
if (CData == nullptr) {
status = STATUS_INVALID_PARAMETER;
break;
}
KdPrint(("напарсили. вот что получилось..\n"));
KdPrint(("%02d", CData->A));
KdPrint(("%02d", CData->B));
}
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
//завершаем обработку IRP и отправляем клиенту отчет
p_IRP->IoStatus.Status = STATUS_SUCCESS;//жестко для теста
p_IRP->IoStatus.Information = 0;
IoCompleteRequest(p_IRP, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
в собранном виде он прикреплен в архиве к посту.
Как видно из кода, драйвер способен отправлять отладочные сообщения при старте и остановке. Эти сообщения возможно перехватить программой DBGView. Об настройке и использовании которой хорошо написано в главе 2 данной ветки форума. надо отметить DbgView это для моего примера единственное средство отображения информации о том что происходит с драйвером. поэтому не имеет смысла тестировать демо не разобравшись с запуском DbgView (по материалу главы 2). хотя работоспособность код не потеряет- однако для нас драйвер станет черным ящиком который что-то там сам в себе делает не имея видимого результата.
При создании клиента использовалась windows10 с установленной RadStudio 10.4
Итак создаем проект в дельфи с пустой формой стандартно. Размещаем на ней элементы:
1)четыре кнопки
2)таймер
3)Мемо лист(проверить свойство автоскрол=true)
никаких других свойств в инспекторе объектов менять не следует (кроме текста на кнопках и автоскрола в мемо, хотя это не повлияет на работоспособность).
все приведем к виду как показано на рисунке(можете что-то улучшить):
нажмите на картинку чтобы сохранить зрение в порядке.
Сохраните проект там где вам будет удобно.
откомпилируйте, запустите этот пустой проект чтобы создались папки с откомпилированным файлом проекта (.exe).
поместите файл подопытного драйвера с именем СDRV.sys (приложен в архиве) в папку в которой создался откомпилированный проект. таким образом .exe и СDRV.sys файл окажутся в одной папке.
и целиком замените весь код модуля (единственного) на приведенный ниже:
код клиента:
unit Unit1;
//во многих коментариях встречается термин Сервис означающий для нас Драйвер
//а также Драйвер означающий Сервис. на самом деле эту путаницу ввели в Микрософте
//так как по их мнению Драйвер это Сервис. и его решили не отделять в какуюто
//отдельную группу. таким образом понимать под Сервисом можно как сервис для режима
//пользователя так и Драйвер режима ядра, также под Драйвером можно понимать Сервис.
//это одно и тоже, Драйвер скорее это разновидность сервиса (подгруппа чтоли).
//чтоб все стало ясно окончательно Служба- это тоже сервис и драйвер.-)
//запустив "командную строку" с правами администратора вы можете использовать
//следующие команды для анализа состояния нашего драйвера:
//sc create СDRV type= kernel binPath= c:\prog\СDRV.sys
//sc start СDRV
//sc stop СDRV
//sc delete СDRV
//sc query СDRV
//также загляните Редактор Реестра Виндоус и отыщите поиском запись об драйвере
//по его имени. она должна там появиться сразу после регистрации сервиса. а после
//удаления исчезнуть.
//DbgView софт- это то с чего вы должны начать. как его настроить подробно написано
//в главе 2. Дело в том, что Драйвер в процессе работы будет отчитываться о
//происходящих с ним событиях именно в логах которые перехватит эта программа.
//без него мы будем слепы и драйвер станет "черным ящиком" который что-то там делает
//сам в себе. Другими словами логи в DbgView это интерфейс для драйвера.
//WinObj.exe софт также относится к маст-хэв. там мы увидим как при старте драйвера
//создались две записи. одна в разделе Device с именем нашего Устройства (которое
//обслуживает драйвер) а также ссылка на него в разде ?Global
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
System.UITypes,//для диалоговых окон
WinSvc;//для использования менеджера сервисов
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Memo1: TMemo;//используем мемо для вывода всякой полезной информации
//например значения переменных интерисующих нас для отладки
Timer1: TTimer;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
//опишем структуру для своих нужд. это порция информации которой будет обмениваться
//программа управления(эта) с драйвером (через устройство).Экземляр такой структуры
//будет отсылаться в пакете IRP от клиента к драйверу.
type
TCData = record
a:integer;
b:integer;
end;
var
Form1: TForm1;
function DRVInstall(): boolean;//регистрация и инициализация запуска драйва
function DRVDelete(): boolean;//остановка и удаление драйва
function DRVQuery(): boolean;//запрос состояния драйва
function IOinit(): boolean;//инициализация ввода-вывода и сразу посылка IRP
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
//регистрируем и запускаем сервис.
DrvInstall();
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
//сначала остановим потом удалим драйвер.
DRVDelete();
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
//для демонстрации можно запросить статус сразу после регистрации сервиса.
//а потом дождавшись заданного интервала для запуска (5сек) повторить запрос
//и убедиться что статус изменится после старта драйва.-)
DRVQuery();//запрос состояния
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
//инициализация Ввода-вывода и там же отсылка пробного IRP
IOinit();
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
//если таймер будет включен по умолчанию, то произойдет попытка запуска сервиса.
//конечно удобнее отключить его в настройках через панель ObjectInspector заранее
//но так как это демо-пример то необходимо свести к минимуму подготовительную работу,
//невыполнив которую код может работать криво. цель демо: copy-past и все работает.
timer1.Enabled:=False;
timer1.Interval:=5000;
end;
//будет установлен драйвер с именем CDRV.SYS
//файл которого должен лежать в каталоге программы управления(этой) там где .EXE
function DrvInstall(): boolean;
var
//может возникнуть вопрос. почему бы хендлы не обьявить для всего модуля, а специально
//объявлять их для регистрации сервиса а также (в будущем) для закрытия сервиса.
//ответ в том что сервис не закроетя до тех пор пока хотябы один хендл на него
//открыт. Объявив хендлы глобально- нам нужно очень внимательно следить за тем где
//хендл открывается и как он закрывается в противном случае удаление сервиса
//потерпит крах и возможны прочие непонятности поведения. их объявление локально
//в функции упрощает контроль за созданием и удалением хендлов.
h_SCMtoCreate: SC_Handle;
h_DRVtoCreate: SC_Handle;
FuncRetVal: Boolean; //вспомогательная для возвращения значения
begin
form1.Memo1.Lines.Add('начинаем пытаться зарегать и запустить драйв..');
FuncRetVal := False;
h_SCMtoCreate := OpenSCManager(nil, nil, SC_MANAGER_CREATE_SERVICE);
if (h_SCMtoCreate <> 0) then
begin
form1.Memo1.Lines.Add('подключились к h_SCMtoCreate:' + inttostr(h_SCMtoCreate));
//эта функция CreateService завершится успешно даже если указанного файла драйвера
//несуществует!ого! Наверняка и при прочих ошибках использования этой функции
//она вернет успех. Однако Ошибки возникнут в будущем при попытке запуска Сервиса
//поэтому сразу после нее следует запустить наш сервис и убедиться что он запускается.
//в противном случае обработать ошибки. Возвращаемое значение =0 в случае ошибки
//при успехе возвращает Хендл объекта сервиса.
form1.Memo1.Lines.Add(PChar(GetCurrentDir+'\CDRV.sys'));
h_DRVtoCreate:= CreateService(h_SCMtoCreate, //хендл менеджера сервисов
PChar('CDRV'), //внутреннее имя нашего сервиса-драйвера в
//терминах Виндос: драйвер-это сервис тоже
PChar('MyСDriver'), //экранное имя нашего Драйвера-сервиса
//неуверен где его можно встретить.
SERVICE_ALL_ACCESS, //полные права на доступ к нашему Драйверу
SERVICE_KERNEL_DRIVER, //тип нашей службы: кернел-драйвер
SERVICE_DEMAND_START, //Служба, запускается диспетчером управления
//службами, когда процесс вызывает функцию
//StartService.
SERVICE_ERROR_NORMAL, //Программа запуска регистрирует ошибку и
//показывает всплывающее окно сообщения, но
//продолжает операцию запуска.
PChar(GetCurrentDir+'\CDRV.sys'), //путь к файлу драйвера и его имя
//я встречал много вариантов исполнения этой строки на форумах
//зачемто 0 какойто добавляют в конец вручную и т.д.
//помучившись пол дня пришел к этому варианту который работает.
//но боюсь не будет ли какойто утечки без нуля на конце.
//Мне кажется формат "строки" в дельфи уже содержит в себе
//завершающий ноль на конце. хотя неуверен. эксперементируйте.
nil, //lpLoadOrderGroup
//[in] Указатель на строку с завершающим нулем, именующую группу
//очередности загрузки, членом которой является эта служба.
//Задайте значение ПУСТО (NULL) или пустую строку, если служба
//не принадлежит группе.
//Программа запуска использует очередность загрузки групп,
//чтобы загрузить группы служб в указанном порядке по отношению
//к другим группам. Список очередности загрузки групп содержатся
//в следующем значении реестра:
//HKLM\System\CurrentControlSet\Control\ServiceGroupOrder
nil, //lpdwTagId
//[out] Указатель на переменную, которая получает значение
//признака, являющееся уникальным в группе, заданной в параметре
//lpLoadOrderGroup. Задайте значение ПУСТО (NULL),
//если Вы не изменяете существующий признак.
//Вы можете использовать признак для того, чтобы упорядочить
//запуск службы в пределах очередности загрузки группы,
//определяя вектор очередности признака в следующем значении
//реестра:HKLM\System\CurrentControlSet\Control\GroupOrderList
//Признаки вычисляются только для служб драйвера,
//которые имеют типы пуска SERVICE_BOOT_START или SERVICE_SYSTEM_START.
nil, //lpDependencies
//[in] Указатель на массив имен служб, разделенных нулем,
//с двойным символом нуля в конце или очередности загрузки
//групп, которую система должна запустить перед этой службой.
//Задайте значение ПУСТО (NULL) или пустую строку, если служба
//не имеет никаких зависимостей. Зависимость от группы означает,
//что эта служба может запуститься тогда, если по крайней мере
//один член группы запущен после попытки запустить все члены
//группы. Вы должны ставить в начале имен группы
//SC_GROUP_IDENTIFIER так, чтобы они могли отличаться
//от имени службы, потому что службы и группы служб совместно
//используют то же самое пространство имен.
nil, //lpServiceStartName
//[in] Указатель на строку с завершающим нулем, которая задает
//имя учетной записи, под которой должна запуститься служба.
//Если тип службы - SERVICE_WIN32_OWN_PROCESS, используйте имя
//учетной записи в форме DomainName\UserName. Процесс службы
//начнет работу как этот пользователь. Если учетная запись
//принадлежит встроенному домену, Вы можете задать .\UserName.
//Если этот параметр - значение ПУСТО (NULL), функция
//CreateService использует учетную запись LocalSystem.
//Если тип службы задан - SERVICE_INTERACTIVE_PROCESS,
//служба должна запуститься в учетной записи LocalSystem.
//Если этот параметр - NT AUTHORITY\LocalService, функция
//CreateService использует учетную запись LocalService.
//Если параметр - NT AUTHORITY\NetworkService,
//CreateService использует учетную запись NetworkService.
//Windows NT: Если тип службы - SERVICE_WIN32_SHARE_PROCESS,
//Вы должны задать учетную запись LocalSystem.
//На более поздних версиях Windows, совместно используемый
//процесс может запуститься как любой пользователь.
//Если тип службы - SERVICE_KERNEL_DRIVER
//или SERVICE_FILE_SYSTEM_DRIVER, ее имя - имя объекта драйвера,
//который система использует, чтобы загрузить драйвер устройства.
//Задайте значение ПУСТО (NULL), если драйвер должен
//использовать имя объекта по умолчанию, созданное системой
//ввода / вывода (I/O).
nil);//lpPassword
//[in] Указатель на строку с завершающим нулем, которая имеет
//в своем составе пароль к имени учетной записи, заданному
//параметром lpServiceStartName. Задайте пустую строку,
//если учетная запись не имеет пароля или, если служба
//запускается в учетной записи LocalService, NetworkService
//или LocalSystem.
//Для служб драйвера пароли игнорируются
//если драйвер зареган то освобождаем хенлд. если ошибка то выводим текст
if h_DRVtoCreate<>0 then
begin
form1.Memo1.Lines.Add('зарегали h_DRVtoCreate:' + inttostr(h_DRVtoCreate));
CloseServiceHandle(h_DRVtoCreate);//закрываем хендл драйвера
FuncRetVal:=True;
end
else
begin
//почемуто захотелось не в лог Мемо1 вывести а такое окошко создать.
//возможно кому-то пригодится просто знать что можно и так.
MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOk],0);
end;
CloseServiceHandle(h_SCMtoCreate);//закрываем интерфейс Менеджера Сервисов
//ладно проще удаляем ненужный более хендл.
end
else
begin
//это если мы к менеджеру сервисов не подключились то вот вам и ошибка
//вот вам и почему...
FuncRetVal:=False;
MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOk],0);
//тут бы бряку(break) поставить чтоб пулей на выход и таймер зря не гонять в конце.
//но мы верим в бога и ошибок никаких у нас быть неможет!
end;
DrvInstall:= FuncRetVal;
//теперь сразу запускаем наш драйвер. Это необходимо чтобы быть уверенным
//что он коректно работает. т.к. регистрация драйвера в системе- не дает никаких
//гарантий что все зарегестрировано как надо. она завершается успешно даже без
//файла драйвера а также при наличии прочих ошибок в подстановке аргументов.
//Хороший способ проверить коректность заполнения текстовых полей с именами
//и путями- это зайти в RegEdit после регистрации сервиса и поиском отыскать
//свой сервис по имени сервиса. затем следует проверить как отображаются
//поля с именем файла и именем сервиса. сравнить с соседними чужими
//сервисами- посматреть как у них а как у нас.
//так как регистрация сервиса может занять время, запускать
//службу следует спустя некоторое время. таким образом если разместим код
//запуска тут- то получим неудачу так как драйвер еще не закончил свою регистрацию.
//видится возможность реализоть это 3мя путями.(1)разместить тут паузу к примеру
//на 5 секунд(достаточную для регистрации сервиса)и следом запустить сервис. но
//это подвесит основной поток выполнения и программа будет мертвой 5 секунд
//что не очень хорошо смотрится.(2)прям тут запустить таймер на 5 секунд или
//на 0,5 секунд и запускать сервис из его процедуры (мноократно до успеха, но без
//фанатизма). (3)ну и есть возможность организовать 2-й поток- в который мы
//можем перенести и функцию регистрации и запуска сервиса и там использовать
//паузу 5 секунд не переживая что это подвесит нашу программу.
//для меня подойдет 2-й вариант с таймером.
//вопщем сделать вывод об успешной регистрации сервиса мы сможем только после
//его запуска.
form1.timer1.Enabled:=True;
form1.Memo1.Lines.Add('включили таймер для запуска драйва..');
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
h_SCMtoStart,h_DRVtoStart: SC_Handle; //хендлы Менеджера и Драйвера
Temp: PChar;//тайное значение
begin
//этот таймер используется чтобы произвести запуск сервиса через интервал времени
//после его регистрации. мы сделаем одну попытку запуска через 5 секунд после
//регистрации сервиса. на это указывает свойство таймера Interval=5000.
//после этого остановим таймер. Однако возможно реализовать многократные попытки
//запуска сервиса до получения положительного результата, и лишь потом остановить таймер.
h_SCMtoStart:= OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);//подключаемся
//к менеджеру сервисов
if h_SCMtoStart > 0 then //если подключились к менеджеру (существует его хендл)
begin
//просим систему дать нам доступ к нашему драйверу вызовом WinApi OpenService
h_DRVtoStart:= OpenService(h_SCMtoStart,//используем хендл открытого менеджера
PChar('CDRV'),//имя сервиса
SERVICE_ALL_ACCESS);//полный доступ
if h_DRVtoStart > 0 then //если получили хендл на наш Драйвер это уже неплохо
begin
//и вот когда мы знаем его имя (хендл) мы имеем власть над ним.
//и произносим следующее заклинание которое оживляет наше творение.
Temp:=nil;//тайная сила сего действа в подстановке входного параметра через
//промежуточную переменную- ускользнула от моего разума. но нужно именно так.
if StartService(h_DRVtoStart,0,temp)=true then //если успешно попросили систему
//запустить наш драйвер
//надо понимать что запуск драйвера занимает время. поэтому мы тут какбы
//не запускаем сервис а просим систему его запустить. Для того чтобы
//понять смогла система его запустить или нет мы можем послать запрос на
//вывод статуса нашего сервиса. именно это мы попробуем реализовать на 3-й
//кнопке на нашей форме.
//однако позже я встретил определение что вышеизложенное заклинание(winapi)
//вернет управление коду после себя только после старта сервиса (или ошибки)
//таким образом возможно сразу начинать работать с ним и ничего не ждать.
begin
//отчитаемся в лог что к менеджеру мы подключились, хендл драйвера получили
//а также попросили систему запустить наш драйвер.
form1.Memo1.Lines.Add('подключились для старта h_SCMtoStart:'+inttostr(h_SCMtoStart));
form1.Memo1.Lines.Add('нашли драйвер CDRV в системе h_DRVtoStart'+inttostr(h_DRVtoStart));
form1.Memo1.Lines.Add('просим систему запустить CDRV');
end;
//убрать за собой мусор очень важно не только в программировании но и в жизни.
CloseServiceHandle(h_DRVtoStart);//убираем за собой
end;
CloseServiceHandle(h_SCMtoStart);//убираем за собой
end;
form1.Timer1.Enabled:=false;//выключаем таймер
end;
function DRVDelete(): boolean;
var
h_DRVtoDel, h_SCMtoDel: SC_HANDLE;//хендлы Драйва и Менеджера сервисов
DRV_status: TServiceStatus;//структура описывает состояние сервиса
//примерно то что мы видим в CMD если запустим
//команду "SC query СDrv"
FunctRetVal: boolean;//функция чтото возвратит, на то она и функция
begin
FunctRetVal := false;
//подключаемся к менеджеру сервисов
h_SCMtoDel := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if (h_SCMtoDel <> 0) then//если подключились
begin
form1.Memo1.Lines.Add('начинаем пытаться удалить сервис..');
//получаем хендл на сервис по его имени
h_DRVtoDel:= OpenService(
h_SCMtoDel,
PChar('CDRV'),
SERVICE_ALL_ACCESS
);
if h_DRVtoDel<>0 then//хендл на сервис получен
begin
//вот собственно и удаление сервиса. Но оно не сработает!-)
//система пометит сервис на удаление и запланирует это действие.
//однако фактическое удаление сервиса произойдет только если
//сервис остановлен а также все хендлы на него закрыты.
//получаеся ситуация что команда на удаление использует Хендл на сервис,
//но удалить не получится если этот Хендл существует!-)
//в процессе отладки программы я столкнулся с ситуацией когда сервис
//удалялся только с закрытием клиентской программы(этой). Это связано
//с тем что при регистрации сервиса я забыл освободить хендл на
//зарегистрированный сервис и он гдето телепался открытым мешая удалить
//сервис из системы. Однако закрытие программы ЮзерМоде освобождает все
//ресурсы которая программа насоздавала и тогда запланированное действие
//удаления сервиса выполняется.
//итак: 1)пометим сервис на удаление.
//2)пошлем ему команду на остановку (неважно запущен он или нет)
//3)освободим Хендлы связанные с ним.
//после этого сервис должен удалиться. Мы верим в бога.
DeleteService(h_DRVtoDel);
ControlService(h_DRVtoDel, //хендл драйвера на удаление
SERVICE_CONTROL_STOP,//команда на удаление
DRV_status//структура с состоянием сервиса
);
//возможно и скорее всего одна или обе предыдущие функции
//вернут код ошибки. ведь служба может быть и не запущена(остановлена).
//но мне кажется это неважно. мы останавливаем ее для гарантии
//без анализа состояния.
//2 шага пройдено, отчитаемся в лог об этом
form1.Memo1.Lines.Add('h_SCMtoDel:'+inttostr(h_SCMtoDel)+' подключились');
form1.Memo1.Lines.Add('пометили сервис h_DRVtoDel:'+inttostr(h_DRVtoDel)+' на удаление');
form1.Memo1.Lines.Add('послали команду на остановку сервиса');
//3-й этап освобождаем ресурсы связанные с сервисом.
CloseServiceHandle(h_SCMtoDel);//хендлы закрываем
CloseServiceHandle(h_DRVtoDel);
form1.Memo1.Lines.Add('освободили Хендлы');
//если все хорошо сервис скоро закроется. откиньтесь на спинку стула.
FunctRetVal:=True;//ну.. надеемся что True. нужно обработать больше исключений
//чтобы так утверждать. Однако в рамках демо-примера
//и так сойдет.
end
else
begin
//неполучилось получить хендл драйвера для его закрытия
form1.Memo1.Lines.Add('неполучилось получить хендл драйвера для его закрытия');
end;
end
else
begin
form1.Memo1.Lines.Add('ошибка подключения к менеджеру служб');
end;
DRVDelete:= FunctRetVal;
end;
function DRVQuery(): boolean;
var
h_SCMtoQuery, h_DRVtoQuery: SC_HANDLE;
svc_status: TServiceStatus;//структура описывает статус сервиса
begin
form1.Memo1.Lines.Add('начинаем пытаться получить статус сервиса..');
h_SCMtoQuery := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if (h_SCMtoQuery <> 0) then
begin
form1.Memo1.Lines.Add('подключились к менеджеру h_SCMtoQuery:'+inttostr(h_SCMtoQuery));
//получаем хендл на сервис по его имени
h_DRVtoQuery:= OpenService(
h_SCMtoQuery,
PChar('CDRV'),
SERVICE_ALL_ACCESS
);
if h_DRVtoQuery<>0 then
begin
form1.Memo1.Lines.Add('получили хендл драйва по его имени h_DRVtoQuery:'+inttostr(h_DRVtoQuery));
if (QueryServiceStatus(h_DRVtoQuery,svc_status)) then
begin
form1.Memo1.Lines.Add('успешно отправили запрос на статус MyDRV');
form1.Memo1.Lines.Add('тип сервиса:'+ inttostr(svc_status.dwServiceType));
form1.Memo1.Lines.Add('состояние:'+inttostr(svc_status.dwCurrentState));
end
else
begin
MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOk],0);
end;
CloseServiceHandle(h_DRVtoQuery);//убираем за собой
end;
CloseServiceHandle(h_SCMtoQuery);//убираем за собой
end;
DRVQuery:=true;
end;
//на воротах написано: "оставь надежду всяк сюда входящий"
//то что ниже написано мне как новичку далось очень сложно.. поэтому тут возможны
//какието неправильности. лучше неидеальности. однако я добился работоспособности этих
//заклятий! тут безусловно есть над чем еще работать!
function IOinit(): boolean;
var
DEVHandle: THandle;//хендл устройства драйвера
Status: bool;//вспомогательная
returned: DWORD;//вспомогательная
CData:TCData;//в начале юнита мы описали сами эту структуру, теперь создаем екземляр
//CTRL:LongWord;//будем использовать чтобы собрать Код Управления для IRP
//(как потом выяснилось безуспешно. поэтому закоментим)
begin
//нужно вызвать winapi CreateFile это будет инициализация. подготовительный этап.
//мы тем самы скажем драйверу что нам есть о чём поговорить с ним тет-а-тет.
DEVHandle:=0;//возможно лишнее.
form1.Memo1.Lines.Add('пробуем подключиться к устройству..');
//драйверу будет направлен IRP типа IRP_MJ_CREATE
DEVHandle:= CreateFile(
pchar('\\.\CDRV'),//указатель на строку, с именем ссылки указывающей на устройство.
//ОПА! вот я пол дня подбирал в каком виде скормить зверю
//этот аргумент! ну я не понимаю почему в Ц++ этот путь подругому?
//в ц++ надо использовать например вот:
//[I]HANDLE hDevice = CreateFile(L"\\\\.\\PriorityBooster", GENERIC_WRITE,...
//или вот:
//UNICODE_STRING const_slDEVName = RTL_CONSTANT_STRING(L"\\??\\CDRV");
//хотя на самом деле (мы то знаем) путь этот.. вы удивитесь если посмотрите в WinObj.exe
// \GLOBAL??\CDRV
//вопщем заклятие это специфично для среды разработки видимо,
//что весьма страннонуто..
GENERIC_READ + GENERIC_WRITE,//права доступа
FILE_SHARE_WRITE,//определяет, может ли устройство быть совместо используемым с другим кодом.
//Т.е. можно ли еще раз получить описатель устройства вызовом функции CreateFile.
nil,//указатель на структуру SECURITY_ATTRIBUTES
OPEN_EXISTING,//определяет действия в случае если устройство с заданным именем
//не найдено или уже существует. Для устройств используется только
//значение OPEN_EXISTING. Т.е. на момент вызова CreateFile устройство
//должно уже существовать.
0,//определяет атрибуты и специальные флаги.У нас будет равен 0.
0//описатель файла-прототипа.
);
//посмотрим что получилось.. отчитаемся в лог..
form1.Memo1.Lines.Add(SysErrorMessage(GetLastError));
if (DEVHandle = INVALID_HANDLE_VALUE) then
form1.Memo1.Lines.Add('кривой хендл')
else
form1.Memo1.Lines.Add('получили хендл устройства:'+inttostr(DEVHandle));
//если не смотря не на что, хендл всё-таки получен!, да к тому же он не ИНВАЛИД-)
//то давайте что-нибудь отправим драйверу. да побыстрее пока всё не сломалось!
if (DEVHandle <> INVALID_HANDLE_VALUE) then //хендл не инвалид
begin
form1.Memo1.Lines.Add('попробуем отправить данные устройству');
//сформируем пакет данных с секретной информацией об расположении ядерных
//объектов на территории врага. следует соблюдать объявленный формат структуры.
CData.a:=100;//широта
CData.b:=200;//долгота
{*попробуем (нерешительно) сформировать Код Управления (ракетной шахтой).
в отличии от ц++, в которой пишется драйвер, в дельфи отсутствует макрос
который поможет нам это сделать легко. Однако мы сможем сделать аналогию
этого макроса в run-time на дельфи.
CTRL:=($8000 shl 16) or ($800 Shl 14) or (METHOD_NEITHER shl 2) or (FILE_ANY_ACCESS);
однако это не работает и я Код Управления просто подглядел в драйвере. вывел его
на экран в логах и вставил сюда такойже. почему не работает? незнаю. я пробывал CRTL
описать как Cardinal а также как LongWord}
Status:= DeviceIoControl(
DEVHandle,
$80002003,//код управления(ракетной шахтой) подсмотренный у драйвера
//в драйвере мы его сформировали макросом сами, вывели в лог
//и сюда копипаст.
@CData, //указатель на структуру с секретной информацией
sizeof(CData), // input buffer and length
nil,//0,//nullptr, //вот так и живем. потыкались nil,//0,//nullptr,
//выбрали то что работает.
0,// output buffer and length
&returned,//в этом аргументе нам что-то возвратится.
//я буду с этим разбираться позже
nil);//nullptr);
if Status=true then
form1.Memo1.Lines.Add('пакет данных улетел(куда-то)')
else
form1.Memo1.Lines.Add(SysErrorMessage(GetLastError));
//дальнейший ход событий будет отображен драйвером в логах DbgView
CloseHandle(DEVHandle);
end;
IOinit:=true;
end;
end.
теперь сохраните откомпилируйте запустите и наблюдайте результат работы. Читая комментарии к коду вы сможете прояснить для себя некоторые моменты и поднять настроение. И научитесь регистрировать, запускать, останавливать, удалять драйверы а также отправлять пакет IRP от клиента к драйверу и принимать, обрабатывать его.
Не следует относиться к приведенному коду критично, так как он невкоем разе не претендует на то что именно так и надо делать. Более того для серьезных задач следует делать очень много всяких проверок на исключительные ситуации которым я уделил мало времени, однако некоторые (на мой взгляд важные) присутствуют.
Данный код следует воспринимать как ознакомительный- чтобы новичок мог понять как управляться с сервисами самоделками чтобы хоть что-то заработало и стало отправной точкой для продвижения вперед к своей цели.
P.S.> прошу не ругать меня за местами криво написанный не по шаблону код. Пользуйтесь на здоровье. Права на репост спрашивайте у владельцев этого великолепного Форума который и помог мне собрать этот пример в процессе изучения материала глав.
Вложения
Последнее редактирование: