A basic rootkit with resource hiding capabilities.
You are welcome to browse the story-oriented documentation or give it a go.
Currently only Ubuntu 22 LTS kernels of version 5.15.0-76 are supported - and to compile the module you'll have to get the corresponding headers as they aren't included.
Headers are assumed to be located at kernel-headers/linux-headers-5.15.0-76-generic,
in the same exact structure as they are present on Ubuntu machines at
/lib/modules/5.15.0-76-generic.
nootkit can receive a configuration through module parameters -
which are at the moment not exposed after the module has been loaded.
An example insmod command to set certain filenames and inodes to be hidden
from common usermode APIs is:
insmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
hide_filenames=hello,bigboy \
hide_inodes=138210,17635,1337The kernel space address of kallsyms_lookup_name must be provided to the module at load time,
as it is used to locate other unexported symbols.
Possible module arguments are:
kallsyms_lookup_name: Must be present, can be obtained via/proc/kallsymsas shown abovehide_filenames: Comma-separated list of file names and absolute paths to hide from usermode dir listingshide_inodes: Comma-separated list of inode numbers to hide from usermode dir listingshide_sockets: Comma-separated list of socket filter strings following the format (explained below) - IPv4 socket matching this filter will be hidden fromnetstatcommands.hide_packets: Comma-separated list of packet filter strings following the format (explained below)
You are also welcome to check out the makefile for some semi-automatic tests.
Pretty simple really, just follow this template:
IP PROTO = 6; LOCAL = 0.0.0.0/0.0.0.0:1300-1400; FOREIGN = 0.0.0.0/0.0.0.0:0-65535;Where PROTO is a valid IP protocol number, although only TCP sockets are supported.
PROTO 0 is magic and matches any protocol number.
LOCAL describes the local IP/Mask:PortRangeStart-PortRangeEnd, and FOREIGN does the same for... The foreign address.
Port ranges are inclusive. Spaces aren't mandatory anywhere.
A lot like the socket filter format above (even represented the same in memory!), but a little more detailed:
ETH PROTO = 0; ETH SRC = 00:00:00:00:00:00; ETH DST = 00:00:00:00:00:00; IP PROTO = 6; IP SRC = 0.0.0.0/0.0.0.0:0-65535; IP DST = 0.0.0.0/0.0.0.0:1300-1350;
# Or ARP for example
ETH PROTO = 0806; ETH SRC = 00:00:00:00:00:00; ETH DST = 00:00:00:00:00:00; IP PROTO = 0; IP SRC = 0.0.0.0/0.0.0.0:0-65535; IP DST = 0.0.0.0/0.0.0.0:0-65535;Again the PROTOs are each valid protocol numbers for their respective protocol, and 0 is magic and matches all protocols.
Note that ETH PROTO parses input as hexadecimal.
Examples:
insmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
hide_filenames=helloinsmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
hide_filenames=hello,/root/bigboy,/home/john/wayneinsmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
hide_inodes=524290Hiding processes from various utilities including ps can be done by hiding the path /proc/[pid] corresponding
to the process. There is no specific API at the moment specific to hiding processes.
For example:
insmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
hide_filenames=/proc/7047,/proc/682Sockets can be hidden from netstat and other utilities by specifying their properties in the socket filter format
shown above.
Example:
insmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
"hide_sockets=\"PROTO = 1; LOCAL = 192.168.122.122/255.255.255.255:10-30; FOREIGN = 0.0.0.0/0.0.0.0:0-65535;\""Packets can be hidden at a very low level - neither AF_PACKET sockets or even XDP BPF running in the generic
context will see them.
Packets can be hidden by specifying their properties in the packet filter format shown above.
Example:
insmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
"hide_packets=\" \
ETH PROTO = 0; ETH SRC = 00:00:00:00:00:00; ETH DST = 00:00:00:00:00:00; IP PROTO = 6; IP SRC = 0.0.0.0/0.0.0.0:0-65535; IP DST = 0.0.0.0/0.0.0.0:1337-1337;, \
ETH PROTO = 0806; ETH SRC = 00:00:00:00:00:00; ETHDST = 00:00:00:00:00:00; IPPROTO = 0; IP SRC = 0.0.0.0/0.0.0.0:0-65535; IP DST = 0.0.0.0/0.0.0.0:0-65535; \
\""nootkit's self hiding feature has two aspects:
-
Hiding from
/proc/modulesand thereforelsmod. -
Removing the
/sys/moduledirectory associated with the module.
To allow removing the module, a hook is installed for the syscall delete_module, such that running rmmod -f nootkit
once disables the hiding of the module from both locations, and running it again removes the module regularly.
See the reason for this implementation in src/arch/x86_64/hide/module_sys.c.
The -f flag is necessary, since without it rmmod checks the directory /sys/module for details about the requested
module to remove, and nootkit's hiding from it prevent's rmmod from following up with a delete_module syscall.
There are 3 core parts to nootkit:
- A standard hooking mechanism, exposed in src/hook.h and defined in an architecture specific source
file (src/arch/x86_64/hook.c).
- The main APIs here are
HOOK_DEFINEandHOOK_EXTERN, used to generate and expose functions for enabling and disabling hooks - macros for syscall hook definitions are also in place.
- The main APIs here are
- A centralized resolution and way of exposing unexported kernel symbols by way of
kallsyms. Function and global variable signatures are defined once in an X-macro in src/ksyms.h, and are automatically set to be resolved by the functionresolve_ksyms(src/ksyms.c) called on module initialization. - A central configuration method by module parameters; Both raw parameters and in some cases parsed parameter values
are available for use by nootkit modules, with some helper functions for performing common operations on configuration
values (e.g.
filter_transportfor matching values against a transport-layer network filter). src/config.h, src/config.c.
Usermode hiding functionalities are defined in the general src/hide directory, or in the architecture specific src/arch/x86_64/hide/ directory in relevant cases.