Skip to main content

Ring 0 Operations: Building a Linux Rootkit from Scratch

·819 words·4 mins
JustThinkingHard
Author
JustThinkingHard
Cybersecurity student | Low level enjoyer

In the world of cybersecurity, there is a distinct line between “User Space” (Ring 3) and “Kernel Space” (Ring 0). User space is where your browser, text editor, and shell live. It is safe, restricted, and monitored. Kernel space is the Wild West. It is where the hardware is controlled, where memory is managed, and crucially where the rules of reality for the operating system are defined.

For my latest project, I decided to cross that line. I developed a Linux Rootkit as a Loadable Kernel Module (LKM).

Disclaimer: This project was developed strictly for educational purposes to understand OS internals and malware behavior. The techniques discussed here should never be used on systems without explicit authorization.

The Objective
#

The goal was to create a stealthy kernel module capable of:

  1. Hiding itself from the list of loaded modules.
  2. Hiding files and directories from standard tools like ls.
  3. Hiding processes from tools like ps or top.
  4. Providing a Backdoor for instant root privilege escalation.
  5. Establishing covert communication channels.

The Core Mechanism: Syscall Hooking
#

The Linux kernel interacts with user space programs via System Calls (syscalls). When you run ls, the program doesn’t actually look at the disk; it asks the kernel to do it via the sys_getdents64 syscall. When you run kill, it calls sys_kill.

To control the system, we don’t need to replace the tools; we just need to intercept these phone calls.

The Challenge: Control Register 0 (CR0)
#

In older versions of Linux, you could simply overwrite the sys_call_table. However, modern kernels have the Write Protect (WP) bit set in the CR0 register, preventing modification of the syscall table.

While it is possible to disable this bit, a cleaner and more modern approach (and the one I utilized) involves using Ftrace (Function Tracer).

The Ftrace Solution
#

Ftrace is a debugging framework built into the kernel. It allows us to attach a callback to almost any function in the kernel. We can modify the instruction pointer (RIP) of a specific syscall to point to our malicious function instead of the real one.

// Simplified Hooking Logic
static struct ftrace_hook hooks[] = {
    HOOK("sys_getdents64", hook_getdents64, &orig_getdents64),
    HOOK("sys_kill", hook_kill, &orig_kill),
};

Feature 1: The Art of Invisibility (Hiding Files)
#

How do you hide a file named malware_data? You hook sys_getdents64.

This syscall is responsible for listing the contents of a directory. It fills a buffer with linux_dirent64 structures. My hook works like a Man-in-the-Middle:

  1. Intercept: The user asks for a directory listing.
  2. Call Original: We call the real sys_getdents64 to get the actual data.
  3. Filter: We iterate through the returned buffer in memory. If we find a filename containing our magic prefix (e.g., hidden_), we memmove the rest of the buffer over it, effectively erasing it from existence.
  4. Return: We give the filtered buffer to the user.

To the user—and to the ls command—the file simply does not exist.

Feature 2: The Magic Backdoor (Privilege Escalation)
#

A rootkit isn’t useful if you don’t have root access. To achieve persistence, I implemented a hook on the sys_kill syscall.

Normally, kill is used to send signals to processes. I modified it to listen for a specific, unused signal (e.g., Signal 64).

asmlinkage int hook_kill(pid_t pid, int sig) {
    if (sig == 64) {
        // The Magic Phrase was spoken!
        struct cred *new_creds = prepare_kernel_cred(NULL); // Create root creds
        commit_creds(new_creds); // Apply to current process
        return 0;
    }
    return orig_kill(pid, sig); // Otherwise, act normal
}

Now, any user on the system can simply type kill -64 1 in the terminal. Instead of sending a signal, the kernel grants that user’s shell uid=0 (Root) instantly. No passwords, no sudo logs.

Feature 3: Secret Connections
#

The project also involved opening sockets directly from kernel space. This bypasses standard user-space firewalls that might be monitoring specific binaries. By utilizing kernel_sock_shutdown and call_usermodehelper, the rootkit can initiate a reverse shell to a remote listener, removing the process from user-space visibility. However, with the correct command, users can still see the connection to the remote server, but not the process itself.

Challenges and Kernel Panics
#

Developing in Ring 0 is unforgiving. In C++, if you make a memory error, you get a “Segmentation Fault” and the program crashes. In Kernel C, if you dereference a null pointer or mess up a list pointer, the entire operating system crashes (Kernel Panic).

Debugging involved setting up QEMU virtualization and analyzing crash dumps to ensure the hooks were stable and thread-safe.

Conclusion
#

This project was a fascinating journey into the depths of the Linux Operating System. It required a solid understanding of:

  • C Programming (Memory management, pointers, structs).
  • Assembly (Understanding registers and calling conventions).
  • Operating System Architecture (VFS, Process Scheduling, Interrupts).

By building the weapon, I learned exactly how to defend against it. Understanding how LKMs can be manipulated is crucial for forensic analysis and hardening Linux systems against advanced persistent threats.