Создание драйвера клавиатуры с нуля: от теории к рабочему коду

Если ваша клавиатура не распознаётся системой как HID-устройство (Human Interface Device) или требует нестандартной обработки нажатий — например, для игровых макросов, мультимедийных кнопок или кастомных раскладок — стандартные драйверы Windows/Linux не подойдут. В 90% таких случаев проблема решается написанием собственного драйвера, который перехватывает IRP-пакеты (I/O Request Packet) или эмулирует ввод через виртуальное устройство. Эта статья покрывает весь цикл: от анализа USB-дескрипторов клавиатуры до компиляции signed-драйвера для 64-битных ОС.

Мы не будем рассматривать тривиальные случаи вроде смены раскладки через SetWindowsHookEx или перехвата клавиш на уровне пользовательского пространства (user-mode). Речь пойдёт о ядре ОС: написании Kernel-Mode Driver (KMDF) для Windows или модуля ядра (input.ko) для Linux. Примером актуальной задачи может служить поддержка клавиатур с нестандартными Usage Page (например, Elgato Stream Deck в режиме клавиатуры) или восстановление работы устройства после обновления ОС, "сломавшего" родные драйверы.

1. Когда нужен кастомный драйвер клавиатуры

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

  • 🔌 Клавиатура не определяется системой (отсутствует в Диспетчере устройств или определяется как "Неизвестное устройство"). Причина — некорректные VID/PID или отсутствие стандартного HID-дескриптора.
  • 🎮 Требуется переопределение скан-кодов на уровне ядра (например, для игровых клавиатур с макросами, где стандартные утилиты вроде AutoHotkey не подходят из-за задержек).
  • 🔒 Нужна защита от кейлоггеров путём виртуализации ввода (драйвер создаёт виртуальную клавиатуру, а физическая отключается от системы).

Во всех остальных случаях (переназначение клавиш, макросы, подсветка) хватит пользовательских утилит. Например, для Windows подойдёт SharpKeys или PowerToys, для Linuxxmodmap или evdev.

⚠️ Внимание: Драйвер ядра — это код, выполняемый с максимальными привилегиями. Ошибка в нём может привести к BSOD (Windows) или kernel panic (Linux). Все тесты проводите на виртуальной машине или резервном ПК.
📊 Для какой ОС вы планируете писать драйвер?
Windows 10/11
Linux (Ubuntu/Debian)
macOS
Другая ОС

2. Архитектура драйвера клавиатуры: ключевые компоненты

Драйвер клавиатуры взаимодействует с тремя слоями системы:

  1. Аппаратный уровень: считывание данных с USB/HID-устройства через USB-стек (в Windows — UCX, в Linux — usbcore).
  2. Ядро ОС: обработка прерываний (IRQ) и формирование событий ввода (в Windows — Kbdclass, в Linux — input subsystem).
  3. Пользовательский уровень: передача данных приложениям через Win32 API (ReadFile, WriteFile) или /dev/input/event* в Linux.

Структура минимального драйвера для Windows (KMDF) включает:

  • 📜 Инф-файл (.inf) — описывает параметры установки и совместимость с устройствами.
  • 🖥️ DriverEntry — точка входа, где регистрируются callback-функции для обработки IRP_MJ_READ/IRP_MJ_WRITE.
  • 🔌 EvtDeviceAdd — вызывается при подключении устройства, здесь настраивается HID-минипорта.
  • 🔄 EvtInterruptDpc — обработчик прерываний от клавиатуры (декодирует скан-коды).
КомпонентWindows (KMDF)Linux (модуль ядра)
Точка входаDriverEntrymodule_init
Обработка прерыванийEvtInterruptDpcirq_handler_t
Регистрация устройстваWdfDeviceCreateinput_register_device
Чтение данныхWdfRequestSendinput_event
ОтладкаDbgPrint + WinDbgprintk + dmesg

3. Подготовка среды разработки

Минимальные требования для начала работы:

  • 🪟 Для Windows:
    • Visual Studio 2022 с компонентом Desktop development with C++.
    • Windows Driver Kit (WDK) для вашей версии ОС (скачивается через Visual Studio Installer).
    • Виртуальная машина с Windows 10/11 (для тестов).
    • Сертификат для подписи драйвера (можно сгенерировать тестовый через MakeCert).
  • 🐧 Для Linux:
    • Ядро версии 5.x+ (проверьте через uname -r).
    • Заголовочные файлы ядра (linux-headers-$(uname -r)).
    • Утилиты make, gcc, dkms.
    • Права root для загрузки модуля (insmod).

Для анализа трафика клавиатуры понадобятся:

  • USBlyzer (Windows) или Wireshark с фильтром usb.
  • HIDDescriptorTool (для парсинга дескрипторов).
  • USBView (просмотр топологии USB-устройств).
⚠️ Внимание: В Windows x64 все драйверы должны быть подписаны доверенным сертификатом. Для тестов можно отключить проверку подписи через bcdedit /set testsigning on, но это сделает систему уязвимой для вредоносного ПО.

Установить Visual Studio + WDK (Windows) или заголовки ядра (Linux)|Скачать утилиты для анализа USB-трафика (USBlyzer, Wireshark)|Создать виртуальную машину для тестов|Подготовить тестовый сертификат для подписи (Windows)|Получить дамп дескрипторов вашей клавиатуры-->

4. Анализ HID-дескрипторов клавиатуры

Любая клавиатура, подключённая по USB, передаёт данные в формате HID-протокола. Чтобы драйвер корректно её распознал, нужно:

  1. Получить дескрипторы устройства:
    # Linux
    

    lsusb -v -d VID:PID | grep -A 20 "Config Descriptor"

    Windows (через USBView)

    Откройте устройство → вкладка "Descriptors"

  2. Найти HID Descriptor (обычно имеет тип 0x21). В нём указаны:
    • bcdHID — версия протокола (например, 1.11).
    • bCountryCode — код страны (важен для раскладок).
    • bNumDescriptors — количество дополнительных дескрипторов.
  • Распарсить Report Descriptor — он описывает формат пакетов данных (какие байты отвечают за модификаторы, скан-коды и т.д.).
  • Пример Report Descriptor для стандартной клавиатуры:

    05 01        // Usage Page (Generic Desktop)
    

    09 06 // Usage (Keyboard)

    A1 01 // Collection (Application)

    05 07 // Usage Page (Key Codes)

    19 E0 // Usage Minimum (Left Control)

    19 E7 // Usage Maximum (Right GUI)

    15 00 // Logical Minimum (0)

    25 01 // Logical Maximum (1)

    75 01 // Report Size (1 bit)

    95 08 // Report Count (8 bits)

    81 02 // Input (Data, Variable, Absolute)

    ...

    Если ваша клавиатура использует нестандартные Usage Page (например, 0xFF00 для мультимедийных кнопок), их придётся обрабатывать вручную в драйвере.

    Пример дескриптора для клавиатуры с мультимедийными кнопками

    05 0C // Usage Page (Consumer)

    09 01 // Usage (Consumer Control)

    A1 01 // Collection (Application)

    05 0C // Usage Page (Consumer)

    09 CD // Usage (Play/Pause)

    09 B5 // Usage (Scan Next Track)

    09 B6 // Usage (Scan Previous Track)

    15 00 // Logical Minimum (0)

    25 01 // Logical Maximum (1)

    75 01 // Report Size (1)

    95 03 // Report Count (3)

    81 02 // Input (Data, Variable, Absolute)

    C0 // End Collection

    5. Пишем драйвер для Windows (KMDF)

    Рассмотрим минимальный рабочий пример драйвера, который перехватывает нажатия клавиш и логирует их в Debug View.

    Шаг 1: Создание проекта

    В Visual Studio выберите File → New → Project → Kernel Mode Driver (KMDF). В настройках проекта укажите:

    • Target OS: Windows 10 (или ваша версия).
    • Driver Type: Function Driver.
    • Use WDF: Yes (Windows Driver Framework).

    Шаг 2: Код драйвера

    Основные файлы:

    • Driver.c — точка входа и обработчики событий.
    • Driver.h — заголовки и структуры.
    • YourKeyboard.inf — инф-файл для установки.

    Пример кода для обработки прерывания от клавиатуры:

    NTSTATUS
    

    EvtInterruptDpc(

    _In_ WDFINTERRUPT Interrupt,

    _In_ WDFDEVICE Device

    )

    {

    UCHAR buffer[8] = {0};

    WDF_REQUEST_READ Options;

    WDF_REQUEST_READ_BUFFER(Options, buffer, sizeof(buffer));

    // Читаем данные из устройства

    WdfRequestSend(WdfInterruptGetRequest(Interrupt), WdfDeviceGetIoTarget(Device), &Options);

    // Логируем скан-код (например, первый байт — модификаторы, второй — ключ)

    DbgPrint("Keyboard scan code: 0x%02X\n", buffer[2]);

    return STATUS_SUCCESS;

    }

    Шаг 3: Компиляция и установка

    После сборки (Ctrl+Shift+B) получите файлы:

    • YourKeyboard.sys — сам драйвер.
    • YourKeyboard.inf — инф-файл.
    • YourKeyboard.cat — каталог безопасности.

    Для установки:

    1. Подключите клавиатуру к тестовой машине.
    2. Откройте Диспетчер устройств, найдите клавиатуру (возможно, в разделе "Другие устройства").
    3. Обновите драйвер вручную, указав путь к .inf-файлу.
    4. Проверьте логи в DebugView (фильтр по Keyboard scan code).

    6. Пишем драйвер для Linux

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

    Шаг 1: Создание заготовки модуля

    Сгенерируйте шаблон:

    # Создайте файл mykeyboard.c
    

    #include

    #include

    #include

    static struct input_dev *my_keyboard;

    static int __init mykeyboard_init(void)

    {

    // Регистрация виртуального устройства

    my_keyboard = input_allocate_device();

    if (!my_keyboard)

    return -ENOMEM;

    // Настройка свойств

    my_keyboard->name = "My Virtual Keyboard";

    my_keyboard->id.bustype = BUS_USB;

    my_keyboard->id.vendor = 0x1234;

    my_keyboard->id.product = 0x5678;

    // Установка поддерживаемых событий

    __set_bit(EV_KEY, my_keyboard->evbit);

    __set_bit(KEY_A, my_keyboard->keybit); // Пример: клавиша 'A'

    // Регистрация в системе

    if (input_register_device(my_keyboard)) {

    input_free_device(my_keyboard);

    return -EFAULT;

    }

    printk(KERN_INFO "Virtual keyboard registered\n");

    return 0;

    }

    static void __exit mykeyboard_exit(void)

    {

    input_unregister_device(my_keyboard);

    printk(KERN_INFO "Virtual keyboard unregistered\n");

    }

    module_init(mykeyboard_init);

    module_exit(mykeyboard_exit);

    MODULE_LICENSE("GPL");

    Шаг 2: Компиляция и загрузка

    Соберите модуль:

    make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
    

    sudo insmod mykeyboard.ko

    Проверьте появление устройства:

    ls /dev/input/by-id/ | grep "My Virtual Keyboard"
    

    cat /proc/bus/input/devices

    Шаг 3: Эмуляция нажатий

    Чтобы сэгмулировать нажатие клавиши KEY_A:

    input_report_key(my_keyboard, KEY_A, 1); // Нажатие
    

    input_sync(my_keyboard);

    input_report_key(my_keyboard, KEY_A, 0); // Отпускание

    input_sync(my_keyboard);

    Для взаимодействия с физической клавиатурой потребуется работать с usb_hid или hidraw. Пример чтения данных:

    int fd = open("/dev/hidraw0", O_RDONLY);
    

    unsigned char buf[8];

    read(fd, buf, sizeof(buf));

    // buf[0] — модификаторы, buf[2] — скан-код

    Как найти hidraw-устройство для клавиатуры

    1. Подключите клавиатуру.

    2. Выполните ls /dev/hidraw* до и после подключения.

    3. Проверьте права доступа: sudo chmod 666 /dev/hidrawX.

    7. Типичные ошибки и их решения

    Даже опытные разработчики сталкиваются с проблемами при написании драйверов. Вот самые распространённые:

    ОшибкаПричинаРешение
    Драйвер не загружается (STATUS_INVALID_IMAGE_FORMAT) Несовместимость разрядности (x86 vs x64) или отсутствие подписи. Соберите драйвер для правильной архитектуры. Подпишите его или отключите проверку (bcdedit /set nointegritychecks on).
    Клавиатура не реагирует на нажатия Неверно обработан Report Descriptor или не настроены evbit/keybit. Проверьте дескрипторы через HIDDescriptorTool. Убедитесь, что скан-коды сопоставлены правильно.
    BSOD с кодом IRQL_NOT_LESS_OR_EQUAL Доступ к памяти с неправильным уровнем IRQL (например, вызов ExAllocatePool в обработчике прерывания). Используйте WdfMemoryCreate с флагом NonPagedPoolNx. Перенесите тяжелые операции в Work Item.
    Драйвер загружается, но устройство не появляется в системе Неверные VID/PID в .inf-файле или ошибка в EvtDeviceAdd. Проверьте логи установки (setupapi.dev.log в Windows). Используйте USBView для сравнения VID/PID.
    В Linux модуль не компилируется (unknown type name ‘input_dev’) Отсутствуют заголовки ядра или неверная версия. Установите linux-headers-$(uname -r). Проверьте совместимость версии ядра и модуля.
    ⚠️ Внимание: Если драйвер вызывает BSOD в Windows, система может заблокировать его загрузку. Чтобы разблокировать, загрузитесь в Safe Mode и удалите драйвер через pnputil /delete-driver.

    8. Тестирование и отладка

    Отладка драйверов требует специальных инструментов. Вот минимальный набор:

    • 🪲 Windows:
      • WinDbg + Kernel Debugging (подключение по COM или USB).
      • DebugView для просмотра выводов DbgPrint.
      • Driver Verifier (verifier.exe) — выявляет утечки памяти и race conditions.
    • 🐧 Linux:
      • dmesg | tail — логи ядра.
      • strace для отслеживания системных вызовов.
      • ftrace — трассировка функций ядра.

    Пример отладки через WinDbg:

    1. Подключите две машины: hostWinDbg) и target (тестовая).
    2. На target выполните:
      bcdedit /debug on
      

      bcdedit /dbgsettings net hostip:192.168.1.100 port:50000 key:1.2.3.4

    3. На host запустите WinDbg и подключитесь:
      File → Kernel Debug → Net
      

      IP: 192.168.1.100, Port: 50000

    4. Установите точку останова на вашу функцию:
      bp YourDriver!EvtInterruptDpc

    Для тестирования клавиатуры в Linux используйте:

    # Просмотр событий ввода
    

    sudo evtest /dev/input/eventX

    Тест виртуальной клавиатуры

    sudo input-events 0 # Нажмите клавишу на виртуальном устройстве

    FAQ: Частые вопросы по написанию драйверов клавиатуры

    Можно ли написать драйвер клавиатуры на Python?

    Нет. Драйверы ядра требуют низкоуровневого доступа к оборудованию, что возможно только на C/C++ (Windows) или C (Linux). Однако вы можете написать пользовательскую утилиту на Python, которая взаимодействует с драйвером через IOCTL (Windows) или /dev/input/event* (Linux).

    Как обойти проверку подписи драйвера в Windows x64?

    Есть три варианта:

    1. Отключить проверку подписи (bcdedit /set testsigning on). Небезопасно!
    2. Купить сертификат EV Code Signing (от ~$200/год).
    3. Использовать DSE (Driver Signature Enforcement) Override (работает только на некоторых версиях Windows 10).

    Для тестов лучше использовать виртуальную машину с отключённой проверкой.

    Можно ли переназначить клавиши без написания драйвера?

    Да, в большинстве случаев. Используйте:

    • 🪟 Windows: AutoHotkey, SharpKeys, PowerToys Keyboard Manager.
    • 🐧 Linux: xmodmap, setkeycodes, evdev.
    • 🍎 macOS: Karabiner-Elements.

    Драйвер нужен только если требуется работа на уровне ядра (например, для античитов в играх).

    Как узнать VID и PID своей клавиатуры?

    Способы для разных ОС:

    • 🪟 Windows:
      1. Откройте Диспетчер устройств.
      2. Найдите клавиатуру → Свойства → Сведения → ИД оборудования.
      3. Формат: VID_XXXX&PID_XXXX.
  • 🐧 Linux:
    lsusb | grep -i keyboard
    

    или

    dmesg | grep -i usb

  • Где взять исходники готовых драйверов для примеров?

    Рекомендуемые репозитории: