Cet article n'a pas pour prétention la mise en oeuvre d'un rootkit super évolué et furtif mais plutôt de mettre en avant les possibilités qu'il y a avec les API Windows. L'injection de DLL a son utilité autrement que pour la création de virus/rootkits, il peut par exemple être utilisé par un processus pour en surveiller un autre, pour gérer plus efficacement les processus plutôt que par les API concernée par les processus.





Les API Windows représentent une liste de fonctions disponibles pour mieux développer nos programmes. Elles sont disponibles à travers plusieurs outils, dont Depends . Elles ne sont pas toutes documentées, c'est à dire que les paramètres ne sont pas toujours connus, les valeurs retournées non plus, c'est alors au développeur de les "deviner". Lors de l'injection de DLL, les API Windows sont utilisées par l'injecteur et parfois par la DLL à injecter. Ils s'agit d'une poignée de fonction nécessaire qui vont permettre de copier le contenu d'un fichier binaire - bibliothèque de fonction (DLL) - dans le processus, grâce à un thread.





- Processus
Un processus est l'image mémoire du résultat de l'exécution d'un programme. Lorsqu'un programme s'exécute il charge l'équivalent binaire de sa section .data - donc ses données - puis de même pour la section .text qui contiendra le flot de commandes assembleur à exécuter. Un processus peut contenir des handles et des thread qui vont lui permettre de ne pas grossir en mémoire ou alors rapidement d'occuper toutes les ressources systèmes. Les handle permettent au système d'exploitation de s'associer aux processus et de les faire interagir avec l'utilisateur (l'écran).

- Thread
Le thread est une partie du programme utilisant une partie plus minime des ressources du processeur ou bien de la mémoire, le processus va gérer le thread de manière à effectuer plusieurs tâches parallèles. La programmation avec les threads permet donc d'effectuer plus de tâches on utilisant moins de ressources, mais cela est bien entendu relatif à son environnement.

- Handle
Un handle est une poignée qui permet à une fonction de s'associer avec un objet. Par exemple les FILE HANDLE, par exemple en langage C, il serait représenter par un pointeur de type FILE : FILE* handle_for_a_file; c'est ensuite sur à travers ce handle que seront effectuées les opérations concernant le fichier : fread(handle_for_a_file, ... ); fscanf(handle_for_a_file, ... );

- PID
Le PID, pour Process IDentifiant est la valeur numérique du processus. Il permet d'identifier un processus rapidement et facilement qu'il ait un nom ou pas.

- PPID
Le PPID est l'identifiant du processus père, par exemple si la calculatrice est lancée, son père sera l'explorateur (explorer.exe) car pour lancer la calculatrice il faut avoir chargé l'explorateur pour trouver oà se trouve la calculatrice. Ainsi on peut expliquer comment se donne les droit d'un processus à un autre, en lançant cmd.exe en tant que administrateur, ses enfants (netstat.exe par exemple) auront les même droits.

- API
Une API est une interface de programmation entre le système d'exploitation et les programmes. Il s'agit plus précisément de fonctions/modules utiles (voir nécessaires dans la plus part des cas) aux autres programmes. Elle permet de se documenter quant à l'utilisation de fonctions système sur notre programme et inversement, pour que le programme interfère avec le système. Les API de Microsoft ne sont pas toutes documentées, ce qui veut dire que le développeur doit parfois tester les arguments d'une fonction afin de pouvoir l'utiliser.

- DLL
Une DLL va permettre au programme de charger une bibliothèque de fonctions. DLL signifie "Dynamic Load Library", cette acronyme démontre l'utilité de tels fichiers. Les DLL sont propres à Windows, c'est à dire que ces ensembles de fonctions, de programmes utilitaires ne sont utilisable qu'à partir de programmes Windows. Les DLL sont des fichiers binaires tout comme les exécutables à trois différences près :

- Une DLL a besoin d'un programme externe pour être utilisée ;
- Du fait que c'est une bibliothèque de fonctions, il faut un programme externe pour connaitre et exploiter ses fonctions ;
- Par défaut elles ne sont exploitables que par le programme qui les utilise.





Pour l'injection de DLL, il est nécessaire d'appeler des fonctions Windows - donc d'utiliser des API, notamment celle concernant les processus ( dans en langage C ), voici donc la liste des fonctions dont nous auront besoin. Nous avons tout d'abord besoin d'élever nos droit sur notre programme afin qu'il ait la permission d'injecter du code binaire dans un autre:


// Pour l'élévation des droits
 OpenProcessToken	
 LookupPrivilegeValue
 AdjustTokenPrivileges
 CloseHandle 

Ensuite, nous aurons évidemment besoin de savoir dans quel processu nous allons injecter notre DLL:


// Pour l'exploration des processus
 CreateToolhelp32Snapshot
 Process32First
 Process32Next 

Puis finalement les fonctions appelées pour l'injection. Vous remarquerez l'utilisation de "CreateRemoteThread" qui est la fonction qui permettra d'attacher au processus (distant) un thread: notre DLL.


// Pour l'injection à proprement parlé
 OpenProcess
 VirtualAllocEx
 WriteProcessMemory
 GetProcAddress
 CreateRemoteThread
 WaitForSingleObject
 VirtualFreeEx 

Voici un petit schéma afin de vous aider à mieux comprendre:



                             1 hProc  = OpenProcess( PROCESS_ALL_ACCESS, FALSE, ProcPID )

                             2 AllocMemo  = (char*)VirtualAllocEx( hProc,
                                                                     NULL,                      
                                                                     strlen(strDLLFileName)+1,  
                                                                     MEM_COMMIT,                
                                                                     PAGE_READWRITE );          
5- CreateRemoteThread (
 hProc ,NULL,0,GPA,
(void*) AllocMemo ,          @: 0x7???????
THREAD_SUSPEND_RESUME,       +---------+
NULL );                      | PROCESS |
-----------------+           |=========|
                 |           | PID     |
                 |           | PPID    |
                 |           | Threads |
                 |           | MID     |
                 |           | Flags   |
                 |           +----+----+
                 |                |
@: 0x????????    |   <-- handle --+ @: 0x????????
+-----+          V                |
| DLL |  ---+---->   <-- thread --+ @: 0x????????
+-----+     |                     |
            |                      
            ^
           3- WriteProcessMemory(  hProc ,                     // Bloc
                                   AllocMemo ,                 // concernant
                                   strDLLFileName,             // l'écriture 
                                   strlen(strDLLFileName)+1,   // dans le 
                                   NULL )                      // process distant

           4- (DWORD*)GetProcAddress( GetModuleHandle("kernel32.dll"),
                                             "LoadLibraryA" );   


1- Ouverture du process, avec tout les accès possible, c'est pour cela que le programme
doit avant s'élever en privilèges

2- Maintenant qu'on a notre poignée sur le process par la fonction précédente on peut
allouer autant de mémoire que la taille du fichier

3- Une fois l'allocation faite, on peut écrire les données, donc notre fichier dans la
mémoire du process.  WriteProcessMemory  prend en paramètre un nom de fichier.

4- Pour utiliser ce qui est écrit, il nous faut un  thread , pas de problème une
API Windows nous le permet grâce à la fonction  CreateRemoteThread , mais avant
il nous faut l'adresse du process qui sera donnée par  GetProcAddress .

5- Il est enfin temps d'appeler la DLL chargée via  CreateRemoteThread 




Les fonctions du programme sont largement commentées ce qui permet de comprendre le but de certains appels ainsi que des API sans avoir à s'étendre sur la théorie. Le nom des fonctions ainsi que les valeurs utilisées sont retrouvables dans la documentation Microsoft.

La fonction permettant l'élévation de privilèges:


int UpPrivileges()
{
   HANDLE hToken;
   LUID Value;
   TOKEN_PRIVILEGES tp;
 
   // On ouvre un handle le process courant, c'est à dire notre programme
   if( ! OpenProcessToken(GetCurrentProcess(),
                        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
                        &hToken) )
      return ERROR_NOT_UPGRADE;
 
   // On vérifie que notre programme a les droits de débogages
   // s'il a déjà les droits, inutile de continuer
   if( ! LookupPrivilegeValue(NULL,
                            SE_DEBUG_NAME,
                            &Value) )
      return ERROR_NOT_UPGRADE;
 
 
   /* On initialise la structure afin de redéfinir les droits */
 
   // Nombre de privilège, effectivement un process peut avoir plusieurs privilèges
   tp.PrivilegeCount = 1;
 
   // On conserve l'identifiant de privilège
   tp.Privileges[0].Luid = Value;
 
   // Définit le privilège en lui m&ecirc;me
   tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 
   // Ajuste les privilèges définis par la structure ci-dessus
   // si l'ajustement ne s'est pas fait avec succès, on quitte la fonction
   if( ! AdjustTokenPrivileges(hToken,
                               FALSE,
                               &tp,
                               sizeof(tp),
                               NULL,
                               NULL) )
      return ERROR_NOT_UPGRADE;
 
   CloseHandle(hToken);
 
   return SUCESSFUL;
} 

Pour aider à l'injection il nous faut la liste des process :


int ProcessList()
{
   int i_NbrProcess = 0;
 
   // Une poignée pour communiquer avec les process
   HANDLE h_Sys = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 
   if( ! h_Sys )
   {
      // Impossible d'accéder aux fonctions de Toolhelp32
      return(ERROR_TH32_ACCESS);
   }
 
   PROCESSENTRY32 p_Proc;               // Structure of a process
   ZeroMemory(&p_Proc, sizeof(p_Proc)); // Initialize to NULL the proc structure
   p_Proc.dwSize = sizeof(p_Proc);      // Dword Size = Size Of Structure proc
 
   // On entre dans la liste des process, on commence par le premier      
   if( ! Process32First(h_Sys, &p_Proc) )
   {
      return(ERROR_P32F_ACCESS);
   }
   printf("\\n");
   printf("%-6s \\t| %-6s \\t| %-4s \\t| %-20s\\n","PPID","PID","USAGE","NAME");
   printf("%-6s \\t+-%-6s \\t+-%-4s \\t+-%-20s\\n","----","---","-----","----");
   do {
      printf("%-6d \\t| %-6d \\t| %-4d \\t\\t| %-20s\\n",p_Proc.th32ParentProcessID
                                                   , p_Proc.th32ProcessID
                                                   , p_Proc.cntUsage
                                                   , p_Proc.szExeFile );
      i_NbrProcess ++;
     // On affiche tant qu'on est sur un process, en avançant de process en process
   } while( Process32Next(h_Sys, &p_Proc) );
 
   return(i_NbrProcess);
}
 

Puis voici l'attache au process de notre DLL, c'est à dire l'injection en elle même. Vous remarquerez que le process est appelé par son PID et que la DLL par son chemin.


int AttachToProcess(DWORD ProcPID, char* strDLLFileName)
{
     HANDLE hProc;
     HANDLE hThread;
     void* GPA;
     char* AllocMemo;
     int res;
 
     // Elevation de privilèges...
     printf("[-] Setting up higher privilege...");
     if( UpPrivileges() )
     {
        no_printf();
        printf("\\n[!] Try to continue load of the DLL...");
     }
     else
        printf("\\b\\b\\b Ok.");
 
     hProc = OpenProcess(PROCESS_ALL_ACCESS,
                         FALSE,
                         ProcPID);
 
     AllocMemo = (char*)VirtualAllocEx(hProc,
                                       NULL,
                                       strlen(strDLLFileName)+1,
                                       MEM_COMMIT,
                                       PAGE_READWRITE);
 
     // Permet de verifier la structure de la DLL
     if( ! CheckDLLStructure(strDLLFileName) )
     {
        printf("\\n[+] Checking for DLL structure : No !\\n\\n");
        exit(DLL_NOT_VALID);
     }
     else
        printf("\\n[-] Checking for DLL structure : Ok.");
 
 
     printf("\\n[-] DLL filename to load : %s" , strDLLFileName);
     printf("\\n[-] Writting DLL %d bytes into process memory..." , N_SizeFile(strDLLFileName));
     Sleep(1000);
     if( WriteProcessMemory(hProc,
                            AllocMemo,
                            strDLLFileName,
                            strlen(strDLLFileName)+1,
                            NULL
                            ) )
     {
         printf("\\b\\b\\b Ok.\\n");
    }
     else
     {
         no_printf();
         exit(ERROR_NOT_WRITTEN);
     }
 
     printf("[-] Getting Process Address...");
     GPA = (DWORD*)GetProcAddress( GetModuleHandle("kernel32.dll"),
                                   "LoadLibraryA"
                                 );
 
     // Laisse un temps pour s'accrocher à l'adresse du processus
     Sleep(2000);
     printf("\\b\\b\\b at %X.",GPA);
 
 
     printf("\\n[-] Creating Thread...");
     hThread = CreateRemoteThread ( hProc,NULL,0,GPA,
                                    (void*)AllocMemo ,
                                    THREAD_SUSPEND_RESUME ,
                                    NULL
                                   );
     printf("\\b\\b\\b %X at %X.",hThread,&hThread);
 
     // Pour vérifier que le process est bien attaché, il faut
     // attendre sa synchronisation avec la DLL injectée
     printf("\\n[-] Waiting for synchronisation...");
     res = WaitForSingleObject(hThread, INFINITE);
 
     // L'attente de synchronisation
     Sleep(2000);
     printf("\\b\\b\\b Ok.\\n");
 
     // On purge la mémoire allouée au handle
     VirtualFreeEx(hProc, (void *) AllocMemo, 0, MEM_RELEASE);
     CloseHandle(hThread);
     return 0;
}
 




Nous allons créer une DLL des plus simplistes dont le code permettra de quitter l'application dans la quelle elle a été injectée. Il s'agit simplement d'utiliser l'API ExitProcess( UINT ) afin que le process "s'auto-termine". La DLL ci-dessous se limite au processus nouvellement créé.


#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
 
#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
#else /* Not BUILDING_DLL */
# define DLLIMPORT __declspec (dllimport)
#endif /* Not BUILDING_DLL */
 
DLLIMPORT void SelfKill ()
{
   MessageBox( NULL, "Attempt to self kill process.", "DLL injected", MB_OK );
   ExitProcess( 0 );
}
 
 
BOOL APIENTRY DllMain (HINSTANCE hInst     /* Library instance handle. */ ,
                       DWORD reason        /* Reason this function is being called. */ ,
                       LPVOID reserved     /* Not used. */ )
{
 
    SelfKill ()
 
    return TRUE;
} 

Afin de tester notre programme, nous allons à partir de la console MSDOS lancer le programme de la manière suivante :


c:\\dll_injexion\\> calc.exe && injexion -n calc.exe self_kill.dll 

Ce qui aura pour effet de lancer la calculatrice, puis (si elle est bien lancée (&&)) d'injecter la DLL "self_kill.dll", qui doit donc afficher un message puis quitter la caculatrice. Il se peut que l'antivirus ou le firewall (ou votre HIPS) réagisse, mais ayant le code sous les yeux vous pouvez voir qu'il n'y a aucun code malsain dans la DLL. L'air de rien, cette petite DLL peut servir à "tuer" un processus d'une autre manière que TerminateProcess .





L'injection de DLL n'est pas forcément utilisé dans le contournement de (vieux) firewall ou pour la création de virus/rootkits, mais également dans des programmes par exemple de surveillance: on peut très bien imaginer une DLL qui vérifie l'intégrité de l'éxecutable dans lequel elle a été injectée. Etant donnée la faible utilité de tel programme à l'heure actuelle je ne m'étendrais pas plus sur le sujet. Voici les sources du programmes complet :
- Injecteur générique de DLL
- Injecteur d'une DLL intégrée




   =>   Écrit par : Nicolas, le 10 novembre 2007


 
Mots clés :  
  reverse 
  
  c 
  
  windows 
    >   Articles connexes :

GDB - Cas d'école

GDB - Cas d'école


Format String


Le "format string" est une vulnérabilité lié au paramètre qu'il est possible de donner à l'instruction printf et ses dérivés. Nous allons l'exploiter dans un cas d'école


/tmp et /var/log en noexec sur macOS

/tmp et /var/log en noexec sur macOS



Comment gagner du temps sur Internet

Comment gagner du temps sur Internet



Durcissement de Windows

Durcissement de Windows


2737651