Capturando el Estado de Todas las Teclas en un Teclado USB: Una Aventura Linux de Bajo Nivel

Volcado del Estado de Todas las Teclas en un Teclado USB: Una Aventura de Bajo Nivel en Linux
¿Alguna vez te has preguntado qué sucede bajo el capó cuando presionas una tecla en tu teclado USB? ¿Qué tal si pudieras echar un vistazo a los datos sin procesar que se envían desde el teclado a tu computadora? En esta publicación de blog, nos sumergiremos en el mundo de los protocolos USB HID (Dispositivo de Interfaz Humana) y escribiremos un programa de bajo nivel en Linux para volcar el estado actual de todas las teclas en un teclado USB. Sin abstracciones de alto nivel, solo acceso directo y sin filtrar al hardware.
El Problema: Leer el Estado del Teclado Sin Eventos
Cuando presionas una tecla en tu teclado, el sistema operativo la procesa como un evento. Estos eventos son convenientes para la mayoría de las aplicaciones, pero ¿qué pasa si quieres conocer el estado actual de todas las teclas, no solo las que activaron eventos? Por ejemplo:
- ¿Qué teclas están presionadas actualmente?
- ¿Cuál es el estado de las teclas modificadoras (Shift, Ctrl, Alt)?
- ¿Y los estados de los LED (Bloq Mayús, Bloq Num, Bloq Despl)?
Este es un desafío común para:
- Investigadores de seguridad que analizan la entrada del teclado.
- Desarrolladores de sistemas embebidos que depuran dispositivos USB.
- Hackers curiosos que quieren entender cómo funcionan los teclados USB.
Pero hay un problema: Si una tecla se presionó mientras el sistema estaba apagado, el kernel de Linux no generará ningún evento a menos que se presione otra tecla. Esto significa que no puedes confiar en el subsistema de entrada del kernel para detectar teclas que se presionaron antes de que el sistema arrancara. Para resolver esto, necesitamos evitar el subsistema de entrada de alto nivel e interactuar directamente con el teclado USB en el nivel más bajo posible.
La Solución: Usar `libusb` para Consultar el Teclado
Usaremos la biblioteca libusb
para interactuar directamente con el teclado USB. Esto es lo que haremos:
- Enumerar todos los dispositivos USB para encontrar teclados.
- Identificar interfaces HID en esos dispositivos.
- Enviar una solicitud
HID GET_REPORT
para recuperar el informe de entrada actual (estado de las teclas). - Decodificar el informe de entrada para determinar qué teclas están presionadas.
El Código: Volcado del Estado de las Teclas
A continuación se muestra el programa en C que realiza todo el trabajo pesado. Utiliza libusb
para interactuar con dispositivos USB y recupera el informe de entrada para todos los teclados conectados.
#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>
// Solicitud HID GET_REPORT
#define HID_GET_REPORT 0x01
#define HID_REPORT_TYPE_INPUT 0x01
// Función para verificar si un dispositivo es un teclado 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, "Error al obtener el descriptor del dispositivo\n");
return 0;
}
// Verificar si el dispositivo es un dispositivo 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, "Error al obtener el descriptor de configuración\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; // Este es un dispositivo HID
}
}
}
libusb_free_config_descriptor(config);
}
return 0; // No es un dispositivo HID
}
// Función para obtener el informe de entrada de un teclado HID
void get_input_report(libusb_device_handle *handle) {
unsigned char input_report[8]; // La mayoría de los teclados usan informes de entrada de 8 bytes
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, // Tipo de informe (Entrada) e ID de informe (0)
0, // Interfaz
input_report,
sizeof(input_report),
1000 // Tiempo de espera en milisegundos
);
if (ret < 0) {
fprintf(stderr, "Error al obtener el informe de entrada: %s\n", libusb_error_name(ret));
} else {
printf("Informe de Entrada:\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;
// Inicializar libusb
ret = libusb_init(NULL);
if (ret < 0) {
fprintf(stderr, "Error al inicializar libusb: %s\n", libusb_error_name(ret));
return 1;
}
// Obtener la lista de dispositivos USB
count = libusb_get_device_list(NULL, &devices);
if (count < 0) {
fprintf(stderr, "Error al obtener la lista de dispositivos: %s\n", libusb_error_name((int)count));
libusb_exit(NULL);
return 1;
}
// Iterar a través de todos los dispositivos
for (ssize_t i = 0; i < count; i++) {
libusb_device *device = devices[i];
// Verificar si el dispositivo es un teclado HID
if (is_hid_keyboard(device)) {
struct libusb_device_descriptor desc;
ret = libusb_get_device_descriptor(device, &desc);
if (ret < 0) {
fprintf(stderr, "Error al obtener el descriptor del dispositivo\n");
continue;
}
printf("Teclado HID encontrado: %04x:%04x\n", desc.idVendor, desc.idProduct);
// Abrir el dispositivo
libusb_device_handle *handle;
ret = libusb_open(device, &handle);
if (ret < 0) {
fprintf(stderr, "Error al abrir el dispositivo: %s\n", libusb_error_name(ret));
continue;
}
// Desvincular el controlador del kernel (si está vinculado)
if (libusb_kernel_driver_active(handle, 0) == 1) {
ret = libusb_detach_kernel_driver(handle, 0);
if (ret < 0) {
fprintf(stderr, "Error al desvincular el controlador del kernel: %s\n", libusb_error_name(ret));
libusb_close(handle);
continue;
}
}
// Reclamar la interfaz
ret = libusb_claim_interface(handle, 0);
if (ret < 0) {
fprintf(stderr, "Error al reclamar la interfaz: %s\n", libusb_error_name(ret));
libusb_close(handle);
continue;
}
// Obtener el informe de entrada
get_input_report(handle);
// Liberar la interfaz
libusb_release_interface(handle, 0);
// Revincular el controlador del kernel (si se desvinculó)
libusb_attach_kernel_driver(handle, 0);
// Cerrar el dispositivo
libusb_close(handle);
}
}
// Liberar la lista de dispositivos
libusb_free_device_list(devices, 1);
// Limpiar libusb
libusb_exit(NULL);
return 0;
}
Cómo Funciona
-
Enumeración de Dispositivos:
- El programa lista todos los dispositivos USB e identifica los teclados HID verificando su clase de interfaz.
-
Apertura del Dispositivo:
- Para cada teclado HID, el programa abre el dispositivo y desvincula el controlador del kernel (si es necesario).
-
Reclamación de la Interfaz:
- El programa reclama la interfaz HID para comunicarse directamente con el dispositivo.
-
Envío de `HID GET_REPORT`:
- El programa envía una solicitud `GET_REPORT` para recuperar el informe de entrada, que contiene el estado actual de todas las teclas.
-
Decodificación del Informe de Entrada:
- El informe de entrada se imprime en formato hexadecimal. Cada byte corresponde a una tecla o modificador específico.
Ejecución del Programa
- Instala
libusb
:
sudo apt install libusb-1.0-0-dev
- Compila el programa:
gcc -o dump_keys dump_keys.c -lusb-1.0
- Ejecuta el programa con privilegios de root:
sudo ./dump_keys
Ejemplo de Salida
Para un teclado con la tecla F9 presionada, la salida podría verse así:
Teclado HID encontrado: 046d:c31c
Informe de Entrada:
00 00 42 00 00 00 00 00
Esto significa:
- No hay teclas modificadoras presionadas (`00`).
- La tecla F9 está presionada (`42`).
- No hay otras teclas presionadas (`00 00 00 00 00`).
Por Qué Esto Importa
Este enfoque de bajo nivel te da control total sobre el teclado USB, permitiéndote:
- Depurar dispositivos USB.
- Analizar la entrada del teclado para investigación de seguridad.
- Construir firmware o controladores personalizados para teclados.
Próximos Pasos
- Experimenta con diferentes teclados y observa sus informes de entrada.
- Extiende el programa para decodificar los estados de los LED o manejar múltiples teclados simultáneamente.
- Profundiza en la especificación USB HID para entender dispositivos más complejos.
¡Feliz hacking! Déjanos saber si tienes alguna pregunta o necesitas más ayuda. 🚀