Legacy Support Team
Technique

Explorer l'État des Touches d'un Clavier USB : Une Aventure Linux Bas Niveau

7 janv. 202512 min de lecture
Clavier USB et terminal Linux

Dumper l'état de toutes les touches sur un clavier USB : Une aventure Linux de bas niveau

Vous êtes-vous déjà demandé ce qui se passe sous le capot lorsque vous appuyez sur une touche de votre clavier USB ? Et si vous pouviez jeter un œil aux données brutes envoyées par le clavier à votre ordinateur ? Dans cet article de blog, nous allons plonger dans le monde des protocoles USB HID (Human Interface Device) et écrire un programme Linux de bas niveau pour dumper l'état actuel de toutes les touches d'un clavier USB. Pas d'abstractions de haut niveau—juste un accès brut et non filtré au matériel.


Le problème : Lire l'état du clavier sans événements

Lorsque vous appuyez sur une touche de votre clavier, le système d'exploitation la traite comme un événement. Ces événements sont pratiques pour la plupart des applications, mais que faire si vous voulez connaître l'état actuel de toutes les touches—pas seulement celles qui ont déclenché des événements ? Par exemple :

  • Quelles touches sont actuellement enfoncées ?
  • Quel est l'état des touches de modification (Shift, Ctrl, Alt) ?
  • Et les états des LED (Caps Lock, Num Lock, Scroll Lock) ?

C'est un défi courant pour :

  • Les chercheurs en sécurité analysant les entrées clavier.
  • Les développeurs embarqués déboguant des périphériques USB.
  • Les hackers curieux qui veulent comprendre comment fonctionnent les claviers USB.

Mais il y a un piège : Si une touche a été enfoncée alors que le système était éteint, le noyau Linux ne générera aucun événement à moins qu'une autre touche ne soit enfoncée. Cela signifie que vous ne pouvez pas compter sur le sous-système d'entrée du noyau pour détecter les touches qui ont été enfoncées avant le démarrage du système. Pour résoudre ce problème, nous devons contourner le sous-système d'entrée de haut niveau et interagir directement avec le clavier USB au niveau le plus bas possible.


La solution : Utiliser `libusb` pour interroger le clavier

Nous allons utiliser la bibliothèque libusb pour interagir directement avec le clavier USB. Voici ce que nous allons faire :

  1. Énumérer tous les périphériques USB pour trouver les claviers.
  2. Identifier les interfaces HID sur ces périphériques.
  3. Envoyer une requête HID GET_REPORT pour récupérer le rapport d'entrée actuel (état des touches).
  4. Décoder le rapport d'entrée pour déterminer quelles touches sont enfoncées.

Le code : Dumper l'état des touches

Voici le programme en C qui fait tout le travail. Il utilise libusb pour interagir avec les périphériques USB et récupère le rapport d'entrée pour tous les claviers connectés.

#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>

// Requête HID GET_REPORT
#define HID_GET_REPORT 0x01
#define HID_REPORT_TYPE_INPUT 0x01

// Fonction pour vérifier si un périphérique est un clavier HID
int is_hid_keyboard(libusb_device *device) {
    struct libusb_device_descriptor desc;
    int ret = libusb_get_device_descriptor(device, &desc);
    if (ret < 0) {
        fprintf(stderr, "Échec de la récupération du descripteur de périphérique\n");
        return 0;
    }

    // Vérifier si le périphérique est un périphérique HID
    if (desc.bDeviceClass == LIBUSB_CLASS_PER_INTERFACE) {
        struct libusb_config_descriptor *config;
        ret = libusb_get_config_descriptor(device, 0, &config);
        if (ret < 0) {
            fprintf(stderr, "Échec de la récupération du descripteur de configuration\n");
            return 0;
        }
    
        for (int i = 0; i < config->bNumInterfaces; i++) {
            const struct libusb_interface *interface = &config->interface[i];
            for (int j = 0; j < interface->num_altsetting; j++) {
                const struct libusb_interface_descriptor *iface_desc = &interface->altsetting[j];
                if (iface_desc->bInterfaceClass == LIBUSB_CLASS_HID) {
                    libusb_free_config_descriptor(config);
                    return 1; // C'est un périphérique HID
                }
            }
        }
    
        libusb_free_config_descriptor(config);
    }
    
    return 0; // Ce n'est pas un périphérique HID
}

// Fonction pour obtenir le rapport d'entrée d'un clavier HID
void get_input_report(libusb_device_handle *handle) {
    unsigned char input_report[8]; // La plupart des claviers utilisent des rapports d'entrée de 8 octets
    int ret = libusb_control_transfer(
        handle,
        LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
        HID_GET_REPORT,
        (HID_REPORT_TYPE_INPUT << 8) | 0x00, // Type de rapport (Entrée) et ID de rapport (0)
        0, // Interface
        input_report,
        sizeof(input_report),
        1000 // Délai d'attente en millisecondes
    );

    if (ret < 0) {
        fprintf(stderr, "Échec de la récupération du rapport d'entrée : %s\n", libusb_error_name(ret));
    } else {
        printf("Rapport d'entrée :\n");
        for (int i = 0; i < ret; i++) {
            printf("%02x ", input_report[i]);
        }
        printf("\n");
    }
}

int main() {
    libusb_device **devices;
    ssize_t count;
    int ret;

    // Initialiser libusb
    ret = libusb_init(NULL);
    if (ret < 0) {
        fprintf(stderr, "Échec de l'initialisation de libusb : %s\n", libusb_error_name(ret));
        return 1;
    }
    
    // Obtenir la liste des périphériques USB
    count = libusb_get_device_list(NULL, &devices);
    if (count < 0) {
        fprintf(stderr, "Échec de l'obtention de la liste des périphériques : %s\n", libusb_error_name((int)count));
        libusb_exit(NULL);
        return 1;
    }
    
    // Parcourir tous les périphériques
    for (ssize_t i = 0; i < count; i++) {
        libusb_device *device = devices[i];
    
        // Vérifier si le périphérique est un clavier HID
        if (is_hid_keyboard(device)) {
            struct libusb_device_descriptor desc;
            ret = libusb_get_device_descriptor(device, &desc);
            if (ret < 0) {
                fprintf(stderr, "Échec de la récupération du descripteur de périphérique\n");
                continue;
            }
    
            printf("Clavier HID trouvé : %04x:%04x\n", desc.idVendor, desc.idProduct);
    
            // Ouvrir le périphérique
            libusb_device_handle *handle;
            ret = libusb_open(device, &handle);
            if (ret < 0) {
                fprintf(stderr, "Échec de l'ouverture du périphérique : %s\n", libusb_error_name(ret));
                continue;
            }
    
            // Détacher le pilote du noyau (si attaché)
            if (libusb_kernel_driver_active(handle, 0) == 1) {
                ret = libusb_detach_kernel_driver(handle, 0);
                if (ret < 0) {
                    fprintf(stderr, "Échec du détachement du pilote du noyau : %s\n", libusb_error_name(ret));
                    libusb_close(handle);
                    continue;
                }
            }
    
            // Revendiquer l'interface
            ret = libusb_claim_interface(handle, 0);
            if (ret < 0) {
                fprintf(stderr, "Échec de la revendication de l'interface : %s\n", libusb_error_name(ret));
                libusb_close(handle);
                continue;
            }
    
            // Obtenir le rapport d'entrée
            get_input_report(handle);
    
            // Libérer l'interface
            libusb_release_interface(handle, 0);
    
            // Réattacher le pilote du noyau (si détaché)
            libusb_attach_kernel_driver(handle, 0);
    
            // Fermer le périphérique
            libusb_close(handle);
        }
    }
    
    // Libérer la liste des périphériques
    libusb_free_device_list(devices, 1);
    
    // Nettoyer libusb
    libusb_exit(NULL);
    
    return 0;
}

Comment ça marche

  1. Énumération des périphériques :

    • Le programme liste tous les périphériques USB et identifie les claviers HID en vérifiant leur classe d'interface.
  2. Ouverture du périphérique :

    • Pour chaque clavier HID, le programme ouvre le périphérique et détache le pilote du noyau (si nécessaire).
  3. Revendication de l'interface :

    • Le programme revendique l'interface HID pour communiquer directement avec le périphérique.
  4. Envoi de `HID GET_REPORT` :

    • Le programme envoie une requête `GET_REPORT` pour récupérer le rapport d'entrée, qui contient l'état actuel de toutes les touches.
  5. Décodage du rapport d'entrée :

    • Le rapport d'entrée est affiché en format hexadécimal. Chaque octet correspond à une touche ou un modificateur spécifique.

Exécution du programme

  1. Installez libusb :
sudo apt install libusb-1.0-0-dev
  1. Compilez le programme :
gcc -o dump_keys dump_keys.c -lusb-1.0
  1. Exécutez le programme avec les privilèges root :
sudo ./dump_keys

Exemple de sortie

Pour un clavier avec la touche F9 enfoncée, la sortie pourrait ressembler à ceci :

Clavier HID trouvé : 046d:c31c
Rapport d'entrée :
00 00 42 00 00 00 00 00

Cela signifie :

  • Aucune touche de modification n'est enfoncée (`00`).
  • La touche F9 est enfoncée (`42`).
  • Aucune autre touche n'est enfoncée (`00 00 00 00 00`).

Pourquoi c'est important

Cette approche de bas niveau vous donne un contrôle total sur le clavier USB, vous permettant de :

  • Déboguer des périphériques USB.
  • Analyser les entrées clavier pour la recherche en sécurité.
  • Construire des firmwares ou des pilotes de clavier personnalisés.

Prochaines étapes

  • Expérimentez avec différents claviers et observez leurs rapports d'entrée.
  • Étendez le programme pour décoder les états des LED ou gérer plusieurs claviers simultanément.
  • Plongez plus profondément dans la spécification USB HID pour comprendre des périphériques plus complexes.

Bon hacking ! Faites-nous savoir si vous avez des questions ou besoin d'une assistance supplémentaire. 🚀

Parlons de Votre Projet

Que vous ayez besoin d'aide pour la maintenance, les mises à jour ou la planification pour l'avenir, nous sommes là pour écouter et vous aider autant que possible.