Linux Kernel (The Queen of OpenSource) and Its Modules (Part-2)

Surbhit Awasthi
6 min readOct 25, 2020

In the last article, we saw one of the interfaces to kernel i.e. /dev. In this article, we will talk a bit about that, and then we will move on to some interesting stuff with the second interface to kernel (any guesses???) /proc file system.

(Some people might have thought of system calls, well let’s keep that for another article)

Source: Smith College, June 2002

Let’s begin the explanation of my previous piece of code first. I won’t go into a line by line explanation otherwise the article will become way too long. So for any device the kernel expects four interfaces namely, read, write, open and release (or close). This is provided to the kernel module with help of file_operations structure which have the reference for the block of code which will run when the stipulated events happens.

struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
// The prototype for the functions are as follows:static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

A kernel module is hot swapped into a running kernel, hence it also has interfaces, these guides the code which runs while a module is loaded and also when the module is unloaded. The interfaces are:

// Old Interfacesint init_module(void);
void cleanup_module(void);
// According to the latest technique (as of linux kernel 2.4), we
// are now not restricted by the name of the interface (init and
// cleanup). We can now name them anything and use module_init() and
// module_exit() macros to specify them.
static int __init demo_name_init(void);
static void __exit demo_name_exit(void);
// Implementation of those functionsmodule_init(demo_name_init);
module_exit(demo_name_exit);

Since our kernel module binary is going to be the part of the kernel, the interfaces define the init and exit methods, it is meant that whatever changes a module does in init should be undone in the exit function. We should also take care that no pointer is dangling with random addresses, because a wrong invocation means an exception in the kernel itself which will lead to system freeze and restarts.

In my code I have also used a semaphore that’s just because I want only one process to access the device at one time. Another unique thing about the kernel module programming is that the standard C library is not at your disposal so you cannot use functions like printf, kernel has a logging command called printk, I have used this command to log output to the kernel log file (situated under /var/log/ by the name kern.log). It is because of this logging nature of printk that it has 8 priority state (we used KERN_INFO and KERN_ALERT).

For compilation and further understanding of how a kernel module works please refer to The Linux Kernel Module Programming Guide. Details regarding major and minor number is also mentioned in the guide. This brings us to a point where we can now venture into this unknown territory, after device the next thing I fumbled upon was the /proc file system which is also another interface to the kernel. Similar to the /dev interface a process also interacts with the kernel using a read and write interface. A process though needs not to be opened or closed.

To play with this I wrote a piece of code which if given a PID, will give the process with that PID root privileges (Yes you heard it right, a BackDoor), so my next code if injected into the kernel of one of your friends computer can actually get you lifetime access to his/her root user. (Although initially you still require a social engineering attack to get the module injected into his/her kernel as your backdoor, I still need to figure this part out :P)

Enough of the boring stuff lets jot down the steps for this nice evil attack.

Before we began the ride, here is the gist of what we are going to do:

  • We will create a proc file which when given a PID, will grant that PID root privileges.
  • To achieve this we will use our kernel module’s privileged mode to escalate the ownership of the requested PID from user to root.
  • The attack will sustain till the victim does not unloads our kernel module or does not removes the malicious proc file.
  • The proc file name is alice… “/proc/alice”

Disclaimer: This is only for knowledge sharing, any malicious use of this script is not the article’s author responsibility. Moreover, I am still in learning phase so if the script bricks your linux machine, remember I warned you to use it on your own risk.

My Environment:
- Ubuntu 20.04
- gcc version 9.3.0
- GNU Make 4.2.1
- Linux Kernel: 5.4.0-51-generic

So firstly, you need to compile the code into a kernel binary using make command. To do this create a folder somewhere in the victim’s system and copy the Makefile and rooty.c file into the folder. Open a terminal session in the folder, and run the make command, this will generate a bunch of files. We are interested with the rooty.ko file. Run the following commands to insert the module into the victim’s kernel

$ sudo insmod rooty.ko// To check if the module is correctly loaded run the below command$ lsmod | grep rooty// expect some output with rooty module mentioned in it
// You can also run ls to see a file named alice is created inside
// /proc
$ ls -l /proc/alice
dmesg Output
State After Running Above Commands

Next, carry on the social engineering attack for one more command

$ sudo chown <username>:<usergroup> /proc/alice
chown Output

Generally <username> and <usergroup> values are same and equal to the victim’s computer username. This marks the complete of the attack, now you can at your will get root privileges at any time by doing the following:

// To make sure that you
// are currently not root, you can run:
$ whoami// You can also try to run a package update to be sure you don't
// have root privileges yet
$ apt update
Before Using the backdoor: When Root Access is not Granted
// To escalate the privileges of current terminal session:$ echo $$ > /proc/alice// You will see no observable change, but wait, type this again:$ whoami// I bet you will see, what you want to see
// Run commands which require root now and see the magic, ex:
$ apt updateor$ rm -rf /* (okay don't run this PLEASE!)
Backdoor Used: Root Access Granted, ENJOY!

This marks the end of yet another quest of mine in learning linux kernel modules, hope you liked this. Kernel modules are the way in which you can contribute to the linux kernel, which is my favourite piece of open source project out there, I think the impact that linux brought to the world in terms of scalability of computer science is unmatched, it opened doors for students to learn advance concepts without spending a fortune on Unix and other paid OS.

The purpose of my articles was not to teach LKMs but to bring in a spark in every reader about how powerful their code can become, and if you get one merged into the master of linux, you helped the whole of computer science indeed.

Peace. ✌️

--

--