Mes premiers Buffers Overflows – Partie 1

Aujourd’hui nous allons partir pour un long article sur les failles applicatives. Nous allons commencer par revoir les bases des programmes informatiques puis nous irons jusqu’a notre premier Buffer Overflow qui nous permettra de devenir root !
Attention, c’est long !

Il était une fois, les programmes !

Je pense qu’ici je ne vais pas vous apprendre grand chose, sans programme vos ordinateurs, tablettes, smartphones ne sont que des briques .. Depuis les débuts de l’informatique les langages ont beaucoup évolués, ce qui a amené à la création de nombreux logiciels. Finalement les programmes ne sont qu’une simple liste d’instruction donnés à l’ordinateur, que celui ci va exécuter.

Parmi les principaux langages on retrouve le C que nous allons utiliser dans tous les exemples à venir.

Je suis un utilisateur, je veux exécuter ce programme !

La volonté est une chose, la possibilité en est une autre ! C’est en effet ici que nous parlons des droits.
Pour cela basons nous sur Linux. Je pense qu’une fois de plus je ne vais rien vous apprendre de plus en vous disant qu’avec un ls -l on peut voir les droits des fichiers. Et si je vous apprend un truc, alors accrochez vous parce qu’on attaque du lourd dans la suite !

Les droits d’exécution correspondent à X. Si vous avez ce droit vous pouvez exécuter le programme.
Cependant, certains programmes ont besoin d’avoir des droits que l’utilisateur n’as pas forcement.. Il faut donc qu’il prenne l’apparence d’un utilisateur ayant des droits supérieurs au moment ou il s’exécute.
C’est surtout si cet utilisateur en question est root que ça deviens intéressant !

Une Histoire de mémoire.

Mettons un petit peu cette histoire de droits de coté pour nous concentrer sur la mémoire de l’ordinateur.
Comme nous l’avons vu précédemment, un programme est une suite d’instruction, au niveau du processeur, c’est toujours une suite d’instructions, mais cette fois ci finit votre code C tout propre et bienvenu dans l’assembleur !
Il existe autant de versions d’assembleur que d’architectures processeurs. Cependant au moment ou j’écris ces lignes le plus rependue est encore le jeu 8086 d’Intel. Concentrons nous donc sur ce jeu.
Toutes ces instructions représentent aussi des calculs, il faut alors un emplacement pour stocker les résultats de ces calculs : Les Registres.

AL/AH/EAX : Registre général, sa valeur change très vite.
BL/BH/EBX :Registre général, peut servir d’offset mémoire (exemple : « mov al, byte ptr ds:[bx+10] »).
CL/CH/ECX : Sert en général de compteur pour les boucles (exemple : « mov ecx, 5 ; rep movsd » : copie 5 doubles mots).
DL/DH/EDX :Registre général, obligatoire pour l’accès aux ports (moyen de communiquer avec toutes les puces de l’ordinateur)
CS : Segment mémoire du code.
DS : Segment mémoire des données.
ES : Segment mémoire.
FS : Autre segment mémoire.
GS : Autre segment mémoire.
SS : Segment mémoire de la pile (« S » = Stack = Pile).
BP : Offset mémoire, très souvent une copie de SP à laquelle on soustrait une valeur pour lire dans la pile (on ne doit pas modifier SP).
EDI/DI : Offset mémoire utilisé avec ES (ou FS ou GS si spécifié, exemple : « mov al, byte ptr gs:[10] »).
EIP/IP : Offset mémoire du code (inaccessible directement, modifiable indirectement avec l’instruction CALL, JMP, ou J[cas]).
ESI/SI : Offset mémoire utilisé avec DS.
ESP/S : Offset mémoire de la pile.

De cette liste nous allons surtout retenir : eax,ebx,ecx,edx,eip,ebp,esp.
Il manque un élément essentiel à cette liste, la Stack.
Nos registres nous permettent de stocker des informations, cependant ils servent aussi à effectuer des appels systemes ou a stocker des résultats de calculs. C’est donc un bon endroit pour facilement perdre nos petites variables que nous venons de stocker ..
Heureusement il y a cette fameuse stack. Elle respecte le principe du LIFO
( Last In First Out)
Pour vous representer la stack imaginez juste une pile d’assiettes, vous prenez toujours la dernière
assiette en premier si non la pile tombe ..

Principe du Buffer Overflow

Maintenant que vous connaissez un peu le fonctionnement mémoire d’un programme, il nous reste peu de choses avant de vraiment mettre les mains dans le cambouis !

Lorsque vous lancez un programme, votre OS lui attribut de l’espace mémoire pour travailler. Le programme se comporte alors comme si il était seul dans l’ordinateur. Comme vous avez pu le lire plus haut, cette mémoire s’organise via plusieurs registres, parmis eux eip : Extended Instruction Pointer . C’est ce registre qui va contenir l’adresse de la prochaine instruction à exécuter.

Et donc ! Si on modifie ce registre, on pourra alors lui indiquer ou se trouve la prochaine instruction, qui peut être n’importe où ! Par exemple dans un code que l’on aurais injecté dans la stack !
Et ce code sera enfait un ShellCode. ( Code assembleur particulier qui exécutera le programme de notre choix )

Mais comment peut on faire ca ?

Depuis le début je parle de Buffer Overflows sans vraiment avoir expliqué comment les provoquer.
Imaginons tout simplement ce code en C

void name() {
char buffer_name[20];

printf("Entrez votre nomn");
scanf("%s",&buffer_name);

printf("Votre nom est %sn",buffer_name);
}

int main() {
name();
return 0;
}

Tout basique, et pourtant une belle faille traine dans ce code ! En effet ici on demande à un utilisateur d’enregistrer son nom. Rien de très complexe. Cependant on part du principe que le nom de l’utilisateur fera moins de 20 caractères. Le programme réserve alors en mémoire une place de 20 caractères pour le nom de l’utilisateur. Mais si jamais l’utilisateur rentre plus de 20 caractères ? Et bien on a un dépassement de tampon. En traduction : Un Buffer Overflow.
Le nom va s’écrire en mémoire et prendre plus de place que prévu. Et par conséquent écraser les donnés existante. Parmis ces données : EIP.

Vous devez surement comprendre ou je veux en venir :
Nous allons remplir la stack avec notre shellcode, puis dans EIP nous allons indiquer l’adresse de la stack. Notre Shellcode sera ensuite exécuté !

Mais bon, pas de précipitation, pour le moment, modifions le comportement d’un binaire grâce à un Overflow.

It’s just memory !

Après tout, vous l’avez compris, on parle de mémoire.
Il est donc temps de sortir GDB !
Pour ma part j’utilise l’extension peda, qui rox vraiment du poney pour rester poli 😉 .
Je vais pas non plus vous donner un cours sur GDB parce que je suis pas super doué avec ce dernier mais au moins les bases. Nous allons donc voir ici comment repérer notre overflow.

Imaginons pour cela un code simple comme celui ci :

#include

CanNeverExecute()
{
        printf("I can never executen");
        exit(0);
}

GetInput()
{
        char buffer[8];

        gets(buffer);
        puts(buffer);
}

main()
{
        GetInput();

        return 0;
}

Très basique, si vous n’y comprenez rien alors commencez par apprendre le C !
Vous aurez donc repéré une fonction appelée : CanNeverExecute.
Qui comme son nom l’indique ne peux jamais s’executer, puisque que l’on ne l’appelle jamais !
Et bien nous nous allons le faire !
Il y a aussi autre chose à remarquer dans ce code, la taille de buffer, 8. Pas bien grande 😉

Lançons notre programme. Il demande des caractères qu’il nous affichera ensuite.
Exemple simple si je tape ShoxX, il m’affichera ShoxX.

Jusqu’ici tout vas bien !
 
Bon et imaginons que l’on ai un pseudo super long du style : superpseudomegalong
SEGFAULT ! ( Overflow )

Mais comment cela se fait-il ?

Et bien je pense avoir trouvé le meme parfait pour cela ! En effet cela est bien la faute du dev ! Dans notre code nous avons définit la taille de buffer à 8. Mais sans contrôler la taille des informations renseignés par l’utilisateur ! Alors au bout de 8 le programme continue d’écrire et écrase les données en mémoire.
Revenons y à la mémoire, car nous allons nous servir de ce dépassement de mémoire pour réécrire l’adresse d’EIP. EIP ? Oui le registre qui contient l’adresse de la prochaine instruction à exécuter!
Et ainsi donc vous l’aurez compris en prenant le contrôle d’EIP nous allons pouvoir exécuter notre fameuse instruction qui ne pouvais pas s’exécuter ! ( CanNeverExecute ).


Passons à la pratique :

Sortons GDB maintenant, tout d’abord regardons un peu le programme :
Ici tout simplement je récupère l’adresse mémoire de la fonction CanNeverExecute, qui est ici 0x0804847d, continuons un petit peu dans l’exécution de ce programme :
Le programme nous demande toujours de remplir le champ demandé, je remplis de « a », un classique 😉
Et comme nous pouvons le voir il va nous répondre SIGSEGV => Segfault.
Et pourquoi ? Et bien parce que le programme cherche a executer les informations contenues a l’adresse vers la quelle pointe EIP, 0x61616161. Qui correspond en fait aux « a » que nous avons préalablement remplis. Ce qui signifie que … Nous contrôlons EIP !
Faisons le donc pointer vers l’adresse de CanNeverExecute.
Pour cela il nous faut :
  • La taille du buffer.
  • L’adresse de la fonction.
  • Connaitre l’ordre des registres.
On a donc, un buffer de 8, et 0x0804847d en adresse. Il nous suffit donc de rentrer tout ca lorsque le programme nous le demande. Cependant il faudrait qu’il ne considère pas notre adresse comme étant des caractères mais bien en tant qu’adresse. Python à la rescousse !

Mais pourquoi « x90″*4 ? Et bien puisque si vous vous souvenez de l’ordre des registres avant EIP on retrouve ESP, il faut donc l’écraser, autant mettre des instructions vide type « NOP »
Ce petit exercice nous aura appris un peu plus comment fonctionnait la mémoire, et comment on peux la manipuler !

Cet article étant un peu long je préfère le couper ici, à vous de travailler les manipulation de la mémoire.
Dans la suite nous verrons un peu plus Peda !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *