An Interactive Char Driver

Creating an interactive char driver is surprisingly simple, but there are a few traps along the way.

Exposing it to the File System

This is by far the hardest part to understand, but honestly a full understanding isn't really necessary. The new intro_init function looks like this:

#define DEVICE_NAME "intro"
#define CLASS_NAME "intro"

// setting up the device
int major;
static struct class*  my_class  = NULL;
static struct device* my_device = NULL;

static int __init intro_init(void) {
    major = register_chrdev(0, DEVICE_NAME, &fops);    // explained later

    if ( major < 0 )
        printk(KERN_ALERT "[Intro] Error assigning Major Number!");
    
    // Register device class
    my_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(my_class)) {
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "[Intro] Failed to register device class\n");
    }

    // Register the device driver
    my_device = device_create(my_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    if (IS_ERR(my_device)) {
        class_destroy(my_class);
        unregister_chrdev(major, DEVICE_NAME);
        printk(KERN_ALERT "[Intro] Failed to create the device\n");
    }

    return 0;
}

A major number is essentially the unique identifier to the kernel module. You can specify it using the first parameter of register_chrdev, but if you pass 0 it is automatically assigned an unused major number.

We then have to register the class and the device. In complete honesty, I don't quite understand what they do, but this code exposes the module to /dev/intro.

Note that on an error it calls class_destroy and unregister_chrdev:

Cleaning it Up

These additional classes and devices have to be cleaned up in the intro_exit function, and we mark the major number as available:

Controlling I/O

In intro_init, the first line may have been confusing:

The third parameter fops is where all the magic happens, allowing us to create handlers for operations such as read and write. A really simple one would look something like:

The parameters to intro_read may be a bit confusing, but the 2nd and 3rd ones line up to the 2nd and 3rd parameters for the read() function itself:

We then use the function copy_to_user to write QWERTY to the buffer passed in as a parameter!

Full Code

Simply use sudo insmod to load it, as we did before.

Testing The Module

Create a really basic exploit.c:

If the module is successfully loaded, the read() call should read QWERTY into buffer:

Success!

Last updated

Was this helpful?