Process injection is a technique used by malware, rootkits, and other vulnerable software to run their malicious code inside an operating system. For example malicious software can appear in the task manager on Windows or in the $ top command in Linux as any name, for example, Google Chrome.
The rootkit goal is to inject malicious code into a system process that runs at the inside kernel with the highest privilege level (ring 0). The software running in the Linux Kernel is able to do anything, access the information of any ruined processes, and do any modification or change process scheduling. It can intercept and modify system calls to hide their presence, tamper with the system's security mechanisms, steal sensitive information, or even take over the entire system.
This article provides an example to show how easy it is to create a simple rootkit that will hide by itself inside of another process name. In the first step let's take a look, at how processes are organized in the Linux Operating System.
Important Disclaimer: This article is for educational purposes only and to help security professionals and developers create safer software. This information should not be used for any purposes other than that.
Each process inside the Linux Kernel has a unique process identifier (PID) assigned by the kernel in the process creation time. The PID is used to manage and monitor the process, for example during interprocess communication. The Linux kernel also has a process table which is implemented as a circular doubly linked list called the task list data structure, which stores information about each process, including the process's state, memory usage, and other relevant data.
Each instance in the task list is a process descriptor of the type struct task_struct (file definition <linux/sched.h>). All mentioned information that is connected with the process is included here.
The state field of the process descriptor describes the current condition which is the following:
TASK_RUNNING: The process is currently running
TASK_INTERRUPTIBLE: The process is currently sleeping
TASK_UNINTERRUPTIBLE: Same as TASK_INTERRUPTIBLE except that it does not wake up and become runnable
TASK_ZOMBIE: Process terminated but the parent still running
TASK_STOPPE: Process is stopped
Now that you’ve learned some basics about Linux Kernel process organization, let's start to edit them from the User space.
First, we need to have a development environment to write, compile and run the kernel modules. These examples have been done in Ubuntu 20.04, but it’s similar in other distributions, so let’s update and upgrade our system and install all the necessary tools.
$ sudo apt update && sudo apt upgrade
$ sudo apt-get install gcc make build-essential libncurses-dev exuberant-ctags build-essential linux-headers-`uname -r` python3.8
Okay, now let’s create and run some Python script in our system, which is just a forever loop, and print something in the console.
Create and edit the file HmmStress.py
#!/usr/bin/python
index = 0
while True:
print("Hmmm!", index)
if index >= 10000000:
index = 0
index += 1
Let's run the script from ./ interpreter\
$./HmmStress.py
And see the process information for this script by top command
$ top
Now, our mission is to hide this script from the system, But for that, we need to create some Linux Kernel module that will traverse through the process descriptor list, then find the HmmStress.py process and replace it with any text, for example, chrome. As a result in the system, our process will run by the name “chrome”.
#include <linux/init.h> // Contains macros and functions used to define and initialize kernel modules
#include <linux/kernel.h> // printk function
#include <linux/module.h> // Needs for loading, and unloading kernel modules.
#include <linux/interrupt.h> // Interrupt handling functions
#include <linux/hrtimer.h> // Timer functions
#include <linux/sched.h> // Includes set of data structures, macros, and functions like task_struct
#include <linux/sched/signal.h> // Working with process signals
// MODULE_* macros. modules specifications
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Areg gasparyan");
MODULE_DESCRIPTION("Kernel process changer!");
MODULE_VERSION("0.1");
static struct hrtimer htimer;
static ktime_t kt_periode;
struct task_struct *task;
// This function runs from high resolution timer callback as soon as possible
static enum hrtimer_restart timer_function(struct hrtimer * timer)
{
for_each_process(task) { // traverse via all task structs
if (strcmp(task->comm, "HmmStress.py") == 0) { // if we found our interested process
strcpy(task->comm, "chrome"); // replace the name with chrome
printk(KERN_INFO "pid: %d | replaced pname: %s\n", task->pid, task->comm); // print information in the system logs
}
}
hrtimer_forward_now(timer, kt_periode);
return HRTIMER_RESTART;
}
// Module initialization
static int __init ps_change_start(void)
{
printk(KERN_INFO "Process changer loaded\n");
printk(KERN_INFO "Timer loaded.\n");
kt_periode = ktime_set(0, 104167); //seconds, nanoseconds
hrtimer_init (& htimer, CLOCK_REALTIME, HRTIMER_MODE_REL); // init high resolution timer
htimer.function = timer_function;
hrtimer_start(& htimer, kt_periode, HRTIMER_MODE_REL); // Start high resolution timer
return 0;
}
// Module deinitialization
static void __exit ps_change_end(void)
{
printk(KERN_INFO "Process changer unloaded.\n");
hrtimer_cancel(& htimer);
}
// Setup init and exit static functions
module_init(ps_change_start);
module_exit(ps_change_end);
Of course, we also need to create Makefile for the program.
obj-m += ps_change.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
\Finally, let’s run make command and insert the module inside the Kernel
$ make
$ sudo insmod ps_change.ko
At the end of file /var/log/syslog you can see the information printed from the kernel module.
For the stop and remove module just need to run:
$ sudo rmmod ps_change.ko
This module uses high-resolution time which is the correct way to receive callbacks very fast for executing special functions. The alternative and not recommended way is the using while (true) loop but when you insert that kind of module inside the kernel you will never be able to kill them anymore. For fixation, you just need to restart the system. Manually inserted modules will be restored on the reboot.
This is just a simple example of working with the process in the Linux Kernel, the malicious rootkit could be very smart and it’s very difficult to detect and remove them from the kernel. There are a couple of tools that could be detected, and remove some kind of rootkits however they are able to detect mostly based on already founded rootkits. Also, rootkits are usually detected manually by highly qualified cybersecurity specialists who are specialized in rootkits.\
What did you think of this guide? Please let me know your thoughts in the comments below.
The lead image for this article was generated with Kadinsky 2 on HackerNoon.
Prompt: Linux kernel