Weaponizing eBPF: de la teor铆a al laboratorio

Empezamos con un poco de historia... eBPF viene de BPF (Berkeley Packet Filter), desarrollado en los a帽os 90 para Linux y BSD. La idea original era filtrar paquetes de red de manera eficiente, permitiendo que los programas inspeccionaran headers de red sin salir del kernel y sin escribir c贸digo kernel complejo. Antes de BPF los filtros de paquetes eran lentos y monol铆ticos, con BPF dispon铆amos de un peque帽o lenguaje de bytecode, ejecutado por una m谩quina virtual ligera en el kernel y nos permit铆a escribir “programas” que analizaban paquetes en tiempo real y solo pasaban al userland lo que te interesaba. Era seguro, r谩pido y muy flexible.

Posteriormente alrededor de 2014-2015 surge eBPF (Extended BPF) como una extensi贸n masiva de BPF cl谩sico. Sus mejoras clave:

  • Hooks gen茅ricos: no solo red, ahora puedes engancharte a syscalls, tracepoints, funciones del kernel (kprobes), funciones de programas userland (uprobes), sockets, etc.
  • Estructuras de datos avanzadas (maps): hash, arrays, stacks, bloom filters… para compartir datos entre kernel y userland.
  • Verificaci贸n de seguridad: el bytecode pasa un verificador que garantiza que no habr谩 bucles infinitos ni accesos ilegales.

Y 10 a帽os despu茅s, eBPF (Extended Berkeley Packet Filter) se ha convertido en una de las piezas m谩s potentes e innovadoras del kernel Linux. Es decir, lo que comenz贸 como un simple mecanismo para filtrar paquetes de red ha evolucionado en un motor de ejecuci贸n de bytecode dentro del kernel, con aplicaciones en observabilidad, seguridad, tracing y rendimiento.

Debido a ello, los equipos de seguridad defensiva han abrazado eBPF para monitorizar llamadas al sistema, detectar anomal铆as en tiempo real y reducir la superficie de ataque. Pero como suele suceder, lo que potencia la defensa tambi茅n puede inspirar al ataque. Investigadores han comenzado a explorar el lado oscuro de eBPF: rootkits invisibles, t茅cnicas de evasi贸n de EDR, persistencia en kernel space y manipulaci贸n sigilosa de syscalls.

Este art铆culo vamos a jugar un poco con eBPF para entender c贸mo un atacante podr铆a abusar de esta tecnolog铆a con varias PoCs sencillas. Primero empezamos con la instalaci贸n:

馃敼 Ubuntu/Debian (kernel moderno recomendado)

# Instalar dependencias b谩sicas sudo apt update sudo apt install -y clang llvm libbpf-dev libelf-dev gcc make pkg-config git # Instalar bpftool (si no viene en el paquete) sudo apt install -y bpftool # Opcional: instalar bcc y bpftrace sudo apt install -y bpfcc-tools python3-bpfcc bpftrace

馃敼 Fedora

sudo dnf install -y clang llvm bpftool bpftrace bcc-tools bcc-devel elfutils-libelf-devel

馃敼 Arch Linux

sudo pacman -S clang llvm bpftool bcc bpftrace

馃敼 Verificar

  1. Comprueba que el kernel soporta eBPF:

uname -r

(idealmente 5.x o 6.x).

  1. Comprueba que el pseudo-filesystem est谩 montado:

mount | grep bpf

Si no aparece, m贸ntalo con:

sudo mount -t bpf none /sys/fs/bpf/
  1. Prueba que funciona con un one-liner de bpftrace:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s called execve\n", comm); }'

Deber铆as ver en tiempo real qu茅 procesos invocan execve.

Ahora que lo tenemos en nuestro sistema, vamos con las PoCs:

PoC 1: Ocultando procesos con eBPF y kprobes

Un cl谩sico rootkit en Linux manipula la syscall getdents para que ls o ps no muestren ciertos procesos. Con eBPF podemos hacer algo similar sin modificar el kernel ni cargar m贸dulos sospechosos.

C贸digo eBPF (C):

// hide_pid.bpf.c
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/dirent.h>
#include <linux/uaccess.h>

#define HIDE_PID 1337   // PID a ocultar

SEC("kprobe/getdents64")
int bpf_prog(struct pt_regs *ctx) {
    struct linux_dirent64 __user *dirent = (struct linux_dirent64 *)PT_REGS_PARM2(ctx);
    // Aqu铆 podr铆amos parsear la lista de entradas y filtrar la que coincida con HIDE_PID.
    // Por simplicidad este PoC s贸lo demuestra el hook.
    bpf_printk("Interceptada llamada a getdents64!\n");
    return 0;
}

char _license[] SEC("license") = "GPL";
Compilamos:

clang -O2 -target bpf -c hide_pid.bpf.c -o hide_pid.bpf.o

Carga con bpftool:

sudo bpftool prog load hide_pid.bpf.o /sys/fs/bpf/hide_pid sudo bpftool prog attach pinned /sys/fs/bpf/hide_pid kprobe/getdents64

Ahora cada vez que un proceso llame a getdents64, nuestro programa eBPF se ejecutar谩.
La versi贸n completa podr铆a inspeccionar los nombres de directorio y eliminar la entrada que corresponde al PID a ocultar.

PoC 2: Persistencia avanzada: mapas “pinneados” en /sys/fs/bpf/

Normalmente, cuando un proceso userland muere, cualquier estructura de memoria que haya creado tambi茅n desaparece. Pero los maps eBPF pueden “pinnearse” en el pseudo-filesystem /sys/fs/bpf/, sobreviviendo al proceso. Esto permite a un atacante almacenar datos o estados que persisten sin depender de un demonio activo.

Ejemplo conceptual seguro:

  • Crear un map y pinnearlo:

sudo bpftool map create /sys/fs/bpf/my_map type hash key 4 value 8 entries 64 name my_persistent_map sudo bpftool map update pinned /sys/fs/bpf/my_map key 42 value 666
  • Incluso si cierras el proceso que cre贸 el map, puedes volver a leerlo:

sudo bpftool map lookup pinned /sys/fs/bpf/my_map key 42

PoC 3: Evasi贸n pr谩ctica del EDR

Los EDR (Endpoint Detection & Response) funcionan principalmente interceptando o registrando syscalls cr铆ticas, como:

  • execve → ejecuci贸n de binarios.
  • openat → apertura de ficheros sensibles.
  • connect / accept → conexiones de red.

Tradicionalmente, los EDR se enganchan a estas llamadas desde userland usando librer铆as, hooking en libc, ptrace o kernel modules. Sin embargo, eBPF permite enganchar estas llamadas directamente en kernel space, antes de que el EDR las vea. Esto abre un vector conceptual de “interceptaci贸n previa”, que puede ser usado tanto para defensa como para ataques.

Veamos como eBPF intercepta la syscall antes de que el EDR la vea, permitiendo observar o analizar la actividad de manera segura en nuestro lab. El diagrama ser铆a:

Flujo conceptual

  1. Kernel space intercepta: eBPF engancha syscalls sensibles (execve, openat, connect).
  2. Map eBPF almacena datos: contadores, rutas de ficheros, PIDs.
  3. Userland lee datos: an谩lisis seguro, agregaci贸n, visualizaci贸n en tiempo real.
  4. Persistencia opcional: objetos pineados en /sys/fs/bpf/ sobreviven al proceso.
C贸digo del hook

a) Programa eBPF: execve_hook.bpf.c

#include <vmlinux.h>
#include <bpf/bpf_helpers.h>

// Map para contar llamadas por PID
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, u32);       // PID
    __type(value, __u64);   // contador
} execve_count SEC(".maps");

// Hook en syscall execve
SEC("kprobe/sys_execve")
int kprobe_execve(struct pt_regs *ctx)
{
    u32 pid = bpf_get_current_pid_tgid() >> 32;
    __u64 zero = 0, *val;

    val = bpf_map_lookup_elem(&execve_count, &pid);
    if (val)
        __sync_fetch_and_add(val, 1);
    else
        bpf_map_update_elem(&execve_count, &pid, &zero, BPF_NOEXIST);

    return 0; // syscall sigue su curso normal
}

char LICENSE[] SEC("license") = "GPL";

b) Loader en userland: execve_hook_user.c

#include <stdio.h>
#include <unistd.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "execve_hook.skel.h"

int main()
{
    struct execve_hook_bpf *skel;
    skel = execve_hook_bpf__open_and_load();
    if (!skel) return 1;

    if (execve_hook_bpf__attach(skel)) return 1;

    printf("Interceptando execve... Ctrl-C para salir\n");

    while (1) {
        sleep(5);
        int map_fd = bpf_map__fd(skel->maps.execve_count);
        u32 key = 0, next_key;
        __u64 value;
        while (bpf_map_get_next_key(map_fd, &key, &next_key) == 0) {
            if (bpf_map_lookup_elem(map_fd, &next_key, &value) == 0) {
                printf("PID %u -> execve count: %llu\n", next_key, (unsigned long long)value);
            }
            key = next_key;
        }
    }

    execve_hook_bpf__destroy(skel);
    return 0;
}

Ejemplos de mapas persistentes y agregaci贸n avanzada

a) Map persistente “pineado”

sudo bpftool map create /sys/fs/bpf/my_persistent_map type hash key 4 value 8 entries 128 name persistent_execve
  • Este mapa sobrevive al cierre del proceso que lo cre贸.

  • Ideal para almacenar contadores agregados de syscalls o eventos en laboratorio.

b) Actualizaci贸n y lectura segura

# Actualizar un valor en el mapa sudo bpftool map update pinned /sys/fs/bpf/my_persistent_map key 42 value 100 # Leer un valor sudo bpftool map lookup pinned /sys/fs/bpf/my_persistent_map key 42
  • Permite experimentar con agregaci贸n de datos de m煤ltiples procesos sin interferir con el kernel o userland.

Limitaciones y detecci贸n

  • Cargar programas eBPF requiere privilegios (CAP_BPF o CAP_SYS_ADMIN en kernels antiguos).
  • Blue Teams pueden monitorizar /sys/fs/bpf/, bpftool y syscalls relacionadas.
  • Herramientas como Falco y BPF LSM comienzan a detectar usos an贸malos.

Aun as铆, el blind spot es considerable, ya que muchos EDRs todav铆a no inspeccionan eBPF.

Comentarios