From c3574b04bbbb2daae885824db704c44b74d761a9 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 12 Sep 2026 21:12:29 +0000 Subject: [PATCH] Add content from: Detection Primitives for eBPF Rootkits --- .../linux-post-exploitation/README.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/linux-hardening/post-exploitation/linux-post-exploitation/README.md b/src/linux-hardening/post-exploitation/linux-post-exploitation/README.md index 893ce2d12c8..1a5c1314b64 100644 --- a/src/linux-hardening/post-exploitation/linux-post-exploitation/README.md +++ b/src/linux-hardening/post-exploitation/linux-post-exploitation/README.md @@ -229,6 +229,54 @@ grep -RInE 'iptables|bpfd|dockerd|hpas|/dev/shm|/var/tmp|/tmp/' \ If the host supports `bpftool`, also baseline legitimate BPF usage. Unexpected packet filters, raw sockets, or process names that do not match the backing executable are strong post-exploitation signals even when no listening port is visible.[[7]](#references) +## eBPF rootkit stealth, anti-debugging, and detection + +These are post-compromise techniques: the operator must already be allowed to load and attach the required eBPF program types. Once attached, however, a program can tamper with user-space observations at kernel-controlled boundaries without changing the underlying socket, process, or BPF object.[[13]](#references)[[14]](#references)[[15]](#references)[[16]](#references) + +### Skip Netlink records by extending the previous length + +`ss -tn` opens `NETLINK_INET_DIAG`, sends a `SOCK_DIAG_BY_FAMILY | NLM_F_DUMP` request, and parses the returned chain of `nlmsghdr`/`inet_diag_msg` records. A kprobe on entry to `__sys_recvmsg` can save the destination user pointer; the paired kretprobe runs after the kernel fills the buffer but before `ss` parses it. To hide contiguous record **B** between **A** and **C**, use `bpf_probe_write_user()` to apply the following modification:[[16]](#references) + +```c +A.nlmsg_len += NLMSG_ALIGN(B.nlmsg_len); +``` + +The bytes for B remain in the response, but `NLMSG_NEXT` advances by `NLMSG_ALIGN(A.nlmsg_len)` and lands on C. Because the enlarged length only incorporates bytes already present, the remaining-buffer check in `NLMSG_OK` still succeeds. This record-skipping primitive can affect any trusted consumer that walks an attacker-writable, length-delimited response using embedded sizes.[[16]](#references) + +### Truncate BPF object enumeration with `-ENOENT` + +An entry tracepoint on `sys_enter_bpf` can identify `BPF_PROG_GET_NEXT_ID` (11), `BPF_MAP_GET_NEXT_ID` (12), and `BPF_LINK_GET_NEXT_ID` (31), then cache the request by `pid_tgid`. A return probe on the architecture-specific `sys_bpf` wrapper reads the selected `next_id`; when it matches a protected ID, `bpf_override_return(ctx, -ENOENT)` makes `bpftool`/libbpf interpret the result as the natural end of the walk. This hides the protected object **and every object with a higher ID**, rather than only skipping one entry.[[14]](#references)[[16]](#references) + +The helper is restricted to compatible kprobe programs and error-injectable kernel functions, and requires `CONFIG_BPF_KPROBE_OVERRIDE=y`. Compare iterative enumeration with direct-ID lookup, which uses a different BPF command; an object that opens directly but never appears in the walk indicates enumeration tampering. LinkPro samples also printed `BPF cmd: %d, start_id: %u` and `HIDING NEXT_ID: %u` to `trace_pipe`, although these mutable debug strings are only secondary indicators.[[14]](#references)[[16]](#references) + +```bash +sudo bpftool prog list +sudo bpftool prog show id +sudo bpftool map list && sudo bpftool map show id +sudo bpftool link list && sudo bpftool link show id +sudo cat /sys/kernel/debug/tracing/trace_pipe | grep -E 'BPF cmd:|HIDING NEXT_ID:' +``` + +Apply the same sequential-versus-direct lookup test to maps and links. A live-response workflow should not treat a clean `bpftool ... list` as kernel ground truth after untrusted BPF programs have attached.[[14]](#references)[[16]](#references) + +### Kill a debugger before `ptrace()` attaches + +A program on `tp/syscalls/sys_enter_ptrace` runs before the kernel processes the request. It can filter for `PTRACE_ATTACH` (`0x10`) or `PTRACE_SEIZE` (`0x4206`), extract the target PID, and query a protected-PID map. On a match, `bpf_send_signal(SIGKILL)` signals the **current process making `ptrace()`**—the debugger or tracer—not the requested target, so `gdb` or `strace` disappears before attachment completes. `bpf_send_signal_thread()` is the thread-scoped variant of the same anti-analysis capability.[[15]](#references)[[16]](#references) + +### Fingerprint capabilities during `BPF_PROG_LOAD` + +A helper call reaches the verifier as an 8-byte `BPF_CALL` whose 32-bit `imm` contains a stable `enum bpf_func_id`. `check_helper_call()` obtains the helper prototype and type-checks it; later, `do_misc_fixups()` replaces `imm` with a signed implementation offset from `__bpf_call_base`. A defensive kprobe on `check_helper_call` can therefore build a per-load helper bitmap before the original IDs disappear, while `security_bpf_prog` supplies the program type, expected attach type, ID, name, tag, and map metadata. Correlate both observations with the in-flight `bpf(2)` syscall and emit the immutable fingerprint at syscall exit.[[16]](#references) + +`BPF_PROG_LOAD` is the highest-trust observation point because the submitted instructions are still inert and cannot yet falsify enumeration, rewrite response buffers, or detach monitoring probes. Capability-oriented rules can alert on rare helpers without depending on attacker-controlled program names:[[16]](#references) + +```text +bpf.cmd == BPF_PROG_LOAD && BPF_PROBE_WRITE_USER in bpf.prog.helpers +bpf.cmd == BPF_PROG_LOAD && BPF_OVERRIDE_RETURN in bpf.prog.helpers +bpf.cmd == BPF_PROG_LOAD && (BPF_SEND_SIGNAL in bpf.prog.helpers || BPF_SEND_SIGNAL_THREAD in bpf.prog.helpers) +``` + +Allowlist known loaders where necessary, but raise confidence when two or more of these helper families are loaded close together, especially by an interactive shell or a binary in a world-writable path.[[16]](#references) + ## References - [1] [0xdf – HTB Planning (Grafana env creds reuse, systemd BASIC_AUTH)](https://0xdf.gitlab.io/2025/09/13/htb-planning.html) @@ -243,5 +291,9 @@ If the host supports `bpftool`, also baseline legitimate BPF usage. Unexpected p - [10] [3snake - dump sshd and sudo credential-related strings](https://github.com/blendin/3snake) - [11] [Gaming the System: How a Chinese-Speaking Actor Turned Brazilian Government Sites into an SEO Weapon](https://research.checkpoint.com/2026/gaming-the-system-how-a-chinese-speaking-actor-turned-brazilian-government-sites-into-an-seo-weapon/) - [12] [`ptrace(2)` Linux manual page](https://man7.org/linux/man-pages/man2/ptrace.2.html) +- [13] [VoidLink: The Cloud-Native Malware Framework](https://research.checkpoint.com/2026/voidlink-the-cloud-native-malware-framework/) +- [14] [LinkPro: eBPF rootkit analysis](https://www.synacktiv.com/en/publications/linkpro-ebpf-rootkit-analysis) +- [15] [Atomic Arch npm Campaign Adds Malicious Dependency](https://www.sonatype.com/blog/atomic-arch-npm-campaign-adds-malicious-dependency) +- [16] [Detection primitives for eBPF rootkits](https://securitylabs.datadoghq.com/articles/detection-primitives-for-ebpf-rootkits/) {{#include ../../../banners/hacktricks-training.md}}