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

Présentation de la mémoire de la pile, description de son architecture et comment l’utiliser pour effectuer une rétro-ingénierie d’un malware à l’aide de x64dbg.
Neil Fox
9 minute de lecture
Dernière mise à jour 29 juin 2022

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 mener à bien ses activités malveillantes sur un appareil compromis.

C’est un sujet que je comparerais au subnetting, qui nécessite un peu d’efforts. 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 en plein milieu, au risque de tout faire s’é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 :

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] » correspond à 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.  
  3. int addFunc (int a, int b);
  4.  
  5. int main (void) {
  6.  
  7. int x = addFunc(1,4);
  8. printf(“%d\n”, x);
  9.  
  10. return 0;
  11. }
  12.  
  13. int addFunc(int a, int b) {
  14. int c = a + b;
  15. 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 ici que de l’espace est créé pour la fonction dans la pile.

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 retourne 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 et 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étails l’une d’entre elles pour voir ce qu’elle fait et quel est le rôle de la pile dans tout cela.

Dans l’image ci-dessous, le curseur de ma souris survole la fonction « 267_unpacked.101AEC9 ». En faisant cela, dans x64dbg, une fenêtre 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 fenêtre 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, on 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.

Que dois-je faire maintenant ?

Vous trouverez ci-dessous trois solutions pour poursuivre vos efforts visant à réduire les risques liés aux données dans votre entreprise:

1

Planifiez une démonstration avec nous pour voir Varonis en action. Nous personnaliserons la session en fonction des besoins de votre organisation en matière de sécurité des données et répondrons à vos questions.

2

Consultez un exemple de notre évaluation des risques liés aux données et découvrez les risques qui pourraient subsister dans votre environnement. Cette évaluation est gratuite et vous montre clairement comment procéder à une remédiation automatisée.

3

Suivez-nous sur LinkedIn, YouTube et X (Twitter) for pour obtenir des informations sur tous les aspects de la sécurité des données, y compris la DSPM, la détection des menaces, la sécurité de l’IA et plus encore.

Essayez Varonis gratuitement.

Obtenez un rapport détaillé sur les risques liés aux données basé sur les données de votre entreprise.
Se déploie en quelques minutes.

Keep reading

Varonis tackles hundreds of use cases, making it the ultimate platform to stop data breaches and ensure compliance.

qu’est-ce-que-x64dbg-et-comment-l’utiliser
Qu’est-ce que x64dbg et comment l’utiliser
Présentation de x64dbg et marche à suivre pour l’utiliser dans le cadre de l’analyse d’un malware. Cet article est le premier d’une série de 4 centrés sur x64dbg.
comment-dépacker-un-malware-avec-x64dbg
Comment dépacker un malware avec x64dbg
Cet article explique comment utiliser x64dbg pour procéder à l’ingénierie inverse d’un malware. Continuez votre lecture pour en savoir plus !
comment-utiliser-ghidra-pour-la-rétro-ingénierie-des-malwares
Comment utiliser Ghidra pour la rétro-ingénierie des malwares
Une présentation de l’outil d’analyse des malwares Ghidra. Cet article explique comment installer et utiliser l’interface Ghidra.
la-chaîne-cybercriminelle-en-8-étapes
La chaîne cybercriminelle en 8 étapes
Comme dans tout bon vieux « casse », il y a plusieurs phases dans le déroulement d’une cyberattaque. Lockheed Martin a dérivé le concept de la chaîne criminelle d’un modèle...