Всем привет. Появилось время и решил написать что-то необычное на VB6, а именно попытаться написать драйвер. Сразу скажу до этого я никогда не писал драйвера и не имею никакого опыта программирования в режиме ядра. Драйвер, по моим задумкам, должен будет читать память недоступную в пользовательском режиме, а именно в диапазоне 0x80000000 - 0xffffffff (в режиме по-умолчанию, без IMAGE_FILE_LARGE_ADDRESS_AWARE). Сразу приведу исходный код драйвера который получился:
- Код: Выделить всё
- ' // modTrickMemReader.bas - модуль драйвера
 ' // © Кривоус Анатолий Анатольевич (The trick), 2014
 Option Explicit
 Public Enum NT_STATUS
 STATUS_SUCCESS = 0
 STATUS_INVALID_PARAMETER = &HC000000D
 End Enum
 Public Type UNICODE_STRING
 Length As Integer
 MaximumLength As Integer
 lpBuffer As Long
 End Type
 Public Type LIST_ENTRY
 Flink As Long
 Blink As Long
 End Type
 Public Type KDEVICE_QUEUE
 Type As Integer
 Size As Integer
 DeviceListHead As LIST_ENTRY
 Lock As Long
 Busy As Long
 End Type
 Public Type KDPC
 Type As Byte
 Importance As Byte
 Number As Integer
 DpcListEntry As LIST_ENTRY
 DeferredRoutine As Long
 DeferredContext As Long
 SystemArgument1 As Long
 SystemArgument2 As Long
 DpcData As Long
 End Type
 Public Type DISPATCHER_HEADER
 Lock As Long
 SignalState As Long
 WaitListHead As LIST_ENTRY
 End Type
 Public Type KEVENT
 Header As DISPATCHER_HEADER
 End Type
 Public Type IO_STATUS_BLOCK
 StatusPointer As Long
 Information As Long
 End Type
 Public Type Tail
 DriverContext(3) As Long
 Thread As Long
 AuxiliaryBuffer As Long
 ListEntry As LIST_ENTRY
 lpCurStackLocation As Long
 OriginalFileObject As Long
 End Type
 Public Type IRP
 Type As Integer
 Size As Integer
 MdlAddress As Long
 Flags As Long
 AssociatedIrp As Long
 ThreadListEntry As LIST_ENTRY
 IoStatus As IO_STATUS_BLOCK
 RequestorMode As Byte
 PendingReturned As Byte
 StackCount As Byte
 CurrentLocation As Byte
 Cancel As Byte
 CancelIrql As Byte
 ApcEnvironment As Byte
 AllocationFlags As Byte
 UserIosb As Long
 UserEvent As Long
 Overlay As Currency
 CancelRoutine As Long
 UserBuffer As Long
 Tail As Tail
 End Type
 Public Type DEVICEIOCTL
 OutputBufferLength As Long
 InputBufferLength As Long
 IoControlCode As Long
 Type3InputBuffer As Long
 End Type
 Public Type IO_STACK_LOCATION
 MajorFunction As Byte
 MinorFunction As Byte
 Flags As Byte
 Control As Byte
 ' // Поле DeviceIoControl из объединения
 DeviceIoControl As DEVICEIOCTL
 pDeviceObject As Long
 pFileObject As Long
 pCompletionRoutine As Long
 pContext As Long
 End Type
 Public Type DRIVER_OBJECT
 Type As Integer
 Size As Integer
 pDeviceObject As Long
 Flags As Long
 DriverStart As Long
 DriverSize As Long
 DriverSection As Long
 DriverExtension As Long
 DriverName As UNICODE_STRING
 HardwareDatabase As Long
 FastIoDispatch As Long
 DriverInit As Long
 DriverStartIo As Long
 DriverUnload As Long
 MajorFunction(27) As Long
 End Type
 Public Type DEVICE_OBJECT
 Type As Integer
 Size As Integer
 ReferenceCount As Long
 DriverObject As Long
 NextDevice As Long
 AttachedDevice As Long
 CurrentIrp As Long
 Timer As Long
 Flags As Long
 Characteristics As Long
 Vpb As Long
 DeviceExtension As Long
 DeviceType As Long
 StackSize As Byte
 Queue(39) As Byte
 AlignRequirement As Long
 DeviceQueue As KDEVICE_QUEUE
 Dpc As KDPC
 ActiveThreadCount As Long
 SecurityDescriptor As Long
 DeviceLock As KEVENT
 SectorSize As Integer
 Spare1 As Integer
 DeviceObjExtension As Long
 Reserved As Long
 End Type
 Private Type BinaryString
 D(255) As Integer
 End Type
 Public Const FILE_DEVICE_UNKNOWN As Long = &H22
 Public Const IO_NO_INCREMENT As Long = &H0
 Public Const IRP_MJ_CREATE As Long = &H0
 Public Const IRP_MJ_CLOSE As Long = &H2
 Public Const IRP_MJ_DEVICE_CONTROL As Long = &HE
 Public Const FILE_DEVICE_MEMREADER As Long = &H8000&
 Public Const IOCTL_READ_MEMORY As Long = &H80002000
 Public DeviceName As UNICODE_STRING ' // Строка с именем устройства
 Public DeviceLink As UNICODE_STRING ' // Строка с именем ссылки
 Public Device As DEVICE_OBJECT ' // Объект устройства
 Dim strName As BinaryString ' // Строка с именем устройства
 Dim strLink As BinaryString ' // Строка с именем ссылки
 Public Sub Main()
 End Sub
 ' // Если ошибка - False
 Public Function NT_SUCCESS( _
 ByVal Status As NT_STATUS) As Boolean
 NT_SUCCESS = Status >= STATUS_SUCCESS
 End Function
 ' // Получить указатель на стек пакета
 Public Function IoGetCurrentIrpStackLocation( _
 ByRef pIrp As IRP) As Long
 IoGetCurrentIrpStackLocation = pIrp.Tail.lpCurStackLocation
 End Function
 ' // Точка входа в драйвер
 Public Function DriverEntry( _
 ByRef DriverObject As DRIVER_OBJECT, _
 ByRef RegistryPath As UNICODE_STRING) As NT_STATUS
 Dim Status As NT_STATUS
 
 ' // Инициализация имен
 Status = Init()
 
 ' // Здесь не обязательна проверка, но я поставил, т.к. возможно усовершенствование функции Init
 If Not NT_SUCCESS(Status) Then
 
 DriverEntry = Status
 Exit Function
 
 End If
 
 ' // Создаем устройство
 Status = IoCreateDevice(DriverObject, 0, DeviceName, FILE_DEVICE_MEMREADER, 0, False, Device)
 
 ' // Проверяем создалось ли устройство
 If Not NT_SUCCESS(Status) Then
 
 DriverEntry = Status
 Exit Function
 
 End If
 
 ' // Создаем связь для доступа по имени из пользовательского режима
 Status = IoCreateSymbolicLink(DeviceLink, DeviceName)
 
 ' // Проверяем корректность
 If Not NT_SUCCESS(Status) Then
 
 ' // При неудаче удаляем устройство
 IoDeleteDevice Device
 DriverEntry = Status
 Exit Function
 
 End If
 
 ' // Определяем функции
 DriverObject.DriverUnload = GetAddr(AddressOf DriverUnload) ' // Выгрузка драйвера
 DriverObject.MajorFunction(IRP_MJ_CREATE) = GetAddr(AddressOf DriverCreateClose) ' // При вызове CreateFile
 DriverObject.MajorFunction(IRP_MJ_CLOSE) = GetAddr(AddressOf DriverCreateClose) ' // При вызове CloseHandle
 DriverObject.MajorFunction(IRP_MJ_DEVICE_CONTROL) = GetAddr(AddressOf DriverDeviceControl) ' // При вызове DeviceIoControl
 
 ' // Успех
 DriverEntry = STATUS_SUCCESS
 
 End Function
 ' // Процедура выгрузки драйвера
 Public Sub DriverUnload( _
 ByRef DriverObject As DRIVER_OBJECT)
 
 ' // Удаляем связь
 IoDeleteSymbolicLink DeviceLink
 ' // Удаляем устройство
 IoDeleteDevice ByVal DriverObject.pDeviceObject
 
 End Sub
 ' // Функция вызывается при открытии/закрытии драйвера
 Public Function DriverCreateClose( _
 ByRef DeviceObject As DEVICE_OBJECT, _
 ByRef pIrp As IRP) As NT_STATUS
 
 pIrp.IoStatus.Information = 0
 pIrp.IoStatus.StatusPointer = STATUS_SUCCESS
 
 ' // Возвращаем IRP пакет менеджеру ввода/вывода
 IoCompleteRequest pIrp, IO_NO_INCREMENT
 
 ' // Успех
 DriverCreateClose = STATUS_SUCCESS
 
 End Function
 ' // Функция обработки IOCTL запросов
 Public Function DriverDeviceControl( _
 ByRef DeviceObject As DEVICE_OBJECT, _
 ByRef pIrp As IRP) As NT_STATUS
 Dim lpStack As Long
 Dim ioStack As IO_STACK_LOCATION
 
 ' // Получаем указатель на стек пакета
 lpStack = IoGetCurrentIrpStackLocation(pIrp)
 
 ' // Проверяем указатель на валидность
 If lpStack Then
 
 ' // Копируем в локальную переменную
 memcpy ioStack, ByVal lpStack, Len(ioStack)
 
 ' // Проверяем IOCTL и объединение AssociatedIrp в котором содержится SystemBuffer
 ' // В SystemBuffer содержится буфер, переданный нами в DeviceIoControl
 If ioStack.DeviceIoControl.IoControlCode = IOCTL_READ_MEMORY And _
 pIrp.AssociatedIrp <> 0 Then
 Dim lpPointer As Long
 Dim DataSize As Long
 
 ' // Копируем параметы из SystemBuffer
 memcpy lpPointer, ByVal pIrp.AssociatedIrp, 4
 memcpy DataSize, ByVal pIrp.AssociatedIrp + 4, 4
 
 ' // Проверяем размер буфера
 If DataSize <= ioStack.DeviceIoControl.OutputBufferLength Then
 
 ' // Проверяем количество страниц, которые мы можем прочитать
 Dim lpStart As Long
 Dim pgCount As Long
 Dim pgSize As Long
 Dim pgOfst As Long
 
 ' // Определяем адрес начала страницы
 lpStart = lpPointer And &HFFFFF000
 
 ' // Определяем смещение от начала страницы
 pgOfst = lpPointer And &HFFF&
 
 ' // Проход по станицам и проверка на PageFault
 Do While MmIsAddressValid(ByVal lpStart) And (pgSize - pgOfst < DataSize)
 
 lpStart = lpStart + &H1000
 pgCount = pgCount + 1
 pgSize = pgSize + &H1000
 
 Loop
 
 ' // Если хоть одна страница доступна
 If pgCount Then
 
 ' // Получаем реальный размер в байтах
 pgSize = pgCount * &H1000 - pgOfst
 
 ' // Корректируем резмер
 If DataSize > pgSize Then DataSize = pgSize
 
 ' // Возвращаем реальный размер прочитанных данных
 pIrp.IoStatus.Information = DataSize
 
 ' // Успех
 pIrp.IoStatus.StatusPointer = STATUS_SUCCESS
 
 ' // Копируем данные в SystemBuffer
 memcpy ByVal pIrp.AssociatedIrp, ByVal lpPointer, DataSize
 
 ' // Возвращаем IRP пакет менеджеру ввода/вывода
 IoCompleteRequest pIrp, IO_NO_INCREMENT
 
 ' // Упех
 DriverDeviceControl = STATUS_SUCCESS
 
 ' // Выход
 Exit Function
 
 End If
 
 End If
 
 End If
 
 End If
 
 ' // Возвращаем реальный размер прочитанных данных
 pIrp.IoStatus.Information = 0
 
 ' // Ошибка DeviceIoControl
 pIrp.IoStatus.StatusPointer = STATUS_INVALID_PARAMETER
 
 ' // Возвращаем IRP пакет менеджеру ввода/вывода
 IoCompleteRequest pIrp, IO_NO_INCREMENT
 
 ' // Ошибка
 DriverDeviceControl = STATUS_INVALID_PARAMETER
 
 End Function
 ' // Функция инициализации
 Private Function Init() As NT_STATUS
 ' // Инициализируем имя устройства
 ' //\Device\TrickMemReader
 strName.D(0) = &H5C: strName.D(1) = &H44: strName.D(2) = &H65: strName.D(3) = &H76: strName.D(4) = &H69:
 strName.D(5) = &H63: strName.D(6) = &H65: strName.D(7) = &H5C: strName.D(8) = &H54: strName.D(9) = &H72:
 strName.D(10) = &H69: strName.D(11) = &H63: strName.D(12) = &H6B: strName.D(13) = &H4D: strName.D(14) = &H65:
 strName.D(15) = &H6D: strName.D(16) = &H52: strName.D(17) = &H65: strName.D(18) = &H61: strName.D(19) = &H64:
 strName.D(20) = &H65: strName.D(21) = &H72:
 
 ' // Создаем UNICODE_STRING
 RtlInitUnicodeString DeviceName, strName
 
 ' // Инициализация ссылки на имя устройства из user-mode
 ' //\DosDevices\TrickMemReader
 strLink.D(0) = &H5C: strLink.D(1) = &H44: strLink.D(2) = &H6F: strLink.D(3) = &H73: strLink.D(4) = &H44:
 strLink.D(5) = &H65: strLink.D(6) = &H76: strLink.D(7) = &H69: strLink.D(8) = &H63: strLink.D(9) = &H65:
 strLink.D(10) = &H73: strLink.D(11) = &H5C: strLink.D(12) = &H54: strLink.D(13) = &H72: strLink.D(14) = &H69:
 strLink.D(15) = &H63: strLink.D(16) = &H6B: strLink.D(17) = &H4D: strLink.D(18) = &H65: strLink.D(19) = &H6D:
 strLink.D(20) = &H52: strLink.D(21) = &H65: strLink.D(22) = &H61: strLink.D(23) = &H64: strLink.D(24) = &H65:
 strLink.D(25) = &H72:
 
 ' // Создаем UNICODE_STRING
 RtlInitUnicodeString DeviceLink, strLink
 End Function
 Private Function GetAddr( _
 ByVal Value As Long) As Long
 GetAddr = Value
 End Function
Итак, драйвер должен иметь точку входа DriverEntry, которую вызывает диспетчер ввода/вывода при загрузке драйвера. В параметрах передается указатель на объект-драйвер и указатель на строку с именем ключа реестра, соответствующего загружаемому драйверу. В процедуре Init мы создаем 2 строки, одна с названием устройства, другая с названием ссылки на устройство. Т.к. мы не можем использовать рантайм в режиме ядра, то приходится создавать строку в виде статического массива, обернутого в пользовательский тип, тем самым VB6 выделяет память под этот массив в стеке. Если использовать строку, то неизбежно будет вызвана одна из функций рантайма для копирования и присваивания строки, а этого мы допустить не можем. Далее мы вызываем IoCreateDevice, которая создает объект-устройство. Объект-устройство является получателем запросов ввода/вывода и к нему мы будем получать доступ при вызове функции CreateFile из пользовательского режима. В качестве первого параметра передается указатель на объект-драйвера; вторым параметром передаем 0, т.к. у нас нет структуры расширения устройства и нам не нужно выделять память; третьим параметром мы передаем имя устройства, оно нам понадобится для реализации доступа к устройству; четвертым параметром передается тип устройства (см. ниже); в пятом мы передаем 0, т.к. у нас "нестандартное устройство"; в шестом передаем False, т.к. нам не нужен монопольный режим; последний параметр - выходной. В качестве имени устройства мы должны использовать строку вида \Device\DeviceName (где DeviceName - TrickMemReader), это имя нам понадобится для того, чтобы мы могли создать ссылку на него, которая в свою очередь нужна для доступа к устройству из пользовательского режима. Тип устройства у нас - FILE_DEVICE_MEMREADER. Все нестандартные устройства должны иметь тип либо FILE_DEVICE_UNKNOWN, либо число от 0x8000 - 0xffff. Я создал константу FILE_DEVICE_MEMREADER со значением 0x8000, что соответствует первому свободному номеру. При успехе, создается устройство и заполняется структура DEVICE_OBJECT. После нужно создать связь по имени между устройством из режима ядра и пользовательским режимом. В качестве имени мы используем \DosDevices\TrickMemReader, из пользовательского режима мы будем обращаться к нему через ссылку '\\.\TrickMemReader". Ссылка создается через IoCreateSymbolicLink. Далее мы определяем callback-процедуры, которые будут вызываться при определенных событиях:
- DriverUnload - при деинициализации драйвера;
- DriverCreateClose - при открытии и закрытии устройства;
- DriverDeviceControl - при вызове DeviceIoControl.
Теперь рассмотрим процедуру DriverUnload. Здесь все просто - мы удаляем связь и созданное устройство. В функции обработки открытия и закрытия устройства DriverCreateClose, в статусе запроса мы возвращаем успех, и возвращаем IRP пакет менеджеру ввода/вывода. Обмен данными между приложением и устройством осуществляется через IRP-пакеты. IRP-пакет состоит из 2-х частей: заголовок и стек переменной длины. Часть структуры представлена типом IRP. Итак, теперь мы добавляем функциональность нашему драйверу в функции DriverDeviceControl. В эту функцию диспетчер ввода/вывода будет передавать IRP-пакет с данными переданными из клиентского приложения, которые мы будем формировать вызовом функции DeviceIoControl. В качестве параметров мы будем передавать 2 Long числа: 1-е адрес, откуда производить чтение, 2-е количество байт для чтения. Также одним из передаваемых параметров в IRP-пакете, при вызове DeviceIoControl, является управляющий код ввода/вывода (IOCTL), который представляет собой структуру из типа устройства, номера функции, типа передачи данных и тип доступа. Можно определить несколько таких кодов для разных операций и использовать их. Я определил код так IOCTL_READ_MEMORY = 0x80002000, 8000 - соответствует типу нешего девайса (FILE_DEVICE_MEMREADER); номер функции = 0x800, значения ниже зарезервированы, для пользовательских функций разрешены значения 0x800 - 0xFFF; тип передачи данных - 0x0 (METHOD_BUFFERED), это значит что мы будем принимать/передавать данные через буфер, определяемый параметром SystemBuffer IRP-пакета); тип доступа - FILE_ANY_ACCESS. Наглядно:

Итак, в функции DriverDeviceControl мы получаем указатель на стек ввода/вывода IRP-запроса с помощью функции IoGetCurrentIrpStackLocation, которая возвращает его из параметра lpCurStackLocation. При упехе (если указатель ненулевой) копируем в локальную структуру IO_STACK_LOCATION параметры на которые указывает этот указатель. Теперь мы проверяем IOCTL-код и поле AssociatedIrp, которое представляет собой объединение (в VB6 нет объединений) в котором хранится указатель на SystemBuffer. Т.к. у нас тип передачи данных соответствует METHOD_BUFFERED, то в параметре SystemBuffer содержится указатель на буфер с параметрами (адрес и размер) DeviceIoControl, в этом буфере мы также можем возвратить данные которые записываются в выходной буфер DeviceIoControl. Теперь если у нас данные содержат корректные значения (IOCTL и SystemBuffer), то мы копируем в локальные переменные параметры (lpPointer, DataSize). Далее проверяем размер буфера. Размер системного буфера ввода/вывода содержится в параметре DeviceIoControl.OutputBufferLength. Если запрошенное количество байт не больше чем размер системного буфера, то все отлично. Теперь мы должны вычислить количество страниц памяти занимаемой данными которые мы хотим скопировать. Для этого мы определяем виртуальный адрес начала страницы соответствующей переданному указателю, и т.к. размер страниц кратен 4 кБ (0x1000) мы просто зануляем 12 бит указателя. Далее мы проверяем в цикле не будет ли вызвано исключение Page fault с помощью функции MmIsAddressValid. Если страница отсутствует в ОЗУ, то функция возвратит False. Таким образом мы проверяем количество страниц которые занимает нужный нам участок памяти и количество страниц которые мы сможем прочитать. Далее мы вычисляем реальный размер данных, которые мы сможем прочесть и при необходимости корректируем размер. Далее в заголовок IRP-пакета мы копируем размер данных, который мы можем прочесть и успешный статус. Поле IoStatus.Information пакета соответствует значению которое возвращает DeviceIoControl в параметре lpBytesReturned. Далее копируем в SystemBuffer нужное количество байт с помощью RtlMoveMemory и возвращаем IRP-пакет менеджеру ввода/вывода. Возвращаем статус успешной операции. Во всех остальных случаях возвращаем ошибку STATUS_INVALID_PARAMETER и нулевой размер данных. Все, код драйвера готов.
Приступим к компиляции. Т.к. мы не можем использовать рантайм, все API-функции мы объявляем в TLB, для того чтобы они попали в импорт:
- Код: Выделить всё
- [uuid(0000001F-0000-0000-0000-000000000AAB)]
 library ImportFunctionsForTrickMemReaderDriver
 {
 [dllname("Ntoskrnl.exe")]
 module Ntoskrnl
 {
 [entry("IoCreateDevice")]int IoCreateDevice
 (void *DriverObject,
 int DeviceExtensionSize,
 void *DeviceName,
 int DeviceType,
 int DeviceCharacteristics,
 int Exclusive,
 void *DeviceObject);
 [entry("IoCreateSymbolicLink")]int IoCreateSymbolicLink
 (void *SymbolicLinkName,
 void *DeviceName);
 [entry("IoDeleteDevice")]void IoDeleteDevice
 (void *DeviceObject);
 [entry("IoDeleteSymbolicLink")]int IoDeleteSymbolicLink
 (void *SymbolicLinkName);
 [entry("IoCompleteRequest")]void IoCompleteRequest
 (void *pIrp,
 unsigned char PriorityBoost);
 [entry("RtlInitUnicodeString")]int RtlInitUnicodeString
 (void *UnicodeString,
 void *StringPtr);
 [entry("RtlMoveMemory")]void memcpy
 (void *Destination,
 void *Source,
 int Length);
 [entry("MmIsAddressValid")]unsigned char MmIsAddressValid
 (void *VirtualAddress);
 [entry("InterlockedExchange")]int InterlockedExchange
 (void *Target,
 void *Value);
 }
 }
PS. InterlockedExchange - я оставил, т.к. вначале драйвер имел немного другую структуру, в последствии оставил объявление в TLB. В драйвере она не попадет в импорт.
Для того чтобы драйвер работал нужно сделать три вещи:
- В поле Subsystem структуры IMAGE_OPTIONAL_HEADER PE-файла драйвера должно быть значение IMAGE_SUBSYSTEM_NATIVE что соответствует драйверу режима ядра.
- Указать в качестве точки входа нашу процедуру DriverEntry
- Добавить секцию релокации, для того чтобы драйвер мог загружаться по любому адресу.
- Исключить MSVBVM60 из иморта.
- Код: Выделить всё
- [VBCompiler]
 LinkSwitches= /ENTRY:DriverEntry /SUBSYSTEM:NATIVE /FIXED:NO
Компилируем проект со всеми опциями оптимизации. Для исключения рантайма из импорта, я использую свою утилиту Patch, которую я использовал тут. После исключения из импорта библиотеки контрольная сумма поменялась, а я не обновлял ее. В EXE файлах, DLL и т.д. это поле не проверяется, а в драйверах проверяется. Для проверки смотрим импорт в любой программе для просмотра PE:

Как видим рантайма нет. Что нам и требовалось.
Для тестирования драйвера я написал простую программку, которая загружает драйвер и работает с ним.
- Код: Выделить всё
- ' // frmTestTrickVBDriver.frm - форма для тестирования драйвера
 ' // © Кривоус Анатолий Анатольевич (The trick), 2014
 Option Explicit
 Private Type SERVICE_STATUS
 dwServiceType As Long
 dwCurrentState As Long
 dwControlsAccepted As Long
 dwWin32ExitCode As Long
 dwServiceSpecificExitCode As Long
 dwCheckPoint As Long
 dwWaitHint As Long
 End Type
 Private Declare Function ControlService Lib "advapi32.dll" ( _
 ByVal hService As Long, _
 ByVal dwControl As Long, _
 ByRef lpServiceStatus As SERVICE_STATUS) As Long
 Private Declare Function OpenSCManager Lib "advapi32.dll" _
 Alias "OpenSCManagerW" ( _
 ByVal lpMachineName As Long, _
 ByVal lpDatabaseName As Long, _
 ByVal dwDesiredAccess As Long) As Long
 Private Declare Function CloseServiceHandle Lib "advapi32.dll" ( _
 ByVal hSCObject As Long) As Long
 Private Declare Function OpenService Lib "advapi32.dll" _
 Alias "OpenServiceW" ( _
 ByVal hSCManager As Long, _
 ByVal lpServiceName As Long, _
 ByVal dwDesiredAccess As Long) As Long
 Private Declare Function CreateService Lib "advapi32.dll" _
 Alias "CreateServiceW" ( _
 ByVal hSCManager As Long, _
 ByVal lpServiceName As Long, _
 ByVal lpDisplayName As Long, _
 ByVal dwDesiredAccess As Long, _
 ByVal dwServiceType As Long, _
 ByVal dwStartType As Long, _
 ByVal dwErrorControl As Long, _
 ByVal lpBinaryPathName As Long, _
 ByVal lpLoadOrderGroup As String, _
 ByRef lpdwTagId As Long, _
 ByVal lpDependencies As Long, _
 ByVal lp As Long, _
 ByVal lpPassword As Long) As Long
 Private Declare Function StartService Lib "advapi32.dll" _
 Alias "StartServiceW" ( _
 ByVal hService As Long, _
 ByVal dwNumServiceArgs As Long, _
 ByVal lpServiceArgVectors As Long) As Long
 Private Declare Function DeleteService Lib "advapi32.dll" ( _
 ByVal hService As Long) As Long
 Private Declare Function CreateFile Lib "kernel32" _
 Alias "CreateFileW" ( _
 ByVal lpFileName As Long, _
 ByVal dwDesiredAccess As Long, _
 ByVal dwShareMode As Long, _
 ByRef lpSecurityAttributes As Any, _
 ByVal dwCreationDisposition As Long, _
 ByVal dwFlagsAndAttributes As Long, _
 ByVal hTemplateFile As Long) As Long
 Private Declare Function CloseHandle Lib "kernel32" ( _
 ByVal hObject As Long) As Long
 Private Declare Function DeviceIoControl Lib "kernel32" ( _
 ByVal hDevice As Long, _
 ByVal dwIoControlCode As Long, _
 ByRef lpInBuffer As Any, _
 ByVal nInBufferSize As Long, _
 ByRef lpOutBuffer As Any, _
 ByVal nOutBufferSize As Long, _
 ByRef lpBytesReturned As Long, _
 ByRef lpOverlapped As Any) As Long
 Private Const ERROR_SERVICE_ALREADY_RUNNING As Long = 1056&
 Private Const ERROR_SERVICE_EXISTS As Long = 1073&
 Private Const SERVICE_CONTROL_STOP As Long = &H1
 Private Const SC_MANAGER_ALL_ACCESS As Long = &HF003F
 Private Const SERVICE_ALL_ACCESS As Long = &HF01FF
 Private Const SERVICE_KERNEL_DRIVER As Long = &H1
 Private Const SERVICE_DEMAND_START As Long = &H3
 Private Const SERVICE_ERROR_NORMAL As Long = &H1
 Private Const GENERIC_READ As Long = &H80000000
 Private Const GENERIC_WRITE As Long = &H40000000
 Private Const FILE_SHARE_READ As Long = &H1
 Private Const FILE_SHARE_WRITE As Long = &H2
 Private Const OPEN_EXISTING As Long = 3
 Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80
 Private Const INVALID_HANDLE_VALUE As Long = -1
 Private Const IOCTL_READ_MEMORY As Long = &H80002000
 Private Const DriverName As String = "TrickMemReader"
 Private Const NumOfRows As Long = 32
 Private DriverFile As String
 Private hMgr As Long
 Private hSrv As Long
 Private hDev As Long
 Private buffer() As Byte
 Private bufLen As Long
 Private Address As Long
 ' // Нажатие на кнопку чтения памяти по адресу
 Private Sub cmdRead_Click()
 Dim param(1) As Long
 
 On Error GoTo Cancel
 
 Address = CLng("&H" & Trim(txtAddress.Text))
 
 ' // Формируем параметры
 param(0) = Address
 param(1) = 16 * NumOfRows
 
 ' // Делаем запрос драйверу
 If DeviceIoControl(hDev, IOCTL_READ_MEMORY, param(0), 8, buffer(0), UBound(buffer) + 1, bufLen, ByVal 0&) = 0 Then
 bufLen = 0
 End If
 
 Update
 
 Cancel:
 
 End Sub
 Private Sub Form_Load()
 Dim sw As Long
 Dim sh As Long
 
 ' // Выделяем буфер
 ReDim buffer(16 * NumOfRows - 1)
 
 ' // Получаем имя файла драйвера
 DriverFile = App.Path & "\" & DriverName & ".sys"
 
 ' // Открываем БД диспетчера управления службами
 hMgr = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS)
 
 If hMgr = 0 Then
 MsgBox "Не удалось установить связь с диспетчером управления службами"
 End
 End If
 
 ' // Создаем службу
 hSrv = CreateService(hMgr, StrPtr(DriverName), StrPtr(DriverName), SERVICE_ALL_ACCESS, _
 SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, StrPtr(DriverFile), _
 0, 0, 0, 0, 0)
 ' // Если служба уже запущена
 If hSrv = 0 And Err.LastDllError = ERROR_SERVICE_EXISTS Then
 ' // Открываем службу
 hSrv = OpenService(hMgr, StrPtr(DriverName), SERVICE_ALL_ACCESS)
 End If
 If hSrv = 0 Then
 MsgBox "Не удалось создать службу"
 Unload Me
 End
 End If
 
 ' // Запускаем драйвер
 If StartService(hSrv, 0, 0) = 0 Then
 
 If Err.LastDllError <> ERROR_SERVICE_ALREADY_RUNNING Then
 MsgBox "Не удалось запустить службу"
 Unload Me
 End
 End If
 
 End If
 
 ' // Подключаемся к драйверу
 hDev = CreateFile(StrPtr("\\.\" & DriverName), GENERIC_READ Or FILE_SHARE_WRITE, FILE_SHARE_READ Or FILE_SHARE_WRITE, ByVal 0&, _
 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
 If hDev = INVALID_HANDLE_VALUE Then
 MsgBox "Не возможно подключится к драйверу"
 Unload Me
 End
 End If
 
 ' // Определяем положение и размер контролов и формы
 sw = picDump.TextWidth("_")
 sh = picDump.TextHeight("_")
 
 picDump.Move 5, 5, (sw * 77) + (picDump.Width - picDump.ScaleWidth), (sh * NumOfRows) + (picDump.Height - picDump.ScaleHeight)
 
 lblAddress.Top = picDump.Top + picDump.Height + 5
 txtAddress.Top = lblAddress.Top
 cmdRead.Top = txtAddress.Top
 
 Me.Width = (picDump.Width + 10 - Me.ScaleWidth) * Screen.TwipsPerPixelX + Me.Width
 Me.Height = (txtAddress.Top + 5 + txtAddress.Height - Me.ScaleHeight) * Screen.TwipsPerPixelY + Me.Height
 
 Update
 
 End Sub
 ' // Обновить данные в окне
 Private Sub Update()
 Dim col As Long
 Dim row As Long
 Dim ptr As Long
 Dim hxd As String
 Dim asi As String
 Dim adr As String
 Dim out As String
 
 For row = 0 To NumOfRows - 1
 adr = Hex(Address + row * 16)
 adr = String(8 - Len(adr), "0") & adr
 asi = ""
 hxd = ""
 
 For col = 0 To 15
 
 If ptr < bufLen Then
 
 hxd = hxd & " " & IIf(buffer(ptr) < &H10, "0" & Hex(buffer(ptr)), Hex(buffer(ptr)))
 asi = asi & IIf(buffer(ptr) >= 32, Chr$(buffer(ptr)), "?")
 
 Else
 hxd = hxd & " ??"
 asi = asi & "?"
 
 End If
 
 ptr = ptr + 1
 
 Next
 
 If row Then out = out & vbNewLine
 
 out = out & adr & ":" & hxd & " | " & asi
 
 Next
 
 picDump.Cls
 picDump.Print out
 
 End Sub
 Private Sub Form_Unload(Cancel As Integer)
 Dim Status As SERVICE_STATUS
 
 ' // Отключаемся от драйвера
 CloseHandle hDev
 
 ' // Останавливаем драйвер
 ControlService hSrv, SERVICE_CONTROL_STOP, Status
 
 ' // Удаляем службу
 DeleteService hSrv
 
 ' // Закрываем описатели
 CloseServiceHandle hSrv
 CloseServiceHandle hMgr
 
 End Sub
Драйвер должен лежать в той же папке что и программа. Код прокомментирован, так что я не буду описывать его работу.
Для отладки драйвера нужно использовать ядерный отладчик. Отлаживать будем на виртуальной системе (VMware) - Windows XP. В качестве отладчика возьмем Syser, выберем наш драйвер и нажмем Load. Система остановится, и мы перейдем в окно отладчика:

Мы находимся в начале функции DriverEntry. Первый CALL соответствует вызову функции Init. Если мы проследим пошагово (F8) что там внутри, то увидим как заполняется структура и вызывается RtlInitUnicodeString для имени устройства и символической связи. Второй CALL соответствует функции NT_SUCCESS, смотрим что она возвращает TRUE (в регистре EAX) и код прыгает после проверки (TEST EAX, EAX) на ноль (False) дальше:

Как видно код заталкивает в стек параметры для функции IoCreateDevice от последнего к первому с помощью PUSH'ей. Начнем проверку параметров. Проверим имя устройства (3-й параметр - PUSH 0f8a2c010), для этого введем команду d 0f8a2c010 (что значит просмотреть дамп памяти по адресу f8a2c010 и смотрим содержимое:

первые 8 байт - это наша переменная DeviceName. Первые два слова - соответственно длина строки и максимальная длина строки в байтах. Следующее двойное слово - указатель на строку, смотрим (d f8a2c0d8 учитываем порядок байтов little-endian):

, то что нужно там Unicode строка с именем устройства. Если посмотреть на параметр Device (последний выходной параметр - PUSH 0f8a2c020), то можно увидеть что он отличается от имени на 0x10 байт. Теперь посмотрим на декларации переменных, переменная Device задекларирована после DeviceName и DeviceLink, общей длиной 8 + 8 = 0x10 байт. Т.е. порядок расположения переменных в памяти соответствует порядку объявления в коде. Проверяем первый неконстантный параметр ESI, в самом начале в него копируется значение по адресу ESP+0xC. Регистр ESP - указывает на вершину стека. Если пройти в начало функции DriverEntry, то можно увидеть сохранение в стеке двух регистров ESI и EDI (по соглашению StdCall эти регистры находятся в списке сохраняемых, т.е. процедура не должна изменять их после вызова). DriverObject передается в первой паременной, т.е. ближе всех к вершине стека, также после всех параметров сохраняется адрес возврата - т.е. параметр DriverObject до выполнения первой инструкции в функции DriverEntry находится по адресу ESP+4 (стек растет в сторону уменьшения адресов), после двух PUSH'ей он соответственно смещается еще на 8 байт, в итоге DriverObject находится по адресу ESP+0С, все правильно. Параметры корректные, можно вызывать функцию. Жмем F10 чтобы не заходить внутрь IoCreateDevice и смотрим значение регистра EAX после вызова, там должно быть неотрицательное число, что сигнализирует что функция отработала без ошибок. У меня она возвратила 0 (STATUS_SUCCESS), все отлично. Дальше идет уже знакомая процедура по адресу 0xF8A2B750 - NT_SUCCESS:

При успехе идет прыжок на 0xf8a2b7bf, где идет заталкивание в стек параметров для функции IoCreateSymbolicLink. Параметр DeviceName мы уже проверяли, проверяем DeviceLink:

То что нужно. Жмем F10, тестируем EAX, при успехе идем дальше при неудаче, удаляем девайс и выходим с ошибкой. Процедура по адресу 0xf8a2bbb0 - это GetAddr, которая просто возвращает переданное ей значение:

Дальше идет копирование адресов по смещениям DriverObject, если посмотреть деларации то можно увидеть что по смещению 0x34 записывается адрес DriverUnload, по смещению 0x38 - MajorFunction(0) и т.д. Записываемые значения соответствуют адресам функций в нашем драйвере. Дальше происходит обнуление EAX (возвращаемой значение) и выход из процедуры DriverEntry. Все работает без ошибок, идем дальше. Итак, чтобы отследить работу драйвера мы поставим точку останова на функцию DriverDeviceControl. Адрес ее можно взять по только что записанным смещениям в структуре DRIVER_OBJECT либо найти простым просмотром и анализированием кода. В моем тесте адрес равен 0xf8a2b870, переходим на него (. 0xf8a2b870) и нажимаем F9, ставя точку останова. Напротив инструкции установится маркер:

Теперь при вызове этой функции отладчик остановит выполнение кода и даст нам возможность пошагово выполнить код. Функции DriverCreateClose и DriverUnload я не буду описывать, т.к. там все просто. Жмем F5, тем самым продолжая выполнение в обычном режиме. Нас тут же переносит обратно в Windows. Теперь мы запускаем наше тестовое приложение, вводим какой-нибудь адрес (например 81234567) и жмем на кнопку Read. Наш вызов перехватывает отладчик и мы можем продолжить тестировать код функции DriverDeviceControl.
Подробно внутри я не буду описывать код, остановлюсь на самом копировании:

Сразу смотрим на стек (регистр ESP), видим что передаются правильные параметры. На всякий случай делаем дамп, потом сравним:

Нажимаем F5 - и возвращаемся в Windows. Смотрим на дамп уже в нашей программе:

Как видим все отлично скопировалось. Попробуем скопировать данные на границе страниц, так чтобы одна страница отсутствовала. Экспериментальным методом была найдена такая страница вот что получаем:

Как видим, что данные скопировались корректно, где не получилось там у нас отображаются вопросительные знаки. В выходном параметре DeviceIoControl у нас возвращается количество реально прочитанных байт, его мы и используем для отображения вопросительных знаков.
_____________________________________________________________________________
Как видим на VB6 можно написать простой драйвер, а если использовать ассемблерные вставки можно и посерьезнее что-нибудь написать. Всем спасибо за внимание. Удачи!


 
 
