Frenchy shellcode (Partie 2): Remplacement de processus et comparaison des versions du shellcode

Article

Frenchy shellcode

Dans cette deuxième partie nous allons continuer l’analyse du « frenchy » shellcode et voir :

  • La manière dont le shellcode dissimule le code malveillant derrière un processus légitime
  • La suite de l’émulation fait par Codebreaker
  • Les différences entre les versions de ce shellcode

Frenchy shellcode v3 – suite

Création de mutex

Après le chargement des DLLs et la résolution des fonctions, que nous avons vu dans la première partie, le shellcode appelle CreateMutexW pour créer un mutex nommé « frenchy_shellcode_<version> ».

frenchy-shell-code

Figure 1 – Creation du mutex « frenchy_shellcode_3 »

Le nom du mutex créé a donné au shellcode son surnom : frenchy_shellcode.

Les logiciels malveillants utilisent souvent un mutex pour s’assurer qu’ils ne réinfectent pas la même machine et donc qu’ils ne s’exécutent qu’une seule fois.

 

Remplacement de processus

Suite à la création du mutex, le shellcode appelle la fonction chargée du remplacement de processus.

Également nommé « process hollowing », le remplacement de processus est une technique d’injection de code qui permet d’exécuter un PE dans l’espace d’adressage d’un autre processus (voir la Figure 2).

Aperçu de la technique de remplacement de processus. Source: Malware Analysis and Detection Engineering, Saldanha

Figure 2 – Aperçu de la technique de remplacement de processus. Source: Malware Analysis and Detection Engineering, Saldanha

Pour mettre en place cette méthode, le processus malveillant doit :

  • Créer un processus en état de suspension,
  • Évider le code et les données du processus crée, puis les remplace par son propre contenu malveillant,
  • Réécrire le point d’entrée du thread pour pointer vers le début de ce nouveau PE,
  • Reprendre l’exécution du processus qui exécutera le code malveillant.

Comme le processus a été à l’origine lancé à partir d’un programme légitime, le chemin du processus pointe toujours vers celui-ci et donne l’illusion de toujours être un processus légitime.

Pour effectuer ce remplacement, le shellcode utilise un ensemble de fonctions de l’API native.

Comme nous pouvons le voir dans la Figure 3, il commence par appeler CreateProcessW et crée un processus en mode suspendu.

Replacement de processus : Création de processus en mode suspendu (Ghidra)

Figure 3 – Replacement de processus : Création de processus en mode suspendu

Replacement de processus : Le handle du processus crée

(Ghidra)

Figure 4 – Replacement de processus : Le handle du processus crée

Après l’appel à CreateProcessW, nous pouvons voir dans la Figure 4 le processus notepad.exe nouvellement créé et la valeur de son descripteur : 0x150.

Pour pouvoir écrire un PE dans le processus cible, le shellcode doit savoir où le processus cible est chargé en mémoire. Il peut obtenir ces informations à partir de la structure PEB du processus.

Il utilisera donc le NtQueryInformationProcess pour obtenir l’adresse PEB du processus cible.

 

- Replacement de processus : NtQueryInformationProcess

Figure 5 – Replacement de processus : NtQueryInformationProcess

Le paramètre « ProcessHandle » va contenir le descripteur du processus créé (0x150 dans notre cas) et « ProcessInformation » contiendra les informations sur le processus, dont l’adresse de la structure PEB.

Maintenant, le shellcode doit obtenir l’adresse de base de l’image du processus cible. Cela se situe à l’offset 8 de la structure PEB.

À cet égard, il utilisera NtReadVirtualMemory avec pour argument le descripteur de processus cible (0x150), l’adresse &PEB + 8 et le pointeur « Buffer », qui va contenir l’adresse de l’image (0xbc0000).

Replacement de processus : NtReadVirtualMemory

Figure 6 -Replacement de processus : NtReadVirtualMemory

Replacement de processus : Adresse de base de l'image cible

Figure 7 – Replacement de processus : Adresse de base de l’image cible

 

Maintenant que le shellcode connaît l’adresse de base du processus cible, il peut commencer le remplacement du code du processus à proprement dit.

Pour ce faire, le shellcode va créer une section en utilisant la fonction NtCreateSection pour partager une partie de son espace d’adressage mémoire avec le processus cible, dans laquelle il écrira le contenu du programme à injecter.

 

Replacement de processus : NtCreateSection

Figure 8 – Replacement de processus : NtCreateSection

Nous pouvons voir dans la Figure 8, que cette section est créée avec tous les accès garantis (voir le paramètre « DesiredAccess »).

Replacement de processus : Section nouvellement créée et ses permissions

Figure 9 – Replacement de processus : Section nouvellement créée et ses permissions

La Figure 9 nous montre également la valeur du descripteur de la zone mémoire nouvellement créée : 0x15c.

Pour qu’un processus puisse lire ou écrire dans la section créée, le shellcode devra mapper une vue de cette dernière.

Dans cette intention, il appellera la fonction NtMapViewOfSection.

Tout d’abord, il mappe une vue de la section dans le processus local.

Replacement de processus : Mapper la vue de section dans le processus local

Figure 10 – Replacement de processus : Mapper la vue de section dans le processus local

Dans la Figure 10 nous voyons souligner :

  • le premier argument de NtMapViewOfSection qui est le handle de la section à mapper (0x15c),
  • le ProcessHandle avec une valeur de 0xffffffff, un pseudohandle qui fait référence au processus courant
  • le pointeur vers la variable qui reçoit l’adresse virtuelle de la mémoire mappée : 0x004ff88c.

NtMapViewOfSection est appelé à nouveau, cette fois pour mapper une vue de la section dans le processus cible.

- Replacement de processus : Mapper la vue de section dans le processus cible

Figure 11 – Replacement de processus : Mapper la vue de section dans le processus cible

Contrairement à l’appel précédant à NtMapViewOfSection, nous voyons dans la Figure 11 que l’argument correspondant au handle du processus prend cette fois la valeur du descripteur du processus cible (0x150) et le troisième paramètre change en 0x004ff888.

Nous allons regarder le contenu des deux pointeurs 0x004ff88c et 0x004ff888 qui reçoivent respectivement l’adresses virtuelle de la section mappée dans le processus local et celle du processus cible.

Figure 12 – Replacement de processus :  Les adresses virtuelles de la mémoire mappée

Les deux valeurs soulignées dans la Figure 12 correspondent à l’adresse virtuelle de la mémoire mappée dans le processus local (0x00540000) et l’adresse de la vue mappée dans le processus cible (0x00400000).

L’exécution du shellcode s’ensuit avec l’appel à memcpy. Dès lors, la vue mappée du processus local contiendra le PE malveillant.

- Replacement de processus : Contenu de la vue mappée du processus local

Figure 13 – Replacement de processus : Contenu de la vue mappée du processus local

Nous pouvons voir dans la Figure 13 le début d’un exécutable avec l’habituel stub MS-DOS.

Replacement de processus : Contenu de la vue mappée du processus cible

Figure 14 – Replacement de processus :  Contenu de la vue mappée du processus cible

Du fait que la section antérieurement remplie avec le PE à injecter est partagé entre les processus, la vue mappée de la section dans le processus cible aura le même contenu que la section mappée dans le processus local (voir Figure 14).

Une fois que le PE à injecter est accessible, le shellcode doit remplacer l’adresse actuelle de l’image du processus cible par celle de la section mappée contenant le nouveau PE.

Il appellera donc NtWriteVirtualMemory (voir la Figure 15) qui prend en argument le descripteur du processus cible (0x150), le pointeur vers l’adresse à changer et un pointeur vers l’adresse de la nouvelle image (voir la Figure 16).

Replacement de processus : Les paramètres de NtWriteVirtualMemory avant qu'elle soit appelée

Figure 15 – Replacement de processus : Les paramètres de NtWriteVirtualMemory avant qu’elle soit appelée

Replacement de processus : La paramètre Buffer de NtWriteVirtualMemory (pointeur vers l’adresse de la nouvelle image)

Figure 16 – Replacement de processus : La paramètre Buffer de NtWriteVirtualMemory (pointeur vers l’adresse de la nouvelle image)

Après NtWriteVirtualMemory, nous voyons dans la Figure 17, l’image de base du processus cible remplacée par l’adresse de la section qui contient le nouveau PE.

Replacement de processus : La nouvelle adresse de base du processus cible

Figure 17 – Replacement de processus : La nouvelle adresse de base du processus cible

Enfin, avec la nouvelle image chargée dans le processus cible, le contexte du thread doit être acquis en utilisant NtGetThreadContext.

Dans ce qui suit, NtSetContextThread change le registre EAX en la valeur du point d’entrée du PE injecté qui est exécuté après l’appel à NtResumeThread.

Nous pouvons voir l’enchaînement de ces fonctions dans la Figure 18.

Replacement de processus : Appel à NtGetThreadContext, NtSetContextThread, NtResumeThread

Figure 18 – Replacement de processus :  Appel à NtGetThreadContext, NtSetContextThread, NtResumeThread

Émulation du frenchy shellcode par Codebreaker

Dans les logs ci-dessus nous pouvons voir la suite de l’émulation Codebreaker avec les fonctions de l’API Windows mentionnées dans les sections «  Creation de mutex » et « Remplacement de processus » :

...
--- Call to Windows API detected ---
kernel32_CreateMutexW('lpMutexAttributes': 0, 'bInitialOwner': 0, 'lpName': 'frenchy_shellcode_003') -> 0
...
--- Call to Windows API detected ---
kernel32_CreateProcessW('lpApplicationName': 'c:\windows\notepad.exe', 'lpCommandLine': None, 'bInheritHandles': False, 'dwCreationFlags': 'CREATE_SUSPENDED|DETACHED_PROCESS|CREATE_NO_WINDOW', 'lpEnvironment': None, 'lpCurrentDirectory': None, 'lpStartupInfo': '[]') -> 1
...
--- Call to Windows API detected ---
ntdll_NtMapViewOfSection('SectionHandle': 'Section (33)', 'ProcessHandle': 'Current Process (-1)', 'pBaseAddress': 0, 'ZeroBits': 0, 'CommitSize': 0, 'pSectionOffset': 0, 'pViewSize': 40960, 'InheritDisposition': 2, 'AllocationType': None, 'Win32Protect': 'PAGE_EXECUTE_READWRITE') -> 1

--- Call to Windows API detected ---
ntdll_memcpy('dest': '0x20000000', 'src': '0x4000016', 'count': 1024) -> 0x20000000
...

 

Les versions de frenchy shellcode

Dans cette section, nous allons voir quelques différences mineures entre les multiples versions du frenchy shellcode.

Dans la version 01, avant de mapper les bibliothèques à partir de KnownDLL ou KnownDLL32, frenchy shellcode vérifie si le processus s’exécute sous WOW64 en utilisant la fonction IsWow64Process (voir la Figure 20).

Chargement des DLLs : frenchy_shellcode_01

Figure 19 – Chargement des DLLs : frenchy_shellcode_01

Dans toutes les autres versions, l’auteur simplifie le chargement en essayant de mapper les DLLs à partir des deux sources : KnownDLL et KnownDLL32.

Chargement des DLLs : frenchy_shellcode_{02,03,05,06}

Figure 20 – Chargement des DLLs : frenchy_shellcode_{02,03,05,06}

Indifféremment de la version, CreateMutexW est la dernière fonction vers laquelle le shellcode obtient un pointeur.

Tel que nous le voyons dans la Figure 21 et Figure 22, à l’exception de la sixième, toutes les versions du shellcode utilisent LoadLbraryA pour charger kernel32.dll, puis appellent GetProcAddress pour trouver l’adresse de CreateMutexW.

 

Cependant, en récupérant le pointeur de cette manière, le shellcode risque de tomber sur un hook d’API, car la fonction provient d’une DLL originale.

Résolution de CreateMutexW : frenchy_shellcode_{01,02,03,05}

Figure 21 – Résolution de CreateMutexW : frenchy_shellcode_{01,02,03,05}

Résolution de CreateMutexW : frenchy_shellcode_06

Figure 22 – Résolution de CreateMutexW : frenchy_shellcode_06

Dans les deux premières versions, le shellcode utilise directement le chemin du processus cible, sans traitement particulier (voir la Figure 23).

Le chemin de la cible : frenchy_shellcode_{01,02}

Figure 23 – Le chemin de la cible : frenchy_shellcode_{01,02}

Les versions ultérieures du shellcode comme nous pouvons le remarquer dans la Figure 24, étendent leur fonctionnalité en prenant en compte les chemins dont l’expansion automatique est désactivée.

 

Ces chemins d’accès sont préfixés avec le caractère « ? ».

Le chemin de la cible : frenchy_shellcode_{03,05,06}

Figure 24 – Le chemin de la cible : frenchy_shellcode_{03,05,06}

Conclusion

L’auteur du frenchy promet à ses acheteurs un shellcode complètement indétectable grâce à l’emploi des méthodes qui permettent de contourner les produits de sécurité et de dissimuler un malware derrière un processus légitime.

Cependant, puisque la technique du remplacement de processus bénéficie d’une grande popularité, elle est détectable par de nombreux produits de sécurité.

Toutefois, la probabilité d’échouer dans la détection de cette technique s’amplifie avec l’utilisation d’un packer ou quand le malware contourne les hooks d’API.

Quant aux différentes versions de shellcode, l’auteur ne fait pas de changements fondamentaux, ce qui permet à Codebreaker de détecter toutes les versions via les mêmes caractéristiques.

 

Auteur : Anastasia Cotorobai

Table des matières

Partager cet article :
Notre dernier article