Détection dynamique du PID du processus cible

Dans l'article "DLL path injection" nous avons injecté une DLL dans le processus notepad.exe. Pour cela nous avons ouvert un handle sur ce processus, allouer de la mémoire puis lancer un thread.

La fonction de la WINAPI qui permet d'obtenir un handle sur le processus est OpenProcess dont voici le prototype:

HANDLE WINAPI OpenProcess(
__in  DWORD dwDesiredAccess,
__in  BOOL bInheritHandle,
__in  DWORD dwProcessId
);

On avait vu que cette fonction prend en paramètre le PID (dwProcessId) du processus à injecter et du coup il fallait soit passer le PID à notr einjector en paramètre soit l'hardcodé. En tant que grand flemmard ça m'embête un peu de devoir modifier cette valeur à chaque fois. De plus l'ensemble des articles de cette série injecteront toujours le processus notepad.exe. Au lieu d'hardcoder le PID du processus on pourrait du coup développer du code qui trouve le PID d'un processus donné.

Comment pourrait-on faire ça ? Avant tout il faut avoir quelques bases sur le fonctionnement de Windows et notamment une structure: _EPROCESS. Sous Windows l'ensemble des processus sont liés les uns aux autres via une double liste chaînée que l'on appelle _EPROCESS. L'adresse mémoire du début de cette structure se situe dans la structure _KDEBUGGER_DATA64:

Chaque processus est lié par un ForwardLink et un BackwardLink. Du coup une fois que nous avons accès à cette structure il devient assez simple de la crawler de manière à trouver le PID du processus que l'on cible (i.e: notepad.exe). Via l'API Windows un procédé similaire peut être réalisé via l'utilisation conjointe des API CreateToolhelper32Snapshot, Process32First et Process32Next.

La première chose que nous allons faire c'est créer deux variable, la première contiendra le nom du processus que l'on cible tandis que la seconde nous permettra d’initialiser la structure PROCESSENTRY32:

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

int main(int, char* []){
    PROCESSENTRY32 entry;
    wchar_t processName[] = TEXT("notepad.exe");
}

La structure PROCESSENTRY32 va nous permettre de stocker le contenu d'un processus et ainsi en récupérer les valeurs suivantes:

typedef struct tagPROCESSENTRY32 {
  DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  CHAR      szExeFile[MAX_PATH];
} PROCESSENTRY32;

On y trouve:

  • dwSize: la taille de la structure en octets.
  • cntUsage: artefact non utilisé et initialiser à 0
  • th32ProcessID: le PID du processus
  • th32DefaultHeapIP: artefact non utilisé et initialiser à 0
  • th32ModuleID: artefact non utilisé et initialiser à 0
  • cntThreads: le nombre de threads en cours d'exécution par le processus
  • th32ParentProcessID: le PID du processus père
  • pcPriClassBase: la priorité des threads créés par ce processus
  • dwFlags: artefact non utilisé et initialiser à 0
  • szExeFile: nom de l'exécutable qui a créé le processus

La documentation nous indique que dwSize doit toujours être initialisé et avoir une valeur correcte sinon on ne pourra pas utiliser les fonctions suivantes. Du coup nous allons l'initialiser dwSize en lui indiquant qu'il vaut la taille de la structure PROCESSENTRY32.

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

int main(int, char* []){
    PROCESSENTRY32 entry;
  	entry.dwSize =  sizeof(PROCESSENTRY32);  
	wchar_t processName[] = TEXT("notepad.exe");    
}

Ensuite nous allons utiliser l'API CreateToolhelp32Snapshot. Cette structure va nous permettre de faire un snapshot de l'état actuel des processus, l'état de leurs heaps, modules ainsi que l'ensemble des threads qu'ils ont lancés. Voici son prototype:

HANDLE CreateToolhelp32Snapshot(
__in  DWORD dwFlags,
__in  DWORD th32ProcessID
);

Elle prend en paramètre:

  • dwFlags: les éléments du système à inclure (est ce qu'on snapshot les processus, les processus et leurs threads, juste leurs heap etc..)
  • th32ProcessID: le PID du processus à inclure dans le snapshot. Ce PID ne nous sera pas utile puisque nous allons snapshoter l'ensemble des processus.

Et voici le code:

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

int main(int, char* []){
    PROCESSENTRY32 entry;
    wchar_t processName[] = TEXT("notepad.exe");
    entry.dwSize = sizeof(PROCESSENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    return 0;
}

Ici la valeur TH32CS_SNAPPROCESS indique que nous inclurons tous les processus en cours sur le système dans notre snapshot.

Il ne nous reste plus qu'à parcourir ce snapshot via le handle afin de trouver le bon nom d'exécutable. Pour cela il faudra d'abord s'assurer que le snapshot a été correctement réalisé et c'est exactement à quoi sert l'API Process32First dont le prototype est le suivant:

BOOL Process32First(
__in  HANDLE hSnapshot,
__in  LPPROCESSENTRY32 lppe
);

Elle prend en paramètre:

  • hSnaphot: un handle sur le retour de la fonction CreateToolhelp32Snapshot
  • lppe: une référence sur la variable entry qui contient la structure PROCESSENTRY32

Le code sera le suivant:

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

int main(int, char* []){
    PROCESSENTRY32 entry;
    wchar_t processName[] = TEXT("notepad.exe");
    entry.dwSize = sizeof(PROCESSENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    if (Process32First(snapshot, &entry) == TRUE){
        printf("Snapshot réussi");
    }
    else{
    	printf("Snapshot failed");
    }
  	return 0;
}

Il ne nous reste plus qu'à itérer sur l'ensemble des processus et de comparer le champ szExeFile avec le nom de notre exécutable (notepad.exe) afin de trouver le PID de ce  dernier. Pour cela on utilisera l'API Process32Next dont voici le prototype:

BOOL Process32Next(
__in  HANDLE hSnapshot,
__in  LPPROCESSENTRY32 lppe
);

Cette fonction prend en paramètre:

  • hSnapshot: Un handle sur la structure obtenu via CreateToolhelp32Snapshot
  • lppe: Une référence sur la variable entry qui contient la structure PROCESSENTRY32

Ce qui nous donne le code suivant:

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>

int main(int, char* []){
    PROCESSENTRY32 entry;
    wchar_t processName[] = TEXT("notepad.exe");
    entry.dwSize = sizeof(PROCESSENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    if (Process32First(snapshot, &entry) == TRUE){
        while (Process32Next(snapshot, &entry) == TRUE){
            if (wcscmp(entry.szExeFile, processName) == 0){
                printf("Found PID: %d\n", entry.th32ProcessID);
            }
        }
    }
    CloseHandle(snapshot);
    return 0;
}

Pour finir on va créer un processus notepad.exe et lancer le tool pour que celui-ci nous retourne le bon PID:

Notre code est fonctionnel! Une fois le PID récupéré nous pourrons ouvrir un handle sur le processus puis y injecter nos DLL's, shellcode et autres code malicieux :) !