KernelRootkit

June 4, 2026 · View on GitHub

A Linux kernel rootkit implemented as a loadable kernel module, written for CSE 306 (Operating Systems) at Stony Brook University.

Overview

This project explores kernel-level hiding techniques by intercepting Linux procfs file operations. The rootkit is a .ko kernel module that hooks function pointers in the kernel's file operations structs at runtime — no system call table patching required. On load it hides a target process from /proc and conceals a specific TCP port from /proc/net/tcp and /proc/net/tcp6.

Disclaimer: This is an academic project completed for a university course. It is intended for educational purposes only.


Exercises

Exercise 1 — Boot-time Init Script

File: initscript.sh

An LSB-compliant init script installed at /etc/init.d/ that:

  • Loads rootkit.ko into the kernel via insmod at system startup.
  • Starts a hidden sshd daemon bound to port 19999 using a custom sshd_config.
  • Writes a fake PID file (/var/run/HACKED.pid) that the script checks on subsequent runs to decide whether to start or skip the daemon.
  • Includes a sleep to account for the fork timing of sshd — without it, the PID check would race against the child process.

The PIDTOHIDE module parameter can be updated post-load by writing to its sysfs entry:

echo "1234" > /sys/module/rootkit/parameters/PIDTOHIDE

Exercise 2 — Process Hiding

File: rootkit.chide_process(), new_proc_readdir(), new_proc_filldir()

Hides a process from the /proc filesystem so it is invisible to ps, top, and direct ls /proc inspection.

How it works:

  1. kern_path("/proc/", ...) resolves the /proc inode.
  2. The inode's i_fop (file operations pointer) is saved, then replaced with a modified copy where readdir points to new_proc_readdir.
  3. new_proc_readdir calls through to the original readdir but substitutes a custom filldir callback (new_proc_filldir).
  4. new_proc_filldir compares each directory entry name against PIDTOHIDE using strcmp. A match causes an early return 0, suppressing that entry from the listing.

On module unload, restore_hide_process() swaps the original i_fop back in.

Module parameter:

insmod rootkit.ko PIDTOHIDE=1234

PIDTOHIDE is a string — it is compared directly against the /proc entry name (which is the PID as a string).


Exercise 4 — Port Hiding

File: rootkit.chide_port(), new_tcp_read(), new_tcp6_read()

Removes connections on a specific port from /proc/net/tcp and /proc/net/tcp6, hiding them from tools like netstat and ss.

The target port is hardcoded as "4E1F" (hex for 19999 decimal), matching the sshd port from Exercise 1.

How it works:

  1. kern_path resolves the inodes for both procfs files.
  2. Each inode's i_fop->read is replaced with a custom read function (new_tcp_read / new_tcp6_read).
  3. The custom read calls the original read to fill the buffer, then walks the result line-by-line (skipping the header).
  4. For each line, it parses the local address port field (second : token). If it matches PORTTOHIDE, the line is overwritten with the remainder of the buffer using strcpy, and the returned byte count is decremented by the line's length.
  5. The same check is repeated for the foreign address port field.

The result is a buffer that appears to /proc consumers as if the connection never existed.


Note: Exercise 3 was not part of this submission.


Techniques & Concepts

  • kallsyms_lookup_name — resolves non-exported kernel symbols (unmap_page_range, TLB functions) at module load time.
  • Procfs i_fop hooking — replaces function pointers in inode file operations structs rather than patching the system call table.
  • filldir callback interception — intercepts the kernel's directory-fill callback to filter individual /proc entries.
  • In-place buffer editing — the port-hiding read hook edits the procfs output buffer directly, shifting content with strcpy and adjusting the read count.
  • Kernel version compatibilitymy_tlb.h provides shims for TLB/MMU gather functions that changed API between Linux < 3.2.0 and >= 3.2.0.
  • LSB init scripts — standard start/stop/status/restart interface for boot-time module loading.

Build & Usage

Requirements: Linux kernel headers for the running kernel (linux-headers-$(uname -r)).

# Build the kernel module
make

# Load the module, hiding PID 1234
sudo insmod rootkit.ko PIDTOHIDE=1234

# Verify it loaded
lsmod | grep rootkit

# Unload (restores all hooks)
sudo rmmod rootkit

The module logs to the kernel ring buffer; use dmesg to inspect startup and teardown messages.