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.kointo the kernel viainsmodat system startup. - Starts a hidden
sshddaemon bound to port 19999 using a customsshd_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
sleepto account for the fork timing ofsshd— 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.c — hide_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:
kern_path("/proc/", ...)resolves the/procinode.- The inode's
i_fop(file operations pointer) is saved, then replaced with a modified copy wherereaddirpoints tonew_proc_readdir. new_proc_readdircalls through to the originalreaddirbut substitutes a customfilldircallback (new_proc_filldir).new_proc_filldircompares each directory entry name againstPIDTOHIDEusingstrcmp. A match causes an earlyreturn 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.c — hide_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:
kern_pathresolves the inodes for both procfs files.- Each inode's
i_fop->readis replaced with a custom read function (new_tcp_read/new_tcp6_read). - The custom read calls the original read to fill the buffer, then walks the result line-by-line (skipping the header).
- For each line, it parses the local address port field (second
:token). If it matchesPORTTOHIDE, the line is overwritten with the remainder of the buffer usingstrcpy, and the returned byte count is decremented by the line's length. - 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_fophooking — replaces function pointers in inode file operations structs rather than patching the system call table. filldircallback interception — intercepts the kernel's directory-fill callback to filter individual/procentries.- In-place buffer editing — the port-hiding read hook edits the procfs output buffer directly, shifting content with
strcpyand adjusting the read count. - Kernel version compatibility —
my_tlb.hprovides 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/restartinterface 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.