CreateRemoteThread pour Linux

Mitsuha apporte de nouveaux flux WinAPI a une fonction CreateRemoteThread qui vous permet de dĂ©marrer un nouveau thread dans l'espace d'adressage d'un autre processus. Il peut ĂȘtre utilisĂ© pour une variĂ©tĂ© d'injections de DLL, Ă  la fois Ă  des fins malveillantes (astuces dans les jeux, vol de mot de passe, etc.), et pour corriger un bug dans un programme en cours d'exĂ©cution Ă  la volĂ©e, ou ajouter des plugins Ă  des endroits oĂč ils n'Ă©taient pas fournis.


En gĂ©nĂ©ral, cette fonction a un utilitaire d'application douteux, il n'est donc pas surprenant que Linux ne dispose pas d'un analogue prĂȘt Ă  l'emploi de CreateRemoteThread. Cependant, je me demandais comment le mettre en Ɠuvre. L'Ă©tude du sujet s'est transformĂ©e en une bonne aventure.


Je vais expliquer en détail comment, à l'aide de la spécification ELF, une certaine connaissance de l'architecture x86_64 et des appels systÚme Linux, écrire votre propre petit débogueur qui peut charger et exécuter du code arbitraire dans un processus déjà en cours d'exécution et de travail.


La compréhension du texte nécessitera des connaissances de base sur la programmation systÚme pour Linux: le langage C, les programmes d'écriture et de débogage dessus, une compréhension du rÎle du code machine et de la mémoire dans l'ordinateur, le concept des appels systÚme, la familiarité avec les principales bibliothÚques et la lecture de la documentation.



En conséquence, j'ai pu «ajouter» la possibilité de prévisualiser les mots de passe dans le centre de contrÎle Gnome:


démonstration d'injection au Gnome Control Center


Idées principales


S'il n'y avait aucune clause dans les exigences relatives au chargement de code dans un processus dĂ©jĂ  en cours d'exĂ©cution, la solution serait extrĂȘmement simple: LD_PRELOAD. Cette variable d'environnement permet de charger une bibliothĂšque arbitraire avec l'application. Dans les bibliothĂšques partagĂ©es, vous pouvez dĂ©finir des fonctions constructeur qui s'exĂ©cutent lors du chargement de la bibliothĂšque.


Ensemble, LD_PRELOAD et les constructeurs permettent l'exécution de code arbitraire dans n'importe quel processus utilisant un chargeur dynamique. Il s'agit d'une fonctionnalité relativement bien connue souvent utilisée pour le débogage. Par exemple, avec l'application, vous pouvez charger votre propre bibliothÚque, qui définit les fonctions malloc () et free (), ce qui pourrait aider à détecter les fuites de mémoire.


Malheureusement, LD_PRELOAD ne fonctionne qu'au dĂ©marrage du processus. Il ne peut pas ĂȘtre utilisĂ© pour charger une bibliothĂšque dans un processus dĂ©jĂ  en cours d'exĂ©cution. Il existe une fonction dlopen () pour charger les bibliothĂšques pendant le processus, mais, Ă©videmment, le processus lui-mĂȘme doit l'appeler pour charger les plugins.


À propos des exĂ©cutables statiques

LD_PRELOAD ne fonctionne qu'avec les programmes qui utilisent le chargeur dynamique. Si le programme a Ă©tĂ© construit avec le commutateur -static , il inclut toutes les bibliothĂšques nĂ©cessaires. Dans ce cas, la rĂ©solution des dĂ©pendances dans les bibliothĂšques est effectuĂ©e au moment de la gĂ©nĂ©ration et le programme n'est gĂ©nĂ©ralement pas prĂȘt et n'est pas en mesure de charger dynamiquement les bibliothĂšques aprĂšs l'assemblage, au moment de l'exĂ©cution.

Dans les programmes assemblĂ©s statiquement, vous pouvez incorporer du code au moment de l'exĂ©cution, mais cela doit ĂȘtre fait d'une maniĂšre lĂ©gĂšrement diffĂ©rente. Et ce n'est pas entiĂšrement sĂ»r, car le programme n'est peut-ĂȘtre pas prĂȘt pour un tel virage.

En gĂ©nĂ©ral, il n'y a pas de solution pratique prĂȘte Ă  l'emploi, vous devez Ă©crire votre vĂ©lo. Sinon, vous ne liriez pas ce texte :)


Conceptuellement, pour forcer le processus de quelqu'un d'autre à exécuter une sorte de code, vous devez effectuer les actions suivantes:


  1. Prenez le contrĂŽle du processus cible.
  2. Chargez le code dans la mémoire du processus cible.
  3. Préparez le code téléchargé pour l'exécution dans le processus cible.
  4. Organisez l'exécution du code téléchargé par le processus cible.

Allons-y ...


Prendre le contrĂŽle du processus


Tout d'abord, nous devons subordonner le processus cible à notre volonté. AprÚs tout, les processus exécutent généralement uniquement leur propre code, ou le code des bibliothÚques chargées, ou les résultats de la compilation JIT. Mais certainement pas notre code.


Une option consiste Ă  utiliser une sorte de vulnĂ©rabilitĂ© dans le processus qui vous permet de prendre le contrĂŽle. Un exemple classique des tutoriels: buffer overflow, permettant de rĂ©Ă©crire l'adresse de retour sur la pile. C'est amusant, ça marche parfois mĂȘme, mais pas adaptĂ© au cas gĂ©nĂ©ral.


Nous utiliserons une autre façon honnĂȘte de prendre le contrĂŽle: le dĂ©bogage des appels systĂšme . Les dĂ©bogueurs interactifs peuvent parfaitement arrĂȘter les processus tiers, Ă©valuer les expressions et bien d'autres choses. Ils peuvent - nous pouvons.


Sous Linux, l'appel systÚme de débogage principal est ptrace () . Il vous permet de vous connecter aux processus, d'examiner leur statut et de contrÎler la progression de leur exécution. ptrace () est assez bien documenté seul, mais les détails de son utilisation ne sont clairs qu'en pratique.


Chargement de code dans la mémoire de processus


Dans le cas de dĂ©bordements de tampon, la charge utile ( code shell ) est gĂ©nĂ©ralement incluse dans le contenu qui dĂ©borde du mĂȘme tampon. Lorsque vous utilisez le dĂ©bogueur, le code nĂ©cessaire peut ĂȘtre Ă©crit directement dans la mĂ©moire de processus. Dans WinAPI, il existe une fonction spĂ©ciale WriteProcessMemory pour cela. Linux Ă  cet effet est conforme Ă  la mĂ©thode UNIX: pour chaque processus du systĂšme, il existe un fichier / proc / $ pid / mem , qui affiche la mĂ©moire de ce processus. Il est possible d'Ă©crire quelque chose dans la mĂ©moire de processus en utilisant l'entrĂ©e-sortie habituelle.


Préparation du code pour l'exécution


Il ne suffit pas d'Ă©crire du code en mĂ©moire. Il doit encore ĂȘtre Ă©crit dans la mĂ©moire exĂ©cutable . Dans le cas de l'enregistrement via une vulnĂ©rabilitĂ©, cela prĂ©sente des difficultĂ©s non triviales, mais comme nous pouvons contrĂŽler complĂštement le processus cible, il ne nous sera pas difficile de trouver ou d'allouer la mĂ©moire «correcte» pour nous-mĂȘmes.


Un autre point important de prĂ©paration est le code shell lui-mĂȘme. Dans ce document, nous voudrons probablement utiliser certaines fonctions des bibliothĂšques, telles que les entrĂ©es-sorties, les primitives graphiques, etc. Cependant, nous devons Ă©crire du code machine nu, qui en soi n'a aucune idĂ©e des adresses de toutes ces fonctions intĂ©ressantes dans les bibliothĂšques. D'oĂč les obtenez-vous?


Pour simplifier la vie du systĂšme d'exploitation et compliquer la vie du code malveillant, les bibliothĂšques n'utilisent gĂ©nĂ©ralement pas d'adresses fixes (et contiennent un code dit indĂ©pendant de la position ). Les adresses ne peuvent donc pas ĂȘtre devinĂ©es.


Lorsque le processus dĂ©marre normalement, le chargeur qui effectue les relocalisations est chargĂ© de dĂ©terminer les adresses exactes des bibliothĂšques. Cependant, il ne remplit qu'une seule fois au dĂ©part. Si le processus autorise le chargement dynamique des bibliothĂšques, il contient un chargeur dynamique qui peut faire de mĂȘme pendant l'exĂ©cution du processus. Cependant, l'adresse du chargeur dynamique n'est pas non plus fixe.


En général, avec les bibliothÚques, il y a quatre options:


  • n'utilisez pas du tout les bibliothĂšques, faites tout sur les appels systĂšme propres
  • mettre des copies de toutes les bibliothĂšques nĂ©cessaires dans le code shell
  • faites vous-mĂȘme le travail du chargeur dynamique
  • trouver un chargeur de dĂ©marrage dynamique et le faire charger nos bibliothĂšques

Nous choisirons ce dernier, car nous voulons les bibliothÚques, et écrire notre chargeur de démarrage complet depuis longtemps. Ce n'est pas la méthode la plus secrÚte, et non la plus intéressante, mais la plus simple, puissante et fiable.


Transfert de contrĂŽle au code


ptrace () vous permet de changer les registres du processeur, donc il ne devrait y avoir aucun problĂšme avec le transfert du contrĂŽle vers le code chargĂ© et prĂ©parĂ©: Ă©crivez simplement l'adresse de notre code dans le registre% rip - et le tour est jouĂ©! Cependant, en rĂ©alitĂ©, tout n'est pas si simple. Les difficultĂ©s sont liĂ©es au fait que le processus dĂ©boguĂ© n'a en fait pas disparu et qu'il a Ă©galement une sorte de code qui a Ă©tĂ© exĂ©cutĂ© et continuera Ă  ĂȘtre exĂ©cutĂ©.


Esquisse de solution


Total, nous mettrons en Ɠuvre notre flux dans un processus tiers comme suit:


  1. Nous sommes connectés au processus cible de débogage.
  2. On retrouve les bibliothÚques nécessaires en mémoire:
    • libdl - pour charger une nouvelle bibliothĂšque
    • libpthread - pour dĂ©marrer un nouveau thread
  3. On trouve les fonctions nécessaires dans les bibliothÚques:
    • libdl: dlopen (), dlsym ()
    • libpthread: pthread_create (), pthread_detach ()
  4. Nous introduisons le code shell dans la mémoire du processus cible:


     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. Nous donnons le code shell Ă  remplir.

En conséquence, les bibliothÚques feront la bonne chose pour nous: elles chargeront notre bibliothÚque avec le code dont nous avons besoin en mémoire et démarreront un nouveau thread exécutant ce code.


Limitations


L'approche décrite ci-dessus impose certaines limites:


  • Le chargeur de dĂ©marrage doit disposer de privilĂšges suffisants pour dĂ©boguer le processus cible.
  • Le processus doit utiliser libdl (prĂȘt pour le chargement dynamique des modules).
  • Le processus doit utiliser libpthread (prĂȘt pour le multithreading).
  • Les applications statiques ne sont pas prises en charge.

De plus, personnellement, je suis trop paresseux pour me soucier du support des architectures tout-en-un, nous nous limiterons donc Ă  x86_64. (MĂȘme un x86 32 bits serait plus compliquĂ©.)


Comme vous pouvez le voir, tout cela met fin Ă  une utilisation secrĂšte avec des cibles malveillantes. Cependant, la tĂąche conserve toujours un intĂ©rĂȘt pour la recherche et laisse mĂȘme une faible opportunitĂ© pour une utilisation industrielle.


Digression: Ă  propos de l'utilisation de libdl et libpthread


Un lecteur-lecteur expĂ©rimentĂ© peut se demander: pourquoi avoir besoin de libdl si les fonctions internes __libc_dlopen_mode () et __libc_dlsym () sont dĂ©jĂ  intĂ©grĂ©es dans glibc, et libdl n'est qu'un wrapper sur elles? De mĂȘme, pourquoi exiger libpthread si un nouveau thread peut ĂȘtre facilement crĂ©Ă© en utilisant l'appel systĂšme clone ()?


En effet, sur Internet, il y a loin d'un exemple de leur utilisation:



Ils sont mĂȘme mentionnĂ©s dans la littĂ©rature populaire des hackers:


  • Apprendre l'analyse binaire Linux
  • L'art de la mĂ©decine lĂ©gale

Alors pourquoi pas? Eh bien, au moins parce que nous n'Ă©crivons pas de code malveillant oĂč une solution appropriĂ©e qui omet 90% des vĂ©rifications, prend 20 fois moins d'espace, mais fonctionne Ă©galement dans 80% des cas. De plus, je voulais tout essayer de mes propres mains.


En effet, libdl n'est pas nĂ©cessaire pour charger la bibliothĂšque dans le cas de glibc. Son utilisation par le processus indique qu'il est clairement prĂȘt pour le chargement de code dynamique. MalgrĂ© cela, en principe, vous pouvez refuser d'utiliser libdl (Ă©tant donnĂ© que nous devrons Ă©galement rechercher la glibc plus tard Ă©galement).


Pourquoi dlopen () à l'intérieur de la glibc?

C'est une question intĂ©ressante Ă  sa maniĂšre. RĂ©ponse courte: dĂ©tails de mise en Ɠuvre.

Le point est le commutateur de service de noms (NSS) - l'une des parties de la glibc qui fournit la traduction de divers noms: noms de machines, protocoles, utilisateurs, serveurs de messagerie, etc. C'est elle qui est responsable de fonctions telles que getaddrinfo () pour obtenir des adresses IP en nom de domaine et getpwuid () pour obtenir des informations sur l'utilisateur par son identifiant numérique.

NSS a une architecture modulaire et les modules se chargent dynamiquement. En fait, pour cela, la glibc avait également besoin de mécanismes de chargement dynamique des bibliothÚques. C'est pourquoi lorsque vous essayez d'utiliser getaddrinfo () dans une application assemblée statiquement, l'éditeur de liens affiche un avertissement «incompréhensible»:
 /tmp/build/socket.o: Dans la fonction `Socket :: bind ':
 socket.o :(. text + 0x374): avertissement: utilisation de 'getaddrinfo' dans un lien statique
 les applications nécessitent au moment de l'exécution les bibliothÚques partagées de la version glibc
 utilisé pour la liaison

En ce qui concerne les threads, un thread n'est généralement pas seulement une pile et un code exécutable, mais également des données globales stockées dans un stockage local de threads (TLS). L'initialisation correcte d'un nouveau thread nécessite le fonctionnement coordonné du noyau du systÚme d'exploitation, un chargeur de code binaire et un runtime de langage de programmation. Par conséquent, un simple appel à clone () suffit pour créer un flux qui peut écrire dans le fichier "Hello world!", Mais cela peut ne pas fonctionner pour un code plus complexe qui a besoin d'accéder à TLS et à d'autres choses intéressantes cachées aux yeux du programmeur d'application.


Un autre point liĂ© au multithreading concerne les processus Ă  un seul thread. Que se passe-t-il si nous crĂ©ons un nouveau thread dans un processus qui n'a pas Ă©tĂ© conçu comme multithread? Bon comportement vague. En effet, dans le processus, il n'y a pas de synchronisation du travail entre les threads, ce qui entraĂźnera tĂŽt ou tard une corruption des donnĂ©es. Si nous exigeons que l'application utilise libpthread, alors nous pouvons ĂȘtre sĂ»rs qu'elle est prĂȘte Ă  fonctionner dans un environnement multithread (au moins elle devrait ĂȘtre prĂȘte).


Étape 1. Connexion au processus


Tout d'abord, nous devons nous connecter au processus cible pour le débogage, puis le déconnecter à nouveau. C'est là que l'appel systÚme ptrace () entre en jeu.


Premier contact avec ptrace ()


Dans la documentation de ptrace (), vous pouvez trouver presque toutes les informations nécessaires:


   Fixation et détachement
        Un fil peut ĂȘtre attachĂ© au traceur Ă  l'aide de l'appel

            ptrace (PTRACE_ATTACH, pid, 0, 0);

        ou

            ptrace (PTRACE_SEIZE, pid, 0, PTRACE_O_flags);

        PTRACE_ATTACH envoie SIGSTOP Ă  ce fil.  Si le traceur le veut
        SIGSTOP n'a aucun effet, il doit le supprimer.  Notez que si
        d'autres signaux sont envoyés simultanément à ce fil pendant l'attachement, le
        le traceur peut voir le tracé entrer dans le signal-livraison-stop avec d'autres signaux
        nal (s) en premier!  La pratique habituelle consiste à réinjecter ces signaux jusqu'à
        SIGSTOP s'affiche, puis supprimer l'injection de SIGSTOP.  Le bug de conception
        voici qu'un attach ptrace et un SIGSTOP livré simultanément peuvent
        course et le SIGSTOP simultanĂ© peuvent ĂȘtre perdus.

La premiĂšre Ă©tape consiste donc Ă  utiliser 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; } 

AprĂšs ptrace (), le processus cible n'est pas tout Ă  fait prĂȘt pour le dĂ©bogage. Nous y sommes connectĂ©s, mais pour une Ă©tude interactive de l'Ă©tat du processus, il faut l'arrĂȘter. ptrace () envoie un signal SIGSTOP au processus, mais nous devons encore attendre que le processus s'arrĂȘte rĂ©ellement.


Pour attendre, utilisez l'appel systĂšme waitpid (). Dans le mĂȘme temps, plusieurs cas limites intĂ©ressants mĂ©ritent d'ĂȘtre notĂ©s. PremiĂšrement, le processus peut simplement se terminer ou mourir sans avoir reçu SIGSTOP. Dans ce cas, nous ne pouvons rien faire. DeuxiĂšmement, un autre signal peut ĂȘtre prĂ©alablement envoyĂ© au processus. Dans ce cas, nous devons laisser le processus le traiter (en utilisant PTRACE_CONT), et nous-mĂȘmes, continuer d'attendre plus longtemps notre SIGSTOP:


 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; } 

DĂ©connexion du processus


ArrĂȘter le processus de dĂ©bogage est beaucoup plus simple: utilisez simplement PTRACE_DETACH:


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

À proprement parler, la dĂ©sactivation explicite du dĂ©bogueur n'est pas toujours nĂ©cessaire. Lorsque le processus de dĂ©bogage se termine, il se dĂ©connecte automatiquement de tous les processus dĂ©boguĂ©s et les processus reprennent s'ils ont Ă©tĂ© arrĂȘtĂ©s par ptrace (). Cependant, si le processus dĂ©boguĂ© a Ă©tĂ© explicitement arrĂȘtĂ© par le dĂ©bogueur Ă  l'aide du signal SIGSTOP sans utiliser ptrace (), il ne se rĂ©veillera pas sans le signal SIGCONT ou PTRACE_DETACH correspondant. Par consĂ©quent, il est prĂ©fĂ©rable de se dĂ©connecter culturellement des processus.


ParamĂštre Ptrace_scope


Le débogueur a un contrÎle total sur le processus en cours de débogage. Si quelqu'un pouvait déboguer quoi que ce soit, quelle serait l'étendue du code malveillant! Il est évident que le débogage interactif est une activité assez spécifique, généralement nécessaire uniquement aux développeurs. Pendant le fonctionnement normal du systÚme, le plus souvent, il n'est pas nécessaire de déboguer les processus.


Pour ces raisons, pour des raisons de sécurité, les systÚmes désactivent généralement la possibilité de déboguer les processus par défaut. Le module de sécurité Yama en est responsable, géré via le fichier / proc / sys / kernel / yama / ptrace_scope. Il propose quatre comportements:


  • 0 - l'utilisateur peut dĂ©boguer tous les processus qu'il a dĂ©marrĂ©s
  • 1 - mode par dĂ©faut, seuls les processus dĂ©marrĂ©s par le dĂ©bogueur peuvent ĂȘtre dĂ©boguĂ©s
  • 2 - seul un administrateur systĂšme racine peut dĂ©boguer les processus
  • 3 - le dĂ©bogage est interdit Ă  tout le monde, le mode ne s'Ă©teint qu'au redĂ©marrage du systĂšme

De toute évidence, pour nos besoins, il sera nécessaire de pouvoir déboguer les processus lancés avant notre débogueur, donc pour les expériences, vous devrez soit basculer le systÚme en mode développement en écrivant 0 dans un fichier ptrace_scope spécial (qui nécessite des droits d'administrateur):


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

ou exécutez le débogueur en tant qu'administrateur:


 $ sudo ./inject-thread ... 

RĂ©sultats de la premiĂšre Ă©tape


Par conséquent, dans la premiÚre étape, nous sommes en mesure de nous connecter au processus cible en tant que débogueur et de nous en déconnecter plus tard.


Le processus cible sera arrĂȘtĂ© et nous pouvons nous assurer que le systĂšme d'exploitation nous voit vraiment comme un dĂ©bogueur:


 $ 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 

Étape 2. Rechercher des bibliothĂšques en mĂ©moire


L'Ă©tape suivante est plus simple: vous devez trouver dans la mĂ©moire du processus cible la bibliothĂšque avec les fonctions dont nous avons besoin. Mais il y a beaucoup de mĂ©moire, par oĂč commencer et quoi exactement?


Fichier / proc / $ pid / maps


Un fichier spĂ©cial nous aidera avec cela, Ă  travers lequel le noyau indique quoi et oĂč se trouve le processus en mĂ©moire. Comme vous le savez , dans le rĂ©pertoire / proc de chaque processus, il y a un sous-rĂ©pertoire. Et il y a un fichier qui dĂ©crit la carte mĂ©moire du processus:


 $ cat / proc / self / maps
 00400000-0040c000 r-xp 00000000 fe: 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 [tas]
 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 [pile]
 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]

Le contenu de ce fichier est généré à la volée par le noyau du systÚme d'exploitation à partir de structures internes qui décrivent les régions mémoire du processus qui nous intéresse et contient les informations suivantes:


  • plage d'adresses attribuĂ©e Ă  la rĂ©gion
  • droits d'accĂšs Ă  la rĂ©gion
    • r/- : lire
    • w/- : Ă©crire
    • x/- : exĂ©cution
    • p/s : partage de mĂ©moire avec d'autres processus
  • dĂ©calage de fichier (le cas Ă©chĂ©ant)
  • code de l'appareil oĂč se trouve le fichier affichĂ©
  • numĂ©ro d'inode du fichier (le cas Ă©chĂ©ant)
  • chemin d'accĂšs au fichier affichĂ© (le cas Ă©chĂ©ant)

Certaines rĂ©gions de la mĂ©moire sont mappĂ©es sur des fichiers: lorsqu'un processus lit une telle mĂ©moire, il lit en fait les donnĂ©es des fichiers correspondants Ă  un dĂ©calage spĂ©cifique. Si vous pouvez Ă©crire dans une rĂ©gion, les modifications de la mĂ©moire peuvent ĂȘtre visibles uniquement pour le processus lui - mĂȘme (mĂ©canisme de copie sur Ă©criture , le mode p est privĂ©) ou synchronisĂ©es avec le disque ( s mode s est partagĂ©).


Les autres rĂ©gions sont anonymes - cette mĂ©moire ne correspond Ă  aucun fichier. Le systĂšme d'exploitation donne simplement au processus un morceau de mĂ©moire physique qu'il utilise. De telles rĂ©gions sont utilisĂ©es, par exemple, pour la mĂ©moire de processus "normale": pile et tas. Les rĂ©gions anonymes peuvent ĂȘtre personnelles Ă  un processus ou partagĂ©es entre plusieurs processus (mĂ©canisme de mĂ©moire partagĂ©e ).


De plus, il y a plusieurs régions spéciales dans la mémoire marquées des pseudo-noms [vdso] et [vsyscall]. Ils sont utilisés pour optimiser certains appels systÚme.


Nous sommes intĂ©ressĂ©s par les rĂ©gions oĂč le contenu des fichiers de bibliothĂšque est affichĂ©. Si nous lisons la carte mĂ©moire et filtrons les entrĂ©es qu'elle contient par le nom du fichier affichĂ©, nous trouverons toutes les adresses occupĂ©es par les bibliothĂšques dont nous avons besoin. Le format de la carte mĂ©moire est spĂ©cialement adaptĂ© au traitement des programmes et se comprend facilement Ă  l'aide des fonctions de la famille scanf ():


 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, :


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 :


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:


Recherche de segment DYNAMIQUE


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


ELF →


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


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


En-tĂȘte ELF


— , , ELF- — , , , , .


e_phoff, , . e_phnum e_phentsize .


( ), ELF — 64 .


→ DYNAMIC


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


Table des segments ELF


:


  • p_vaddr — , ;
  • p_memsz — .

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


DYNAMIC → .dynsym, .dynstr, .hash


.dynamic, DYNAMIC, . Elf64_Dyn , :


Balises de section 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 :


Table de caractĂšres ELF


:


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

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


, .



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


Table de rang ELF


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


-


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


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


table de hachage ELF


oĂč


  • 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 :


rechercher un personnage dans une bibliothĂšque


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() :


  • ?
  • ?
  • -?


: -?


, - : , , , .


. , , . Comment? : 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); } 

C’est tout. 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 , . , — . , , .


MĂ©moire libre


, - . , - , 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; } 


, . , . , .


Conclusion


? . , , .


démonstration d'injection au 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"); } 

C’est tout.


GitHub (GPLv2).


, . gdb :


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

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


All Articles