Skip to content

PersistenceAnti-forensicsExecution

Linux Kernel Modules: lsmod, Taint Flags and Boot Config

Loaded and configured Linux kernel modules (/proc/modules, /sys/module, modules-load.d, modprobe.d, taint flags) for spotting rootkits and module persistence.

Location
/proc/modules
Proves
Which kernel modules are loaded or set to load at boot, and whether unsigned or out-of-tree code entered the kernel
Timestamps
Kernel log in seconds since boot (dmesg) or journal usec since Unix epoch (UTC); config file times
Access
root; kernel addresses in /proc/modules are zeroed for unprivileged readers
Retention
Loaded state and taint until reboot; config files until deleted; log lines per journal/syslog limits
Collection
UAC, Velociraptor, AVML, LiME, journalctl

What it is

Loadable kernel modules (LKMs, .ko files) extend the running kernel with drivers, file systems and security hooks. They run with full kernel privileges, so a malicious module can hide processes, files, network connections and itself from every userland tool. Modules are also a persistence mechanism: a module name in a boot configuration file loads on every start (MITRE ATT&CK T1547.006).

Two kinds of evidence matter: the live state (what is loaded now, and the kernel's taint flags) and the configuration on disk (what loads at boot and how modprobe behaves).

Where it lives

ArtifactPathNotes
Loaded module list/proc/moduleslsmod only formats this file
Per-module sysfs/sys/module/<name>/Also contains entries for built-in code with parameters, so it is not a 1:1 match with /proc/modules
Kernel taint/proc/sys/kernel/taintedBitmask, sticky until reboot
Module lock/proc/sys/kernel/modules_disabled1 means no loading or unloading until reboot
Module tree/lib/modules/<release>/ (kernel/, updates/, extra/, modules.dep)/usr/lib/modules on merged-/usr systems
Boot-time load lists/etc/modules-load.d/*.conf, /run/modules-load.d/, /usr/local/lib/modules-load.d/, /usr/lib/modules-load.d/Read by systemd-modules-load.service
Legacy load list (Debian/Ubuntu)/etc/modulesLinked in as /etc/modules-load.d/modules.conf
modprobe configuration/etc/modprobe.d/, /run/modprobe.d/, /usr/local/lib/modprobe.d/, /lib/modprobe.d/install, options, blacklist, alias, softdep
depmod configuration/etc/depmod.d/, /lib/depmod.d/override can prefer a module from another directory
Kernel messagesdmesg, journal (-k), /var/log/kern.log (Debian/Ubuntu), /var/log/messages (RHEL family)Taint and signature warnings

What it proves

  • Which modules the kernel reports as loaded, their size, dependencies and state.
  • Whether any proprietary, out-of-tree, unsigned or force-loaded module has been loaded since boot (taint flags), even if later unloaded.
  • Which modules are configured to load at boot, and whether a modprobe install directive runs a command instead of (or besides) loading a module.
  • With auditd rules on init_module/finit_module, who loaded which module and when.
  • Not proven: a clean /proc/modules does not prove a clean kernel. A rootkit can unlink itself from the module list; only memory analysis gives an independent view.

Key fields

/proc/modules columns (from the kernel source):

ColumnMeaning
nameModule name
sizeMemory size in bytes
refcount / used byReference count and dependent modules (- if none)
stateLive, Loading or Unloading
addressBase address; zeros depending on kptr_restrict and privileges
taint flagsTrailing (POE)-style letters when the module tainted the kernel

Taint bits worth knowing (Documentation/admin-guide/tainted-kernels): bit 0 P proprietary module, bit 1 F force-loaded, bit 3 R force-unloaded, bit 10 C staging driver, bit 12 O out-of-tree, bit 13 E unsigned, bit 15 K live patched. /sys/module/<name>/taint shows the letters per module.

modinfo on a .ko file shows filename, vermagic, srcversion, depends, license and, when signed, signer, sig_key and sig_hashalgo.

Kernel log messages when a module taints the kernel:

<mod>: loading out-of-tree module taints kernel.
<mod>: module verification failed: signature and/or required key missing - tainting kernel
<mod>: module license taints kernel.

Timestamps

dmesg prints seconds since boot; convert with the boot time, or read the same messages from the journal, which stores wall-clock time in microseconds since 1970-01-01 UTC. Configuration and .ko files have only file system times: package modules carry build-time mtimes, so a module with a recent ctime that no package owns stands out. auditd records use msg=audit(<epoch seconds>.<ms>:<serial>).

# offline: filter on the kernel transport (-k would imply the analysis host's current boot)
journalctl -D /mnt/evidence/var/log/journal _TRANSPORT=kernel -o short-iso | grep -iE 'taint|module verification'

Retention

The loaded list and taint flags live only in kernel memory and reset at reboot. Configuration files persist until deleted. Kernel messages survive in the journal or kern.log/messages within rotation limits; the ring buffer shown by dmesg is small and overwritten on busy systems.

Collection

Follow the order of volatility: capture memory first (AVML or LiME; LiME is itself an out-of-tree module and taints the kernel), then live state, then disk.

cat /proc/modules > modules.txt; ls -la /sys/module > sys_module.txt
cat /proc/sys/kernel/tainted; grep '(' /proc/modules      # modules that set taint
dmesg | grep -i taint
tar -C /mnt/evidence -czf modconf.tgz etc/modules etc/modules-load.d etc/modprobe.d \
  etc/depmod.d usr/lib/modules-load.d usr/lib/modprobe.d 2>/dev/null

UAC's live response collects lsmod, /sys/module listings with module parameters, the taint value, taint-related dmesg lines and tainting modules from /proc/modules. Velociraptor's Linux.Proc.Modules parses /proc/modules across a fleet.

Parsing

# decode the taint bitmask
t=$(cat tainted.txt); for b in $(seq 0 19); do [ $(( (t>>b)&1 )) -eq 1 ] && echo "bit $b set"; done
# module files changed after install (then check ownership with dpkg -S / rpm -qf)
find /mnt/evidence/lib/modules -name '*.ko*' -newer /mnt/evidence/etc/hostname -printf '%C+ %p\n'
# commands hidden in modprobe config, and every module set to load at boot
grep -rnE '^\s*(install|remove)\s' /mnt/evidence/etc/modprobe.d
grep -rhvE '^\s*([#;]|$)' /mnt/evidence/etc/modules-load.d /mnt/evidence/etc/modules 2>/dev/null

In memory, Volatility 3 offers linux.lsmod, linux.malware.check_modules (compares the module list with sysfs), linux.malware.hidden_modules (carves module structures missing from the list), linux.malware.modxview, linux.module_extract and linux.malware.check_syscall. The older names without malware. were deprecated shims with a 2026-06-07 removal date.

Investigator tips

  • The out-of-tree and unsigned warnings are printed only for the first such module per boot, so later modules can load without a new message. Use /proc/modules taint letters and /sys/module/*/taint to find every culprit.
  • A module can be loaded with insmod from any path, such as /tmp or /dev/shm; there may be no copy under /lib/modules.
  • install <name> <command> in /etc/modprobe.d runs a shell command whenever that module is requested, a quiet persistence and execution path.
  • A server with no third-party drivers that shows O or E taint needs an explanation; compare with DKMS packages and vendor agents.
  • Record modules_disabled during live response: when it is 1, nothing can be loaded or unloaded until reboot, which also blocks LiME.
  • Add auditd rules for init_module, finit_module and delete_module; the KERN_MODULE record names the module even when a custom loader is used. See auditd.
  • Userland tools cannot see a hiding LKM; when ld.so.preload checks and /proc look clean but behaviour does not, rely on memory acquisition.

See also