- PVSM.RU - https://www.pvsm.ru -
Многие считают что самому создать драйвер для Windows это что-то на грани фантастики. Но на самом деле это не так. Конечно, разработка драйвера для какого-то навороченного девайса бывает не простой задачей. Но ведь тоже самое можно сказать про создание сложных программ или игр. В разработке простого драйвера нет ничего сложного и я попытаюсь на примерах это показать.
Сперва нам нужно определится в чем мы же будем создавать наш первый драйвер. Поскольку материал ориентирован на новичков, то язык программирования был выбран один из простых, и это не Си или ассемблер, а бейсик. Будем использовать один из диалектов бейсика — PureBasic. Из коробки он не обучен создавать драйверы, но у него удачный набор файлов, используемых для компиляции и небольшое шаманство позволяет добавить эту возможность. Процесс компиляции состоит из нескольких этапов. Если кратко, то он происходит следующим образом: Сначала транслятор «перегоняет» basic-код в ассемблер, который отдается FASM'у (компилятор ассемблера), который создает объектный файл. Далее в дело вступает линкер polink, создающий исполняемый файл. Как компилятор ассемблера, так и линкер могут создавать драйверы и если немного изменить опции компиляции, то получим не исполняемый файл, типа EXE или DLL, а драйвер режима ядра (SYS).
Скачать немного модифицированную бесплатную демо версию PureBasic 4.61 x86 можно на файлопомойке [1], зеркало. [2]
Если нужно создать драйвер для x64 системы, качайте эту версию [3], зеркало. [4]
Дистрибутивы имеют небольшие размеры, около 3 МБ каждый. С помощью этой версии можно создавать только драйвера.
Скачиваем, распаковываем и запускаем, кликнув по файлу «PureBasic Portable». При этом запустится IDE и вылезет окошко с сообщением что это демо-версия и списком ограничений. Из него наиболее существенным является ограничение числа строк кода, равное 800, а для создания простых драйверов этого может хватить. Остальные ограничения в нашем случае, не существенны.
Окно IDE с загруженным кодом драйвера показано на скрине.
Компиляция драйвера выполняется через меню «Компилятор» (это если кто не понял).
Теперь определимся что будет делать наш первый драйвер. Обычно при изучении программирования начинают с простых вещей, скажем, выполнения математических операций и вывода результата. Вот пусть наш драйвер делает тоже самое, ведь банальная математика производимая в режиме ядра это очень круто!
Код драйвера:
Declare DriverEntry(*DriverObject, *RegistryPath)
!public PureBasicStart
!section '.code' code readable executable align 8
!PureBasicStart:
*A=@DriverEntry()
!jmp [p_A] ; Переход в процедуру DriverEntry().
#IOCTL_MyPlus = $200
!extrn PB_PokeL
CompilerSelect #PB_Compiler_Processor
CompilerCase #PB_Processor_x86
!extrn _IoCompleteRequest@8 ; Объявление импортируемых функций ядра.
!extrn _RtlInitUnicodeString@8
!extrn _IoCreateDevice@28
!extrn _IoDeleteDevice@4
!extrn _IoCreateSymbolicLink@8
!extrn _IoDeleteSymbolicLink@4
!extrn _PB_PeekI@4
Import "ntoskrnl.lib"
CompilerCase #PB_Processor_x64
!extrn IoCompleteRequest; Объявление импортируемых функций ядра.
!extrn RtlInitUnicodeString
!extrn IoCreateDevice
!extrn IoDeleteDevice
!extrn IoCreateSymbolicLink
!extrn IoDeleteSymbolicLink
!extrn PB_PeekI
ImportC "ntoskrnl.lib"
CompilerEndSelect
; Импорт функций ядра системы.
IoCompleteRequest(*IRP, PriorityBoost)
RtlInitUnicodeString(*UString, *String)
IoCreateDevice(*DriverObject, DeviceExtensionSize, *UDeviceName, DeviceType, DeviceCharacteristics, Exclusive, *DeviceObject)
IoDeleteDevice(*DeviceObject)
IoCreateSymbolicLink(*SymbolicLinkName, *DeviceName)
IoDeleteSymbolicLink(*SymbolicLinkName)
EndImport
Structure MyData ; Данные, передаваемые в драйвер.
Plus_1.l
Plus_2.l
EndStructure
; Прцедура обмена данными с программой.
Procedure DeviceIoControl(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP)
Protected *Stack.IO_STACK_LOCATION
Protected *InpBuff, *OutBuff
Protected InBuffSize, OutBuffSize
Protected ntStatus, *MyData.MyData
ntStatus = #STATUS_SUCCESS ; Все ОК.
*Stack = *pIrpTailOverlayCurrentStackLocation
; Размеры буферов (см. WinAPI функцию DeviceIoControl())
InBuffSize = *StackParametersDeviceIoControlInputBufferLength
OutBuffSize = *StackParametersDeviceIoControlOutputBufferLength
If InBuffSize >= SizeOf(Integer) And OutBuffSize >= 4
Select *StackParametersDeviceIoControlIoControlCode
Case #IOCTL_MyPlus
*Point = *pIrpSystemBuffer
If *Point
*MyData = PeekI(*Point)
If *MyData
Result.l = *MyDataPlus_1 + *MyDataPlus_2
PokeL(*pIrpSystemBuffer, Result)
*pIrpIoStatusInformation = 4
Else
ntStatus = #STATUS_BUFFER_TOO_SMALL
*pIrpIoStatusInformation = 0
EndIf
EndIf
Default
ntStatus = #STATUS_UNSUCCESSFUL
*pIrpIoStatusInformation = 0
EndSelect
Else
ntStatus = #STATUS_BUFFER_TOO_SMALL ; Размер буфера слишком мал.
*pIrpIoStatusInformation = 0
EndIf
*pIrpIoStatusStatus = ntStatus
IoCompleteRequest(*pIrp, #IO_NO_INCREMENT)
ProcedureReturn ntStatus
EndProcedure
; Выгрузка драйвера. Вызывается при завершении работы драйвера.
Procedure UnloadDriver(*DriverObject.DRIVER_OBJECT)
Protected uniDOSString.UNICODE_STRING
; Инициализация объектов-строк.
RtlInitUnicodeString(@uniDOSString, ?DosDevices)
; Удаление символьной связи.
IoDeleteSymbolicLink (@uniDOSString)
; Удаление устройства.
IoDeleteDevice(*DriverObjectDeviceObject)
EndProcedure
; Вызывается при доступе к драйверу с помощью функци CreateFile().
Procedure CreateDispatch(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP)
*pIrpIoStatusInformation = 0
*pIrpIoStatusStatus = #STATUS_SUCCESS
IoCompleteRequest(*pIrp, #IO_NO_INCREMENT)
ProcedureReturn #STATUS_SUCCESS
EndProcedure
; Вызывается при осовбождении драйвера функцией CloseHandle().
Procedure CloseDispatch(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP)
*pIrpIoStatusInformation = 0
*pIrpIoStatusStatus = #STATUS_SUCCESS
IoCompleteRequest(*pIrp, #IO_NO_INCREMENT)
ProcedureReturn #STATUS_SUCCESS
EndProcedure
; Процедура загрузки драйвера. Вызывается однократно при его запуске.
Procedure DriverEntry(*DriverObject.DRIVER_OBJECT, *RegistryPath.UNICODE_STRING)
Protected deviceObject.DEVICE_OBJECT
Protected uniNameString.UNICODE_STRING
Protected uniDOSString.UNICODE_STRING
; Инициализация объектов-строк.
RtlInitUnicodeString(@uniNameString, ?Device)
RtlInitUnicodeString(@uniDOSString, ?DosDevices)
; Создание устройства.
status = IoCreateDevice(*DriverObject, 0, @uniNameString, #FILE_DEVICE_UNKNOWN, 0, #False, @deviceObject)
If status <> #STATUS_SUCCESS
ProcedureReturn status
EndIf
; Создане символьной связи между именем этого устройства и именем,
; находящимся в видимой области для user-mode, для того, чтобы
; приложение могло получить доступ к этому устройству.
status = IoCreateSymbolicLink(@uniDOSString, @uniNameString)
If status <> #STATUS_SUCCESS
IoDeleteDevice(@deviceObject)
ProcedureReturn status
EndIf
; Указатель на функцию выгрузки драйвера.
*DriverObjectDriverUnload = @UnloadDriver()
*DriverObjectMajorFunction[#IRP_MJ_CREATE] = @CreateDispatch()
*DriverObjectMajorFunction[#IRP_MJ_CLOSE] = @CloseDispatch()
; Указываем какая функция будет обрабатывать запросы WinAPI DeviceIoControl().
*DriverObjectMajorFunction[#IRP_MJ_DEVICE_CONTROL] = @DeviceIoControl()
ProcedureReturn #STATUS_SUCCESS
EndProcedure
; Имя драйвра (юникод).
DataSection
Device:
!du 'DevicepbDrPlus', 0, 0
DosDevices:
!du 'DosDevicespbDrPlus', 0, 0
EndDataSection
Может показаться что это куча бессмысленного кода, но это не так.
У каждого драйвера должна быть точка входа, обычно у нее имя DriverEntry() и выполнена она в виде процедуры или функции. Как видите, в этом драйвере есть такая процедура. Если посмотрите на начало кода, то в первых строках увидите как ей передается управление. В этой процедуре происходит инициализация драйвера. Там же назначается процедура завершения работы драйвера, которая в нашем случае имеет имя UnloadDriver(). Процедуры CreateDispatch() и CloseDispatch() назначаются обработчиками соединения и отсоединения проги из юзермода.
Процедура DeviceIoControl() будет обрабатывать запросы WinAPI функции DeviceIoControl(), являющейся в данном драйвере связью с юзермодом. В конце кода расположена так называемая ДатаСекция (DataSection), в которой находятся имена драйвера, сохраненные в формате юникода (для этого использована одна из фишек ассемблера FASM).
Теперь рассмотрим как драйвер будет взаимодействовать с внешним миром. Это происходит в процедуре DeviceIoControl(). В ней отслеживается одно сообщение, а именно — #IOCTL_MyPlus, которое отправляет юзермодная прога, когда ей нужно сложить два числа в режиме ядра (круто звучит, правда?). Когда такое сообщение получено, то считываем из системного буфера, адрес указателя на структуру со слагаемыми, производим сложение и результат помещаем в системный буфер. Собственно это основная задача нашего первого драйвера.
Видите сколько понадобилось кода для выполнения простейшей математической операции — сложения двух чисел?
А теперь рассмотрим программу, работающую с этим драйвером. Она написана на том же PureBasic.
#DriverName = "pbDrPlus"
#IOCTL_MyPlus = $200
XIncludeFile "..DrUserModeFramework.pbi"
Structure MyData ; Данные, передаваемые в драйвер.
Plus_1.l
Plus_2.l
EndStructure
; Абсолютный путь к файлу-драйверу.
DrFile.s = GetPathPart(ProgramFilename())+#DriverName+".sys"
; Загружает драйвер и если успешно, то порлучаем его хэндл.
hDrv=OpenDriver(DrFile, #DriverName, #DriverName, #DriverName)
If hDrv=0
; Деинсталляция драйвера из системы.
Driver_UnInstall(#DriverName)
MessageRequester("", "Ошибка загрузки драйвера")
End
EndIf
; Обмен данными с драйвером.
Procedure.q Plus(hDrv, x1, x2)
Protected MyData.MyData, Result, *Point
MyDataPlus_1=x1
MyDataPlus_2=x2
*Point = @MyData
DeviceIoControl_(hDrv, #IOCTL_MyPlus, @*Point, SizeOf(MyData), @Result, 4, @BytesReturned, 0)
ProcedureReturn Result
EndProcedure
OpenWindow(1,300,300,140,90,"Title",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
StringGadget(1,10,10,50,20,"")
StringGadget(2,10,40,50,20,"")
TextGadget(3,70,30,70,20,"")
Repeat
ev=WaitWindowEvent()
If ev=#PB_Event_Gadget
op1=Val(GetGadgetText(1))
op2=Val(GetGadgetText(2))
Result = Plus(hDrv, op1, op2)
SetGadgetText(3,Str(Result))
EndIf
Until ev=#PB_Event_CloseWindow
; Если драйвер загружен, то закрываем связь с ним.
If hDrv
CloseHandle_(hDrv)
hDrv=0
EndIf
; Деинсталляция драйвера из системы.
Driver_UnInstall(#DriverName)
При старте программы вызывается функция OpenDriver(), которая загружает драйвер. Для упрощения, имя драйвера, имя службы и описание службы заданы одинаковыми — «pbDrPlus». Если загрузка неудачная, то выводится соответствующее сообщение и программа завершает свою работу.
Процедура Plus() осуществляет связь с драйвером. Ей передаются хэндл, доступа к драйверу и слагаемые числа, которые помещаются в структуру и указатель на указатель которой, передается драйверу. Результат сложения чисел будет в переменной «Result».
Далее следует код простейшего GUI калькулятора, скопированного из википедии. [5]
Когда закроют окно, то перед завершением работы программы, закрывается связь с драйвером и производится его деинсталляция из системы.
Исходные коды драйвера и программы, можно найти в папке «Examples», PureBasic на файлопомойке, ссылку на который давал в начале статьи.
PS.
Помните, работа в ядре чревата мелкими неожиданностями аля, BSOD (синий экран смерти), поэтому экспериментируйте осторожно и обязательно всё сохраняйте перед запуском драйвера.
За возможную потерю данных, я ответственности не несу!
Автор: hachik
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-programmirovanie/10466
Ссылки в тексте:
[1] файлопомойке: http://depositfiles.com/files/g0fv0zx1f
[2] зеркало.: http://ifolder.ru/31383732
[3] эту версию: http://depositfiles.com/files/lwnwll6bz
[4] зеркало.: http://ifolder.ru/31383802
[5] википедии.: http://ru.wikipedia.org/wiki/PureBasic
Нажмите здесь для печати.