Repository navigation
Keyboard monitor
Let's play with some input devices, like a keyboard connected to one of the board USB ports. Between a USB keyboard and the actual key press being used in userspace, there are a lot of steps:
USB keyboard
↓
USB controller (like XHCI)
↓
USB Core (Manages data transfer from USB, doesn't know what a keyboard is)
↓
USB HID subsystem (Creates reports from raw USB packets)
↓
HID driver (Converts raw reports into logical controls)
↓
input subsystem (Defines event, like EV_KEY and EV_REL)
↓
/dev/input/eventX (Events are exposed through evdev)
↓
libinput, X11, Wayland (Read and/or consume events)
↓
Application
We could plug at different places, the earlier the more intrusive we are. As a first approach, we will keep the existing HID management and catch the event generated by the input subsystem using an input_handler. In short, we will plug here:
input subsystem
↓
kbdmon input_handler
Keep in mind that we will not be developing a full-fledged keyboard driver, but rather a consumer of standardised input events.
Note
Watch out that the top left USB port is not connected to the SoC.
The full driver is available in the repository.
An input handler first needs a structure defining a bunch of callbacks:
static struct input_handler kbdmon_handler = {
.event = kbdmon_event, /* catching and processing events */
.connect = kbdmon_connect, /* when a new input device is connected */
.disconnect = kbdmon_disconnect, /* when an input device is disconnected */
.name = "kbdmon",
.id_table = kbdmon_ids, /* the type of event we want to handle */
};All we then to do, is to register the handler in the module entry point, then unregister in the exit point:
static int __init kbdmon_init(void)
{
return input_register_handler(&kbdmon_handler);
}
static void __exit kbdmon_exit(void)
{
input_unregister_handler(&kbdmon_handler);
}kbdmon_connect will be automatically called whenever a new input device (button, keyboard, mouse, controller, joystick, ...) is connected to the board.
Upon registration in the kernel, all already connected devices will trigger this callback, meaning we don't need to unplug/plug back each device to catch them with our handler.
Without any error handling, connect and disconnect would look like this:
struct kbdmon {
struct input_handle handle;
};
static int kbdmon_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
struct kbdmon *kbd;
kbd = kzalloc(sizeof(*kbd), GFP_KERNEL);
kbd->handle.dev = dev;
kbd->handle.handler = handler;
kbd->handle.name = "kbdmon";
input_register_handle(&kbd->handle);
input_open_device(&kbd->handle);
return 0;
}
static void kbdmon_disconnect(struct input_handle *handle)
{
struct kbdmon *kbd = container_of(handle, struct kbdmon, handle);
input_close_device(handle);
input_unregister_handle(handle);
kfree(kbd);
}Last but not least, we need to define the event callback, kbdmon_event in our case. This is where we will be mainly working for the following examples and use cases.
Each event has a type, a code and a value.
- Type: We want to catch key presses, thus
type == EV_KEY. - Code: The code is the standardised translation managed by the input subsystem, like
KEY_AorKEY_SPACE. - Value can be:
- 0 → key released
- 1 → key pressed
- 2 → key held on
Compile and observe the behaviour of the basic input handler in the repository.
Update kbdmon_event to catch a specific key press combination or sequence. You might need to create an internal buffer and/or structure to store the successive key presses.
The sequence you have to catch is the konami code: ↑↑↓↓←→←→BA.
Note that you first need to be able to detect capital letters, a combination of KEY_LEFTSHIFT or KEY_RIGHTSHIFT and a letter.
Precise timing measurement between two key presses can be really useful to protect against a Rubber Ducky Attack, where key strokes are sent to the computer at inhuman speed.
The kernel has a way to keep track of time in a precise fashion using ktime.
This exercise has two stages:
- Update
kbdmon_eventto tell you the time in microseconds since the last key press. - Detect inhumanly fast key presses.
Here are the functions and type you can use to that end:
-
ktime_tvariable type for time -
ktime_get()get current time -
s64 ktime_to_ns(ktime_t t)converttto nanoseconds (returns ans64, signed 64 bits) -
ktime_t ktime_sub(ktime_t a, ktim_t b)to get time delta betweenb(older) anda(newer) -
s64 div64_s64(s64 a, s64 b)to do a 64-bit division as the board SoC can't do it, don't forget to#include <linux/math64.h>
You can use xdotool to emulate keyboard events.
You might already have noticed by now that the input handler would make a fine keylogger, registering each key stroke and then exfiltrate them to a nefarious entity. Update your monitor to register the keystrokes and periodically print them to the kernel log as a proof-of-concept. For that, you will need to:
- Set up a buffer (say of 256 bytes)
- Register the key presses to the buffer when the occur (in
kbdmon_event) → Register only the key presses, not the key release or keys being held on! - Set up a timer like in the crypto challenge:
static struct timer_list exfil_timer;
static void exfiltrate(struct timer_list *t)
{
/* Exfiltrate data here */
mod_timer(&exfil_timer, jiffies + 10 * HZ);
}
static int __init init_mod(void)
{
timer_setup(&exfil_timer, exfiltrate, 0);
mod_timer(&exfil_timer, jiffies + 10 * HZ);
return 0
}
static void __exit exit_mod(void)
{
del_timer_sync(&exfil_timer);
}When inserting your input_handler driver, you should see it catching other input devices: gpio-keys and pm8941_pwrkey.
Those are the inputs exposed on the top left of the board, a remainder of the SoC previous life.
Find a way to use them and catch the events.
→ Stuck ? Check the troubleshooting page.
→ More resources.
→ Suggest updates and new topics by submitting an issue.