Modern Linux Rootkit with Kernel Modules and Ftrace – Introduction

The Linux kernel is a messy complicated codebase that totals nearly 28 million lines of code1 as of December 2023. Of course this is incredibly daunting for anyone who wants to poke around the kernel internals (for whatever reasons )) or anyone who wants to get started with contributing to the development of the Linux kernel. While there are multiple ways to get acquainted with this monolithic codebase (one of the best being the book Linux Device Drivers, now freely available under the CC BY-SA 2.0 license), we’re going to start by writing a (somewhat mischievous) Linux kernel module.

Important note: I do not encourage you to use any potential knowledge you gleam here for nefarious purposes, nor am I responsible for what happens should you decide to do so. These blog posts are purely for educational purposes.


Table of Contents


Requirements

  • Basic knowledge of Linux
  • Programming knowledge

Kernel Modules

So what are kernel modules, exactly? Taken from the Arch Wiki, “Kernel Modules are pieces of code that can be loaded and unloaded into the kernel upon demand. They extend the functionality of the kernel without the need to reboot the system.” Being able to modify the functionality of the kernel at runtime is a tasty ability indeed. With this you could make the kernel do… pretty much anything you want. You can hide files, directories, processes, open ports, other kernel modules, give yourself a backdoor to root, and a million other useful things. Of course the simple “rootkit” we make in this series won’t be anything particularly fancy, and will primarily just be for demonstration and educational purposes, mainly because I don’t want to go to jail.

Rootkits

Ok, so what is a rootkit then? Taken from Wikipedia, “A rootkit is a collection of computer software, typically malicious, designed to enable access to a computer or an area of its software that is not otherwise allowed (for example, to an unauthorized user) and often masks its existence or the existence of other software.” Knowing this you can see how kernel modules are a perfect fit for this type of software, though there are other ways one can potentially make a rootkit. There are rootkits that take advantage of the dynamic linker, rootkits that take advantage of firmware, rootkits that take advantage of DKOM, hypervisor rootkits, eBPF rootkits, and probably many others2.

Rootkits don’t even necessarily have to run with kernel level privileges either. You could very possibly make a rootkit that operates perfectly fine within userland (though doing so does make it less “root”, doesn’t it?). After all most functions of a kernel level rootkit are possible within userland. Doing so does give one significantly less power, however. Still, often times that power is unecessary.

A particularly interesting type of rootkit is one that extends beyond even the kernel and lives right in the boot process. These so called “bootkits” are incredibly powerful pieces of software which are unimaginably pesky to deal with, but they go far beyond the scope of this small and simple series. Still, if one is interested in the topic it is incredibly fascinating to look into.

Ftrace

Alright, so we know what rootkits are and the technique that we’re going to use to make one, but how exactly will our kernel module modify the Linux kernel to make it do what we want? This is where a fun little Linux utility called ftrace comes it.

Ftrace is a tracing framework released in 2008 for the purpose of helping developers find out what’s going on inside the Linux kernel. Although it’s name literally means “function tracer”, ftrace’s capabilities extend far beyond that and cover a much broader range of the kernel’s internal operations. Using ftrace we can hook particular system calls (more on that in the next post) such as open, read, write, close, etc. and replace them with out own custom code. How we do this exactly is a little complicated so we’ll save it for the next post, but it is all rather interesting believe me.

Now it’s important to note that ftrace is a utility, and not an inherent piece of the kernel. It can be disabled in the kernel configuration since it isn’t critical for any of the system’s functioning, leaving our little rootkit dead in the water if it is. Luckily for us however in practice many popular Linux distros keep ftrace enabled, as it doesn’t significantly affect system performance and can be quite useful for debugging purposes. Still it’s important to keep in mind that this technique may not work everywhere, so be sure to check if it’s supported beforehand.

Also keep in mind that ftrace isn’t the only way to modify the kernel to our wants and needs. There are many other ways to go about it, but they tend to be more complicated so for the sake of simplicity we’re going to go with ftrace.

Project Setup

Before we can move on to actually developing our rootkit we need to setup an environment to run it in. Any old Linux environment with ftrace enabled should do, so you could use VirtualBox, VMWare, anything really, even a real machine if you’re so inclined (though this is not advised). I however will be using a nifty piece of software called Vagrant. If you already have VirtualBox installed Vagrant will automatically use it for virtualization, without any configuration needed.

Getting started with Vagrant is dead simple:

mkdir Ubuntu
cd Ubuntu
vagrant init generic-x64/ubuntu2204
vagrant up
vagrant sshCode language: Bash (bash)

And just like that we have a working virtual environment for developing our rootkit. Of course we’re gonna need a couple more things to compile our code, so (if you’re following along entirely and using Ubuntu) just run these couple commands within the virtual machine:

sudo apt update
sudo apt install build-essential linux-headers-$(uname -r)Code language: Bash (bash)

And we’re all good to go.


Note: if you’d prefer to program the rootkit on your host system (which is fair since your host system will probably have a much better text editor than the guest system) you can add:

config.vm.synced_folder "./src", "/vagrant", create: true, SharedFoldersEnableSymlinksCreate: falseCode language: JavaScript (javascript)

to the Vagrantfile before calling vagrant up (if you already called vagrant up you can simply call vagrant halt and call vagrant up again, or simply call vagrant reload) to create a folder that’s shared between the guest and host. This way you can create and edit files on your host system but compile them inside of the guest system with ease. It’s what I’ll be doing in this series.

Compiling Kernel Modules

With our environment setup all that’s left to do is create a basic kernel module. Create a Makefile and insert this simple code:

obj-m += rootkit.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
Code language: LLVM IR (llvm)

Afterwards create a file called rootkit.c and insert this code:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("HandleHHandle");
MODULE_DESCRIPTION("A completely innocent kernel module");
MODULE_VERSION("0.01");

static int __init rootkit_init(void) {
  printk(KERN_INFO "Hello world\n");
  return 0;
}

static void __exit rootkit_exit(void) {
  printk(KERN_INFO "Goodbye world\n");
}

module_init(rootkit_init);
module_exit(rootkit_exit);
Code language: C++ (cpp)

This is perhaps the simplest kernel module that one could make. It’s fairly self-explanatory, but I’ll go over it quickly anyway.

At the top of the file is simply our includes. These includes give us access the the data structures and functions needed to interact with the kernel. After that is just a few macros that give some information about our kernel module.

After all that comes the two actually interesting functions, rootkit_init and rootkit_exit. The rootkit_init function is called when the module is loaded, and inversely rootkit_exit is called when the module is unloaded. rootkit_init will be perhaps the most important function in the entire codebase, after all it’s the entry point of our kernel module and sets up all of our hooks.

The module_init and module_exit functions both tell the kernel about the entry and exit functions (rootkit_init and rootkit_exit) respectively.

With all of that done you should be able to run make and find yourself with a shiny rootkit.ko file, which is our kernel module file. You can load that file into the kernel using sudo insmod rootkit.ko and unload it with sudo rmmod rootkit. Upon doing so and checking the kernel output buffer using sudo dmesg you should see the messages “Hello world” and “Goodbye world”. If you do then congratulations, you’ve successfully made your first kernel module.

Conclusion

In this post we created a simple kernel module as well as an environment to run it in. In the next post we’ll go over system calls, ftrace, and create a basic hook for the mkdir system call.

You can find all the code for this project at:
https://github.com/HandleHHandle/LKMRootkit

Resources

https://xcellerator.github.io/posts/linux_rootkits_01/
https://wiki.archlinux.org/title/Kernel_module
https://www.kernel.org/doc/html/v4.17/trace/ftrace.html

Footnotes

  1. Okok admittedly this is a bit misleading. While the entirety of the Linux kernel codebase does in fact have nearly 28 million lines of code, most of that doesn’t belong to the actual Linux kernel and instead represents the combined total code for drivers, architecture, and miscellaneous other things. ↩︎
  2. If there’s significant demand for any of these other techniques — or I just end up getting curious of and tinkering with them myself — I may just write another series of blog posts on said technique. ↩︎

1 Comment

  1. These are truly great ideas in regarding blogging. You have touched some pleasant factors here.
    Any way keep up wrinting.

Leave a Reply

Your email address will not be published. Required fields are marked *