Conseils, services, ingénierie en informatique. Mise en place de solutions technologiques, et support, pour les entreprises.

Etoiles inactivesEtoiles inactivesEtoiles inactivesEtoiles inactivesEtoiles inactives
 

 

Buffer overflow attack example website attaques systèmes par buffer overflow stack overflow

 

Attaques systèmes, programmation bas niveau

 

 Buffer overflow attack example website piratage

 

"Ethical hacking", ou piratage éthique: formation dispensée dans le but d'étendre ses connaissance dans le domaine de la sécurité informatique

Comment la NSA le FBI prennent le contrôle des machines distantes? Grâce aux failles 0 day découvertes dans les programmes (stuxnet, faille windows, blablabla...). On étudie en mémoire l'execution d'un programme. Quand on trouve le return d'une fonction, on remplace ce return de cette fonction par l'instrution de notre code malveillant. On fait en sorte que notre return se fasse ailleurs... C'est parti...

 

 

On va regarder le code source, c'est-à-dire de l’assembleur à la fin

On étudie le comportement du programme.

LE grand classique : buffer overflow. Ca peut etre assez violent, car on passer root

On va se baser sur des erreurs de programmation, voir des erreurs de conception des ordinateurs eux-mêmes.

Cette attaque du buffer overflow représente 90% voir plus des attaques systèmes.

Le code source de prédilection : le C et l’assembleur. Pourquoi ? Pare que tous les compilateurs genèrent de l’assembleur, bon, à part le java un cas particulier avec sa JVM.

D’autres langages comme python sont intérpretes donc les attaques sont un peu différentes voire un peu plus difficiles

 

 

On utilise les debugguers

 

ASM2C : rétro compilateur : on passe du code assembleur au code source. Bon, ca ne marche pas super bien car quand le code est compilé on perd des informations. Mais quand on écrit uen boucle « for… i » on retrouve toujours le meme pattern . Pourquoi on met i ? Parce que les programmeurs choisissent i généralement. Ensuite ils utilisent j pour créer une boucle imbriquée…

2 techniques peuvent-être utilisées : le black-box

La technique du post-mortem : on tente de faire planter plein de fois le programme et ca laisse des traces dans le noyau. On demande au noyau d’afficher quel est le contenu de la mémoire au moment où ça a planté. Tous les systèmes linux peuvent faire ça.

Pour arriver à faire ça, il faut faire de l’assembleur.

Ca dépend de l’ordinateur et du système d’exploitation.

Par exemple ici sur Linux, on a les formats ELF.

On va avoir 2 bases dans ce format : un header qui contient 2 offsets, donc 2 pointeurs , vers la header table, et un section header table qui contient la section de la liste des executables.

On va avoir l’en-tête qui va nous donner l’adresse du section header table.

Le program header table contient des pointers vers les datas

On trouve une section principale : .text qui contient le code executable

Une autre section : stack qui contient la pile

Linking view -> au moment de l’exécution par le noyau : execution view

On a génération d’un segment qui contient tout le code executable

Le format ELF est public. On peut rechercher pour connaitre tout ce qu’il y a dedans, c’est très complexe.

Un executable est une suite d’octets.

Il faut bien commencer à chercher quelque part. Les premières adresses sont des pointeurs vers 2 autres zones qui vont contenir les segments et des sections (headers…) A quel endroit est la pile ? Cette adresse là doit êtr l’adresse de la pile. Cet adresse là doit être le début du chargement des bibliothèques dynamiques….

L’espace adressable par l’utilisateur va de 0x00000000 à 0xBFFFFFF

L’espace adressable pour le noyau est situé dans l’intervalle 0xC0000000 à 0xFFFFFFF

 

 

Une question? Posez-la ici

 

 

Les exécutables au format ELF sont chargés à partir de l’adresse virtuelle mémoire 0x08048000 

 

 

C’est toujours la même, elle correspond au début du programme, par exemple le « main » et tout ce que le microprocesseur doit exécuter, il va y avoir les fonctions, et les appels systèmes qui ne sont pas les mêmes selon les systèmes d’exploitation.

Ensuite toutes les instructions seront visibles après cette adresse.

Il faut bien commencer quelquepart…

Dans notre ram, il y a plusieurs executables qui s’executent en même temps.

Le noyau se charge de faire la correspondance entre l’adresse virtuelle du début du programme et l’adresse réelle en mémoire. Le noyau fait la correspondance ente les adresse virtuelles et les adresses réelles.

Il y a une zone où le noyau copie les bibliothèques dynamiques.

Quand on fait un malloc en C, on a une bilbliothèque qui demande au noyau de récupérer de la mémoire.

Dans notre zone mémoire de programme, on va avoir des pointeurs que le noyau connait en adresse virtuelle, et le noyau est capable de convertir cette adresse virtuelle en adresse physique. On passe de l’executable virtuel à l’executable physique.

La stack, ou pile, est une zone mémoire particulière qui demande au microprocesseur de se charger différement. Le noyau va précise au microcesseur comment la charger, et il ne charge pas cette zone mémoire comme les autres. Ce qu’on veut c’est que le pile de l’executable soit celle du microprocesseurs.

Des mémoires sont plus ou moins proches du microprocesseurs. Il y a des caches, L1, L2, L3, plus la pile (qui est des fois dans L1 ou L2 ou L3, le noyau a sa propre stratégie  et la met là où il veut selon les machines)

 

Organisation de l’espace utilisateur

 

On touve par exemple ces sections les plus connues :

.text qui contient le code du programe

.data qui contient les données globales initialisées

.bss qui contient les données globlaes non initialisées

.stack

Sur les anciens systèmes MAC, la stack était pas construit de la même manière.

Sur les systèmes Linux, la pile est remplie de bas en haut. Dans la mémoire RAM, on va avoir l’adresse par exemple 1000, 1001, 1002 etc.

On empile, on dépile…

Généralement, quand on programme, si on représente la pile comme un tableau, on a une case 0, qui pointe vers la case 1, qui pointe vers la case 2, qui pointe vers la case 3, des adresses les plus petites vers les adresses les plus haute. MAIS le système fait le contraire. Quand on regarde du code assembleur, on visualise que les adresses diminuent, c’est le contraire.

Donc, par exemple un espace utilisateur, en remontant :

Départ : 0x0804800

.data

.bss

.stack

On regarde un code c :

On déclare les pointeurs avec « * »

Si on ne sait pas programmer en C, il faut savoir en faire.

Le C est l’assembleur universel, car les assembleurs sont différents entre les machines. Tout se base sur le C, c’est le point de comparaison entre tous les langages : « si je compare à du C, qu’est-ce que ça donnerait » ?

L’assembleur n’est pas fait pour être compris par les humains, le C, oui.

Si vous ne connaissez pas le C, taper sur internet « cours de C ». En 1h pour développeurs Java, on arrive vite à tout comprendre. La friction c’est juste cette étoile, avec la notion de pointeur.

En C (définit fin des années 70), c’est comme en java, on a le « main ».

Si je déclare un

char a ;

est-ce que c’est initialisé ? Non. On a une zone mémoire, on ne sait pas ce qu’il y a dedans. En C, tout ce qui est 0, c’est le faux (contrairement à d’autres langages, comme le list, qui quand on est à 0 est le vrai…) Bref.

Si je déclare

Char b [] = « b » ; Idem

Dans le main, quand on

Static : global, oui,

Var6 = malloc(512) Est-ce que c’est initialiés ? OUI car malloc met à 0.

 

 

Une question? Posez-la ici

 

 

Comment connaitre les zones mémoires sous Linux ?

 

Heap2$ size –A –x /bin/ls

On peut faire ‘readelf –e’ ou ‘objdump –h’ …

 

Les registres

 

En assembleur :

Il y a des zones mémoires spécifiques.

Quand on écrit par exemple : a=2*a+1, le microprocesseur ne sait pas faire. Comment fait-il alors ?

Il sait multiplier un a par une constante et il sait mettre ça dans une case particulière.

Donc il faut que je mette ce résultat temporaire dans une zone temporaire : les registres qui sont un ensemble de petites variables qui existent de base dans le micro processeur et qui permettent de calculer des opérations intermédiaires.

Je calcule 2*a et je met dans un registre

J’augmente mon résultat de 1 et je le met dans un autre registre

Je prends ce 2eme registre, je le met dans un autre registre…

Combien y a-t-il de registres ?

Pas beaucoup car on doit avoir plein de transistors entre le processeur et la mémoire dans le processeur, ca coute cher. « branché sur le processeur »

Donc il y en a une dizaine.

Les principaux en taille 64 bits:

Eax, ebx, ecx, edx, edi, esi

Et sur certains super processeurs très cher, on trouve

Ce sont des mémoires très très très rapides.

Registres particuliers :

Ebp, esp, eip, eflags…

 

Retenons pour l’instant ces 3 : ebp, esp, eip

 

Un registre eax est un registre sur 64 bits, il est divisé en 2 sous registres, ax et ea, et le ax est divisé en 2 sous registres ah et al.

Il y a des instructions de XOr pour faire des décalages dans les bits, on modifie ces sous registres.

 

Eip : instruction pointeur

 

On a des instruction 1 add, 2mule, 3 push, 4 jump

On a un registre particulier, %eip qui contient le numéro des instructions à executer.

%eip% = 2 donc je vais executer l’instruction 2, et je rajoute 1 à %eip%

%eip% = 3 donc je vais executer l’instruction 3, et je rajoute 1 à %eip% …

Le microprocesseur regarde en priorité eip pour qu’il sache quelle instruction est à executer.

Quand on a un programme multithreadé, qu’est-ce qu’il se passe ? Il y a plusieurs registre %eip% virtuels.

Dans les années 90, les premières versions de Linux ne supportaient pas le multithreading, alors qu’Unix oui

 

ESP : Le registre esp indique où est le sommet de la pile

 

Zone mémoire où on va des adresses les plus hautes vers les adresses les plus bases. Où est me sommet de la pile ? Si je fait un push ? (Push c’est pour empiler une adresse dans la pile)

Le registre %esp% indique au processeur où est le sommet de la pile, c’est un repère, un référentiel.

 

 

Une question? Posez-la ici

 

 

 

EBP : pointeur de base

 

Il va être mis de temps en temps dans la pile : on est dans la sous partie de la pile qui correspond à l’execution d’une fonction. La base.

 

Les instructions

 

Déplacement dans la mémoire :

Mov : permet de deplacer le contenu d’un registre dans un autre

Lea

Push

Pop

Instruction arithmetiques :

Add, sub, mul, div, inc, dec…

La rumeur :  « i++ est plus rapide que i=i+1 » : bullshit ! Le compilateur considère ces opérations de la même façon, c’est aussi rapide. Gcc est imbatable.

Les compilateurs génèrent du code assembleur meilleur qu’un humain.

 

Instructions de contrôle

 

Cmp : qui compare 2 registres

Call : appelle une fonction

Int : interruption logicielle

Jmp

Logiques : and, or, xor, not

Nop = ne rien faire, son code est (x90) bien connu des pirates qui font des attaques systèmes et qui rajoutent du code virus à exécuter. Quand on arrive pas à le place, on rajoute du code NOP à la suite, x90 : plein de NOP, ça veut dire qu’il y a un virus, ou une attaque, car l’attaquant ne sait pas comment l’injecter, alors il en met plein.

Les microprocesseurs sont hyperthreadés, ils peuvent executer des NOP pour vider le flot d’instruction pour forcer le microprocesseur à vider ses instruction d’un thread alors qu’un autre ne fait rien.

 

Code assembleur : accès à la mémoire

 

Pour lire la valeur 42, on met un dollar, soit $42

Si je veux lire un registre, je met un pourcent devant : mov %ebx, %eax

Quand on met des parenthèses, on considère les pointeurs. (%eax) ca veut dire qu’on considère l’adresse pointeur du registre.

(%ecx,1), ça veut dire qu’on lit une case après notre buffer

 

 

Une question? Posez-la ici

 

 

 

La où il y a des attaques : les systèmes de sous fonction

 

Quand on a une fonction qui fait un tri de tableau, le code généré : on génère un code pour le tri d’un tableau, et régulièrement, mon code assembleur ira à l’adresse de ma fonction « tri de tableau » pour trier le tableau dont l’adresse aura été passée en paramètre.

 

Le Schéma le plus important : appel de fonction

 

 

buffer overflow attack example website schema 319b

Merci à F.Guava

 

 

Méthodes, procédures, fonctions appelées : ave en paramètre un « a » par exemple. Dans mon code C, je prends en paramètre un a, mais je peux potentiellement avoir mes variables locales. Les fonctions, ça prend en paramètre des variables. Dans une fonction, on a nos propres variables locales.

Pour le code assembleur, il faut aller au début du code de la méthode (fonction f appelée) et il faut passer en argument les valeurs appelées.

Comment est généré le code assembleur de tout ça ?

Cadre de l’appelant : c’est le code du main, on a la pile, on sauvegarde

Quand on déclare quelquechose en local c’est mis dans la pile

On appelle la fonction f, elle a des arguments. On empile les arguments dans la pile

Comment récupérer cette valeur dans la pile ?

Grace aux registres %ebp et %eip

Je trouve au dessus toutes les valeurs locales

Ensuite, sommet de la pile, registre %esp

Avec %ebp sauvé, juste en dessous j’ai %ebp sauvée, donc je sais que mes arguments, sont sauvegardés au dessous.

Gace à mon %ebp et mon %ebp sauvé, je retrouve mes arguments

On arrive à l’endroit le plus critique du code assembleur ! Quand on fait un return. Que se passe-t-il ? Que l’on soit en C ou en Java ? On quitte la fonction. En assembleur, la notion de « quitter une fonction » n’existe pas. Ce qu’on veut c’est revenir à l’adresse où on était.

Donc comment vider tout ça ? On enlève la référence.

On retrouve une nouvelle pile propre, vidée.

Je dois revenir dans mon code où est-ce que j’en étais dans l’exécution de mon code

Je sauvegarde %eip dans ma pile et mon return va réinitialiser son retour.

C’est à cause de ça qu’il y a des attaques.

But : avoir une trace de toutes les fonctions dans la pile, qui est une zone très proche du processeur, et donc l’accès est très rapide.

 

 

Une question? Posez-la ici

 

 

 

A retenir cette formule universelle: Arg %eip %ebp %valeur locale

 

C’est ultra dangereux, on ne peut pas faire autrement.

Pourquoi critique ? Car on a mis %eip dans la pile

On voit tout ce que fait le programme…

Si on voit passer un malloc ? On execute le malloc

PrintF ? On execute le printF…

Si j’arrive à modifier %eip, au lieu de revenir au main, prochaine instruction, on lance le virus, et voilà.

99% des attaques, voire 99,999% des attaques systèmes sont ciblées sur ce mécanisme : on prend le contrôle de la machine. Les noyaux essayent de se protéger mais ils ne peuvent pas à 100%.

 

Une question? Posez-la ici

.

 

Besoin d'aide en buffer overflow attack example website attaques systèmes par buffer overflow stack overflow?

Remplissez ce formulaire:

Voir la fiche métier consultant en referencement naturel