T1547.006
Kernel Modules and Extensions
Kernel modules and extensions offer adversaries a reliable means of establishing persistence on Linux systems.
Pairs with this songThreat sounds
We don’t hate Linux, not at all! But we understand the frustration around trying to secure the kernel might lead you to say so.
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:
- call the syscalls directly
- use the glibc wrappers for the syscalls
- 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:
Take Action
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:
- 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. - 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.
- 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.
- Ensure UEFI secure boot is enabled. Enabling UEFI secure boot allows features such as signed driver loading to be enforced.
- Enable Linux IAM if possible and practical for your environment.
- 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 theinit_module
andfinit_module
functions, and so policies can be created to affect who is allowed to load kernel modules and under what conditions.
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
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.
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
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:
- 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.