-------------------------------------------------------------------------------------------- VII. Buffer Overlows par Lex Icon -------------------------------------------------------------------------------------------- --| INTRODUCTION |-- Cet article vous expliquera en gros ce que sont les Buffer Overflows et comment les exploiter. Une connaissance basique du C, de l'assembleur et de GDB seront très utiles pour comprendre la suite de l'article. Les fichiers source sont téléchargeables depuis notre site à http://IOC.multimania.com/issue2/sourcecode-bof.zip --|ORGANISATION DE LA MEMOIRE|-- La mémoire est partagée en 3 régions : - la zone texte, qui est utilisée pour le stockage des instructions du programme. A cause de ça, cette région est marquée en lecture seule et une tentative d'écriture se soldera par une erreur. - La zone de données. Les variables statiques sont stockées ici et sa taille peut être modifiée par l'appel système brk(). - La pile. Sa particularité est que la dernière chose placée dessus est la première chose a en être enlevé, ce qui en gros veut dire 'dernier arrivé, premier dehors'. Elle a été faîte pour l'utilisation de fonctions et de procédures. Une procédure est comme un jump, sauf qu'elle retourne après qu'elle ait exécuté ses instructions. Une adresse de retour sera placée sur la pile pour ça. Elle est aussi utilisée pour allouer dynamiquement des variables utilisées dans les fonctions, ses paramètres et valeurs de retour. --|ADRESSES DE RETOUR ET POINTEURS D'INSTRUCTION|-- L'ordinateur exécute les instructions et garde un pointeur d'instructions (IP pour Instruction Pointer) qui pointe vers l'instruction suivante. Quand une fonction ou procédure est appelée, l'ancien pointeur est sauvé sur la pile comme adresse de retour (RET). Après l'exécution, le RET remplacera l'IP et le programme continuera. --|BUFFER OVERFLOW|-- Pour mieux comprendre, vous allez regarder le fichier EXEMPLE.C Le programme crée deux chaînes, memset() fout 0x41 (A) dans grosse_chaine, et ensuite, le contenu de la grosse chaine est copié dans la petite. Comme la petite chaîne ne peut pas supporter 100 caractères, il y a un buffer overflow. On regarde la mémoire : [ grosse chaine ] [ petite chaine ] [ SFP ] [ RET ] Lors du buffer overflow, le SFP (Stack Frame Pointer) et le RET seront remplacés par des 'A', ce qui veut dire que RET aura la valeur 0x414141 (0x41 est la valeur hexadécimale de A). Quand la fonction sera terminée, l'IP sera remplacé par le RET erroné. L'ordinateur essaiera d'exécuter l'instruction à 0x414141 ce qui mènera à une segmentation violation vu que l'adresse est en dehors de l'espace du processus. --|EXPLOITATION|-- Maintenant qu'on sait qu'on peut changer le fonctionnement du programme en modifiant le RET, on peut essayer de l'exploiter. Au lieu de mettre des 'A', on peut mettre une adresse spécifique. --|EXECUTION DE CODE ARBITRAIRE|-- Maintenant on a besoin de l'adresse de quelque chose à pointer et a exécuter. Dans la plupart des cas, on voudra afficher un shell, mais ce n'est pas la seule chose qu'il est possible de faire. Avant : FFFFF BBBBBBBBBBBBBBBBBBBBB EEEE RRRR FFFFFFFFFF B=Buffer E=SFP R=RET F=Autres Après : FFFFF SSSSSSSSSSSSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF S=Shellcode A=Adresse pointant vers le shellcode F=Autres SHELL.C pour voir comment afficher un shell. Je ne vais pas expliquer ici comment générer du shellcode vu que ça demande de savoir programmer en assembleur. C'est un processus long et chiant qu'on a pas besoin de connaître vu qu'il en existe suffisament. Pour ceux qui veulent savoir comment on fait pour en générer : - Compilez le programme du sessus avec le flag '-static' - ouvrez le dans gdb et utilisez la commande 'disassemble main' - prenez tout le code inutile - changez le et re-écrivez le, ce coup ci en assembleur - compilez, ouvrez le dans gdb et utilisez la commande 'disassemble main' - utilisez les commandes x/bx sur les adresses des instructions et récupérez le code hexa Ou alors, vous pouvez prendre ce code : char shellcode[]= "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; --|TROUVER L'ADRESSE|-- Quand on essaye de faire déborder le buffer d'un autre programme, le problème est de trouver l'adresse du buffer. La réponse à ce problème est que pour chaque programme, la pile commence à la même adresse. En sachant où commence la pile, on peut essayer de deviner quelle est l'adresse du buffer. GET_SP.C nous donne son pointeur de pile : --|ESSAYONS D'EXPLOITER UN EXEMPLE|-- On va exploiter ce programme: --HOLE.C-- --EXPLOIT1.C-- Maintenant on peut essayer de deviner l'offset (bufferaddress = stackpointer + offset) [meik]$ exploit1 600 Utilise l'adresse: 0xbffff6c3 [meik]$ ./hole $BUF [meik]$ exploit1 600 100 Utilise l'adresse: 0xbffffce6 [meik]$ ./hole $BUF Segmentation fault Comme vous pouvez le constater, ce processus est carrément impossible - on doit connaître l'adresse exacte du buffer. Pour améliorer nos chances, on peut utiliser NOP avant le shellcode de notre bufferoverflow. L'instruction NOP permet de retarder l'exécution. On l'utilise car on a pas besoin de deviner l'adresse exacte du buffer. Si l'adresse de retour écrasée pointe à l'intérieur de la chaîne NOP. Notre code sera exécuté quelques secondes après. La mémoire devrait ressembler à ceci : FFFFF NNNNNNNNNNNSSSSSSSSSSSSSSAAAAAAAAFFFFFFFFF F = NOP S = Shellcode A = adresse pointant vers le shellcode F = autres données On re-écrit notre vieil exploit. [EXPLOIT2.C] [meik]$ exploit2 600 Using address: 0xbffff6c3 [meik]$ ./hole $BUF segmentation fault [meik]$ exploit2 600 100 Using address: 0xbffffce6 [meik]$ ./hole $BUF #exit [meik]$ Pour améliorer encore plus notre exploit, on peut placer le shellcode dans une variable d'environement. On pourra ainsi faire déborder le buffer avec l'adresse de sa variable. Cette méthode augmentera nos chances encore plus. On modifie notre code afin qu'il utilise l'appel setenv() pour mettre le shellcode dans l'environement. [EXPLOIT3.C] --|TROUVER DES BUFFER OVERFLOWS|-- Il existe un seul moyen de trouver des buffer overflows, c'est en lisant le code source. Linux est un système OpenSource, il est ainsi facile d'obtenir le code source. Longue vie à l'opensource ! -Cherchez des librairies de fonction qui n'exécutent pas un contrôle de limite comme: strcpy(),strcat(), sprintf(), vsprintf(), scanf() -D'autres dangers comme: getc() et getchar() dans un while ou encore une mauvaise utilisation de strncat. --|REFERENCE BIBIOGRAPHIQUE|-- Smashing the stack for fun and profit - Aleph1 BufferOverflows - Mudge --|CONCLUSION|-- J'espère que vous aurez apris des tas de choses en lisant ce document. En gros, vous aurez compris que ça consiste à envoyer plus de données que prévu dans une variable afin de lui faire exécuter du code qui permet de faire des tas de choses intéressantes. Imaginez un programme en SUID, vous lui faîtes exécuter de quoi lancer un shell et vous vous retrouvez root. Elle est pas belle la vie ?