Mémoire de la pile : présentation (partie 3)

Détection de Menaces

bannière mémoire de la pile

La mémoire de la pile est une section de la mémoire utilisée par les fonctions pour stocker des données telles que les variables et paramètres locaux, qui seront utilisées par le malware pour mettre à bien ses activités malveillantes sur un appareil compromis.

C’est un sujet que je comparerais au subnetting, qui nécessite un peu d’effort. Vous devrez peut-être lire cet article plusieurs fois avant de comprendre tous les concepts. Au début, j’ai eu beaucoup de mal à comprendre, mais je vous assure que vous réussirez à le maîtriser et que vous deviendrez meilleur(e) en analyse de malware une fois que vous aurez tout intégré. Mais pour le moment, commençons !

Cet article est le troisième d’une série en 4 parties sur l’outil d’analyse de malware x64dbg :

  • Partie 1 : Qu’est-ce que x64dbg et comment l’utiliser
  • Partie 2 : Comment dépacker un malware avec x64dbg
  • Partie 3 : Mémoire de la pile : présentation
  • Partie 4 : Tutoriel d’utilisation de x64dbg

Qu’est-ce que la mémoire de la pile ?

La mémoire de la pile est souvent expliquée par le sigle LIFO (Last In, First Out) ou dernier entré, premier sorti. Pour mieux comprendre, imaginez des briques empilées les unes sur les autres. Vous ne pouvez pas prendre une brique au plein milieu au risque de tout faire écrouler, c’est donc la brique du dessus qui doit être retirée en premier. La pile fonctionne un peu comme cela.

Dans un article précédent, j’ai décrit les registres dans x64dbg et j’ai donné des instructions d’assemblage de base. Ces informations sont utiles pour comprendre comment fonctionne la mémoire de la pile. Quand de nouvelles données sont ajoutées à la pile, le malware utilise la commande PUSH. Pour retirer un élément de la pile, le malware utilise la commande POP. Les données peuvent aussi être retirées de la pile et ajoutées à un registre.

Le registre « ESP » est utilisé pour désigner l’élément suivant de la pile et on l’appelle le « stack pointer » ou pointeur de la pile.

« EBP » aussi appelé « frame pointer » ou pointeur de frame, sert de point de référence invariable pour les données de la pile. Cela permet au programme de déterminer à quelle distance se trouve un élément dans la pile à partir de ce point de référence. Si une variable se trouve à deux « briques » du point de référence, alors elle se trouve à [EBP+8], car chaque « brique » de la pile fait 4 octets.

Chaque fonction d’un programme génère son propre frame de pile pour pointer vers ses propres variables et paramètres selon cette technique.

Architecture de la mémoire de la pile

Le diagramme suivant illustre la structure de la pile, semblable à des briques empilées les unes sur les autres :

architecture mémoire de la pile

Les adresses de mémoire inférieures sont sur le dessus et les adresses de mémoire supérieures sont tout en bas.

Chaque fonction crée son propre frame de pile. Le frame de pile de l’exemple ci-dessus pourrait donc être empilé sur un autre frame de pile utilisé par une autre fonction.

L’EBP, comme mentionné plus haut, est stocké comme point de référence invariable dans la pile. Pour cela, la valeur de l’ESP (le pointeur de la pile) est déplacée dans l’EBP. En effet, l’ESP change constamment, car il pointe toujours vers le haut de la pile. En le stockant dans l’EBP, on obtient un point de référence invariable dans la pile. La fonction peut alors pointer vers ses variables et paramètres au sein de la pile, à partir de ce point de référence.

Dans cet exemple, les paramètres transférés dans la fonction sont stockés dans « [EBP+8] », « [EBP+12] » et « [EBP+16] ». « [EBP+8] » est la distance dans la pile à partir de l’EBP.

Les variables seront stockées après le début de l’exécution de la fonction. Elles seront donc stockées plus haut dans la pile mais dans des espaces d’adresse inférieurs, dans notre exemple, elles seront à « [EBP-4] ».

Exemple type de mémoire de la pile

Pour illustrer ces notions, j’ai pris pour exemple un programme C simple qui appelle une fonction nommée « addFunc » qui additionne deux chiffres (1+4) et affiche le résultat à l’écran.

  1. #include “stdio.h”
  2. int addFunc (int a, int b);
  3. int main (void) {
  4. int x = addFunc(1,4);
  5. printf(“%d\n”, x);
  6. return 0;
  7. }
  8. int addFunc(int a, int b) {
  9. int c = a + b;
  10. return c;

Si l’on regarde de plus près le code de la fonction « addFunc », il y a deux paramètres (a et b) qui sont passés comme arguments et une variable locale (c), où est stocké le résultat. Une fois que le programme est compilé, il peut ensuite être chargé dans x64dbg. Ci-dessous, voici à quoi ressemblerait le code d’assemblage pour ce programme :

  1. push ebp
  2. mov ebp,esp
  3. sub esp,10
  4. mov edx,dword ptr ss:[ebp+8]
  5. mov eax,dword ptr ss:[ebp+C]
  6. add eax,edx
  7. mov dword ptr ss:[ebp-4],eax
  8. mov eax,dword ptr ss:[ebp-4]
  9. leave
  10. ret

Les trois premières lignes sont le prologue de la fonction, c’est là que de l’espace est créé pour la fonction dans la pile.

composants du prologue de la fonction

push ebp conserve l’ESP, l’ancien pointeur dans le frame de la pile, pour qu’il puisse retrouver son ancien emplacement à la fin de la fonction. Un frame de pile est utilisé pour stocker les variables locales ; chaque fonction ayant son propre frame de pile dans la mémoire.

mov ebp, esp déplace la position actuelle de la pile dans l’EBP, qui constitue la base de la pile. Nous avons à présent un point de référence qui nous permet de pointer vers nos variables locales stockées dans la pile. La valeur de l’EBP ne change jamais.

sub esp, 10 augmente la pile de 16 octets (10 en hexadécimal) pour attribuer de l’espace dans la pile pour toute variable vers laquelle nous devons pointer.

Ci-dessous, voici à quoi ressemblerait la pile pour ce programme. Chaque élément de données utilisé est empilé les uns sur les autres, dans une section de la mémoire, comme le montre le diagramme fourni plus haut.

EBP-10

EBP-C

EBP-8

EBP-4 (int c)

EBP = lancé dans la pile au commencement de la fonction. C’est le départ de notre frame de pile.

EBP+4 = Adresse de retour de l’ancienne fonction

EBP+8 = Paramètre 1 (int a)

EBP+C = Paramètre 2 (int b)

Dans cet exemple, on voit en regardant la pile que l’on a attribué de l’espace pour quatre variables locales, toutefois nous n’en avons qu’une seule, « int c ».

mov edx,dword ptr ss:[ebp+8] –  Ici nous déplaçons « int a », qui est la valeur 1, dans le registre EDX.

La partie la plus importante ici est [ebp+8]. Elle se trouve entre crochets, ce qui signifie que vous faites appel à la mémoire directement à cet emplacement. Cela pointe vers l’emplacement de la mémoire qui se trouve 8 octets plus haut que ce qui se trouve dans l’EBP.

J’ai mentionné plus haut que les paramètres passés dans une fonction se trouveront toujours dans des adresses supérieures, qui se trouvent plus bas dans la pile. Nos paramètres « int a » et « int b » ont été passés à la fonction avant la création du frame de la pile. C’est pourquoi ils se trouvent dans les emplacements « ebp+8 » et « ebp+c ».

mov eax,dword ptr ss:[ebp+C] – Même chose que ci-dessus, bien que nous pointions maintenant vers à « ebp+C », qui correspond à « int b », la valeur 4, et que nous la déplaçons dans le registre EAX.

add eax, edx – Cela lance l’addition et stocke le résultat dans « EAX ».

mov dword ptr ss:[ebp-4],eax – Ici, nous déplaçons le résultat stocké dans EAX, dans la variable locale « int c ».

La variable locale « c » est définie au sein de la fonction, par conséquent elle se trouve dans une adresse mémoire plus basse que le haut de la pile. Puisqu’elle se trouve dans le frame de la pile et qu’elle fait 4 octets, nous pouvons simplement utiliser l’espace que nous avions auparavant réservé pour les variables, en soustrayant 10 de l’ESP, et utiliser dans ce cas « EBP-4 ».

mov eax,dword ptr ss:[ebp-4] – La plupart des fonctions retournent la valeur stockée dans « EAX ». Puisque la valeur retournée se trouve dans EAX et que nous l’avons déplacée dans la variable « c », ici elle est à nouveau déplacée dans EAX pour qu’elle puisse être retournée.

Leave – Il s’agit d’un masque pour une opération qui déplace à nouveau l’EBP dans l’ESP et le retire de la pile, c’est-à-dire préparer le frame de la pile pour la fonction qui a appelé cette fonction.

ret  – Passe directement à l’adresse de retour pour revenir à la fonction source, dont le frame de pile a été conservé, car nous avons fait en sorte de l’enregistrer au début de cette fonction.

Exemple pratique : mémoire de la pile et x64dbg

Dans l’article précédent, j’ai montré comment dépacker un malware à l’aide de x64dbg. Nous pouvons à présent voir quelques-unes des fonctions utilisées par le malware et comment la pile est utilisée dans ce cas-là.

Tout d’abord, ouvrez le malware dépacké dans x64dbg, dans cet exemple, mon malware s’appelle « 267_unpacked.bin ».

Rendez-vous dans le point d’entrée du malware en cliquant sur Debug puis Run.

exécution de débogage

Nous sommes à présent dans le point d’entrée du malware. J’ai encadré deux fenêtres en rouge qui contiennent les informations de la mémoire de la pile :

point d’entrée

La première fenêtre contient les paramètres envoyés sur la pile. Nous savons qu’il s’agit de paramètres et non de variables car ils commencent par « esp+ » et non « esp- » comme expliqué plus haut.

paramètres

La deuxième fenêtre est la mémoire de la pile à proprement parler.

stackx64

La première colonne liste les adresses dans la mémoire de la pile. Comme je l’ai dit plus haut, les adresses supérieures se situent tout en bas et les adresses inférieures se situent tout en haut de la pile.

La deuxième colonne contient les données envoyées sur la pile, les crochets en bleu représentent des frames de pile individuels. Rappelez-vous, chaque fonction a son propre frame de pile pour stocker ses propres paramètres.

La troisième colonne contient des informations automatiquement remplies par x64dbg, dans cet exemple, on peut voir les adresses vers lesquelles reviendra x64dbg une fois l’exécution de la fonction terminée.

Dans l’image ci-dessous, la première commande vers laquelle pointe EIP est « push ebp », la valeur actuelle de l’EBP que j’ai encadré sur l’image est « 0038FDE8 ».

push ebp

Dans la fenêtre de la pile, j’ai encadré cette adresse qui est le pointeur actuel de base du frame de la pile.

En cliquant sur « Step over », l’EBP est alors envoyé dans la pile. Lorsque la fonction sera terminée, le malware pourra revenir à cette adresse.

On doit maintenant déplacer le pointeur de la pile vers l’ESP, il s’agit de l’adresse « 0038FDDC » encadrée ci-dessous.

mov ebp esp

En exécutant cette commande, on déplace l’ESP dans le registre EBP, encadré ci-dessous.

sub esp 420

Ensuite, le malware doit créer de l’espace dans la pile, ce qui est fait en soustrayant « 420 » à l’ESP. Il utilise la soustraction car de l’espace sera créé dans l’espace d’adresses inférieures, situé en haut de la pile. Sur l’image ci-dessous, l’espace d’adresses inférieures se situe au-dessus du frame de la pile actuel.

espace adresses supérieures dans la pile

On exécute la commande « sub esp, 420 » puis on met à jour la pile.

espace affecté

Notez que nous sommes maintenant dans l’espace d’adresses inférieures, qui se trouve en haut de la pile et qu’ESP a été mis à jour et indique désormais le nouvel emplacement en haut de la pile.

C’est un schéma assez courant que l’on observe au lancement des fonctions d’un malware et que vous apprendrez à reconnaître.

Ensuite, on a trois instructions Push qui envoient les valeurs de trois registres dans la pile. En passant ces instructions, on met à jour la pile comme prévu et on met également à jour la fenêtre des paramètres :

instructions push

Ensuite, plusieurs fonctions ont été écrites par le créateur du malware, étudions plus en détail l’une d’entre elles pour voir ce qu’elle fait et quel est le rôle de la pile dans tout ça.

Dans l’image ci-dessous, le curseur de ma souris survole la fonction « 267_unpacked.101AEC9 ». En faisant cela, dans x64dbg, une pop-up apparaît avec un aperçu de cette fonction. Cela permet à l’utilisateur de voir une partie du code d’assemblage de la fonction qui est appelée. Dans cette pop-up, on voit qu’un grand nombre de chaînes est transformé en variables. Et on sait que ce sont des variables en raison du préfixe « ebp- ». Ces chaînes sont des appels API Windows masqués, qui seront utilisés par le malware pour effectuer diverses actions, comme créer des processus et des fichiers sur le disque.

appel de 101AEC9

Quand on s’intéresse de près à cette fonction, on peut voir ce qui se passe plus en détail et comment la pile intervient dans x64dbg.

Un nouveau frame de pile est créé, que j’ai encadré en bas à droite et comme prévu, on obtient le prologue de la fonction.

fonctions hachées

En ouvrant cette fonction, on met à jour l’ESP, qui est l’adresse « 0038F9AC » dans la mémoire de la pile, contient l’adresse de retour de la fonction « principale » et crée de l’espace dans la pile en soustrayant 630 de l’ESP. Les instructions commençant par « mov » déplacent ensuite les noms de fonctions hachés dans leurs propres variables.

fonctions hachées 2

En faisant défiler le code d’assemblage, on arrive à la fin de la fonction et on peut voir plusieurs appels de la fonction. Ceux-ci sont utilisés pour mettre en clair les hashs qui ont été transformés en variables.

Les commandes que j’ai encadrées sont ce qu’on appelle « l’épilogue de la fonction », qui nettoie la mémoire de la pile une fois la fonction terminée. Je vais sélectionner celle qui m’intéresse, à savoir « add esp, C » puis cliquer sur « Debug » dans la barre d’outils puis « Run until selection ».

run until selection

Cela permet de mettre à jour l’EIP sur l’instruction que nous avons encadrée et également de montrer la pile avant qu’elle ne soit nettoyée.

épilogue 2

Dans le prologue de la fonction, pour créer de l’espace dans la pile, le malware a dû soustraire à partir de l’ESP, pour pouvoir affecter de l’espace dans la pile, au niveau des adresses inférieures. Nous devons à présent supprimer l’espace affecté. En exécutant la commande « add esp, C », on ajoute la valeur hex « C » à la pile, pour que l’on se déplace vers le bas, vers les adresses plus élevées.

L’image ci-dessous montre la pile mise à jour, une fois que l’on exécute « add esp, C ».

épilogue 3

Ensuite, on arrive à la commande « mov esp, ebp », qui transfère la valeur de l’EBP à l’ESP. Notre EBP actuel est « 0042F3EC », lorsque l’on fait défiler les données dans la fenêtre de la pile, on peut voir que cette adresse contient l’ancien ESP, le pointeur de la pile.

En exécutant cette commande maintenant, on nettoie la pile.

épilogue 5

La commande « pop ebp » fait apparaître l’adresse « 00E0CDA8 » qui était stockée en haut de la pile et la déplace alors dans l’EBP.

épilogue 6

Cela signifie que lorsque la prochaine instruction « ret » sera exécutée, nous reviendrons à l’adresse « 00E0CDA8 ».

revenir à la fonction principale

L’image ci-dessus montre que l’on est désormais revenu à la fonction « principale » du malware et que l’on est à l’adresse « 00E0CDA8 » directement après la fonction que l’on vient d’analyser dans x64dbg.

Vous êtes désormais capable d’effectuer une rétro-ingénierie sur un malware à l’aide de x64dbg ! Dans le prochain article, je vous montrerai comment mettre à profit ce que vous avez appris dans les derniers articles pour commencer à effectuer des rétro-ingénieries par vous-même.

Enfin, pour vous assurer que votre organisation est équipée pour détecter les menaces et y répondre, inscrivez-vous pour une démo de DatAlert et découvrez les bonnes pratiques à mettre en place pour vous protéger des malwares.

Neil Fox

Neil Fox

Neil est un professionnel de la cybersécurité spécialisé dans la réponse aux incidents et l'analyse des logiciels malveillants. Il crée également du contenu de cybersécurité pour sa chaîne YouTube et son blog à l'adresse 0xf0x.com.

 

Votre cybersécurité est-elle au cœur de votre infrastructure ?

Bénéficiez d'une évaluation personnalisée des risques auxquels sont exposées vos données, effectuée par des ingénieurs passionnés par la sécurité des données.