СОДЕРЖАНИЕ 1. Краткое описание 2 2. Реализация драйвер

advertisement
СОДЕРЖАНИЕ
1. Краткое описание .............................................................................................. 2
2. Реализация драйвер фильтров.......................................................................... 3
2.1. Драйвер фильтр клавиатуры ............................................................... 3
2.2. Подключение к функциональному драйверу .................................... 5
2.3. Механизм работы стека клавиатуры с
установленным фильтром ......................................................................... 7
2.4. Алгоритм работы KbFilter_ServiceCallback ...................................... 9
2.5. Драйвер фильтр мыши......................................................................... 10
3. Управляющее приложение. .............................................................................. 11
4. Анализ клавиатурного почерка........................................................................ 12
5. Текст программы ............................................................................................... 14
5.1 Драйвер фильтр клавиатуры ................................................................ 14
5.2 Управляющее приложение ................................................................... 36
5.2 Драйвер фильтр мыши.......................................................................... 42
6. Литература ......................................................................................................... 43
1. КРАТКОЕ ОПИСАНИЕ
Система позволяет выявить подмену идентифицированного пользователя, проводя
непрерывный мониторинг клавиатурного почерка. В её состав входят два драйвер фильтра
клавиатуры и мыши, а так же управляющее приложение. Система может работать в трёх
режимах: обучение, анализ, блокировка:
 Режим обучение – определяются эталонные характеристики клавиатурного почерка
 Режим анализ – система сравнивает эталонные характеристиками с вновь
введёнными, после чего переходит либо остаётся в режиме анализа, либо
переходит в режим блокировки.
 Режим блокировки – в этом режиме блокированы клавиатура и мышь, система
ожидает ввода пароля на разблокировку.
Управляющее приложение предназначено для просмотра временных характеристик, их
сохранения и перевода системы в режим анализ.
Рис. 1. Переход между режимами работы системы
2
2. РЕАЛИЗАЦИЯ ДРАЙВЕР ФИЛЬТРОВ
2.1. Драйвер фильтр клавиатуры
Cвязь клавиатуры с шиной осуществляет микроконтроллер клавиатуры Intel 8042 (или
совместимый с ним). На современных компьютерах он интегрирован в чипсет
материнской платы. Все клавиатуры являются PS/2-совместимыми или клавиатурами,
подключаемыми через интерфейс USB. В PS/2-совместимом режиме микроконтроллер
клавиатуры также связывает с шиной и PS/2-совместимую мышь. Всем этим управляет
функциональный драйвер i8042prt (Intel 8042 Port Driver). Драйвер i8042prt создает два
безымянных объекта "устройство" и подключает один к стеку клавиатуры.
Поверх драйвера i8042prt, точнее поверх его устройств, располагаются именованные
объекты "устройство" драйверов Kbdclass и Mouclass. Имя "KeyboardClass" является
базовым, и к нему добавляются индексы (0, 1 и т.д.). Базовое имя хранится в параметре
реестра
HKLM\SYSTEM\CurrentControlSet\Services\Kbdclass\Parameters\KeyboardDeviceBaseName
Драйверы Kbdclass и Mouclass являются так называемыми драйверами класса (class
drivers) и реализуют общую функциональность для всех типов клавиатур и мышей, т.е.
для всего класса этих устройств. Оба эти драйвера устанавливаются как высокоуровневые
драйверы.
Стек клавиатуры обрабатывает несколько типов. P
Запросы типа IRP_MJ_READ несут в себе коды клавиш. Генератором этих IRP является
поток необработанного ввода RawInputThread системного процесса csrcc.exe. Этот поток
открывает объект "устройство" драйвера класса клавиатуры для эксклюзивного
использования и с помощью функции ZwReadFile направляет ему IRP типа
IRP_MJ_READ. Получив IRP, драйвер Kbdclass, используя макрос IoMarkIrpPending,
отмечает его как ожидающий завершения (pending), ставит в очередь и возвращает
STATUS_PENDING. Потоку необработанного ввода придется ждать завершения IRP. То
есть RawInputThread получает клавиатурные события как вызов асинхронной процедуры.
Рис. 2. Схема стека драйверов клавиатуры и мыши
3
Рис. 3. Схема стека драйверов клавиатуры и мыши после установки системы
4
2.2. Подключение к функциональному драйверу
Для подключения к функциональному драйверу клавиатуры (I8042ptr), высокоуровневый
драйвер фильтр kbdclass посылает внутренний запрос IRP_MJ_INTERNAL_CONTROL .
Все нижележащие драйверы имеют процедуру обработки таких внутренних запросов.
Внутренний
запрос
посылается
с
параметром
IO_CTL_INTERNAL_KEYBOARD_CONNECT.
(заносится
в
IrpStack–
>Parametrs.DeviceIoControl.IoControlCode). С этим запросом kbdclass посылает
структуру
CONNECT_DATA
.
(заносится
в
IrpStack–
>Parametrs.DeviceIoControl.Type3bufer)
typedef struct _CONNECT_DATA {
IN PDEVICE_OBJECT ClassDeviceObject;
IN PVOID ClassService;
} CONNECT_DATA, *PCONNECT_DATA;
Так как инициатором этого запроса является kbdclass, то он соответственно и будет
заполнять первым эту структуру. В ClassDeviceObject занесётся адрес объекта, который
создается в драйвере kbdclass. В ClassService занесётся адрес функции
KeyboardClassServiceCallback, которую будут вызывать IRP пакеты по возвращению.
(когда встречается функция IoCompileIrp).
Получив такой запрос на соеденение kbfilter (если установлен) или функциональный
драйвер I8042ptr выполняет следующее:
1. Сохраняет полученную структуру CONNECT_DATA
2. Переопределяет структуру: заносит в ClassDeviceObject указатель на свой объект
устройство, в ClassService указатель на свою процедуру обратного вызова.
3. Передаёт IRP дальше по стеку или завешает запрос.
5
Рис. 4. Схема подключения к функциональному драйверу
6
2.3. Механизм работы стека клавиатуры с установленным фильтром
Когда будет нажата или отпущена клавиша, контроллер клавиатуры выработает
аппаратное прерывание. Его обработчик вызовет I8042KeyboardInterruptService, которая
прочитает из внутренней очереди контроллера клавиатуры необходимые данные. Так как
обработка аппаратного прерывания происходит на повышенном IRQL, ISR делает только
самую неотложную работу и ставит в очередь вызов отложенной процедуры (Deferred
Procedure Call, DPC). DPC работает при IRQL = DISPATCH_LEVEL. Когда IRQL
понизится до DISPATCH_LEVEL, система вызовет процедуру I8042KeyboardIsrDpc,
которая вызовет зарегистрированную драйвером фильтром kbfilter процедуру обратного
вызова KbFilter_ServiceCallback. (также выполняется на IRQL = DISPATCH_LEVEL). В
этой процедуре произойдёт заполнение структуры KEY_INFO:
typedef struct _KEY_INFO{
ULONG
MakeCode;
// код клавиши
ULONG
CountUp;
// количество отжатий
ULONGLONG
LastDown;
// последнее нажатие
ULONGLONG
LastUp;
// последнее отжатие
ULONGLONG
LastLastUp;
// предпоследнее отжатие
ULONG
LastFromDownToUp;
// посленее нажатие-отпуск
ULONG
MeanFromDownToUp;
// среднее нажатие отпуск
ULONGLONG
LastBetweenTwoUp;
// последне между двумя отжатиями
ULONG
MeanBetweenTwoUp;
// среднее между двумя отжатиями
ULONG
ToOtherBreak;
// время до другой
ULONG
MeanToOtherBreak;
// среднее время до другой
ULONG
KeyVector[NUMBER_OF_KEYS]; // среднее до определённой
} KEY_INFO,*PKEY_INFO;
Затем произойдёт вызов процедуры обратного вызова KeyboardClassServiceCallback в
драйвере Kbdclas. KeyboardClassServiceCallback извлечет из своей очереди ожидающий
завершения IRP, заполнит структуру KEYBOARD_INPUT_DATA, несущую всю
необходимую информацию о нажатиях/отпусканиях клавиш и завершит IRP. Поток
необработанного ввода пробуждается, обрабатывает полученную информацию и вновь
посылает IRP типа IRP_MJ_READ драйверу класса, который опять ставится в очередь до
следующего нажатия/отпускания клавиши. Таким образом, у стека клавиатуры всегда
есть, по крайней мере, один, ожидающий завершения IRP и находится он в очереди
драйвера Kbdclass. "Мышиный" стек ведет себя подобным же образом.
7
Рис.5. Схема работы стека клавиатуры с установленным фильтром
8
2.4. Алгоритм работы KbFilter_ServiceCallback
Процедура KbFilter_ServiceCallback вызывается всякий раз когда происходят события,
связанные с клавиатурой имеет следующий алгоритм:
Рис. 6. Схема алгоритма работы KbFilter_ServiceCallback
9
2.5. Драйвер фильтр мыши
Драйвер фильтр мыши полностью аналогичен описанному выше драйвер фильтру
клавиатуры. Взаимодействие между драйверами осуществляется, как показано на рисунке.
Рис. 7.Схема взаимодействия м между драйвер фильтрами
10
3. УПРАВЛЯЮЩЕЕ ПРИЛОЖЕНИЕ.
Управляющее приложение предназначено для управления драйвер фильтром клавиатуры.
Оно отображает все временные характеристики по каждой клавише на экране. Сохраняет
сформированный клавиатурный почерк. Так же оно предназначено для запуска самой
системы защиты.
Рис. 8. Вид управляющего приложения
Рис. 9. Вид окна «О программе»
11
4. АНАЛИЗ КЛАВИАТУРНОГО ПОЧЕРКА
Для анализа клавиатурного почерка используется матрица межклавишных
взаимодействий, которая имеет вид:
t11

t21
 ...

t
 n1
t12
t22
...
tn2
...
...
...
...
t1n 

t2n 
... 

tnn 
где n – количество клавиш на клавиатуре,
tij – время нажатия от i-й клавиши на j-ю
tij = keyinf[i].keyvector[j]
Каждые 30 нажатий клавиш, составляется вектор времён, имеющий 29 элементов.
Если система работает в режиме анализа, то для этого вектора составляется эталонный
вектор из эталонной матрицы межклавишных взаимодействий. Для эталонного вектора
определяется доверительный интервал. Если все значения проверяемого вектора входят в
доверительный интервал с определённой точностью, то клавиатурный почерки совпадают.
Получены при помощи программы результаты представлены в таблице 1.
Таблица 1.
Клавиша
а
н
а
л
и
з
пробел
к
л
а
в
и
а
т
у
р
н
о
г
о
пробел
п
о
ч
е
р
к
а
Фраза
введённая 50
раз
1676042
1798288
1364853
2439249
3214778
1787140
1605824
3153753
2030067
1495901
1876526
2339301
1848360
3289886
1947722
2886572
881932
1933248
2712689
1049556
1812371
2182239
2095982
1966695
2162288
1148330
1989579
Выборка по вектору межклавишного взаимодействия
Нижняя граница
Верхняя граница
Фраза
доверительного
доверительного
введённая
интервала
интервала
1 раз
1176042
1298288
864853
1939249
2714778
1287140
1105824
2653753
1530067
995901
1376526
1839301
1348360
2789886
1447722
2386572
381932
1433248
2212689
549556
1312371
1682239
1595982
1466695
1662288
648330
1489579
2176042
2298288
1864853
2939249
3714778
2287140
2105824
3653753
2530067
1995901
2376526
2839301
2348360
3789886
2447722
3386572
1381932
2433248
3212689
1549556
2312371
2682239
2595982
2466695
2662288
1648330
2489579
801152
2103024
1101584
2002880
4907056
1101584
1502160
1802592
1902736
1402016
1802592
2002880
1602304
4005760
1902736
2804032
801152
1902736
2403456
801152
1802592
2002880
2203168
1902736
1702448
901296
2002880
Фраза
введенная
другим
человеком
2203168
2603744
3304752
1502160
2203168
2103024
3705328
2303312
2203168
2002880
4005760
1602304
1902736
2203168
1802592
1802592
1502160
2103024
1502160
1702448
2603744
2403456
3905616
1402016
1702448
2002880
3104464
12
Рис. 10. Фраза введённая 50 раз
Рис. 11. Фраза введённая 1 раз
Рис. 12. Фраза введённая другим человеком
13
5. ТЕКСТ ПРОГРАММЫ
5.1 Драйвер фильтр клавиатуры
#include "kbfiltr.h"
#pragma
#pragma
#pragma
#pragma
#pragma
#pragma
#pragma
#pragma
alloc_text
alloc_text
alloc_text
alloc_text
alloc_text
alloc_text
alloc_text
alloc_text
(INIT,
(PAGE,
(PAGE,
(PAGE,
(PAGE,
(PAGE,
(PAGE,
(PAGE,
DriverEntry)
FilterAddDevice)
FilterDispatchPnp)
FilterUnload)
FilterInternIoCtl)
FilterCreateControlObject)
FilterDeleteControlObject)
FilterDispatchIo)
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
/*
Точка входа инициализации драйвера
Вызывается непосредстенно системой в/в
Аргументы:
DriverObject - указатель на объект драйвер
RegistryPath - указатель на unicode string пути
в регистре к подразделу драйвера
Возвращаемое значение:
STATUS_SUCCESS если успешно,
STATUS_UNSUCCESSFUL в противном случае.
*/
{
NTSTATUS
ULONG
PDRIVER_DISPATCH
status = STATUS_SUCCESS;
ulIndex;
* dispatch;
UNREFERENCED_PARAMETER (RegistryPath);
//все запросы будут приводить к вызову функции FilterPass,
//которая переадресует запросы нижним драйверам в стеке.
for (ulIndex = 0, dispatch = DriverObject->MajorFunction;
ulIndex <= IRP_MJ_MAXIMUM_FUNCTION;
ulIndex++, dispatch++) {
*dispatch = FilterPass;
}
//исключение составят функции, зарегестрированные ниже
//регестрация функций, которые будет обрабатывать объект в стеке
DriverObject->MajorFunction[IRP_MJ_PNP]
DriverObject->MajorFunction[IRP_MJ_POWER]
DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL]
DriverObject->DriverExtension->AddDevice
DriverObject->DriverUnload
=
=
=
=
=
FilterDispatchPnp;
FilterDispatchPower;
FilterInternIoCtl;
FilterAddDevice;
FilterUnload;
//регестрация функций, которые будет обрабатывать объект устройство управленя
DriverObject->MajorFunction[IRP_MJ_CREATE]
=
DriverObject->MajorFunction[IRP_MJ_CLOSE]
=
DriverObject->MajorFunction[IRP_MJ_CLEANUP]
=
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
FilterDispatchIo;
// Инициализация FastMutex для понижения уровня IRQL при
// создании и удалении объекта устройство управления
ExInitializeFastMutex(&ControlMutex);
return status;
}
14
NTSTATUS
FilterAddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
/*
Созданию объекта устройства,
подготовка устройства к работе
Аргументы:
DeviceObject - указатель на объект данного драйвера
PhysicalDeviceObject - указатель на объект физического
устройства, созданного родительским (шинным) драйвером
Возвращаемое значение:
STATUS_SUCCESS или код ошибки
*/
{
NTSTATUS
PDEVICE_OBJECT
PDEVICE_EXTENSION
ULONG
PKEY_INFO
USHORT i;
PAGED_CODE ();
status = STATUS_SUCCESS;
deviceObject = NULL;
deviceExtension;
deviceType = FILE_DEVICE_UNKNOWN;
KeyInfo;
// Создание объекта устройство фильтр
status = IoCreateDevice (DriverObject,
sizeof (DEVICE_EXTENSION),
NULL, // без имени
deviceType,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&deviceObject);
if (!NT_SUCCESS (status)) {
return status;
}
deviceExtension = (PDEVICE_EXTENSION) deviceObject->DeviceExtension;
deviceExtension->Type = DEVICE_TYPE_FIDO;
deviceExtension->NextLowerDriver = IoAttachDeviceToDeviceStack (
deviceObject,
PhysicalDeviceObject);
// при не удачном присоеденении к стеку, возвращаем STATUS_UNSUCCESSFUL
if(NULL == deviceExtension->NextLowerDriver) {
IoDeleteDevice(deviceObject);
return STATUS_UNSUCCESSFUL;
}
deviceObject->Flags |= deviceExtension->NextLowerDriver->Flags &
(DO_BUFFERED_IO | DO_DIRECT_IO |
DO_POWER_PAGABLE );
deviceObject->DeviceType = deviceExtension->NextLowerDriver->DeviceType;
deviceObject->Characteristics =
deviceExtension->NextLowerDriver->Characteristics;
deviceExtension->Self = deviceObject;
//Выделяем память под массив структур KEY_INFO, хранящих информацию о каждой клавише
//обнуляем все её поля и сохраняем указатель в поле deviceExtension->KeyInfo
KeyInfo=(PKEY_INFO)ExAllocatePool(PagedPool,NUMBER_OF_KEYS*sizeof(KEY_INFO));
ResetKeyInfo(KeyInfo);
deviceExtension->KeyInfo=KeyInfo;
for (i=0;i<NUMBER_OF_CHEK_KEYS;i++)
{
KeyList[i]=0;
}
15
//инициализируем remove lock для веденя учёта IRP, чтобы не удалить
//объект устройство, когда запрос в/в в стеке еще не завершён
IoInitializeRemoveLock (
&deviceExtension->RemoveLock ,
//указатель на структуру IO_REMOVE_LOCK
POOL_TAG,
//#define POOL_TAG 'arty'
1,
//максимальное
количество минут удержки
100);
//макс число незавершённых
захватов блокировки
// Устанавливаем статус Filter DO
INITIALIZE_PNP_STATE(deviceExtension);
deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
NTSTATUS
FilterPass (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*
Стандартная функция обработки
Если драйвер не обрабатывает запрс IRP,
то просто посылает его вниз по стеку
Аргументы:
DeviceObject - указатель на объект данного драйвера
Irp - указатель на пакет запроса ввода/вывода
Возвращаемое значение:
NT status code
*/
{
PDEVICE_EXTENSION
NTSTATUS
status;
deviceExtension;
deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
//увеличиваем счётчик захватов Remove Lock
status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
//завершаем IRP запрос если ошибка при увелечении счётчика
if (!NT_SUCCESS (status)) {
Irp->IoStatus.Status = status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return status;
}
//иначе передаем запрос нижележащему драйверу по стеку
IoSkipCurrentIrpStackLocation (Irp);
status = IoCallDriver (deviceExtension->NextLowerDriver, Irp);
//уменьшаем счётчик захватов Remove Lock
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
return status;
}
NTSTATUS
FilterDispatchPnp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*
Функция диспетчеризации PnP.
Аргументы:
DeviceObject - указатель на объект данного драйвера
Irp - указатель на пакет запроса ввода/вывода
Возвращаемое значение:
16
NT status code
*/
{
PDEVICE_EXTENSION
PIO_STACK_LOCATION
NTSTATUS
KEVENT
deviceExtension;
irpStack;
status;
event;
PAGED_CODE();
deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
irpStack = IoGetCurrentIrpStackLocation(Irp);
//Стандартная проверка счётчика Remove Lock
status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
if (!NT_SUCCESS (status)) {
Irp->IoStatus.Status = status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return status;
}
switch (irpStack->MinorFunction) {
case IRP_MN_START_DEVICE:
// Устройство запущено
// Неможем трогать устройство пока не
// перенаправим запрос нижележащим драйверам
KeInitializeEvent(&event, NotificationEvent, FALSE);
//Копируем содержимое ячейки стека IRP для текущего
//драйвера в ячейку стека для нижестоящего драйвера
IoCopyCurrentIrpStackLocationToNext(Irp);
//Регестрируем процедуру завершения
IoSetCompletionRoutine(Irp,
(PIO_COMPLETION_ROUTINE) FilterStartCompletionRoutine,
&event,
TRUE,
TRUE,
TRUE);
status = IoCallDriver(deviceExtension->NextLowerDriver, Irp);
//Перед таем как запустить устройство нужно дождаться пока
//стартуют низкоуровневые драйверы
if (status == STATUS_PENDING) {
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
status = Irp->IoStatus.Status;
}
if (NT_SUCCESS (status)) {
//если NT_SUCCESS значит внизу всё нормально, устанавливаем статус
SET_NEW_PNP_STATE(deviceExtension, Started);
if (deviceExtension->NextLowerDriver->Characteristics &
FILE_REMOVABLE_MEDIA) {
DeviceObject->Characteristics |= FILE_REMOVABLE_MEDIA;
}
if(Stopped != deviceExtension->PreviousPnPState) {
// запущено первый раз
FilterCreateControlObject(DeviceObject);
}
}
Irp->IoStatus.Status = status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
return status;
case IRP_MN_REMOVE_DEVICE:
//Если существует KeyInfoTemp (переходили в режим анализ), отчищаем
if (deviceExtension->KeyInfoTemp)ExFreePool(deviceExtension->KeyInfoTemp);
//Освобождаем память под KeyInfo
ExFreePool(deviceExtension->KeyInfo);
17
//Ждём завершения всех незавершенных IRP
IoReleaseRemoveLockAndWait(&deviceExtension->RemoveLock, Irp);
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(deviceExtension->NextLowerDriver, Irp);
SET_NEW_PNP_STATE(deviceExtension, Deleted);
FilterDeleteControlObject();
IoDetachDevice(deviceExtension->NextLowerDriver);
IoDeleteDevice(DeviceObject);
return status;
case IRP_MN_QUERY_STOP_DEVICE:
SET_NEW_PNP_STATE(deviceExtension, StopPending);
status = STATUS_SUCCESS;
break;
case IRP_MN_CANCEL_STOP_DEVICE:
if(StopPending == deviceExtension->DevicePnPState)
{
RESTORE_PREVIOUS_PNP_STATE(deviceExtension);
}
status = STATUS_SUCCESS; // We must not fail this IRP.
break;
case IRP_MN_STOP_DEVICE:
SET_NEW_PNP_STATE(deviceExtension, Stopped);
status = STATUS_SUCCESS;
break;
case IRP_MN_QUERY_REMOVE_DEVICE:
SET_NEW_PNP_STATE(deviceExtension, RemovePending);
status = STATUS_SUCCESS;
break;
case IRP_MN_SURPRISE_REMOVAL:
SET_NEW_PNP_STATE(deviceExtension, SurpriseRemovePending);
status = STATUS_SUCCESS;
break;
case IRP_MN_CANCEL_REMOVE_DEVICE:
if(RemovePending == deviceExtension->DevicePnPState)
{
RESTORE_PREVIOUS_PNP_STATE(deviceExtension);
}
status = STATUS_SUCCESS;
break;
default:
status = Irp->IoStatus.Status;
break;
}
// Посылаем IRP дальше и забываем про него
Irp->IoStatus.Status = status;
IoSkipCurrentIrpStackLocation (Irp);
status = IoCallDriver (deviceExtension->NextLowerDriver, Irp);
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
return status;
}
18
NTSTATUS
FilterStartCompletionRoutine(
IN PDEVICE_OBJECT
IN PIRP
IN PVOID
DeviceObject,
Irp,
Context
)
/*
Функция завершения, используется когда вызываются
нижележащие объекты устройства
Аргументы:
DeviceObject - указательна объект устройство
Irp
- указатель на PnP Irp.
Context
- NULL
Возвращаемое значение:
NT status code
*/
{
PKEVENT event = (PKEVENT)Context;
UNREFERENCED_PARAMETER (DeviceObject);
// если нижележащий драйвер не вернул STATUS_PENDING
// мы не должны отмечать событие
if (Irp->PendingReturned == TRUE) {
KeSetEvent (event, IO_NO_INCREMENT, FALSE);
}
// функция диспетчеризации вызовит IoCompleteRequest
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS
FilterDispatchPower(
IN PDEVICE_OBJECT
IN PIRP
DeviceObject,
Irp
)
/*
Функция обработки IRP электропитания.
Аргументы:
DeviceObject - указательна объект устройство
Irp
- указатель на пакет запроса.
Возвращаемое значение:
NT status code
*/
{
PDEVICE_EXTENSION
deviceExtension;
NTSTATUS
status;
//стандартная проверка RemoveLock
deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
status = IoAcquireRemoveLock (&deviceExtension->RemoveLock, Irp);
if (!NT_SUCCESS (status)) {
Irp->IoStatus.Status = status;
PoStartNextPowerIrp(Irp);
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return status;
}
//просто передаём запрос вниз
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
status = PoCallDriver(deviceExtension->NextLowerDriver, Irp);
IoReleaseRemoveLock(&deviceExtension->RemoveLock, Irp);
return status;
}
19
VOID
FilterUnload(
IN PDRIVER_OBJECT DriverObject
)
/*
Функция выгрузки драйвера
освобаждаем все ресурсы, выделенные в DriverEntry
Аргументы:
DeviceObject - указательна объект устройство
Возвращаемое значение:
VOID.
*/
{
PAGED_CODE ();
// устанавливаем DeviceObject в NULL
ASSERT(DriverObject->DeviceObject == NULL);
return;
}
NTSTATUS
FilterCreateControlObject(
IN PDEVICE_OBJECT
)
/*
DeviceObject
Функция создания устройства управления
Аргументы:
DeviceObject - указательна объект устройство
Возвращаемое значение:
NT status code
*/
{
UNICODE_STRING
ntDeviceName;
UNICODE_STRING
symbolicLinkName;
PCONTROL_DEVICE_EXTENSION
deviceExtension;
NTSTATUS status = STATUS_UNSUCCESSFUL;
PAGED_CODE();
// понижаем уровень IRQL, IoCreateDeviceSecure и IoCreateSymbolicLink
// должны быть вызваны на уровне PASSIVE_LEVEL
ExAcquireFastMutexUnsafe(&ControlMutex);
// если это первый экземпляр устройства, то созжаём объект управления
if(1 == ++InstanceCount)
{
RtlInitUnicodeString(&ntDeviceName, NTDEVICE_NAME_STRING);
RtlInitUnicodeString(&symbolicLinkName, SYMBOLIC_NAME_STRING);
status = IoCreateDevice(DeviceObject->DriverObject,
sizeof(CONTROL_DEVICE_EXTENSION),
&ntDeviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&ControlDeviceObject);
if (NT_SUCCESS( status )) {
ControlDeviceObject->Flags |= DO_BUFFERED_IO;
status = IoCreateSymbolicLink( &symbolicLinkName, &ntDeviceName );
if ( !NT_SUCCESS( status )) {
IoDeleteDevice(ControlDeviceObject);
20
goto End;
}
deviceExtension =
deviceExtension->Type =
deviceExtension->ControlData=
deviceExtension->Deleted =
deviceExtension->StackObject=
deviceExtension->MouseLock=
ControlDeviceObject->DeviceExtension;
DEVICE_TYPE_CDO;
NULL;
FALSE;
DeviceObject;
FALSE;
//в начале мышь
работает
deviceExtension->ModeFlag=
MODE_EDUCATION;
режиме обучение
ControlDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
//начинаем работу в
}else {
DbgPrint("IoCreateDevice failed %x\n", status);
}
}
End:
ExReleaseFastMutexUnsafe(&ControlMutex);
return status;
}
VOID
FilterDeleteControlObject(
)
/*
Функция удаления устройства управления
Возвращаемое значение:
VOID
*/
{
UNICODE_STRING
PCONTROL_DEVICE_EXTENSION
symbolicLinkName;
deviceExtension;
PAGED_CODE();
//захватываем быстрый мьютекс для понижения irql
ExAcquireFastMutexUnsafe (&ControlMutex);
// если это последний экземпляр устройства то удаляем controlobject
if(!(--InstanceCount) && ControlDeviceObject)
{
RtlInitUnicodeString(&symbolicLinkName, SYMBOLIC_NAME_STRING);
deviceExtension = ControlDeviceObject->DeviceExtension;
deviceExtension->Deleted = TRUE;
IoDeleteSymbolicLink(&symbolicLinkName);
IoDeleteDevice(ControlDeviceObject);
ControlDeviceObject = NULL;
}
ExReleaseFastMutexUnsafe (&ControlMutex);
}
NTSTATUS
FilterDispatchIo(
IN PDEVICE_OBJECT
IN PIRP
)
/*
DeviceObject,
Irp
Routine Description:
Функция обаботки IRP не проходящих через стек
определяем входне значение объекта устройство
если это устройство управления, обрабатываем IRP и завершаем
если это устройство в стеке, передаем IRP вниз
Аргументы:
21
DeviceObject - указательна объект устройство
Irp
- указатель на пакет запроса.
Возвращаемое значение:
NT status code
*/
{
PIO_STACK_LOCATION
NTSTATUS
PCOMMON_DEVICE_DATA
ULONG
irpStack;
status;
commonData;
outBufLength;
PCONTROL_DEVICE_EXTENSION
PKEY_INFO
PDEVICE_EXTENSION
CtrlDevExtension;
keyinfo;
MainDevExt;
PCONTROL_MOUSE_DEVICE_EXTENSION MouCtrlDevExt;
UNICODE_STRING
PFILE_OBJECT
PDEVICE_OBJECT
ObjectName;
FileObject=NULL;
DeviceMouCtrlObject=NULL;
PKEY_INFO KeyInfoTemp;
USHORT i;
PAGED_CODE();
commonData = (PCOMMON_DEVICE_DATA)DeviceObject->DeviceExtension;
// определяем тип устройства
if(commonData->Type == DEVICE_TYPE_FIDO) {
// Если IRP адресован устройству в стеке
// просто перенаправляем вниз по стеку
return FilterPass(DeviceObject, Irp);
}
ASSERT(commonData->Type == DEVICE_TYPE_CDO);
// Иначе это устройство для контроля
// для открытия устройства управленя мышью
RtlInitUnicodeString(&ObjectName, L"\\Device\\MouCtrlFilter");
//получаем указатель на объект Расширение устройства управления
CtrlDevExtension = (PCONTROL_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
// из Расширения Устройства Управления получаем указатель
// на Расширение устройства в стеке
MainDevExt=(PDEVICE_EXTENSION)CtrlDevExtension->StackObject->DeviceExtension;
// из Расширения устройства в стеке получаем указатель на KeyInfo
keyinfo=MainDevExt->KeyInfo;
if(! CtrlDevExtension->Deleted) {
status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
irpStack = IoGetCurrentIrpStackLocation (Irp);
switch (irpStack->MajorFunction) {
case IRP_MJ_CREATE:
DbgPrint("Create in kbdfiltr");
break;
case IRP_MJ_CLOSE:
DbgPrint("Close
break;
in kbdfiltr");
case IRP_MJ_CLEANUP:
DbgPrint("Cleanup
break;
in kbdfiltr");
case
IRP_MJ_DEVICE_CONTROL:
// получаем длину буфера переданного с запросом
outBufLength = irpStack>Parameters.DeviceIoControl.OutputBufferLength;
switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
case GET_KEY_INFO:
22
RtlCopyMemory(Irp>AssociatedIrp.SystemBuffer,keyinfo,outBufLength);
Irp->IoStatus.Information=outBufLength;
status=STATUS_SUCCESS;
break;
case GET_KEY_LIST:
RtlCopyMemory(Irp>AssociatedIrp.SystemBuffer,&KeyList,outBufLength);
Irp->IoStatus.Information=outBufLength;
status=STATUS_SUCCESS;
break;
case WRITE_FILE:
ReadWriteKeyInfoToFile(keyinfo,FLAG_WRITE);
status=STATUS_SUCCESS;
break;
case READ_FILE:
ReadWriteKeyInfoToFile(keyinfo,FLAG_READ);
status=STATUS_SUCCESS;
break;
case RESET_KEY_INFO:
ResetKeyInfo(MainDevExt->KeyInfo);
status=STATUS_SUCCESS;
break;
case START_KBD_SCAN:
//Получаем указатель на объект устройство управление
мыши
status=IoGetDeviceObjectPointer (&ObjectName,
FILE_ALL_ACCESS,
&FileObject,
&DeviceMouCtrlObject);
if( NT_SUCCESS(status) )
{
// из полученного указателя получем указатель на УУ
мыши
MouCtrlDevExt=(PCONTROL_MOUSE_DEVICE_EXTENSION)
DeviceMouCtrlObject->DeviceExtension;
//захват управления над мышью
CtrlDevExtension->MouseLock=&MouCtrlDevExt>MouseLock;
}
//выделение памяти под структру почерка
KeyInfoTemp=(PKEY_INFO)ExAllocatePool(PagedPool,
NUMBER_OF_KEYS*sizeof(KEY_INFO));
// записываем в KeyInfoTemp клавиатурный почерк
status=ReadWriteKeyInfoToFile(KeyInfoTemp,FLAG_READ);
// ели операция чтения успешна
if( NT_SUCCESS(status) )
{
// записываем указатель
MainDevExt->KeyInfoTemp=KeyInfoTemp;
// обнуляем KeyInfo для режима анализ
ResetKeyInfo(MainDevExt->KeyInfo);
// обнуляем список, зопиманаемых клавиш
CountKeyList=0;
// переходим в режим анализ
CtrlDevExtension->ModeFlag=MODE_ANALYSIS;
}
status=STATUS_SUCCESS;
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}
default:
break;
23
}
} else {
ASSERTMSG(FALSE, "Requests being sent to a dead device\n");
status = STATUS_DEVICE_REMOVED;
}
// и завершаем запрос
Irp->IoStatus.Status = status;
IoCompleteRequest (Irp, IO_NO_INCREMENT);
return status;}
NTSTATUS
ReadWriteKeyInfoToFile(
IN PKEY_INFO keyinfo,
ULONG flag
)
/*
Routine Description:
Функция записи/чтения структуры почерка в фаило
Аргументы:
keyinfo - указательна структуру почерка
flag
- режим работы FLAG_READ/FLAG_WRITE
Возвращаемое значение:
NT status code
*/
{
ULONG Length;
IO_STATUS_BLOCK IoStatus;
OBJECT_ATTRIBUTES objectAttributes;
NTSTATUS status;
HANDLE FileHandle;
UNICODE_STRING fileName;
// инициализируем имя фаила
fileName.Buffer = NULL;
fileName.Length = 0;
fileName.MaximumLength = sizeof(DEFAULT_LOG_FILE_NAME) + sizeof(UNICODE_NULL);
fileName.Buffer = ExAllocatePool(PagedPool, fileName.MaximumLength);
if (!fileName.Buffer) {
DbgPrint("LogMessage: FAIL. ExAllocatePool Failed");
return FALSE;
}
RtlZeroMemory(fileName.Buffer, fileName.MaximumLength);
status = RtlAppendUnicodeToString(&fileName, (PWSTR)DEFAULT_LOG_FILE_NAME);
InitializeObjectAttributes (&objectAttributes,
(PUNICODE_STRING)&fileName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL );
switch(flag)
{
// если операция на чтение
case FLAG_WRITE:
status = ZwCreateFile( &FileHandle,
FILE_WRITE_DATA,
&objectAttributes,
&IoStatus,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0 );
ZwWriteFile( FileHandle,
NULL,NULL,
NULL,&IoStatus,
keyinfo,
24
NUMBER_OF_KEYS*sizeof(KEY_INFO),
NULL, NULL );
break;
// если операция на запись
case FLAG_READ:
status = ZwCreateFile( &FileHandle,
FILE_READ_DATA,
&objectAttributes,
&IoStatus,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0 );
ZwReadFile( FileHandle,
NULL,NULL,
NULL,&IoStatus,
keyinfo,
NUMBER_OF_KEYS*sizeof(KEY_INFO),
NULL, NULL );
break;
}
if( NT_SUCCESS(status) ) {
DbgPrint("ZwCreateFile SUCCESS");
ZwClose( FileHandle );
}
if( fileName.Buffer ) {
ExFreePool (fileName.Buffer);
}
return status;;
}
NTSTATUS
FilterInternIoCtl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*
Функция обработки фнутренних запросов
IOCTL_INTERNAL_KEYBOARD_CONNECT:
сохраняем старый контекст и указатель функции
и заменяем на свой для потключения к стеку клавиатуры
Аргументы:
DeviceObject - указательна объект устройство
Irp
- указатель на пакет запроса.
Возвращаемое значение:
NT status code
*/
{
PIO_STACK_LOCATION
PDEVICE_EXTENSION
KEVENT
PCONNECT_DATA
NTSTATUS
irpStack;
devExt;
event;
connectData;
status = STATUS_SUCCESS;
devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
Irp->IoStatus.Information = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_INTERNAL_KEYBOARD_CONNECT:
if (devExt->UpperConnectData.ClassService != NULL) {
status = STATUS_SHARING_VIOLATION;
25
break;
}
else if (irpStack->Parameters.DeviceIoControl.InputBufferLength <
sizeof(CONNECT_DATA)) {
// неверный буфер
status = STATUS_INVALID_PARAMETER;
break;
}
// копируем параметры соеденения в device extension.
connectData = ((PCONNECT_DATA)
(irpStack->Parameters.DeviceIoControl.Type3InputBuffer));
devExt->UpperConnectData = *connectData;
// заменяем данные на свои, утанавливаем свю функцию
connectData->ClassDeviceObject = devExt->Self;
connectData->ClassService = KbFilter_ServiceCallback;
break;
case IOCTL_INTERNAL_KEYBOARD_DISCONNECT:
// не потдерживается
status = STATUS_NOT_IMPLEMENTED;
break;
case
case
case
case
case
case
IOCTL_KEYBOARD_QUERY_ATTRIBUTES:
IOCTL_KEYBOARD_QUERY_INDICATOR_TRANSLATION:
IOCTL_KEYBOARD_QUERY_INDICATORS:
IOCTL_KEYBOARD_SET_INDICATORS:
IOCTL_KEYBOARD_QUERY_TYPEMATIC:
IOCTL_KEYBOARD_SET_TYPEMATIC:
break;
}
if (!NT_SUCCESS(status)) {
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
// передаем запрос дальше по стеку
return FilterPass(DeviceObject, Irp);
}
VOID
KbFilter_ServiceCallback(
IN
IN
IN
IN
)
/*
PDEVICE_OBJECT DeviceObject,
PKEYBOARD_INPUT_DATA InputDataStart,
PKEYBOARD_INPUT_DATA InputDataEnd,
OUT PULONG InputDataConsumed
Функция, которую вызывает i4082ptr после
генерирования прерывания от клавиатуры
Аргументы:
DeviceObject
- указательна объект устройство
InputDataStart
- указатель на первый пакет
InputDataEnd
- указатель на последний пакет
InputDataConsumed - количество обработаных сообщений
Возвращаемое значение:
VOID
*/
{
int jk;
USHORT
PDEVICE_EXTENSION
PKEY_INFO
PKEY_INFO
PCONTROL_DEVICE_EXTENSION
CHAR buf [300];
i,t,NumberOfKeys,ik,r;
devExt;
keyInf;
keyEtalon;
ControlDevExt;
26
// получаем указатель на устройство расширения устройства стека
devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
// получаем указатель на устройство расширения утсройства управления
ControlDevExt=(PCONTROL_DEVICE_EXTENSION)ControlDeviceObject->DeviceExtension;
// вытаскиваем указатель на KEY_INFO
keyInf=devExt->KeyInfo;
// определяем количество структур KEYBOARD_INPUT_DATA
NumberOfKeys=(USHORT)(InputDataEnd-InputDataStart);
// если режим анализ, существует указатель на почерк
if(devExt->KeyInfoTemp) keyEtalon=devExt->KeyInfoTemp;
//по всем PKEYBOARD_INPUT_DATA
for (i=0;i<NumberOfKeys;i++)
{
// предварительная обработка нажатия клавиши
if (InputDataStart[i].Flags==KEY_MAKE){
// определяем интервал от предидущего нажатия
gLastLastUp=KeQueryInterruptTime();
// фильтр на слишком долгие паузы, если вермя между нажатием 2х
клавиш
// более 3 сек, то эту паузу заменяем на паузу в 1 секунду
if ((gLastLastUp-gLastUp)>30000000) gDelay=gDelay+(gLastLastUpgLastUp)-10000000;
gLastUp=gLastLastUp;
// проверка на ввод пароля --------------if
((PasswLock[PasswLock[6]])==(InputDataStart[i].MakeCode)){
PasswLock[6]++;
} else {PasswLock[6]=0;}
if
((PasswUnLock[PasswUnLock[6]])==(InputDataStart[i].MakeCode)){
PasswUnLock[6]++;
} else {PasswUnLock[6]=0;}
if (PasswLock[6]==6){
if(ControlDevExt->MouseLock) {*(ControlDevExt>MouseLock)=TRUE;}
// в режиме обученя пароли не работают
if(ControlDevExt>ModeFlag!=MODE_EDUCATION){ControlDevExt>ModeFlag=MODE_BLOCK;}
}
if (PasswUnLock[6]==6){
if(ControlDevExt->MouseLock) *(ControlDevExt>MouseLock)=FALSE;
// в режиме обученя пароли не работают
if(ControlDevExt>ModeFlag!=MODE_EDUCATION){ControlDevExt>ModeFlag=MODE_ANALYSIS
;}
}
// --------------------------------------}
// если в режиме анализа или обучения просчтываем статистику
if((ControlDevExt->ModeFlag==MODE_ANALYSIS)||(ControlDevExt>ModeFlag==MODE_EDUCATION)){
//по всем клавишам (104 штуки)
for (t=0;t<NUMBER_OF_KEYS;t++)
{
//определяем в контексте какой keyinf будем работать
if ((InputDataStart[i].MakeCode==keyInf[t].MakeCode)){
switch (InputDataStart[i].Flags)
{
case KEY_MAKE:
27
// определяем время последнего нажатия
keyInf[t].LastDown=KeQueryInterruptTime()gDelay;
break;
case KEY_BREAK:
case KEY_E0:
case KEY_E1:
// подсчёт остальных верменных характеристик
AnalizKeyInfo(keyInf,t);
// если режим анализ, и нажато 30 клавиш
if((ControlDevExt>ModeFlag==MODE_ANALYSIS)&&(CountKeyList==NUMBER_OF_C
HEK_KEYS))
{
DbgPrint("BEGIN ALGORITM");
r=0;
for (ik=0;ik<NUMBER_OF_CHEK_KEYS1;ik++)
{
// считаем те интервалы которые попали в границы
эталона
if ((keyEtalon[KeyList[ik]].KeyVector[KeyList[ik+1]]500000<keyInf[KeyList[ik]].KeyVector[KeyList[ik+1]])&&
(keyInf[KeyList[ik]].KeyVector[KeyList[ik+1]]<keyEtalon[KeyList[ik]].KeyVector[KeyList[ik+
1]]+500000))
{r++;}
}
// если почерки не сходятся (ALG_FACTOR=2, совпадений меньше половины)
if (r<((NUMBER_OF_CHEK_KEYS1)/ALG_FACTOR))
{
// блокируем клавиатуру и мыш
ControlDevExt>ModeFlag=MODE_BLOCK;
if(ControlDevExt->MouseLock)
{*(ControlDevExt>MouseLock)=TRUE;}
}
ResetKeyInfo(keyInf);
}
// -------------------------------// если нажато 30 клавиш, заполняем KeyList
по новому
if(CountKeyList==NUMBER_OF_CHEK_KEYS)
{
for (ik=0;ik<NUMBER_OF_CHEK_KEYS;ik++)
{KeyList[ik]=0;}
CountKeyList=0;
}
KeyList[CountKeyList]=InputDataStart[i].MakeCode;
CountKeyList++;
break;
}
}
}
}
}
// если режим блокировки заменяем коды всех клавиш
if(ControlDevExt->ModeFlag==MODE_BLOCK)
{
for (i=0;i<NumberOfKeys;i++)
InputDataStart[i].MakeCode=69;
}
// передаем управление в callback функцию драйвера класса
(*(PSERVICE_CALLBACK_ROUTINE) devExt->UpperConnectData.ClassService)(
devExt->UpperConnectData.ClassDeviceObject,
InputDataStart,
InputDataEnd,
InputDataConsumed);
}
28
VOID AnalizKeyInfo(
PKEY_INFO keyInf,
USHORT t
)
/*
Функция подсчёта временных характеристк
при отжатии клавишши
Аргументы:
keyInf - указатель на структуру почерка
t
- номер клавиши
Возвращаемое значение:
VOID
*/
{
// время последнего отжатия
keyInf[t].LastLastUp=KeQueryInterruptTime()-gDelay;
// последнеие между отжатием и нажатием
keyInf[t].LastFromDownToUp=(ULONG)(keyInf[t].LastLastUp-keyInf[t].LastDown);
// последние между двумя отжатиями
keyInf[t].LastBetweenTwoUp=keyInf[t].LastLastUp-keyInf[t].LastUp;
// перезапоминаем последнее отжатие
keyInf[t].LastUp=keyInf[t].LastLastUp;
// последнее до другой
keyInf[gLastKey].ToOtherBreak=(ULONG)(keyInf[t].LastLastUp-keyInf[gLastKey].LastLastUp);
// если запуск не первый, считаем средние с деление на 2
if(keyInf[t].MeanFromDownToUp!=0){
// среднее между двумя отжатиями
keyInf[t].MeanBetweenTwoUp=(ULONG)((keyInf[t].MeanBetweenTwoUp+
keyInf[t].LastBetweenTwoUp)/2);
// среднеие меду нажатием и отжатием
keyInf[t].MeanFromDownToUp=(ULONG)((keyInf[t].MeanFromDownToUp+
keyInf[t].LastFromDownToUp)/2);
// среднее до другой
keyInf[gLastKey].MeanToOtherBreak=(ULONG)((keyInf[gLastKey].MeanToOtherBreak+
keyInf[gLastKey].ToOtherBreak)/2);
}
// если первый запуск, средние значения считаем без деления на 2:
if(keyInf[t].MeanFromDownToUp==0){
// среднее между двумя отжатиями
keyInf[t].MeanBetweenTwoUp=(ULONG)((keyInf[t].MeanBetweenTwoUp+
keyInf[t].LastBetweenTwoUp));
// среднеие меду нажатием и отжатием
keyInf[t].MeanFromDownToUp=(ULONG)((keyInf[t].MeanFromDownToUp+
keyInf[t].LastFromDownToUp));
// среднее до другой
keyInf[gLastKey].MeanToOtherBreak=(ULONG)((keyInf[gLastKey].MeanToOtherBreak+
keyInf[gLastKey].ToOtherBreak));
}
// первый запуск для вектора межклавишных взаимодействий
if(keyInf[gLastKey].KeyVector[t]==0){
// время нажатия от клавиши gLastKey до t
keyInf[gLastKey].KeyVector[t]= ((keyInf[gLastKey].ToOtherBreak
+keyInf[gLastKey].KeyVector[t]));
}
// не первый запуск для вектора межклавишных взаимодействий
if(keyInf[gLastKey].KeyVector[t]!=0){
// время нажатия от клавиши gLastKey до t
29
keyInf[gLastKey].KeyVector[t]= (ULONG)((keyInf[gLastKey].ToOtherBreak
+keyInf[gLastKey].KeyVector[t])/2);
}
// количество нажатий данной клавишы
keyInf[t].CountUp++;
// запоминаем текущуб клавишу
gLastKey=t;
}
VOID ResetKeyInfo(
PKEY_INFO KeyInfo
)
/*
Функция обнуления структуры KEY_INFO
Аргументы:
keyInf - указатель на структуру KEY_INFO
Возвращаемое значение:
VOID
*/
{
USHORT i,j;
for(i=0;i<NUMBER_OF_KEYS;i++)
{
KeyInfo[i].CountUp=0;
KeyInfo[i].LastBetweenTwoUp=0;
KeyInfo[i].LastDown=0;
KeyInfo[i].LastFromDownToUp=0;
KeyInfo[i].LastUp=0;
KeyInfo[i].LastLastUp=0;
KeyInfo[i].MakeCode=i;
KeyInfo[i].MeanBetweenTwoUp=0;
KeyInfo[i].MeanFromDownToUp=0;
KeyInfo[i].MeanToOtherBreak=0;
KeyInfo[i].ToOtherBreak=0;
for(j=0;j<NUMBER_OF_KEYS;j++)
{
KeyInfo[i].KeyVector[j]=0;
}
}
}
30
#include
#include
#include
#include
#include
#define
#define
#define
#define
#define
#define
"ntddk.h"
"kbdmou.h"
"ntddkbd.h"
"ntdd8042.h"
<stdio.h>
GET_KEY_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
WRITE_FILE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x901, METHOD_BUFFERED, FILE_ANY_ACCESS)
READ_FILE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS)
START_KBD_SCAN CTL_CODE(FILE_DEVICE_UNKNOWN, 0x903, METHOD_BUFFERED, FILE_ANY_ACCESS)
RESET_KEY_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x904, METHOD_BUFFERED, FILE_ANY_ACCESS)
GET_KEY_LIST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x905, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define NUMBER_OF_KEYS 104
#define NUMBER_OF_CHEK_KEYS 30
#define DEFAULT_LOG_FILE_NAME L"\\??\\C:\\keydata.dat"
#define NTDEVICE_NAME_STRING
#define SYMBOLIC_NAME_STRING
L"\\Device\\KbdCtrlFilter"
L"\\DosDevices\\KbdCtrlFilter"
#define FLAG_WRITE 210
#define FLAG_READ 211
#define ALG_FACTOR 2 // чувствительность алгоритма анализа почерка
#ifndef STATUS_CONTINUE_COMPLETION //required to build driver in Win2K and XP build environment
#define STATUS_CONTINUE_COMPLETION
STATUS_SUCCESS
#endif
//задание ярлыка для функции IoInitializeRemoveLock
#define POOL_TAG
'arty'
#define MODE_EDUCATION 100
#define MODE_BLOCK
#define MODE_ANALYSIS 102
101
//Глобальные пнрнманные -----------USHORT PasswLock[7]
375127
USHORT PasswUnLock[7]
248975
ULONGLONG gLastUp
ULONGLONG gLastLastUp
отжатия
ULONGLONG gDelay
={4,8,6,2,3,8,0};
// пароль на блокировки,переход в режим бокировки =
={3,5,9,10,8,6,0};
// пароль на разблокировку,переход в режим анализ =
=0;
=0;
// время последнего отжатия
// время предпоследнего
=0;
// глобальная задержка
31
USHORT
клавиши
gLastKey
=0;
FAST_MUTEX ControlMutex;
устройства управления
ULONG InstanceCount = 0;
PDEVICE_OBJECT ControlDeviceObject;
// номер полседней нажатой
// для понижения IRQL до PASSIVE_LEVEL при создании
// счётчик экземпляров устройств управления
// указатель на объект устройство управления
ULONG KeyList [NUMBER_OF_CHEK_KEYS];
ULONG CountKeyList=0;
//----------------------------------// Структура, описывающая клавиатурный почерк
typedef struct _KEY_INFO{
ULONG
MakeCode;
ULONG
CountUp;
ULONGLONG
LastDown;
ULONGLONG
LastUp;
ULONGLONG
LastLastUp;
ULONG
LastFromDownToUp;
ULONG
MeanFromDownToUp;
ULONGLONG
LastBetweenTwoUp;
ULONG
MeanBetweenTwoUp;
ULONG
ToOtherBreak;
ULONG
MeanToOtherBreak;
ULONG KeyVector[NUMBER_OF_KEYS];
//
//
//
//
//
//
//
//
//
//
//
//
код клавиши
количество отжатий
последнее нажатие
последнее отжатие
предпоследнее отжатие
посленее нажатие-отпуск
среднее нажатие отпуск
последне между двумя отжатиями
среднее между двумя отжатиями
время до другой
среднее время до другой
среднее до определённой
} KEY_INFO,*PKEY_INFO;
typedef enum _DEVICE_PNP_STATE {
NotStarted = 0,
Started,
StopPending,
Stopped,
RemovePending,
SurpriseRemovePending,
Deleted
//
//
//
//
//
//
//
еще не запущено
устройство получило
устройство получило
устройство получило
устройство получило
устройство получило
устройство получило
START_DEVICE IRP
QUERY_STOP IRP
STOP_DEVICE IRP
QUERY_REMOVE IRP
SURPRISE_REMOVE IRP
REMOVE_DEVICE IRP
} DEVICE_PNP_STATE;
#define INITIALIZE_PNP_STATE(_Data_)
\
(_Data_)->DevicePnPState = NotStarted;\
(_Data_)->PreviousPnPState = NotStarted;
#define SET_NEW_PNP_STATE(_Data_, _state_) \
(_Data_)->PreviousPnPState = (_Data_)->DevicePnPState;\
(_Data_)->DevicePnPState = (_state_);
#define RESTORE_PREVIOUS_PNP_STATE(_Data_)
\
(_Data_)->DevicePnPState =
(_Data_)->PreviousPnPState;\
typedef enum _DEVICE_TYPE {
DEVICE_TYPE_INVALID = 0,
DEVICE_TYPE_FIDO,
DEVICE_TYPE_CDO,
} DEVICE_TYPE;
typedef struct _COMMON_DEVICE_DATA
{
DEVICE_TYPE Type;
} COMMON_DEVICE_DATA, *PCOMMON_DEVICE_DATA;
//устройство расширение устройства стека
typedef struct _DEVICE_EXTENSION
{
COMMON_DEVICE_DATA;
PDEVICE_OBJECT
Self;
PDEVICE_OBJECT
NextLowerDriver;
добавлен.
DEVICE_PNP_STATE
DevicePnPState;
DEVICE_PNP_STATE
PreviousPnPState;
IO_REMOVE_LOCK
RemoveLock;
CONNECT_DATA
UpperConnectData;
драйвер
// указатель на сам объект устройство
// вершина стека перед тем как фильтр будет
//
//
//
//
текущее PnP состояние устройства
предидущее PnP состояние устройства
Removelock для защещённой выгрузки
данные на соеденение, которые отправляет
32
PVOID
(and context)
PKEY_INFO
в неё
PKEY_INFO
UpperContext;
// Previous initialization and hook routines
KeyInfo;
// указатель на структуру почерка для записи
KeyInfoTemp;
// указатель на структуру почерка эталона
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
//устройство расширение устройства управления
typedef struct _CONTROL_DEVICE_EXTENSION {
COMMON_DEVICE_DATA;
ULONG
PVOID
PDEVICE_OBJECT
Deleted;
ControlData;
StackObject;
// TRUE если устройство удалено
// Store your control data here
// объеект устройство подключенное к
PULONG
MouseLock;
// указатель на флаг блокировки мыши в
// устройстве расширения устройства
стеку
управления мыши
ULONG ModeFlag;
данный момент
//в каком режиме работает драйвер в
} CONTROL_DEVICE_EXTENSION, *PCONTROL_DEVICE_EXTENSION;
//устройство расширение устройства управления мыши
typedef struct _CONTROL_MOUSE_DEVICE_EXTENSION {
COMMON_DEVICE_DATA;
ULONG
PVOID
NTSTATUS
ULONG
Deleted;
ControlData;
status;
MouseLock;
//
//
//
//
TRUE если устройство удалено
для сохранения ControlData
NTSTATUS
если TRUE мышь блоктрована
} CONTROL_MOUSE_DEVICE_EXTENSION, *PCONTROL_MOUSE_DEVICE_EXTENSION;
//Объявленя функций \/\/\/\/\/\/\/\/\/\/\/\/\/\/
PCHAR
PnPMinorFunctionString (
UCHAR MinorFunction
);
NTSTATUS
FilterAddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
);
NTSTATUS
FilterDispatchPnp (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
NTSTATUS
FilterDispatchPower(
IN PDEVICE_OBJECT
IN PIRP
);
DeviceObject,
Irp
VOID
FilterUnload(
IN PDRIVER_OBJECT DriverObject
);
NTSTATUS
FilterPass (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
);
NTSTATUS
FilterStartCompletionRoutine(
IN PDEVICE_OBJECT
DeviceObject,
33
IN PIRP
IN PVOID
);
Irp,
Context
NTSTATUS
FilterDeviceUsageNotificationCompletionRoutine(
IN PDEVICE_OBJECT
DeviceObject,
IN PIRP
Irp,
IN PVOID
Context
);
NTSTATUS
FilterInternIoCtl (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
);
VOID
KbFilter_ServiceCallback(
IN
IN
IN
IN
);
PDEVICE_OBJECT DeviceObject,
PKEYBOARD_INPUT_DATA InputDataStart,
PKEYBOARD_INPUT_DATA InputDataEnd,
OUT PULONG InputDataConsumed
NTSTATUS
FilterCreateControlObject(
IN PDEVICE_OBJECT
);
DeviceObject
VOID
FilterDeleteControlObject(
);
NTSTATUS
FilterDispatchIo(
IN PDEVICE_OBJECT
IN PIRP
);
DeviceObject,
Irp
NTSTATUS
ReadWriteKeyInfoToFile(
IN PKEY_INFO
ULONG
);
keyinfo,
flag
VOID
ResetKeyInfo(
PKEY_INFO KeyInfo
);
VOID
AnalizKeyInfo(
PKEY_INFO KeyInfo,
USHORT t
);
34
5.2 Управляющее приложение
#include "ControlApp.h"
int
WINAPI WinMain (
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow
)
/*
Точка входа программы
Аргументы:
hInstance
- описателем экземпляра
hPrevInstanceIrp - предыдущий экземпляр
szCmdLine
- указатель на командную
строку
iCmdShow
- начальный вид
Возвращаемое значение:
int
*/
{
static char
HWND
MSG
WNDCLASSEX
szAppName[] = "HexCalc" ;
hwnd ;
msg ;
wndclass ;
// Регистрация класса окна
wndclass.cbSize
= sizeof (wndclass) ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc
= WndProc ;
wndclass.cbClsExtra
= 0 ;
wndclass.cbWndExtra
= DLGWINDOWEXTRA ;
wndclass.hInstance
= hInstance ;
wndclass.hIcon
= LoadIcon (hInstance, szAppName) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 0) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm
= LoadIcon (hInstance, szAppName) ;
RegisterClassEx (&wndclass) ;
// создание окна
hwnd = CreateDialog (hInstance, szAppName, 0, NULL) ;
hinst=hInstance;
ShowWindow (hwnd, iCmdShow) ;
// цикл обработки сообщений
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
35
return msg.wParam ;
}
LRESULT
CALLBACK WndProc (
HWND hwnd,
UINT iMsg,
WPARAM wParam,
LPARAM lParam
)
/*
Точка входа программы
Аргументы:
hwnd
- описатель окна
iMsg
- принятое сообщение
wParam - параметр сообщения
wParam - параметр сообщения
Возвращаемое значение:
LRESULT
*/
{
DWORD bytesReturned;
DWORD Code, i;
int count,j,temp;
char buf[100];
float p,pcount;
char bufer[2000];
switch (iMsg)
{
case WM_CREATE:
keyinfo=(PKEY_INFO)malloc(104*sizeof(KEY_INFO));
KeyList=(PULONG)malloc(NUMBER_OF_CHEK_KEYS*sizeof(ULONG));
hDevice = CreateFile( "\\\\.\\KbdCtrlFilter",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
return 0;
case WM_COMMAND :
// нажата кнопка на нарисованой клавиатуре
Code=(DWORD)(LOWORD (wParam))-999;
if (Code<94 )
{
sprintf(buf,"%d",Code);
SetDlgItemText(hwnd,IDC_EDIT1,buf);
sprintf(buf,"%d",keyinfo[Code].CountUp);
SetDlgItemText(hwnd,IDC_EDIT2,buf);
sprintf(buf,"%I64d",keyinfo[Code].LastDown);
SetDlgItemText(hwnd,IDC_EDIT3,buf);
36
sprintf(buf,"%I64d",keyinfo[Code].LastUp);
SetDlgItemText(hwnd,IDC_EDIT4,buf);
sprintf(buf,"%d",keyinfo[Code].LastFromDownToUp);
SetDlgItemText(hwnd,IDC_EDIT5,buf);
sprintf(buf,"%d",keyinfo[Code].MeanFromDownToUp);
SetDlgItemText(hwnd,IDC_EDIT7,buf);
sprintf(buf,"%I64d",keyinfo[Code].LastBetweenTwoUp);
SetDlgItemText(hwnd,IDC_EDIT8,buf);
sprintf(buf,"%d",keyinfo[Code].MeanBetweenTwoUp);
SetDlgItemText(hwnd,IDC_EDIT9,buf);
sprintf(buf,"%d",keyinfo[Code].ToOtherBreak);
SetDlgItemText(hwnd,IDC_EDIT12,buf);
sprintf(buf,"%d",keyinfo[Code].MeanToOtherBreak);
SetDlgItemText(hwnd,IDC_EDIT13,buf);
pcount =0;
for (i=0;i<104;i++)
{pcount+=keyinfo[i].CountUp; }
p=(double)(keyinfo[Code].CountUp/pcount);
sprintf(buf,"%f",p);
SetDlgItemText(hwnd,IDC_EDIT10,buf);
j=0;
for (i=0;i<NUMBER_OF_KEYS;i++)j+=sprintf( bufer+j,"%d%c",
keyinfo[Code].KeyVector[i],' ');
SetDlgItemText(hwnd,IDC_EDIT14,bufer);
}
// нажата кнопка получить KeyList
if (LOWORD (wParam)==IDC_GET_KEY_LIST)
{
DeviceIoControl(hDevice,
GET_KEY_LIST,0,0,KeyList,
NUMBER_OF_CHEK_KEYS*sizeof(ULONG),
&bytesReturned,NULL);
j=0;temp=0;
for (i=0;i<NUMBER_OF_CHEK_KEYS;i++)
{
if (KeyList[i]==0) {temp++;}
j+=sprintf( bufer+j,"%d%c", KeyList[i],' ');
}
SetDlgItemText(hwnd,IDC_EDIT15,bufer);
j=0; //if (temp==0){temp=1;}
for (i=0;i<NUMBER_OF_CHEK_KEYS-temp-1;i++)
j+=sprintf( bufer+j,"%d%c",
keyinfo[KeyList[i]].KeyVector[KeyList[i+1]],' ');
SetDlgItemText(hwnd,IDC_EDIT16,bufer);
}
// нажата кнопка пуск
if (LOWORD (wParam)==IDC_PUSK)
{
DeviceIoControl(hDevice, START_KBD_SCAN,0,0,0,
0,&bytesReturned,NULL);
37
}
// нажата кнопка сбросить все показатели
if (LOWORD (wParam)==IDC_RESET)
{
DeviceIoControl(hDevice,RESET_KEY_INFO,0,0,0,
0,&bytesReturned,NULL);
}
// нажата кнопка сохраниьт все показатели
if (LOWORD (wParam)==IDC_SAVE)
{
DeviceIoControl(hDevice,WRITE_FILE,0,0,0,
0,&bytesReturned,NULL);
}
// нажата кнопка обнавить данные
if (LOWORD (wParam)==IDC_UPDATE)
{
DeviceIoControl(hDevice,
GET_KEY_INFO,0,0,keyinfo,
NUMBER_OF_KEYS*sizeof(KEY_INFO),
&bytesReturned,NULL);
count =0;
for (i=0;i<NUMBER_OF_KEYS;i++)
{count+=keyinfo[i].CountUp; }
sprintf(buf,"%d",count);
SetDlgItemText(hwnd,IDC_EDIT6,buf);
}
if (LOWORD (wParam)==IDC_ABOUT)
{
DialogBox (hinst, (LPCTSTR)IDD_ABOUT, hwnd, AboutDlgProc);
}
return 0;
case WM_DESTROY :
free(keyinfo);
free(KeyList);
CloseHandle(hDevice);
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
BOOL
CALLBACK AboutDlgProc (
HWND hDlg,
UINT iMsg,
WPARAM wParam,
LPARAM lParam
)
/*
Точка входа программы
Аргументы:
hInstance
- описателем экземпляра
hPrevInstanceIrp - предыдущий экземпляр
szCmdLine
- указатель на командную строку
iCmdShow
- начальный вид
Возвращаемое значение:
38
BOOL
*/
{
switch (iMsg)
{
case WM_COMMAND :
switch (LOWORD (wParam))
{
case IDOK :
EndDialog (hDlg, 0) ;
return TRUE ;
}
break ;
}
return FALSE ;
}
39
ControlApp.h
#include
#include
#include
#include
#include
#include
<windows.h>
<limits.h>
<stdlib.h>
<string.h>
<ctype.h>
"resource.h"
#define NUMBER_OF_KEYS 104
#define NUMBER_OF_CHEK_KEYS 30
typedef struct _KEY_INFO{
ULONG
MakeCode;
ULONG
CountUp;
ULONGLONG
LastDown;
ULONGLONG
LastUp;
ULONGLONG
LastLastUp;
ULONG
LastFromDownToUp;
ULONG
MeanFromDownToUp;
ULONGLONG
LastBetweenTwoUp;
ULONG
MeanBetweenTwoUp;
ULONG
ToOtherBreak;
ULONG
MeanToOtherBreak;
ULONG KeyVector[NUMBER_OF_KEYS];
} KEY_INFO,*PKEY_INFO;
#define
#define
#define
#define
#define
#define
#define
GET_KEY_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)
WRITE_FILE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x901, METHOD_BUFFERED, FILE_ANY_ACCESS)
READ_FILE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS)
START_KBD_SCAN CTL_CODE(FILE_DEVICE_UNKNOWN, 0x903, METHOD_BUFFERED, FILE_ANY_ACCESS)
RESET_KEY_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x904, METHOD_BUFFERED, FILE_ANY_ACCESS)
GET_KEY_LIST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x905, METHOD_BUFFERED, FILE_ANY_ACCESS)
ID_TIMER 1
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK AboutDlgProc (HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam);
PKEY_INFO keyinfo;
HANDLE hDevice;
PLONG KeyList ;
HINSTANCE hinst;
40
5.2 Драйвер фильтр мыши
Код аналогичен клавиатурному фильтру, интерес составляет только
MouFilter_ServiceCallback
VOID
MouFilter_ServiceCallback(
IN
IN
IN
IN
)
PDEVICE_OBJECT DeviceObject,
PMOUSE_INPUT_DATA InputDataStart,
PMOUSE_INPUT_DATA InputDataEnd,
OUT PULONG InputDataConsumed
{
USHORT i,num;
PDEVICE_EXTENSION
devExt;
PCONTROL_MOUSE_DEVICE_EXTENSION MouCtrlDevExt;
devExt = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
MouCtrlDevExt=(PCONTROL_MOUSE_DEVICE_EXTENSION)ControlDeviceObject>DeviceExtension;
num=(USHORT)(InputDataEnd-InputDataStart);
if(!MouCtrlDevExt->MouseLock)
{
(*(PSERVICE_CALLBACK_ROUTINE) devExt>UpperConnectData.ClassService)(
devExt->UpperConnectData.ClassDeviceObject,
InputDataStart,
InputDataEnd,
InputDataConsumed
);
}
}
41
6. ЛИТЕРАТУРА
1. Петзолд Ч. Программирование для Windows 95 в двух томах. «BHV — СанктПетербург», 2003 г.
2. Рихтер Д. Windows для профессионалов: создание эффективных Win32
приложений с учетом специфики 64-разрядной версии Windows/Пер, англ - 4-е изд.
- СПб; Питер; М.: Издательско-торговый дом "Русская Редакция", 2001. - 752 с.; ил.
3. Румянцев П.В. Азбука программирования в WIN32 API
4. Сорокина С.И. Программирование драйверов и систем безопасности: Учеб.
Пособие – Спб.: БХВ – Петербург, 2003. – 256 с.:ил.
5. Соломон Д. и Руссинович М.Внутреннее устройство Microsoft Windows 2000.
Мастер-класс / Пер.с англ. — СПб.: Питер; М.: Издательско-торговый дом
«Русская Редакция». 2004. — 746 стр.: ил.
6. Солдатов В.П. Программирование драйверов Windows. Изд. 2-е, перераб. и доп. М.:
ООО «Бином-Пресс», 2004 г.
7. Савинков А.Ю. Лекционный материал по курсу «Операционные системы»
8. Таненбаум Э. Современные операционные системы. 2-е изд. – СПб.: Питер, 2002. –
1040 с.:ил.
9. Фролов А.В., Фролов Г.В. Программирование для Windows NT части 1,2. 1996 г.
10. http://www.wasm.ru/article.php?article=drvw2k16
11. MSDN Library, Copyright 1987-2005 Microsoft Corporation
12. Driver Development Kit (DDK) documentation, built on Tuesday, February 22, 2005 г.
42
Download