Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux Kernel: How to capture a key press and replace it with another key?

I'm attempting to dabble with low level programming. My goal is to have a user type a key into the terminal, capture that and output another key. So for example, if the user types "a" I would type out "b", if he types in "b" I output "c", etc.

What would be the steps to do that? I'm already familiar with how to access the Linux kernel source code, compile it, and use it.

Thanks.

like image 327
Abushawish Avatar asked Nov 20 '15 21:11

Abushawish


1 Answers

Consider next simple kernel module:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <asm/io.h>

#define KBD_IRQ             1       /* IRQ number for keyboard (i8042) */
#define KBD_DATA_REG        0x60    /* I/O port for keyboard data */
#define KBD_SCANCODE_MASK   0x7f
#define KBD_STATUS_MASK     0x80

static irqreturn_t kbd2_isr(int irq, void *dev_id)
{
    char scancode;

    scancode = inb(KBD_DATA_REG);
    /* NOTE: i/o ops take a lot of time thus must be avoided in HW ISRs */
    pr_info("Scan Code %x %s\n",
            scancode & KBD_SCANCODE_MASK,
            scancode & KBD_STATUS_MASK ? "Released" : "Pressed");

    return IRQ_HANDLED;
}

static int __init kbd2_init(void)
{
    return request_irq(KBD_IRQ, kbd2_isr, IRQF_SHARED, "kbd2", (void *)kbd2_isr);
}

static void __exit kbd2_exit(void)
{
    free_irq(KBD_IRQ, (void *)kbd2_isr);
}

module_init(kbd2_init);
module_exit(kbd2_exit);

MODULE_LICENSE("GPL");

This is most minimal and primitive key-logger. It can be easily reworked for replacing of scan code.

Disclaimers

  • This module is not cross-platform (will work only on x86 architecture, because it's using inb() function)
  • I believe it only works with PS/2 keyboard (won't work with USB keyboard)
  • It's performing slow I/O operation (I mean pr_info()) in hardware IRQ handler, which should be avoided (ideally threaded IRQs should be used)).

But I think it's good for educational purposes -- it's really small and demonstrates the idea pretty well (without messing with API like input_dev, input_register_device(), serio_write(), input_event(), input_report_key(), etc).

Details

Real interrupt handler (in keyboard driver) requested as shared interrupt, which allows us also request that interrupt and thus handle it also in our ISR (in addition to ISR in original keyboard driver). Interrupt requesting is done in kbd2_init().

This module works as follows:

  1. Catches key press event (hardware interrupt handler kbd2_isr() is called for each key press event)
  2. Reads scan code of pressed key (via inb() function)
  3. And prints it via pr_info()

Now, you want to replace that scan code. I believe you can use outb() function for this (on x86). So I leave it for you.

If you wonder why we are requesting IRQ with number 1, see at drivers/input/serio/i8042-io.h:

#else
# define I8042_KBD_IRQ  1

Also be sure to check that this IRQ is shared in drivers/input/serio/i8042.c:

error = request_irq(I8042_KBD_IRQ, i8042_interrupt, IRQF_SHARED,
                    "i8042", i8042_platform_device);

Here is documentation for i8042 keyboard controller: AT keyboard controller.

Useful constants

To avoid magic numbers, you can use next definitions.

From drivers/input/serio/i8042-io.h:

/*
 * Register numbers.
 */

#define I8042_COMMAND_REG       0x64
#define I8042_STATUS_REG        0x64
#define I8042_DATA_REG          0x60

From include/linux/i8042.h:

/*
 * Status register bits.
 */

#define I8042_STR_PARITY        0x80
#define I8042_STR_TIMEOUT       0x40
#define I8042_STR_AUXDATA       0x20
#define I8042_STR_KEYLOCK       0x10
#define I8042_STR_CMDDAT        0x08
#define I8042_STR_MUXERR        0x04
#define I8042_STR_IBF           0x02
#define I8042_STR_OBF           0x01
like image 103
Sam Protsenko Avatar answered Oct 19 '22 09:10

Sam Protsenko