CreateRemoteThread fĂŒr Linux

Mitsuha bringt neue Streams WinAPI verfĂŒgt ĂŒber eine CreateRemoteThread- Funktion, mit der Sie einen neuen Thread im Adressraum eines anderen Prozesses starten können. Es kann fĂŒr eine Vielzahl von DLL-Injektionen verwendet werden, sowohl fĂŒr schlechte Zwecke (Cheats in Spielen, Passwortdiebstahl usw.) als auch, um einen Fehler in einem laufenden Programm im laufenden Betrieb zu beheben oder Plugins an Stellen hinzuzufĂŒgen, an denen dies nicht der Fall war zur VerfĂŒgung gestellt.


Im Allgemeinen hat diese Funktion ein zweifelhaftes Anwendungsdienstprogramm, daher ist es nicht verwunderlich, dass Linux kein fertiges Analogon zu CreateRemoteThread hat. Ich habe mich jedoch gefragt, wie es implementiert werden kann. Das Studium des Themas wurde zu einem guten Abenteuer.


Ich werde im Detail darĂŒber sprechen, wie mit Hilfe der ELF-Spezifikation, einigen Kenntnissen der x86_64-Architektur und Linux-Systemaufrufen Ihr eigener kleiner Debugger geschrieben wird, der beliebigen Code in einem bereits laufenden und funktionierenden Prozess laden und ausfĂŒhren kann.


Zum VerstĂ€ndnis des Textes sind Grundkenntnisse ĂŒber die Systemprogrammierung fĂŒr Linux erforderlich: die C-Sprache, das Schreiben und Debuggen von Programmen, das VerstĂ€ndnis der Rolle von Maschinencode und Speicher im Computer, das Konzept von Systemaufrufen, die Kenntnis der Hauptbibliotheken und das Lesen der Dokumentation.



Infolgedessen konnte ich die Möglichkeit zur Vorschau von Kennwörtern im Gnome Control Center hinzufĂŒgen:


Injektionsdemonstration im Gnome Control Center


Hauptideen


Wenn die Anforderungen zum Laden des Codes in einen bereits laufenden Prozess keine Klausel enthalten wĂŒrden, wĂ€re die Lösung Ă€ußerst einfach: LD_PRELOAD. Diese Umgebungsvariable ermöglicht das Laden einer beliebigen Bibliothek mit der Anwendung. In gemeinsam genutzten Bibliotheken können Sie Konstruktorfunktionen definieren, die beim Laden der Bibliothek ausgefĂŒhrt werden.


Zusammen ermöglichen LD_PRELOAD und die Konstruktoren die AusfĂŒhrung von beliebigem Code in jedem Prozess unter Verwendung eines dynamischen Loaders. Dies ist eine relativ bekannte Funktion, die hĂ€ufig zum Debuggen verwendet wird. Sie können beispielsweise Ihre eigene Bibliothek mit der Anwendung herunterladen, die die Funktionen malloc () und free () definiert, um Speicherlecks zu erkennen.


Leider funktioniert LD_PRELOAD nur, wenn der Prozess startet. Es kann nicht zum Laden einer Bibliothek in einen bereits laufenden Prozess verwendet werden. Es gibt eine dlopen () -Funktion zum Laden von Bibliotheken, wĂ€hrend der Prozess ausgefĂŒhrt wird, aber natĂŒrlich muss der Prozess sie selbst aufrufen, um die Plugins zu laden.


Über statische ausfĂŒhrbare Dateien

LD_PRELOAD funktioniert nur mit Programmen, die den dynamischen Loader verwenden. Wenn das Programm mit dem Schalter -static , enthĂ€lt es alle erforderlichen Bibliotheken. In diesem Fall wird die Auflösung von AbhĂ€ngigkeiten in Bibliotheken zur Erstellungszeit durchgefĂŒhrt, und das Programm ist normalerweise nicht bereit und kann Bibliotheken nach der Assemblierung zur Laufzeit nicht dynamisch laden.

In statisch zusammengestellten Programmen können Sie Code zur Laufzeit einbetten, dies sollte jedoch auf etwas andere Weise erfolgen. Und dies ist nicht ganz sicher, da das Programm möglicherweise nicht fĂŒr eine solche Wende bereit ist.

Im Allgemeinen gibt es keine vorgefertigte bequeme Lösung, Sie mĂŒssen Ihr Fahrrad schreiben. Andernfalls wĂŒrden Sie diesen Text nicht lesen :)


Um den Prozess eines anderen zur AusfĂŒhrung eines Codes zu zwingen, mĂŒssen Sie konzeptionell die folgenden Aktionen ausfĂŒhren:


  1. Holen Sie sich die Kontrolle ĂŒber den Zielprozess.
  2. Laden Sie den Code in den Speicher des Zielprozesses.
  3. Bereiten Sie den heruntergeladenen Code fĂŒr die AusfĂŒhrung im Zielprozess vor.
  4. Organisieren Sie die AusfĂŒhrung des heruntergeladenen Codes durch den Zielprozess.

Lass uns gehen ...


Kontrolle bekommen


ZunĂ€chst mĂŒssen wir den Zielprozess unserem Willen unterordnen. Schließlich fĂŒhren Prozesse normalerweise nur ihren eigenen Code oder den Code geladener Bibliotheken oder die Ergebnisse der JIT-Kompilierung aus. Aber sicher nicht unser Code.


Eine Möglichkeit besteht darin, eine Art SicherheitslĂŒcke in dem Prozess zu verwenden, mit der Sie die Kontrolle ĂŒbernehmen können. Ein klassisches Beispiel aus Tutorials: PufferĂŒberlauf, mit dem die RĂŒcksprungadresse auf dem Stapel neu geschrieben werden kann. Es macht Spaß, funktioniert manchmal sogar, ist aber nicht fĂŒr den allgemeinen Fall geeignet.


Wir werden einen anderen, ehrlichen Weg einschlagen, um die Kontrolle zu erlangen: das Debuggen von Systemaufrufen . Interaktive Debugger können Prozesse von Drittanbietern perfekt stoppen, AusdrĂŒcke auswerten und viele andere Dinge. Sie können - wir können.


Unter Linux ist der Hauptaufruf des Debugging-Systems ptrace () . Sie können eine Verbindung zu Prozessen herstellen, deren Status ĂŒberprĂŒfen und den Fortschritt ihrer AusfĂŒhrung steuern. ptrace () ist fĂŒr sich genommen ziemlich gut dokumentiert, aber die Details seiner Verwendung sind nur in der Praxis klar.


Laden von Code in den Prozessspeicher


Bei PufferĂŒberlĂ€ufen ist die Nutzlast ( Shell-Code ) normalerweise in den Inhalten enthalten, die denselben Puffer ĂŒberlaufen. Bei Verwendung des Debuggers kann der erforderliche Code direkt in den Prozessspeicher geschrieben werden. In WinAPI gibt es hierfĂŒr eine spezielle Funktion WriteProcessMemory. Linux entspricht zu diesem Zweck der UNIX-Methode: FĂŒr jeden Prozess im System gibt es eine Datei / proc / $ pid / mem , die den Speicher dieses Prozesses anzeigt. Es ist möglich, mit der ĂŒblichen Eingabe / Ausgabe etwas in den Prozessspeicher zu schreiben.


Code fĂŒr die AusfĂŒhrung vorbereiten


Nur Code in den Speicher zu schreiben, reicht nicht aus. Es muss noch in den ausfĂŒhrbaren Speicher geschrieben werden . Bei der Aufzeichnung ĂŒber eine SicherheitsanfĂ€lligkeit gibt es nicht triviale Schwierigkeiten, aber da wir den Zielprozess vollstĂ€ndig steuern können, ist es fĂŒr uns kein Problem, den „richtigen“ Speicher fĂŒr uns selbst zu finden oder zuzuweisen.


Ein weiterer wichtiger Punkt bei der Vorbereitung ist der Shell-Code selbst. Darin werden wir wahrscheinlich einige Funktionen aus Bibliotheken verwenden wollen, wie Eingabe-Ausgabe, grafische Grundelemente und so weiter. Wir mĂŒssen jedoch nackten Maschinencode aufschreiben, der an sich keine Ahnung von den Adressen all dieser coolen Funktionen in Bibliotheken hat. Woher bekommen Sie sie?


Um die Lebensdauer des Betriebssystems zu vereinfachen und die Lebensdauer von Schadcode zu verkomplizieren, verwenden Bibliotheken normalerweise keine festen Adressen (und enthalten sogenannten positionsunabhÀngigen Code ). Die Adressen können also nicht erraten werden.


Wenn der Prozess normal startet, ist der Loader , der die Verschiebungen durchfĂŒhrt , fĂŒr die Ermittlung der genauen Adressen der Bibliotheken verantwortlich. Allerdings erfĂŒllt er zu Beginn nur einmal. Wenn der Prozess das dynamische Laden von Bibliotheken ermöglicht, befindet sich ein dynamischer Loader darin, der dasselbe tun kann, wĂ€hrend der Prozess ausgefĂŒhrt wird. Die Adresse des dynamischen Laders ist jedoch ebenfalls nicht festgelegt.


Bei Bibliotheken gibt es im Allgemeinen vier Optionen:


  • Verwenden Sie ĂŒberhaupt keine Bibliotheken, machen Sie alles bei sauberen Systemaufrufen
  • FĂŒgen Sie Kopien aller erforderlichen Bibliotheken in den Shell-Code ein
  • erledigen Sie die Arbeit des dynamischen Laders selbst
  • Finden Sie einen dynamischen Bootloader und lassen Sie ihn unsere Bibliotheken laden

Wir werden uns fĂŒr Letzteres entscheiden, weil wir die Bibliotheken wollen und unseren vollstĂ€ndigen Bootloader fĂŒr eine lange Zeit schreiben. Dies ist nicht die geheimste Methode und nicht die interessanteste, sondern die einfachste, leistungsfĂ€higste und zuverlĂ€ssigste.


Übertragung der Kontrolle auf den Code


Mit ptrace () können Sie die Prozessorregister Ă€ndern, sodass es keine Probleme bei der Übertragung der Steuerung auf den geladenen und vorbereiteten Code geben sollte: Schreiben Sie einfach die Adresse unseres Codes in das% rip-Register - und voila! In Wirklichkeit ist jedoch nicht alles so einfach. Die Schwierigkeiten hĂ€ngen mit der Tatsache zusammen, dass der debuggte Prozess tatsĂ€chlich nicht verschwunden ist und dass auch eine Art Code ausgefĂŒhrt wurde und weiterhin ausgefĂŒhrt wird.


Lösungsskizze


Insgesamt werden wir unseren Ablauf in einem Drittanbieterprozess wie folgt implementieren:


  1. Wir sind mit dem Zielprozess fĂŒr das Debuggen verbunden.
  2. Wir finden die notwendigen Bibliotheken im Speicher:
    • libdl - um eine neue Bibliothek zu laden
    • libpthread - um einen neuen Thread zu starten
  3. Die notwendigen Funktionen finden wir in den Bibliotheken:
    • libdl: dlopen (), dlsym ()
    • libpthread: pthread_create (), pthread_detach ()
  4. Wir fĂŒhren den Shell-Code in den Speicher des Zielprozesses ein:


     void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void *entry = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

  5. Wir geben den zu erfĂŒllenden Shell-Code an.

Infolgedessen werden Bibliotheken das Richtige fĂŒr uns tun: Sie laden unsere Bibliothek mit dem Code, den wir im Speicher benötigen, und starten einen neuen Thread, der diesen Code ausfĂŒhrt.


EinschrÀnkungen


Der oben beschriebene Ansatz bringt bestimmte EinschrÀnkungen mit sich:


  • Der Bootloader muss ĂŒber ausreichende Berechtigungen verfĂŒgen, um den Zielprozess zu debuggen.
  • Der Prozess sollte libdl verwenden (bereit zum dynamischen Laden von Modulen).
  • Der Prozess sollte libpthread verwenden (bereit fĂŒr Multithreading).
  • Statische Anwendungen werden nicht unterstĂŒtzt.

Außerdem bin ich persönlich zu faul, um mich mit der UnterstĂŒtzung von All-All-Architekturen zu beschĂ€ftigen, sodass wir uns auf x86_64 beschrĂ€nken werden. (Sogar ein 32-Bit x86 wĂ€re komplizierter.)


Wie Sie sehen, macht all dies der verdeckten Verwendung mit böswilligen Zielen ein Ende. Die Aufgabe behĂ€lt jedoch weiterhin das Forschungsinteresse und lĂ€sst sogar eine schwache Chance fĂŒr die industrielle Nutzung.


Exkurs: ĂŒber die Verwendung von libdl und libpthread


Ein erfahrener professioneller Leser mag sich fragen: Warum sollte libdl benötigt werden, wenn die internen Funktionen __libc_dlopen_mode () und __libc_dlsym () bereits in glibc integriert sind und libdl nur ein Wrapper darĂŒber ist? Warum sollte libpthread ebenfalls benötigt werden, wenn mit dem Systemaufruf clone () einfach ein neuer Thread erstellt werden kann?


In der Tat gibt es im Internet bei weitem kein Beispiel dafĂŒr, wie sie verwendet werden:



Sie werden sogar in der populÀren Hackerliteratur erwÀhnt:


  • Lernen der binĂ€ren Linux-Analyse
  • Die Kunst der GedĂ€chtnisforensik

Warum also nicht? Zumindest, weil wir keinen Schadcode schreiben, bei dem eine Lösung geeignet ist, die 90% der ÜberprĂŒfungen auslĂ€sst, 20-mal weniger Speicherplatz beansprucht, aber auch in 80% der FĂ€lle funktioniert. Außerdem wollte ich alles mit meinen eigenen HĂ€nden ausprobieren.


In der Tat ist libdl nicht erforderlich , um die Bibliothek im Fall von glibc zu laden. Die Verwendung durch den Prozess zeigt an, dass es eindeutig zum dynamischen Laden von Code bereit ist. Trotzdem können Sie die Verwendung von libdl grundsĂ€tzlich ablehnen (da wir spĂ€ter auch noch nach glibc suchen mĂŒssen).


Warum ĂŒberhaupt dlopen () in glibc?

Dies ist auf ihre Weise eine interessante Frage. Kurze Antwort: Implementierungsdetails.

Dies ist eine Frage des Name Service Switch (NSS) - einer der Teile von glibc, der die Übersetzung verschiedener Namen ermöglicht: Namen von Maschinen, Protokollen, Benutzern, Mailservern usw. Sie ist fĂŒr Funktionen wie getaddrinfo () verantwortlich, ĂŒber die IP-Adressen abgerufen werden DomĂ€nenname und getpwuid (), um Informationen ĂŒber den Benutzer anhand seiner numerischen Kennung abzurufen.

NSS hat eine modulare Architektur und Module werden dynamisch geladen. TatsĂ€chlich benötigte glibc dafĂŒr auch Mechanismen zum dynamischen Laden von Bibliotheken. Wenn Sie versuchen, getaddrinfo () in einer statisch zusammengestellten Anwendung zu verwenden, gibt der Linker daher eine "unverstĂ€ndliche" Warnung aus:
 /tmp/build/socket.o: In der Funktion `Socket :: bind ':
 socket.o :(. text + 0x374): Warnung: Verwenden von 'getaddrinfo' in statisch verknĂŒpften
 Anwendungen benötigen zur Laufzeit die gemeinsam genutzten Bibliotheken aus der glibc-Version
 zum VerknĂŒpfen verwendet

Bei Threads ist ein Thread normalerweise nicht nur ein Stapel und ausfĂŒhrbarer Code, sondern auch globale Daten, die im Thread-Local Storage (TLS) gespeichert sind. Die korrekte Initialisierung eines neuen Threads erfordert den koordinierten Betrieb des Betriebssystemkerns, eines BinĂ€rcode-Laders und einer Programmiersprachen-Laufzeit. Daher reicht ein einfacher Aufruf von clone () aus, um einen Stream zu erstellen, der in die Datei „Hello world!“ Schreiben kann. Dies funktioniert jedoch möglicherweise nicht fĂŒr komplexeren Code, der Zugriff auf TLS und andere interessante Dinge benötigt, die vor den Augen des Anwendungsprogrammierers verborgen sind.


Ein weiterer Punkt im Zusammenhang mit Multithreading sind Single-Thread-Prozesse. Was passiert, wenn wir in einem Prozess, der nicht als Multithreading konzipiert wurde, einen neuen Thread erstellen? Richtig, vages Verhalten. In der Tat gibt es dabei keine Synchronisation der Arbeit zwischen Threads, was frĂŒher oder spĂ€ter zu einer BeschĂ€digung der Daten fĂŒhren wird. Wenn wir verlangen, dass die Anwendung libpthread verwendet, können wir sicher sein, dass sie in einer Multithread-Umgebung einsatzbereit ist (zumindest sollte sie bereit sein).


Schritt 1. Verbindung zum Prozess


Zuerst mĂŒssen wir zum Debuggen eine Verbindung zum Zielprozess herstellen und spĂ€ter die Verbindung wieder trennen. Hier kommt der Systemaufruf ptrace () ins Spiel .


Erster Kontakt mit ptrace ()


In der Dokumentation zu ptrace () finden Sie fast alle notwendigen Informationen:


   Anbringen und Abnehmen
        Mit dem Aufruf kann ein Thread an den Tracer angehÀngt werden

            ptrace (PTRACE_ATTACH, pid, 0, 0);

        oder

            ptrace (PTRACE_SEIZE, pid, 0, PTRACE_O_flags);

        PTRACE_ATTACH sendet SIGSTOP an diesen Thread.  Wenn der Tracer das will
        SIGSTOP hat keine Wirkung, es muss unterdrĂŒckt werden.  Beachten Sie, dass wenn
        WÀhrend des AnhÀngens werden gleichzeitig andere Signale an diesen Thread gesendet
        Der Tracer kann sehen, dass der Tracee mit einem anderen Signal in den Signal-Delivery-Stop eintritt
        nal (s) zuerst!  Die ĂŒbliche Praxis besteht darin, diese Signale bis erneut zu injizieren
        SIGSTOP wird angezeigt, dann wird die SIGSTOP-Injektion unterdrĂŒckt.  Der Designfehler
        Hier ist, dass ein Ptrace Attach und ein gleichzeitig gelieferter SIGSTOP können
        Rennen und der gleichzeitige SIGSTOP können verloren gehen.

Der erste Schritt ist also die Verwendung von PTRACE_ATTACH:


 int ptrace_attach(pid_t pid) { /*     */ if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; /*    */ if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 

Nach ptrace () ist der Zielprozess noch nicht zum Debuggen bereit. Wir haben uns damit verbunden, aber fĂŒr eine interaktive Untersuchung des Zustands des Prozesses muss es gestoppt werden. ptrace () sendet ein SIGSTOP-Signal an den Prozess, aber wir mĂŒssen noch warten, bis der Prozess tatsĂ€chlich stoppt.


Verwenden Sie zum Warten den Systemaufruf waitpid (). Gleichzeitig sind einige interessante GrenzfÀlle erwÀhnenswert. Erstens kann der Prozess einfach enden oder sterben, ohne SIGSTOP erhalten zu haben. In diesem Fall können wir nichts tun. Zweitens kann zuvor ein anderes Signal an den Prozess gesendet werden. In diesem Fall sollten wir den Prozess es verarbeiten lassen (mit PTRACE_CONT) und uns selbst weiter auf unser SIGSTOP warten:


 static int wait_for_process_stop(pid_t pid, int expected_signal) { for (;;) { int status = 0; /* ,    -  */ if (waitpid(pid, &status, 0) < 0) return -1; /*      —   */ if (WIFSIGNALED(status) || WIFEXITED(status)) return -1; /*   ,     */ if (WIFSTOPPED(status)) { /* *  WSTOPSIG()   , *   ptrace()   *     . */ int stop_signal = status >> 8; /*    ,    */ if (stop_signal == expected_signal) break; /*        */ if (ptrace(PTRACE_CONT, pid, 0, stop_signal) < 0) return -1; continue; } /*   —   */ return -1; } return 0; } 

Prozessabschaltung


Das Stoppen des Debugging-Prozesses ist viel einfacher: Verwenden Sie einfach PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 

Genau genommen ist es nicht immer erforderlich, den Debugger explizit zu deaktivieren. Wenn der Debugger-Prozess endet, wird die Verbindung zu allen debuggten Prozessen automatisch getrennt, und die Prozesse werden fortgesetzt, wenn sie von ptrace () gestoppt wurden. Wenn der Debugging-Prozess jedoch vom Debugger mithilfe des SIGSTOP-Signals ohne Verwendung von ptrace () explizit gestoppt wurde, wird er ohne das entsprechende SIGCONT- oder PTRACE_DETACH-Signal nicht aktiviert. Daher ist es besser, sich kulturell von Prozessen zu trennen.


Ptrace_scope-Einstellung


Der Debugger hat die volle Kontrolle ĂŒber den zu debuggenden Prozess. Wenn jemand irgendetwas debuggen könnte, was wĂ€re die Weite fĂŒr bösartigen Code! Es ist offensichtlich, dass interaktives Debuggen eine ziemlich spezifische AktivitĂ€t ist, die normalerweise nur fĂŒr Entwickler erforderlich ist. WĂ€hrend des normalen Betriebs des Systems besteht meistens keine Notwendigkeit, Prozesse zu debuggen.


Aus diesen GrĂŒnden deaktivieren Systeme aus SicherheitsgrĂŒnden normalerweise die Möglichkeit, alle Prozesse standardmĂ€ĂŸig zu debuggen. Verantwortlich dafĂŒr ist das Yama- Sicherheitsmodul , das ĂŒber die Datei / proc / sys / kernel / yama / ptrace_scope verwaltet wird. Es bietet vier Verhaltensweisen:


  • 0 - Der Benutzer kann alle von ihm gestarteten Prozesse debuggen
  • 1 - Standardmodus, nur vom Debugger gestartete Prozesse können debuggt werden
  • 2 - Nur ein Root-Systemadministrator kann Prozesse debuggen
  • 3 - Das Debuggen ist fĂŒr alle Personen verboten. Der Modus wird erst nach einem Neustart des Systems ausgeschaltet

FĂŒr unsere Zwecke mĂŒssen Sie natĂŒrlich in der Lage sein, Prozesse zu debuggen, die vor unserem Debugger gestartet wurden. FĂŒr Experimente mĂŒssen Sie entweder das System in den Entwicklungsmodus schalten, indem Sie 0 in eine spezielle ptrace_scope-Datei schreiben (fĂŒr die Administratorrechte erforderlich sind):


 $ sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope' 

oder fĂŒhren Sie den Debugger als Administrator aus:


 $ sudo ./inject-thread ... 

Ergebnisse des ersten Schritts


Auf diese Weise können wir im ersten Schritt als Debugger eine Verbindung zum Zielprozess herstellen und spÀter die Verbindung zu diesem trennen.


Der Zielprozess wird gestoppt und wir können sicherstellen, dass das Betriebssystem uns wirklich als Debugger sieht:


 $ sudo ./inject-thread --target $(pgrep docker) $ cat /proc/$(pgrep docker)/status | head Name: docker State: t (tracing stop) <---    Tgid: 31330 Ngid: 0 Pid: 31330 PPid: 1 TracerPid: 2789 <--- PID   Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 64 $ ps a | grep [2]789 2789 pts/5 S+ 0:00 ./inject-thread --target 31330 

Schritt 2. Suchen Sie nach Bibliotheken im Speicher


Der nĂ€chste Schritt ist einfacher: Sie mĂŒssen im Speicher des Zielprozesses die Bibliothek mit den Funktionen finden, die wir benötigen. Aber es gibt viel GedĂ€chtnis, wo man anfangen soll und was genau?


Datei / proc / $ pid / maps


Eine spezielle Datei hilft uns dabei, durch die der Kernel angibt, was und wo sich der Prozess im Speicher befindet. Wie Sie wissen , befindet sich im Verzeichnis / proc fĂŒr jeden Prozess ein Unterverzeichnis. Darin befindet sich eine Datei, die die Prozessspeicherkarte beschreibt:


 $ cat / proc / self / maps
 00400000-0040c000 r-xp 00000000 zB: 01 1044592 / bin / cat
 0060b000-0060c000 r - p 0000b000 fe: 01 1044592 / bin / cat
 0060c000-0060d000 rw-p 0000c000 fe: 01 1044592 / bin / cat
 013d5000-013f6000 rw-p 00000000 00:00 0 [Heap]
 7f9920bd1000-7f9920d72000 r-xp 00000000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920d72000-7f9920f72000 --- p 001a1000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f72000-7f9920f76000 r - p 001a1000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f76000-7f9920f78000 rw-p 001a5000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7fc3f8381000-7fc3f8385000 rw-p 00000000 00:00 0
 7fc3f8385000-7fc3f83a6000 r-xp 00000000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f83ec000-7fc3f840e000 rw-p 00000000 00:00 0
 7fc3f840e000-7fc3f8597000 r - p 00000000 fe: 01 657286 / usr / lib / locale / locale-archive
 7fc3f8597000-7fc3f859a000 rw-p 00000000 00:00 0
 7fc3f85a3000-7fc3f85a5000 rw-p 00000000 00:00 0
 7fc3f85a5000-7fc3f85a6000 r - p 00020000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a6000-7fc3f85a7000 rw-p 00021000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a7000-7fc3f85a8000 rw-p 00000000 00:00 0
 7ffdb6f0e000-7ffdb6f2f000 rw-p 00000000 00:00 0 [Stapel]
 7ffdb6f7f000-7ffdb6f81000 r-xp 00000000 00:00 0 [vdso]
 7ffdb6f81000-7ffdb6f83000 r - p 00000000 00:00 0 [vvar]
 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

Der Inhalt dieser Datei wird vom Betriebssystemkern im laufenden Betrieb aus internen Strukturen generiert, die die Speicherbereiche des fĂŒr uns interessanten Prozesses beschreiben, und enthĂ€lt die folgenden Informationen:


  • Adressbereich, der der Region zugeordnet ist
  • Zugangsrechte zur Region
    • r/- : lesen
    • w/- : schreiben
    • x/- : AusfĂŒhrung
    • p/s : Speicher mit anderen Prozessen teilen
  • Dateiversatz (falls vorhanden)
  • Code des GerĂ€ts, auf dem sich die angezeigte Datei befindet
  • Datei-Inode-Nummer (falls vorhanden)
  • Pfad zur angezeigten Datei (falls vorhanden)

Einige Speicherbereiche werden auf Dateien abgebildet: Wenn ein Prozess einen solchen Speicher liest, liest er tatsĂ€chlich Daten aus den entsprechenden Dateien mit einem bestimmten Versatz. Wenn Sie in eine Region schreiben können, können Änderungen im Speicher entweder nur fĂŒr den Prozess selbst sichtbar sein ( Copy-on-Write- Mechanismus, p Modus ist privat) oder mit der Festplatte synchronisiert ( s Modus wird gemeinsam genutzt).


Andere Regionen sind anonym - dieser Speicher entspricht keiner Datei. Das Betriebssystem gibt dem Prozess einfach einen physischen Speicher, den es verwendet. Solche Bereiche werden beispielsweise fĂŒr "normalen" Prozessspeicher verwendet: Stapel und Heap. Anonyme Regionen können entweder fĂŒr einen Prozess persönlich sein oder von mehreren Prozessen gemeinsam genutzt werden ( Shared Memory- Mechanismus).


DarĂŒber hinaus gibt es im Speicher mehrere spezielle Bereiche, die mit den Pseudonamen [vdso] und [vsyscall] gekennzeichnet sind. Sie werden verwendet, um einige Systemaufrufe zu optimieren.


Wir interessieren uns fĂŒr Regionen, in denen der Inhalt von Bibliotheksdateien angezeigt wird. Wenn wir die Speicherkarte lesen und die darin enthaltenen EintrĂ€ge nach dem Namen der angezeigten Datei herausfiltern, finden wir alle Adressen, die von den benötigten Bibliotheken belegt werden. Das Format der Speicherkarte ist speziell fĂŒr die Programmverarbeitung geeignet und mit den Funktionen der scanf () -Familie leicht verstĂ€ndlich:


 static bool read_proc_line(const char *line, const char *library, struct memory_region *region) { unsigned long vaddr_low = 0; unsigned long vaddr_high = 0; char read = 0; char write = 0; char execute = 0; int path_offset = 0; /*    /proc/$pid/maps */ sscanf(line, "%lx-%lx %c%c%c%*c %*lx %*x:%*x %*d %n", &vaddr_low, &vaddr_high, &read, &write, &execute, &path_offset); /* ,       */ if (!strstr(line + path_offset, library)) return false; /*           */ if (region) { region->vaddr_low = vaddr_low; region->vaddr_high = vaddr_high; region->readable = (read == 'r'); region->writeable = (write == 'w'); region->executable = (execute == 'x'); region->content = NULL; } return true; } 


, libc-2.19.so, :


Loch in libc-2.19.so


2 - ? 51? ? ?


, , .


, , . , , , (, , ).


, ( 4 ). , .


, . — — . 2 — , ( x86_64 4 , 2 , 1 ). .



, :


  • libdl: dlopen() dlsym()
  • libpthread: pthread_create() pthread_detach()

, , . Linux ( address space layout randomization , ASLR). (- , ), — - .


, , , /proc/$pid/maps. , .


3. ELF-


, , , .


:


 $ nm -D /lib/x86_64-linux-gnu/libdl-2.19.so | grep dlopen 0000000000001090 T dlopen 

nm . .


- , nm , . , dlsym().



— ELF-, . procfs. UNIX way, /proc/$pid/mem , — ( /proc/$pid/maps).


Linux mmap(), ( , ). :


 static int map_region(pid_t pid, struct memory_region *region) { size_t length = region->vaddr_high - region->vaddr_low; off_t offset = region->vaddr_low; char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*     */ int fd = open(path, O_RDONLY); if (fd < 0) goto error; /*      */ void *buffer = malloc(length); if (!buffer) goto error_close_file; /*   */ if (read_region(fd, offset, buffer, length) < 0) goto error_free_buffer; region->content = buffer; close(fd); return 0; error_free_buffer: free(buffer); error_close_file: close(fd); error: return -1; } static int read_region(int fd, off_t offset, void *buffer, size_t length) { /*      */ if (lseek(fd, offset, SEEK_SET) < 0) return -1; size_t remaining = length; char *ptr = buffer; /* *     .   , *      ,  . */ while (remaining > 0) { ssize_t count = read(fd, ptr, remaining); if (count < 0) return -1; remaining -= count; ptr += count; } return 0; } 

ELF- . , -, , -, .


ELF


ELF — Linux. , , .


ELF . ELF . — , . , — . ELF-.


, libdl-2.19.so :


Abschnitte und Segmente libdl-2.19.so


( readelf --headers .)


, , (29 9). — , , . ELF — , . Linux, , LOAD, ( ).


ELF- , . , .


, . «» . .bss, , ( ).


, ELF — , . ...


?


() . , dlsym(), . - .


ELF (. 2-10). , .dynamic , DYNAMIC . .dynamic , :


  • .dynsym — ;
  • .dynstr — ;
  • .hash — -, .

, , ELF:


DYNAMISCHE Segmentsuche


ELF, (1), (2), (3), (4) , .


ELF →


() ELF <elf.h>, , , . , ELF — . 32- 64- , , , . x86_64, ELF .


ELF- ( Elf64_Ehdr ). ( program headers ), e_phoff e_phnum :


ELF


— , , ELF- — , , , , .


e_phoff, , . e_phnum e_phentsize .


( ), ELF — 64 .


→ DYNAMIC


. — Elf64_Phdr ( 64- ELF-), . PT_DYNAMIC p_type :


ELF


:


  • p_vaddr — , ;
  • p_memsz — .

.dynamic 0x2D88 ( ). DYNAMIC — 0x202D88. 0x210 (8448) . .


DYNAMIC → .dynsym, .dynstr, .hash


.dynamic, DYNAMIC, . Elf64_Dyn , :


DYNAMIC


8 d_val d_ptr , 8- d_tag , , . :


  • DT_HASH (4) — .hash ( d_ptr)
  • DT_STRTAB (5) — .dynstr ( d_ptr)
  • DT_SYMTAB (6) — .dynsym ( d_ptr)
  • DT_STRSZ (10) — .dynstr ( d_val)
  • DT_NULL (0) —

. .dynamic : , , , .


, DYNAMIC , . , , - , .


.dynamic , . -, .dynstr , ? .



. , .dynsym , . ( «» .symtab, , , . .)



Elf64_Sym , ELF — , , , . dlopen :


ELF


:


  • st_name — ,
  • st_info — ( )
  • st_value —

( , nm , dlopen() .text, 0x1090 .)


, .



— - , . ( ). .dynstr , libdl-2.19.so :


ELF


, ( «dlopen», 0xA5) , . .


-


.hash - , . - — — ELF-, . , .dynsym, , . ( ) - .


- <elf.h>, (. 2-19). - , :


- ELF


wo


  • nbuckets — buckets
  • nchains — chains ( )
  • buckets —
  • chains —

- :


  1. h .
  2. i buckets[h % nbuckets] , .
  3. ( ) , .
  4. — chains[i % nchains] .
  5. 3—4 , .

-, ELF:


 static uint32_t elf_hash(const char *name) { uint32_t h = 0; uint32_t g; while (*name) { h = (h << 4) + *name++; g = h & 0xF0000000; if (g) h ^= g >> 24; h &= ~g; } return h; } 

, "dlopen" - 112420542 :



libdl — , 39 , . - .



, :


  • dlopen() dlsym() libdl
  • pthread_create() pthread_detach() libpthread

, .


. . , .


ELF- . , ( ). , . , , . .


4. -


, , - , : , . - .


-


, -:


 void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void (*entry)(void) = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

?


, — . , , , - — - ! .


— - . , , : .


 /* *      .rodata:   * .         , *        . */ .section .rodata /* *   .       . *      -:    ,  *  ,       . */ .global shellcode_start .global shellcode_address_dlopen .global shellcode_address_dlsym .global shellcode_address_pthread_create .global shellcode_address_pthread_detach .global shellcode_address_payload .global shellcode_address_entry .global shellcode_end /* *   dlopen().     #include <dlfcn.h>, *       . */ .set RTLD_LAZY, 1 .align 8 shellcode_start: /* * void *payload = dlopen(shellcode_address_payload, RTLD_LAZY); * *        x86_64: * * -     %rdi, %rsi, %rdx, %rcx * -     %rax * -      * *         . * *       %rax,    *     . */ lea shellcode_address_payload(%rip),%rdi mov $RTLD_LAZY,%rsi mov shellcode_address_dlopen(%rip),%rax callq *%rax /* * void (*entry)(void) = dlsym(payload, shellcode_address_entry); */ mov %rax,%rdi lea shellcode_address_entry(%rip),%rsi mov shellcode_address_dlsym(%rip),%rax callq *%rax /* * pthread_t thread; * pthread_create(&thread, NULL, entry, NULL); * *            * ,     pthread_create(). */ sub $8,%rsp mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax /* * pthread_detach(thread); * *    ,   ,  *     . */ mov (%rsp),%rdi add $8,%rsp mov shellcode_address_pthread_detach(%rip),%rax callq *%rax /* *   - —    ,     *      ret.    *     ,  *      . */ int $3 /* *       ,   *   ,    - *     .   “  *  ” (global offset table, GOT),   *           . */ .align 8 shellcode_address_dlopen: .space 8 shellcode_address_dlsym: .space 8 shellcode_address_pthread_create: .space 8 shellcode_address_pthread_detach: .space 8 shellcode_address_payload: .space 256 shellcode_address_entry: .space 256 /* *  - . */ shellcode_end: .end 

, . :


 $ as -o shellcode.o shellcode.S 

, , , . : (procedure linkage table, PLT), .


- , (, ) . - .


-


- . , , , . ?


-


, . , . , . , .


(- ), : , , . , , JIT- , . ?



:


  • - ,
  • - ,

, . -, - , . -, . -, , - -, .


, . . x86_64 int $3 — 0xCC — . ptrace() PTRACE_POKETEXT — , 8 , . , , .


, , , : . - , .


?


, ! malloc()!


. , -, . . , mmap():


 void inject_shellcode(const void *shellcode_src, size_t shellcode_size) { void *shellcode_dst = mmap(NULL, shellcode_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); copy_shellcode(shellcode_dst, shellcode_src, shellcode_size); } 

, ptrace() , .



, ? , . Linux x86_64 :


  • %rax
  • — — %rsi, %rdi, %rdx, %r10, %r8, %r9
  • SYSCALL,
  • %rax

ptrace() PTRACE_GETREGS PTRACE_SETREGS. , . - SYSCALL.


: , %rip. , , SYSCALL.


SYSCALL


SYSCALL? , . - , - . — libc. , , , :


 unsigned long find_syscall_instruction(struct library *library) { for (size_t i = 0; i < library->region_count; i++) { struct memory_region *region = &library->regions[i]; if (!(region->readable && region->executable)) continue; const uint8_t *region_data = region->content; size_t region_size = region->vaddr_high - region->vaddr_low; if (region_size < 2) continue; /* * 0F 05 syscall */ for (size_t offset = 0; offset < region_size - 1; offset++) { if (region_data[offset + 0] == 0x0F && region_data[offset + 1] == 0x05) { return region->vaddr_low + offset; } } } return 0; } 

, /proc/$pid/maps . x86_64 , - . , 0x0F 0x05. , , ARM, 0xDF 0x00 ( SVC #0), .


PTRACE_{GET,SET}REGS


:


 int get_registers(pid_t pid, struct user_regs_struct *registers) { int err = 0; if (ptrace(PTRACE_GETREGS, pid, registers, registers) < 0) err = -errno; return err; } 

struct user_regs_struct , <sys/user.h>. . . , varargs :


 static int set_regs_for_syscall(struct user_regs_struct *registers, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, va_list args) { registers->rip = syscall_insn_vaddr; registers->rax = syscall_number; for (int i = 0; i < args_count; i++) { switch (i) { case 0: registers->rdi = va_arg(args, long); break; case 1: registers->rsi = va_arg(args, long); break; case 2: registers->rdx = va_arg(args, long); break; case 3: registers->r10 = va_arg(args, long); break; case 4: registers->r8 = va_arg(args, long); break; case 5: registers->r9 = va_arg(args, long); break; default: return -E2BIG; } } return 0; } static long perform_syscall(pid_t pid, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, ...) { struct user_regs_struct old_registers; struct user_regs_struct new_registers; /* *    ,   *      . */ get_registers(pid, &old_registers); /* *      ,   * ,     . */ new_registers = old_registers; va_list args; va_start(args, args_count); set_regs_for_syscall(&new_registers, syscall_insn_vaddr, syscall_number, args_count, args); va_end(args); set_registers(pid, &new_registers); /* *    ,    *   ,    * (  ),    . *     . */ wait_for_syscall_completion(pid); /* *       *    . *        . */ get_registers(pid, &new_registers); long result = new_registers.rax; set_registers(pid, &old_registers); return result; } 

PTRACE_SYSCALL


: , ?


PTRACE_SYSCALL. PTRACE_CONT, . , - : , .


PTRACE_SYSCALL SIGTRAP : ( ) ( ). , ptrace() , , .


, SIGTRAP:


 static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGTRAP) < 0) return -1; return 0; } void wait_for_syscall_completion(pid_t pid) { wait_for_syscall_enter_exit_stop(pid); wait_for_syscall_enter_exit_stop(pid); } 

— , — (wait_for_process_stop() ). . , .


PTRACE_O_TRACESYSGOOD


, PTRACE_SYSCALL : , , - . , SIGTRAP ( ).


SIGTRAP . PTRACE_O_TRACESYSGOOD, :


  • SIGTRAP — -
  • SIGTRAP | 0x80 —

  int ptrace_attach(pid_t pid) { if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; + /*     */ + unsigned long options = PTRACE_O_TRACESYSGOOD; + if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) + return -1; return 0; } static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; - if (wait_for_process_stop(pid, SIGTRAP) < 0) + if (wait_for_process_stop(pid, SIGTRAP | 0x80) < 0) return -1; return 0; } 

-


- :


 void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); } 

- : dlopen(), .


 static inline void copy_shellcode(char *shellcode_text, const char *shellcode_addr, const void *data, size_t length) { ptrdiff_t offset = shellcode_addr - shellcode_start; memcpy(shellcode_text + offset, data, length); } static void prepare_shellcode(char *shellcode_text, size_t shellcode_size) { copy_shellcode(shellcode_text, shellcode_start, shellcode_start, shellcode_size); copy_shellcode(shellcode_text, shellcode_address_dlopen, &dlopen_vaddr, sizeof(dlopen_vaddr)); copy_shellcode(shellcode_text, shellcode_address_dlsym, &dlsym_vaddr, sizeof(dlsym_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_create, &pthread_create_vaddr, sizeof(pthread_create_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_detach, &pthread_detach_vaddr, sizeof(pthread_detach_vaddr)); copy_shellcode(shellcode_text, shellcode_address_payload, payload, sizeof(payload)); copy_shellcode(shellcode_text, shellcode_address_entry, entry, sizeof(entry)); } 

, , -:


 extern const char shellcode_start[]; extern const char shellcode_address_dlopen[]; extern const char shellcode_address_dlsym[]; extern const char shellcode_address_pthread_create[]; extern const char shellcode_address_pthread_detach[]; extern const char shellcode_address_payload[]; extern const char shellcode_address_entry[]; extern const char shellcode_end[]; 

, .


- . /proc/$pid/mem, :


 int write_remote_memory(pid_t pid, unsigned long vaddr, const void *data, size_t size) { char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*       */ int fd = open(path, O_WRONLY); if (fd < 0) return -1; /*     */ if (lseek(fd, vaddr, SEEK_SET) < 0) { close(fd); return -1; } /*    */ int err = do_write_remote_memory(fd, data, size); close(fd); return err; } static int do_write_remote_memory(int fd, const void *data, size_t size) { size_t left = size; /* *    ,  ,     *   ,       *      . */ while (left > 0) { ssize_t wrote = write(fd, data, left); if (wrote < 0) return -1; data += wrote; left -= wrote; } return 0; } 


, - — « » . . - , .


5.


- . , : %rip -, PTRACE_SETREGS, PTRACE_CONT . .


, , . -? ?



, . , « » . , . :


  • (async-signal-safe)

— . dlopen() pthread_create() . - dlopen(), dlopen() ?


-, , , . , pthread_create() . , ( ). clone().


pthread_create()?

, - , ?

: clone().

, (libc) (pthread). clone() (thread control block, TCB) (thread-local storage, TLS), , . . pthread_create() , .

«», clone() libc pthread. , .


clone() :


  • ?
  • ?
  • -?


: -?


, - : , , , .


. , , . Wie? : exit(). , .


. exit() -:


 +.set __NR_exit, 60 .set RTLD_LAZY, 1 @@ - /* - *  . - */ - int $3 + /* + * exit(0); + */ + xor %rdi,%rdi + mov $__NR_exit,%rax + syscall 

: exit() — exit() . exit() , exit() — . Linux exit_group().



. . , , PROT_EXEC:


 shellcode_stack_vaddr = remote_mmap(target, syscall_vaddr, 0, SHELLCODE_STACK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN, -1, 0); 

, Linux x86_64 — «» , . mmap() , clone() . , mmap() MAP_GROWSDOWN, , .


PTRACE_O_TRACECLONE


. , - . waitpid(), : , .


— PTRACE_O_TRACECLONE. . , . , , , . , PTRACE_ATTACH , .


-, :


 - unsigned long options = PTRACE_O_TRACESYSGOOD; + unsigned long options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE; if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) return -1; 

-, clone(), PTRACE_EVENT_CLONE, , PTRACE_SYSCALL. :


 -void wait_for_syscall_completion(pid_t pid) +void wait_for_syscall_completion(pid_t pid, long syscall) { wait_for_syscall_enter_exit_stop(pid); + + /*  clone()   PTRACE_EVENT_CLONE */ + if (syscall == __NR_clone) + wait_for_clone_event(pid); wait_for_syscall_enter_exit_stop(pid); } 

:


 static int wait_for_clone_event(pid_t pid) { if (ptrace(PTRACE_CONT, pid, 0, 0) < 0) return -1; int event = SIGTRAP | (PTRACE_EVENT_CLONE << 8); if (wait_for_process_stop(pid, event) < 0) return -1; return 0; } 

clone() PID , . :


 void clear_ptrace_options(pid_t pid) { ptrace(PTRACE_SETOPTIONS, pid, 0, 0); } 

, clone() ptrace(), PTRACE_O_TRACECLONE. , , - .



, - . clone() :


 static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); if (!shell_tid) return -1; return 0; } 

clone() : , , , . , .


CLONE_FILES, CLONE_FS, CLONE_IO, CLONE_SIGHAND, CLONE_SYSVSEM, CLONE_VM — . , CLONE_FILES , ( fork()). — — , . . , CLONE_VM , , .


CLONE_THREAD : Linux — « », . , , getpid() , kill() — - , execve() — , .


, clone() fork(): , . clone() : , — . . ( , , .)


, pthread_create() , , . ?



fork() :


 pid_t child = fork(); if (child < 0) { /* fork() ,    */ } if (child == 0) { /*     execve() */ } /*     */ 

, . clone() . .


. , clone() , . syscall ret, , . .


SYSCALL + RET


, . , syscall ret:


 -if (region_size < 2) +if (region_size < 3) continue; /* * 0F 05 syscall + * C3 retq */ -for (size_t offset = 0; offset < region_size - 1; offset++) { +for (size_t offset = 0; offset < region_size - 2; offset++) { if (region_data[offset + 0] == 0x0F && - region_data[offset + 1] == 0x05) + region_data[offset + 1] == 0x05 && + region_data[offset + 2] == 0xC3) { return region->vaddr_low + offset; } } 

, .



. prepare_shellcode() , , :


  void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); + /*    «»   */ + unsigned long retaddr_vaddr = + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8; + write_remote_memory(target, retaddr_vaddr, + &shellcode_text_vaddr, sizeof(shellcode_text_vaddr)); } 

, , .


, , . System V ABI , ( %rsp) 16 . shellcode_stack_vaddr + SHELLCODE_STACK_SIZE : ( 4096 ), 1 . 8 , , retq, - . - :


 - sub $8,%rsp + sub $16,%rsp /*   */ mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax 

, %rsp 16 pthread_create(). SIGSEGV, — pthread_create() , .



, - , clone():


  static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ - shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8); if (!shell_tid) return -1; return 0; } 

ptrace() SIGSTOP, :


 int ignore_thread_stop(pid_t pid) { return wait_for_process_stop(pid, SIGSTOP); } 

Das ist alles. ptrace():


 void resume_thread(pid_t pid) { ptrace(PTRACE_CONT, pid, 0, 0); } 


, , , exit(). waitpid(). — CLONE_THREAD wait() ,— PTRACE_O_TRACECLONE, :


 int wait_for_process_exit(pid_t pid) { int status = 0; if (waitpid(pid, &status, 0) < 0) return -1; if (!WIFEXITED(status)) return -1; return WEXITSTATUS(status); } 

pthread , , pthread_join() pthread , . , — . , , .


Freier Speicher


, - . , - , munmap():


 void remote_munmap(pid_t pid, unsigned long syscall_insn_vaddr, unsigned long addr, size_t len) { perform_syscall(pid, syscall_insn_vaddr, __NR_munmap, 2, (long) addr, (long) len); } static void unmap_shellcode() { remote_munmap(target, syscall_ret_vaddr, shellcode_text_vaddr, SHELLCODE_TEXT_SIZE); remote_munmap(target, syscall_ret_vaddr, shellcode_stack_vaddr, SHELLCODE_STACK_SIZE); } 

, , , — ptrace() . (, SIGSTOP), , ( ):


 int stop_thread(pid_t pid) { if (kill(pid, SIGSTOP) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 


, , . PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 


, . , . , .


Fazit


Was kann man damit machen? . , , .


Gnome Control Center


Linux . GTK+ . , make:


 libpayload.so: payload.c $(CC) $(CFLAGS) $(shell pkg-config --cflags --libs gtk+-3.0) -shared -o $@ $< 

entry() GTK- — GTK UI , :


 #include <glib.h> #include <gtk/gtk.h> static gboolean actual_entry(gpointer _arg) { /*       : */ hook_gtk_entry_constructor(); /*   FALSE,       */ return FALSE; } void entry(void) { /*    -,   */ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, actual_entry, NULL, NULL); } 

, GTK , GtkEntry . "input-purpose" . «», , .


GTK glib — — GtkEntry . constructed(), . :


 static void (*old_gtk_entry_constructed)(GObject *object); static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); /*    */ old_gtk_entry_constructed(object); /*    ,  ,   entry */ } static void hook_gtk_entry_constructor(void) { /*     GtkEntry */ GTypeClass *entry_type_class = g_type_class_peek(GTK_TYPE_ENTRY); GObjectClass *entry_object_class = G_OBJECT_CLASS(entry_type_class); /* *     "constructed"     . */ old_gtk_entry_constructed = entry_object_class->constructed; entry_object_class->constructed = new_gtk_entry_constructed; } 

GtkEntry :


  • , ,

, GtkEntry , , . , :


 static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); old_gtk_entry_constructed(object); /*       */ g_signal_connect(entry, "notify::input-purpose", G_CALLBACK(input_purpose_changed), NULL); /*      */ g_signal_connect(entry, "icon-press", G_CALLBACK(icon_pressed), NULL); /*      */ g_signal_connect(entry, "icon-release", G_CALLBACK(icon_released), NULL); } 

. , . .


 static void input_purpose_changed(GtkEntry *entry) { GtkInputPurpose purpose = gtk_entry_get_input_purpose(entry); if (purpose == GTK_INPUT_PURPOSE_PASSWORD) { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } else { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, NULL); } } 

: , , , - , :


 static void icon_pressed(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-add"); } static void icon_released(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } 

Das ist alles.


GitHub (GPLv2).


, . gdb :


 $ gdb --pid $(pgrep target) \ --batch \ -ex 'compile file -raw shell-code.c' 

Source: https://habr.com/ru/post/de473740/


All Articles