PersistenceAnti-forensicsNetwork
sysctl, Boot Parameters and binfmt_misc on Linux
Linux kernel settings in sysctl.d, /proc/sys and the boot command line: core_pattern and modprobe hijacks, weakened protections, ip_forward, binfmt_misc.
- Location
- /etc/sysctl.conf, /etc/sysctl.d/, /usr/lib/sysctl.d/, /proc/sys/ (live), /proc/cmdline, /etc/default/grub, /etc/binfmt.d/
- Proves
- Which kernel security settings were weakened, and whether a kernel callback (core_pattern, modprobe, binfmt_misc) was pointed at attacker code
- Timestamps
- File system times of configuration files only; live values carry no timestamp
- Access
- root to change; most values world-readable in /proc/sys
- Retention
- Files persist; runtime changes via sysctl -w or /proc/sys writes are lost at reboot
- Collection
- UAC, Velociraptor, cp -a, sysctl -a
Tools
Compare all tools- sysctlCLI · built into Linux
- systemd-analyzeCLI · built into Linux
- findCLI · built into Linux
- grep / zgrepCLI · built into Linux
- rpmCLI · built into Linux
- dpkgCLI · built into Linux
What it is
The kernel exposes hundreds of tunables under /proc/sys. systemd-sysctl (or sysctl --system on older setups) applies the files in the sysctl.d directories at boot, and anyone with root can change a value at runtime with sysctl -w or by writing to /proc/sys. Boot parameters on the kernel command line set others before user space starts.
A few of these settings are powerful attack surfaces. kernel.core_pattern and kernel.modprobe name programs the kernel itself executes as root. binfmt_misc registers interpreters that run whenever a matching file is executed. Other values quietly disable protections: ASLR, ptrace restrictions, pointer hiding, unprivileged BPF limits, or turn a compromised host into a router with ip_forward.
Where it lives
| Item | Path | Notes |
|---|---|---|
| Admin config | /etc/sysctl.conf, /etc/sysctl.d/*.conf | Debian 12 and later may ship no sysctl.conf at all |
| Vendor config | /usr/lib/sysctl.d/, /lib/sysctl.d/ | Package-owned; a file with the same name in /etc overrides it |
| Runtime config | /run/sysctl.d/ | Volatile |
| Live values | /proc/sys/** | Current state, including runtime-only changes |
| Boot command line | /proc/cmdline (live); /etc/default/grub GRUB_CMDLINE_LINUX; /boot/grub/grub.cfg or /boot/grub2/grub.cfg; BLS entries in /boot/loader/entries/ (RHEL, Fedora) | selinux=0, apparmor=0, audit=0, init=, module.sig_enforce=0 |
| binfmt_misc | /etc/binfmt.d/*.conf, /usr/lib/binfmt.d/; live in /proc/sys/fs/binfmt_misc/ | Applied by systemd-binfmt |
What it proves
- The intended kernel configuration after reboot (files) and the actual one right now (
/proc/sys). A difference means a runtime change. - Code-execution hooks: a
kernel.core_patternstarting with|pipes every core dump to that program as root;kernel.modprobenames the helper the kernel runs to load modules on demand, so pointing it at a script and triggering a module request (on many kernels, executing a file with unknown magic bytes is enough) runs that script as root. - binfmt_misc registrations whose interpreter is an attacker binary, with the
C(credentials) flag even giving SUID-style privileges. - Defense evasion:
kernel.randomize_va_space = 0,kernel.yama.ptrace_scope = 0,kernel.kptr_restrict = 0,kernel.dmesg_restrict = 0,kernel.unprivileged_bpf_disabled = 0,fs.suid_dumpable = 2,kernel.modules_disablednever set when policy says it should be. - Network abuse:
net.ipv4.ip_forward = 1on a host that is not a router,accept_redirectsorrp_filterchanges that support pivoting. - It does not show who made a runtime change or when; look for
sysctl -w,echo ... > /proc/sys/in shell history and audit records.
Key fields
# /etc/sysctl.d/99-perf.conf (attacker)
kernel.core_pattern = |/usr/lib/systemd/systemd-coredump-helper %P
kernel.yama.ptrace_scope = 0
net.ipv4.ip_forward = 1
# /proc/sys/fs/binfmt_misc/pyc-helper
enabled
interpreter /var/tmp/.pyc/loader
flags: OC
offset 0
magic 550d0d0a
| Setting | Normal value (typical) | Why it matters |
|---|---|---|
kernel.core_pattern | core, ` | /usr/share/apport/apport ...(Ubuntu), |
kernel.modprobe | /sbin/modprobe | Helper run by the kernel |
kernel.randomize_va_space | 2 | 0 disables ASLR |
kernel.yama.ptrace_scope | 1 on Ubuntu, 0 on some others | 0 allows attaching to any same-user process |
kernel.kptr_restrict, kernel.dmesg_restrict | 1 on Ubuntu | Hide kernel addresses and logs from users |
kernel.unprivileged_bpf_disabled | 2 (or 1) on current distros | 0 lets any user load BPF |
net.ipv4.ip_forward | 0 unless router, container host or VPN | 1 on a plain server suggests pivoting |
Timestamps
sysctl and binfmt files carry only file system times. For vendor files, compare with the package install time; for /etc files, birth time and ctime date their creation and last edit. Runtime values have no time at all. The only timing evidence for a runtime change is audit (a watch on /proc/sys or execve rules catching sysctl), shell history, or the effect itself, such as a crash handled by an unexpected helper in the journal.
Retention
Files persist until removed. Runtime changes vanish at reboot, which makes sysctl -a output from a live host important evidence; the order of volatility puts it right after memory.
Collection
# Live, early
sysctl -a > /media/ir/sysctl_a.txt 2>/dev/null
cat /proc/cmdline > /media/ir/cmdline.txt
for f in /proc/sys/fs/binfmt_misc/*; do echo "== $f"; cat "$f"; done > /media/ir/binfmt_misc.txt 2>/dev/null
systemd-analyze cat-config sysctl.d > /media/ir/sysctl_merged.txt
# Dead box
tar -C /mnt/evidence -cpf /cases/2026-017/sysctl.tar etc/sysctl.conf etc/sysctl.d usr/lib/sysctl.d lib/sysctl.d \
etc/binfmt.d usr/lib/binfmt.d etc/default/grub boot/grub/grub.cfg boot/grub2/grub.cfg boot/loader/entries 2>/dev/null
UAC runs sysctl -a in live response and collects /etc. Velociraptor can read /proc/sys values and the files directly.
Parsing
R=/mnt/evidence
grep -rhvE '^\s*(#|;|$)' "$R"/etc/sysctl.conf "$R"/etc/sysctl.d/ 2>/dev/null | sort
grep -rnE 'core_pattern|kernel\.modprobe|randomize_va_space|ptrace_scope|kptr_restrict|dmesg_restrict|unprivileged_bpf|ip_forward|suid_dumpable' \
"$R"/etc/sysctl.conf "$R"/etc/sysctl.d/ "$R"/usr/lib/sysctl.d/ 2>/dev/null
grep -rn . "$R"/etc/binfmt.d/ 2>/dev/null
grep -E 'GRUB_CMDLINE' "$R"/etc/default/grub; grep -h '^options' "$R"/boot/loader/entries/*.conf 2>/dev/null
# Live: runtime values that differ from the configured ones
systemd-analyze cat-config sysctl.d | grep -vE '^\s*(#|$)' | while IFS='=' read -r k v; do
k=$(echo $k); v=$(echo $v); [ "$(sysctl -n "$k" 2>/dev/null)" = "$v" ] || echo "drift: $k configured=$v live=$(sysctl -n "$k")"; done
Investigator tips
- Resolve every
core_patternpipe target andkernel.modprobepath to a file, then check package ownership. The crash reports page lists the legitimate handlers. - A binfmt_misc entry nobody installed (legitimate ones come from QEMU user emulation, Wine, Java or Mono packages) is a strong persistence lead; check its interpreter and flags.
- Boot parameters such as
audit=0,selinux=0,apparmor=0ormodule.sig_enforce=0added to GRUB defaults quietly switch off detection after the next reboot. Compare/proc/cmdlinefrom the live host with the configured line. ip_forward = 1together with new firewall NAT rules is the classic sign of a compromised host used as a pivot.- Sort
sysctl.dfiles by ctime; an attacker's file often has a high number prefix (99-) so it wins over vendor files.
See also
Related artifacts
- Linux Crash Reports and Core Dumps: coredump, apport
- Linux Kernel Modules: lsmod, Taint Flags and Boot Config
- eBPF Programs and Pinned Maps: Linux Kernel Implants
- dmesg, kern.log and the Kernel Ring Buffer on Linux
- /proc Live Artifacts: Processes, Sockets and Deleted Files
- Linux Firewall Logs: iptables, nftables, ufw, firewalld
- /etc/ld.so.preload and LD_PRELOAD: Linux Linker Hijacking