Skip to content

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
  • 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

ItemPathNotes
Admin config/etc/sysctl.conf, /etc/sysctl.d/*.confDebian 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_pattern starting with | pipes every core dump to that program as root; kernel.modprobe names 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_disabled never set when policy says it should be.
  • Network abuse: net.ipv4.ip_forward = 1 on a host that is not a router, accept_redirects or rp_filter changes 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
SettingNormal value (typical)Why it matters
kernel.core_patterncore, `/usr/share/apport/apport ...(Ubuntu),
kernel.modprobe/sbin/modprobeHelper run by the kernel
kernel.randomize_va_space20 disables ASLR
kernel.yama.ptrace_scope1 on Ubuntu, 0 on some others0 allows attaching to any same-user process
kernel.kptr_restrict, kernel.dmesg_restrict1 on UbuntuHide kernel addresses and logs from users
kernel.unprivileged_bpf_disabled2 (or 1) on current distros0 lets any user load BPF
net.ipv4.ip_forward0 unless router, container host or VPN1 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_pattern pipe target and kernel.modprobe path 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=0 or module.sig_enforce=0 added to GRUB defaults quietly switch off detection after the next reboot. Compare /proc/cmdline from the live host with the configured line.
  • ip_forward = 1 together with new firewall NAT rules is the classic sign of a compromised host used as a pivot.
  • Sort sysctl.d files by ctime; an attacker's file often has a high number prefix (99-) so it wins over vendor files.

See also