34

A Standalone Linux Kernel Module

 4 years ago
source link: https://medium.com/@eitan.levinzon/a-standalone-linux-kernel-module-df54283d4803
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

If you’ve ever compiled a module for the Linux Kernel you’ve probably seen (or assumed there’s) quite a lot of voodoo going on between the stages of running make and insmod .

As a quick reminder of the black magic involved in the process, take a look at the following simplest of kernel modules, and it’s appropriate makefile:

A super simple module
It takes a while to decipher what’s going on

The makefile is in itself pretty obscure, but the make command is where the real fun occur:

qmQ73iA.png!web
This is just the tip of the iceberg

Right of the start we see no “standard” compilation calls, involving directories mostly untouched by the everyday user. Runing make V=1 reveals a mountain of calls, some of them to non-compilation-related binaries and scripts. What’s going on?

The .ko compilation process may seem scary and complicated, but upon simple inspection, it’s not so hard to figure out exactly what’s going on.

In this post we’ll walk through the journey of compiling a kernel module, without using any kernel headers or complicated makefiles. Our goal would be to write a short, simple, and easy to understand .c file, that when compiled with gcc and a few minimal flags, outputs a valid .ko that can be insmod ed.

This can be achieved using several different approaches:

  • Reading the documentation.
  • Inspecting the standard compilation process.
  • Looking through the kernel sources, attempting to understand what the init_module syscall does (the one responsible for loading a .ko ).

But our method of choice will be a fourth one — The Blackbox Way : Tinkering with the loader, looking at valid modules, writing invalid ones, and attempting to do whatever seems to work until we finally succeed.

IMHO, blackboxing is the most useful and effective skill to have when researching unkown or uncharetd systems, as oftentimes none of former approaches is applicable, or is probably more time-consuming and exhausting. Also, it’s the most rewarding process, as you get quick and fun payoffs along the way, with little effort.

But first, we must understand what a kernel module actually is.

What $(make)s a Kernel Module?

The .ko suffix means “ kernel object ”, hinting at what a module really is — just an ELF object file . In fact, before kernel version 2.6 , kernel modules used the .o suffix, just like standard object files.

So what distinguishes between conventional .o files and .ko ones? Let’s try to load an “empty” object file into our kernel:

Loading an “empty” object file into the kernel fails

Luckily for us, the kernel is pretty descriptive when it comes to errors regarding modules. dmesg reveals that our empty object file is missing a section called .modinfo . Let’s add it to our module!

The simplest way to do that is by creating any variable, and telling gcc to store it in a section of our choice using __attribute__((section(<name>))) . See the gcc documentation for more info on attributes.

BZra6rY.png!web
`highlight` is like `cat` but with syntax highlighting!

The error changed! Looks like we’re missing some name field in our modinfo section. To learn how to properly set that field, let’s look at a “real” kernel module, from /lib/modules/$(uname -r)/ , dumping the contents of that section using objdump -s -j .modinfo :

vAZB7jF.png!web

In the muggle world, use the handy ` modinfo ` command

Looks like it’s just made up of key=value strings, separated by null bytes. So let’s add the name field and see what happens:

1*zB7UgbGAGXPfjGFHwdrqqA.png?q=20
I solemnly swear that I am up to no good

It worked! The kernel successfully identified our module’s name as standalone !

This next error seems a bit obscure. With no useful tips from the kernel, it’s time to look again at a “real” kernel module. Upon examination of its sections, using either readelf -S or objdump -h , we find another candidate with a rather suspicious name, that probably has something to do with the loading process: [.rela].gnu.linkonce.this_module .

(The .rela part is just the relocation data for the values of that section)

On my Ubuntu 18.04, this section is of size 0x380 bytes, and has 2 relocatable symbols: init_module at offset 0x178 , and cleanup_module at offset 0x330 . Dumping the contents of the section ( objdump -s ) shows that it’s mostly zeroed out, except for the module name, at offset 0x18 .

This section is actually the C struct module from the Linux Kernel sources, include/linux/module.h , embedded as a section inside the ELF. It’s usually named __this_module , and during the loading process, the kernel loader initializes the other relevant fields in the struct.

But we’re not using any kernel header files. Luckily, we have all the information we need. Let’s add some init and exit functions, and the relevant section:

almost there!
Lumos Maxima!

(We’re using __attribute__((packed)) to force the compiler to not add any extra padding between the struct’s fields, so as to force our known offsets.)

Rather bafflingly, even though insmod failed, there’s no error log in dmesg . We can verify that the module isn’t in fact loaded using lsmod | grep standalone . So what’s going on?

Usually the kernel alerts us on any missing parameters, but apparantely on some kernel configurations, sometimes no message is printed for some missing fields. Let’s look back at a real kernel module. The only major difference between our module and a real one is the fields in the .modinfo section.

This is the part where we can start to copy the remaining fields one by one until we succeed or get a different error message. However, luckily for us, an enchanted oracle whispered in our ear at night that we should start by copying the .vermagic field first!

vermagic is short for “Version Magic”, a string used by the loader to sanity check that a module was indeed compiled for that kernel release. We can extract its content from a module compiled for the kernel, or alternatively, let the kernel spit it out for us!

Makefiles and Charms are my two favorite classes in Hogwarts
vqe2MnF.png!web
Thank’s for the tip!

Changing the vermagic value one last time yields:

UfUfM3u.png!web
It worked!

…And we’re done! We successfully loaded a module into the kernel, compiled without using any kernel header files!

To see the final standalone.c file in it’s entirety, visit https://github.com/0xEitan/standalone-ko .

The Demystification Of Complex Systems

Many people get overwhelmed when faced with a complex and often uninviting system. Be it some collosal project they’re getting acquainted with, an unknown embedded device they want to program, or the Linux Kernel in general — it’s always intimidating diving into the deep waters.

Even though we didn’t use pure blackboxing in this post, I find methods such as the one shown, to be extremely effective in such scenarios. It may not produce successful results like in the present case, but it can certainly always reveal important insights, and help to overcome the first obstacles when facing unknown code bases.

Nothing is too complex to understand, it just takes the right tool to do it.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK