Kernel Modules & Extensions - Red Canary Threat Report Skip Navigation
Get a Demo
 
 
 
 
 
 
 
 
 
 

T1547.006

Kernel Modules and Extensions

Kernel modules and extensions offer adversaries a reliable means of establishing persistence on Linux systems.

Pairs with this song
 

Analysis Icon

Analysis

Why do adversaries abuse kernel modules and extensions?

When an adversary gains access and execution on a system, they are often hamstrung by the reality that their execution exists in memory only. Thus, if the machine restarts, the program they had running on the machine goes away. Kernel Modules and Extensions allow adversaries to establish persistence by leveraging autoloading Linux kernel modules (LKM). LKMs are programs that run within the context of the Linux kernel. They are essential to allowing a system to function properly. Many LKMs need to start before the user mode portions like the desktop environment, web browsers, and more. Therefore, as part of the boot process, many LKMs are loaded automatically by the system.

How do adversaries abuse kernel modules and extensions?

One way for an adversary to establish persistence is to provide an LKM that can be loaded when the machine reboots. There are specific files and directories on the system that will be checked for files indicating LKMs that should autostart, such as /etc/modules and /lib/modules-load.d/. If an adversary configures the system properly and writes their LKM to disk, then upon reboot their LKM will be loaded into memory and run.

NOTE: Depending on the Linux distribution and the tools installed on it, the directories that need to be configured may vary slightly. For this analysis, we will focus on the common techniques that work across many Linux distributions, but first some background on loading LKMs

Kernel modules are loaded by one of two syscalls: init_module and finit_module. The init_module syscall is used to load kernel modules from a buffer in memory while the finit_module syscall is used to load kernel modules from a file descriptor.

Typically, if a program wants to load kernel modules they can do it in one of three ways:

  1. call the syscalls directly
  2. use the glibc wrappers for the syscalls
  3. use the libkmod library, which in turn just calls the glibc wrappers

When a Linux system boots up, there is a well-defined sequence of events that eventually leads to having the system up and running. Early on in the chain of events, core system LKMs are loaded. These allow the operating system to interact with different pieces of hardware such as the network card, hard drive, and peripherals. A bit later on in the boot sequence, systemd will then load more kernel modules if necessary, using a service called systemd-modules-load.service. This service looks in a few predefined directories for configuration files. The configuration files, if present, specify which LKMs need to be loaded. The directories systemd looks for can be found in the systemd unit file:

ConditionDirectoryNotEmpty=|/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/lib/modules-load.d
ConditionDirectoryNotEmpty=|/usr/local/lib/modules-load.d
ConditionDirectoryNotEmpty=|/etc/modules-load.d
ConditionDirectoryNotEmpty=|/run/modules-load.d

systemd-modules-load.service will also look at the modules-load and rd.modules-load kernel command-line parameters.

Another way that an adversary can achieve persistence is by creating a systemd unit file. An example of this is described in the Testing section below. This variation works by creating a systemd service that will get started as the machine is booting up. That service will use a tool such as insmod (or something similar) to load the kernel module. On older systems that don’t have systemd, rc.d or a similar init system may be used.

This technique is effective in cloud-hosted Linux systems as well, but the main difference is that a cloud-hosted machine is most likely a virtual machine (VM). The technique would still behave the same regardless.

If the cloud environment is hosting containers, either directly or through an orchestrator such as Kubernetes, the containers themselves run on top of the host system. If a container were to write files to these same directories, it may affect the host system if those directories on the container are shared with the host, as seen here. If the container is able to modify files on the host, then the persistence would only apply to the host itself, which in turn could affect each container.

If you’d like to read about in-the-wild examples of kernel module and extension abuse, check out ATT&CK’s procedural examples, this write-up from CrowdStrike, this NSA/FBI security advisory, and the following GitHub samples:

The best way to prevent or mitigate this technique is to ensure that relevant software is up to date and patched and that proper access controls are in place. There are various mechanisms that can be leveraged to prevent loading a kernel module:

  1. Restricting access to the root user. Loading an LKM requires root privileges, so ensure that access to the root user or sudo privileges is monitored and properly restricted.
  2. Don’t give containers excessive permissions. Privileged containers or containers with the CAP_SYS_MODULE permission can load kernel modules. Containers that are privileged or share directories with the host can potentially created the necessary files for an autoloading LKM.

Advanced options

NOTE: These actions may not be possible if the host machine is managed by a cloud provider.

  1. Enforce signed kernel module loading. This works in conjunction with the next step of enabling UEFI secure boot. Enforcing signed kernel module loading makes it more difficult for an attacker to load a malicious kernel module onto a system as the kernel module must be signed with a valid signature.
  2. Ensure UEFI secure boot is enabled. Enabling UEFI secure boot allows features such as signed driver loading to be enforced.
  3. Enable Linux IAM if possible and practical for your environment.
  4. Leverage Linux security modules (LSM). For example, by default SELinux prevents systemd from inserting a kernel module created by a normal user. SELinux by default hooks into the init_module and finit_module functions, and so policies can be created to affect who is allowed to load kernel modules and under what conditions.

Visibility icon

Visibility

Note: The visibility sections in this report are mapped to MITRE ATT&CK data sources and components.

Process monitoring

By examining process data, it is possible to detect this behavior by observing commands that load LKMs or write LKMs and their config files to specific directories. You can try to detect writes to files in the directories below. Looking for calls to insmod or modprobe coming either from a shell or systemd.

File monitoring

By looking for newly created files in the specific directories where auto load LKMs reside, a defender could uncover unknown files being written here. Directories of interest would be:

  • /lib/modules/<kernel-version/
  • /etc/modules-load.d/
  • /lib/modules-load.d/
  • /usr/lib/modules-load.d/
  • /usr/local/lib/modules-load.d/
  • /run/modules-load.d/

Driver and kernel module monitoring

By tracking which LKMs are typically loaded, it may be possible to detect the abnormal loading of an LKM or discover an unknown LKM that wasn’t previously on the system. On most systems, the LKMs that are loaded don’t change very much across reboots.

Collection Icon

Collection

Note: The collection sections of this report showcase specific log sources from Windows events, Sysmon, and elsewhere that you can use to collect relevant security information.

Linux security modules

Linux security modules (LSMs) are a piece of the Linux kernel that allow administrators to configure MAC (mandatory access control) policies on the system. LSMs are hooked into the kernel in many different locations and provide the ability to block certain actions based on the policies defined by administrators. They are also able to audit those same actions as well. SELinux is an example of an LSM that has the ability to audit or even block the loading of a kernel module. By configuring SELinux to audit security events, you can see when kernel modules are loaded.

Endpoint detection and response (EDR) tools

EDR tools can be very effective for collecting the relevant data to help identify this type of threat. EDRs can monitor file creation in specific directories that would allow an LKM to achieve persistence.

An EDR could also monitor LKM loading and other behaviors related to establishing persistence. This is crucial because once an LKM is loaded, it is run with the highest privilege level and therefore is able to modify all other aspects of logging on the system. This means the best time to detect the installation and execution of an LKM is as it is happening. Any further logging or analysis of the running system is subject to showing incorrect results as they may be modified by the LKM.

Icon-threat detection

Detection opportunities

Detecting this technique is fairly straightforward but could be prone to false positives.

Shells modifying files in known LKM directories

This pseudo detector looks for configuration files being written to specific directories that are searched when looking for loadable LKMs:

process_name == ("bash", "sh", "dash", "zsh", ...) 
&& 
file_create_in ("/lib/modules/<kernel-version>/" || "/etc/modules-load.d/" || "/lib/modules-load.d/" || "/usr/lib/modules-load.d/" || "/usr/local/lib/modules-load.d/")

systemd loading a LKM using insmod

This detector looks for the systemd process running commands that would load an LKM:

parent_process == ("systemd") 
&&
process_name == ("insmod")

systemd loading an LKM using modprobe

This pseudo detector looks for systemd loading a kernel module using modprobe:

parent_process == ("systemd") 
&&
process == "modprobe" 
&& 
command_includes ("-a" || "-af" || "-fa") 
||
command_has_arg

# An arg is any non flag command line parameter (i.e. doesn't start with a -)

Note: This may work better as two detectors. One looking for modprobe with the -a, -af, and -fa flags, and a second that looks for modprobe execution without any flags.

Non-depmod process modifying modules.dep

This pseudo detector looks for some other process modifying the modules.dep file. The modules.dep and modules.dep.bin files should only be modified by the depmod utility:

process != ("depmod") 
&&
file_modification_path_includes == ("/lib/modules/<kernel-version>/modules.dep") 
|| 
file_modification_path_includes == ("/lib/modules/<kernel-version>/modules.dep.bin")

Kernel module load from non-standard directory

This detector looks for kernel modules being loaded from unusual directories. Another variation of this that might work would be looking for any syscall to init_module that attempts to load a module outside /usr/lib, /usr/src/, or /var/lib/.

kernel_module_load 
&&
kernel_module_load_dir != ("/usr/lib/") 
&&
kernel_module_load_dir != ("/usr/src/") 
&&
kernel_module_load_dir != ("/var/lib/")

Testing Icon

Testing

Start testing your defenses against Kernel Modules and Extensions using Atomic Red Team—an open source testing framework of small, highly portable detection tests mapped to MITRE ATT&CK.

Getting started

View atomic tests for T1547.006: Kernel Modules and Extensions. In most environments, these should be sufficient to generate a useful signal for defenders.

This particular atomic runs the insmod tool to load an LKM. While this does not specifically imply persistence, it would potentially trigger any detectors that are looking for the loading of LKMs. If an EDR was monitoring the loading of LKMs, it would have visibility into this particular atomic. The file create detectors would not work however as no file is being created as part of this atomic

Outside of Atomic Red Team, you could test the case where an LKM is being loaded by a systemd service. To do this:

  1. Create a systemd unit file for the service in /etc/systemd/system/mylkmloader.service
[Unit]
Description=A simple unit file for loading a kernel module

[Service]
Type=simple
ExecStart=insmod /home/someuser/lkm.ko

[Install]
WantedBy=multi-user.target

2. Then you can run systemctl enable mylkmloader.service to make the service run upon reboot.

3. Reboot the system and run lsmod to verify that lkm.ko is running.

 

NOTE: On some systems, LSM policy may prevent systemd from loading LKMs this way. If the LKM is not loading after a reboot be sure to check your LSM logs to diagnose the issue. For SELinux on RHEL based systems you could do something like: ausearch -c "insmod"

Review and repeat

Now that you have executed one or several common tests and checked for the expected results, it’s useful to answer some immediate questions:

  • Were any of your actions detected?
  • Were any of your actions blocked or prevented?
  • Were your actions visible in logs or other defensive telemetry?

Repeat this process, performing additional tests related to this technique. You can also create and contribute tests of your own.

 
 
Back to Top