内容简介: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 runningAs a quick reminder of the black magic involved in the process, take a look at the following simples
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:
The makefile is in itself pretty obscure, but the make
command is where the real fun occur:
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:
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.
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
:
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:
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:
(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!
Changing the vermagic value one last time yields:
…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.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。