Legacy Support Team
Technical

Dumping the State of All Keys on a USB Keyboard: A Low-Level Linux Adventure

Jan 7, 202512 min read
USB keyboard and Linux terminal

Dumping the State of All Keys on a USB Keyboard: A Low-Level Linux Adventure

Have you ever wondered what’s happening under the hood when you press a key on your USB keyboard? What if you could peek into the raw data being sent from the keyboard to your computer? In this blog post, we’ll dive deep into the world of USB HID (Human Interface Device) protocols and write a low-level Linux program to dump the current state of all keys on a USB keyboard. No high-level abstractions—just raw, unfiltered access to the hardware.


The Problem: Reading Keyboard State Without Events

When you press a key on your keyboard, the operating system processes it as an event. These events are convenient for most applications, but what if you want to know the current state of all keys—not just the ones that triggered events? For example:

  • Which keys are currently pressed?
  • What’s the state of modifier keys (Shift, Ctrl, Alt)?
  • What about the LED states (Caps Lock, Num Lock, Scroll Lock)?

This is a common challenge for:

  • Security researchers analyzing keyboard input.
  • Embedded developers debugging USB devices.
  • Curious hackers who want to understand how USB keyboards work.

But there’s a catch: If a key was pressed while the system was off, the Linux kernel won’t generate any event unless another key is pressed. This means you can’t rely on the kernel’s input subsystem to detect keys that were pressed before the system booted. To solve this, we need to bypass the high-level input subsystem and interact directly with the USB keyboard at the lowest level possible.


The Solution: Using `libusb` to Query the Keyboard

We’ll use the libusb library to interact with the USB keyboard directly. Here’s what we’ll do:

  1. Enumerate all USB devices to find keyboards.
  2. Identify HID interfaces on those devices.
  3. Send a HID GET_REPORT request to retrieve the current input report (key states).
  4. Decode the input report to determine which keys are pressed.

The Code: Dumping Key States

Below is the C program that does all the heavy lifting. It uses libusb to interact with USB devices and retrieves the input report for all connected keyboards.

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

// HID GET_REPORT request
#define HID_GET_REPORT 0x01
#define HID_REPORT_TYPE_INPUT 0x01

// Function to check if a device is a HID keyboard
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, "Failed to get device descriptor\n");
        return 0;
    }

    // Check if the device is a HID device
    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, "Failed to get config descriptor\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; // This is a HID device
                }
            }
        }
    
        libusb_free_config_descriptor(config);
    }
    
    return 0; // Not a HID device
}

// Function to get the input report from a HID keyboard
void get_input_report(libusb_device_handle *handle) {
    unsigned char input_report[8]; // Most keyboards use 8-byte input reports
    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, // Report Type (Input) and Report ID (0)
        0, // Interface
        input_report,
        sizeof(input_report),
        1000 // Timeout in milliseconds
    );

    if (ret < 0) {
        fprintf(stderr, "Failed to get input report: %s\n", libusb_error_name(ret));
    } else {
        printf("Input Report:\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;

    // Initialize libusb
    ret = libusb_init(NULL);
    if (ret < 0) {
        fprintf(stderr, "Failed to initialize libusb: %s\n", libusb_error_name(ret));
        return 1;
    }
    
    // Get the list of USB devices
    count = libusb_get_device_list(NULL, &devices);
    if (count < 0) {
        fprintf(stderr, "Failed to get device list: %s\n", libusb_error_name((int)count));
        libusb_exit(NULL);
        return 1;
    }
    
    // Iterate through all devices
    for (ssize_t i = 0; i < count; i++) {
        libusb_device *device = devices[i];
    
        // Check if the device is a HID keyboard
        if (is_hid_keyboard(device)) {
            struct libusb_device_descriptor desc;
            ret = libusb_get_device_descriptor(device, &desc);
            if (ret < 0) {
                fprintf(stderr, "Failed to get device descriptor\n");
                continue;
            }
    
            printf("Found HID keyboard: %04x:%04x\n", desc.idVendor, desc.idProduct);
    
            // Open the device
            libusb_device_handle *handle;
            ret = libusb_open(device, &handle);
            if (ret < 0) {
                fprintf(stderr, "Failed to open device: %s\n", libusb_error_name(ret));
                continue;
            }
    
            // Detach the kernel driver (if attached)
            if (libusb_kernel_driver_active(handle, 0) == 1) {
                ret = libusb_detach_kernel_driver(handle, 0);
                if (ret < 0) {
                    fprintf(stderr, "Failed to detach kernel driver: %s\n", libusb_error_name(ret));
                    libusb_close(handle);
                    continue;
                }
            }
    
            // Claim the interface
            ret = libusb_claim_interface(handle, 0);
            if (ret < 0) {
                fprintf(stderr, "Failed to claim interface: %s\n", libusb_error_name(ret));
                libusb_close(handle);
                continue;
            }
    
            // Get the input report
            get_input_report(handle);
    
            // Release the interface
            libusb_release_interface(handle, 0);
    
            // Reattach the kernel driver (if detached)
            libusb_attach_kernel_driver(handle, 0);
    
            // Close the device
            libusb_close(handle);
        }
    }
    
    // Free the device list
    libusb_free_device_list(devices, 1);
    
    // Clean up libusb
    libusb_exit(NULL);
    
    return 0;
}

How It Works

  1. Device Enumeration:

    • The program lists all USB devices and identifies HID keyboards by checking their interface class.
  2. Opening the Device:

    • For each HID keyboard, the program opens the device and detaches the kernel driver (if necessary).
  3. Claiming the Interface:

    • The program claims the HID interface to communicate directly with the device.
  4. Sending `HID GET_REPORT`:

    • The program sends a `GET_REPORT` request to retrieve the input report, which contains the current state of all keys.
  5. Decoding the Input Report:

    • The input report is printed in hexadecimal format. Each byte corresponds to a specific key or modifier.

Running the Program

  1. Install libusb:
sudo apt install libusb-1.0-0-dev
  1. Compile the program:
gcc -o dump_keys dump_keys.c -lusb-1.0
  1. Run the program with root privileges:
sudo ./dump_keys

Example Output

For a keyboard with the F9 key pressed, the output might look like this:

Found HID keyboard: 046d:c31c
Input Report:
00 00 42 00 00 00 00 00

This means:

  • No modifier keys are pressed (`00`).
  • The F9 key is pressed (`42`).
  • No other keys are pressed (`00 00 00 00 00`).

Why This Matters

This low-level approach gives you complete control over the USB keyboard, allowing you to:

  • Debug USB devices.
  • Analyze keyboard input for security research.
  • Build custom keyboard firmware or drivers.

Next Steps

  • Experiment with different keyboards and observe their input reports.
  • Extend the program to decode LED states or handle multiple keyboards simultaneously.
  • Dive deeper into the USB HID specification to understand more complex devices.

Happy hacking! Let us know if you have any questions or need further assistance. 🚀

Let's Talk About Your Project

Whether you need help with maintenance, updates, or planning for the future, we're here to listen and help however we can.