aujourd’hui on va voir un grand classique du développement système Windows : les processus.
C’est un sujet fondamental, parce qu’en pratique tout programme utilisateur s’exécute dans un processus, mais beaucoup de gens utilisent CreateProcess, OpenProcess ou WaitForSingleObject sans vraiment avoir une vision claire de ce qu’ils manipulent. Or si on ne comprend pas ce qu’est réellement un processus, on comprend mal l’isolation, la mémoire, les handles, la sécurité, les modules, l’héritage ou encore la relation avec les threads.
Pourquoi les processus sont un sujet central
Comprendre les processus permet de mieux voir :
- comment Windows structure l’exécution des programmes
- pourquoi un programme possède son propre espace mémoire
- comment sont gérés les handles et les objets noyau
- comment créer, ouvrir, attendre ou terminer un processus
- comment fonctionnent l’héritage de handles, les modules et le token de sécurité
- comment plusieurs processus peuvent communiquer sans partager directement leur mémoire
Ce qu’est réellement un processus
Un processus doit etre vu comme un conteneur d’exécution. Il regroupe un ensemble d’éléments cohérents que Windows associe à une instance d’exécution donnée.
Un processus contient notamment :
- un espace d’adressage virtuel propre
- un ou plusieurs threads
- une table de handles
- un contexte de sécurité
- des modules chargés, comme l’EXE principal et des DLL
- un ou plusieurs heaps
- diverses structures internes gérées par le système
Autrement dit :
- le processus contient
- le thread exécute
Isolation entre processus
L’une des propriétés les plus importantes du modèle Windows est l’isolation. Chaque processus possède son propre espace mémoire virtuel, sa propre table de handles, ses propres variables globales, son propre heap, et plus généralement sa propre vision user-mode de beaucoup de ressources.
En pratique, cela signifie qu’un processus ne peut pas, par simple déréférencement de pointeur :
- lire la mémoire d’un autre processus
- écrire dans sa mémoire
- accéder directement à ses handles
- réutiliser ses objets simplement parce qu’ils existent ailleurs
- OpenProcess
- ReadProcessMemory
- WriteProcessMemory
- DuplicateHandle
- pipes
- sockets
- mémoire partagée
- RPC ou autres mécanismes IPC
Le processus courant
Pour récupérer des informations sur le processus courant, plusieurs API simples existent.
Exemple :
Code: Select all
HANDLE hProc = GetCurrentProcess();
DWORD pid = GetCurrentProcessId();
Conséquence importante :
- le pseudo-handle retourné par GetCurrentProcess ne doit pas etre fermé avec CloseHandle
- GetCurrentThread
- GetCurrentThreadId
Si l’on veut un vrai handle du processus courant, utilisable dans d’autres contextes, il faut passer par DuplicateHandle ou ouvrir explicitement le processus.
Créer un processus avec CreateProcessW
L’API centrale pour créer un processus user-mode est CreateProcessW. C’est l’une des fonctions majeures de Win32.
Exemple de base :
Code: Select all
STARTUPINFOW si = { 0 };
PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si);
BOOL ok = CreateProcessW(
L"C:\\Windows\\System32\\notepad.exe",
NULL,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi
);
if (!ok)
return 1;
- un nouveau processus
- un thread principal initial
- les structures noyau associées
- les handles de processus et de thread retournés dans PROCESS_INFORMATION
- hProcess : handle du processus créé
- hThread : handle du thread principal
- dwProcessId : PID
- dwThreadId : TID du thread principal
STARTUPINFOW et ce qu’il permet de contrôler
La structure STARTUPINFOW sert à préciser différents paramètres de démarrage. Ce n’est pas seulement une formalité.
Elle peut servir à configurer :
- la redirection de stdin, stdout et stderr
- certaines propriétés de fenêtre
- le desktop ou la station de fenêtre
- divers flags de démarrage
- cb
- dwFlags
- hStdInput
- hStdOutput
- hStdError
- wShowWindow
- lpDesktop
- STARTF_USESTDHANDLES
- STARTF_USESHOWWINDOW
Pour des scénarios plus avancés, il faut connaitre STARTUPINFOEX. Cette structure étend STARTUPINFOW avec une liste d’attributs de processus.
Elle devient importante lorsqu’on veut utiliser :
- PROC_THREAD_ATTRIBUTE_LIST
- UpdateProcThreadAttribute
- InitializeProcThreadAttributeList
- DeleteProcThreadAttributeList
- la liste explicite des handles hérités
- certaines politiques d’atténuation
- l’association à un pseudo-console
- des réglages plus précis du lancement
Nettoyage après CreateProcess
Une fois CreateProcess réussi, il faut fermer les handles quand on n’en a plus besoin :
Code: Select all
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
Il faut bien distinguer :
- le processus créé lui-meme
- le handle que ton programme possède vers ce processus
Les flags de création de processus
CreateProcess permet aussi de spécifier des flags qui changent fortement le comportement du lancement.
Parmi les plus connus :
- CREATE_SUSPENDED
- CREATE_NEW_CONSOLE
- CREATE_NO_WINDOW
- DETACHED_PROCESS
- CREATE_UNICODE_ENVIRONMENT
- CREATE_BREAKAWAY_FROM_JOB
- EXTENDED_STARTUPINFO_PRESENT
Pour reprendre le thread principal :
- ResumeThread
CreateProcess permet aussi de contrôler :
- la ligne de commande
- le bloc d’environnement
- le répertoire courant du nouveau processus
Il faut faire attention à un détail classique : sous Windows, la ligne de commande est une chaine unique, pas un tableau argv déjà découpé. Le parsing dépend ensuite du runtime ou du programme cible.
Pour récupérer la ligne de commande du processus courant, il existe :
- GetCommandLineW
- CommandLineToArgvW
Sous Windows, un processus peut en créer un autre, mais il n’existe pas une hiérarchie aussi forte que dans certains modèles Unix. Le parent n’obtient pas automatiquement un contrôle permanent fort sur l’enfant simplement parce qu’il l’a créé.
En pratique :
- le parent peut garder un handle vers l’enfant
- il peut attendre sa fin
- il peut l’ouvrir ou le manipuler selon ses droits
- mais il n’existe pas une relation parent/enfant structurante au meme niveau que dans certains autres systèmes
Attendre la fin d’un processus
Un processus est aussi un objet noyau signalable. Cela signifie qu’on peut l’attendre avec WaitForSingleObject.
Exemple :
Code: Select all
WaitForSingleObject(pi.hProcess, INFINITE);
Pour récupérer son code de sortie :
Code: Select all
DWORD exitCode = 0;
if (!GetExitCodeProcess(pi.hProcess, &exitCode))
return 1;
Il est aussi possible d’attendre plusieurs objets, par exemple un processus et un event d’arrêt, avec :
- WaitForMultipleObjects
Si on connait le PID d’un processus existant, on peut tenter de l’ouvrir avec OpenProcess.
Exemple :
Code: Select all
HANDLE hProc = OpenProcess(
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
pid
);
if (!hProc)
return 1;
Parmi les droits importants, on retrouve :
- PROCESS_QUERY_INFORMATION
- PROCESS_QUERY_LIMITED_INFORMATION
- PROCESS_VM_READ
- PROCESS_VM_WRITE
- PROCESS_VM_OPERATION
- PROCESS_CREATE_THREAD
- PROCESS_DUP_HANDLE
- PROCESS_SUSPEND_RESUME
- PROCESS_TERMINATE
- SYNCHRONIZE
- PROCESS_ALL_ACCESS
Terminer un processus
L’API la plus directe pour arrêter un processus est :
Code: Select all
TerminateProcess(hProc, 0);
En pratique :
- les destructeurs C++ ne s’exécutent pas comme lors d’une sortie normale
- les nettoyages applicatifs peuvent ne pas se faire
- des données peuvent rester dans un état incohérent
- des verrous ou ressources logiques peuvent etre abandonnés
Suspendre, reprendre, interroger
Autour des processus et de leurs threads, plusieurs API sont importantes à connaitre :
- SuspendThread
- ResumeThread
- GetPriorityClass
- SetPriorityClass
- GetProcessId
- GetExitCodeProcess
- GetProcessVersion
GetPriorityClass et SetPriorityClass permettent de consulter ou modifier la classe de priorité du processus.
Les classes fréquentes sont :
- IDLE_PRIORITY_CLASS
- BELOW_NORMAL_PRIORITY_CLASS
- NORMAL_PRIORITY_CLASS
- ABOVE_NORMAL_PRIORITY_CLASS
- HIGH_PRIORITY_CLASS
- REALTIME_PRIORITY_CLASS
Les handles d’un processus et leur héritage
Chaque processus possède une table de handles. Quand un processus enfant est créé, il n’hérite pas automatiquement de tous les handles du parent. L’héritage doit etre explicitement autorisé.
Cela implique plusieurs éléments :
- SECURITY_ATTRIBUTES avec bInheritHandle
- le paramètre bInheritHandles de CreateProcess
- éventuellement SetHandleInformation
Dans les scénarios modernes plus précis, STARTUPINFOEX et PROC_THREAD_ATTRIBUTE_HANDLE_LIST permettent de mieux contrôler quels handles sont réellement hérités.
Les modules d’un processus
Un processus charge son image principale, ainsi que des DLL. Ces modules sont visibles dans son espace mémoire, typiquement sous forme d’images mappées.
API utiles à connaitre :
- GetModuleHandleW
- GetModuleHandleExW
- GetModuleFileNameW
- LoadLibraryW
- LoadLibraryExW
- FreeLibrary
Code: Select all
HMODULE hMod = GetModuleHandleW(NULL);
Pour lister les modules d’un autre processus, on rencontre souvent des API PSAPI comme :
- EnumProcessModules
- EnumProcessModulesEx
- GetModuleBaseNameW
- GetModuleInformation
- MODULEINFO
L’isolation n’empêche pas toute interaction, mais elle impose des API spécifiques.
Parmi les plus connues :
- ReadProcessMemory
- WriteProcessMemory
- VirtualAllocEx
- VirtualFreeEx
- VirtualProtectEx
- VirtualQueryEx
- les outils de diagnostic
- certains débogueurs
- l’inspection mémoire
- des scénarios d’injection ou d’outillage système
Créer du code dans un autre processus
Dans les scénarios plus avancés, on croise souvent l’enchainement suivant :
- OpenProcess
- VirtualAllocEx
- WriteProcessMemory
- CreateRemoteThread
Autres fonctions proches à connaitre :
- CreateRemoteThread
- CreateRemoteThreadEx
- QueueUserAPC
Chaque processus possède un contexte de sécurité, principalement représenté par un token d’accès. Ce token décrit notamment l’utilisateur, les groupes, les privilèges, le niveau d’intégrité, et d’autres informations d’autorisation.
Les API importantes à connaitre sont :
- OpenProcessToken
- GetTokenInformation
- AdjustTokenPrivileges
- LookupPrivilegeValueW
- DuplicateToken
- DuplicateTokenEx
- ImpersonateLoggedOnUser
- RevertToSelf
- TOKEN_PRIVILEGES
- LUID
- TOKEN_USER
- TOKEN_GROUPS
- SID_AND_ATTRIBUTES
L’environnement d’exécution et quelques structures importantes
Quand on travaille sur les processus, plusieurs structures Win32 reviennent souvent. Il est bon de les reconnaitre rapidement.
Les plus importantes pour commencer :
- STARTUPINFOW
- STARTUPINFOEXW
- PROCESS_INFORMATION
- SECURITY_ATTRIBUTES
- MODULEINFO
- TOKEN_PRIVILEGES
- LUID
- PROCESS_MEMORY_COUNTERS
- FILETIME
- GetProcessMemoryInfo
Windows fournit aussi un mécanisme très important pour encadrer un ou plusieurs processus : les job objects.
API à connaitre au moins de nom :
- CreateJobObjectW
- AssignProcessToJobObject
- SetInformationJobObject
- QueryInformationJobObject
- TerminateJobObject
- de limiter certaines ressources
- de tuer tout un groupe de processus
- de structurer un environnement d’exécution plus contrôlé
Enumérer les processus du système
Pour lister les processus, il existe plusieurs approches. L’une des plus classiques en Tool Help repose sur :
- CreateToolhelp32Snapshot
- Process32FirstW
- Process32NextW
- PROCESSENTRY32W
Code: Select all
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
return 1;
PROCESSENTRY32W pe = { 0 };
pe.dwSize = sizeof(pe);
if (Process32FirstW(hSnap, &pe))
{
do
{
// pe.th32ProcessID
// pe.szExeFile
} while (Process32NextW(hSnap, &pe));
}
CloseHandle(hSnap);
Autres API utiles autour des processus
Parmi les fonctions qu’il vaut la peine de connaitre, on peut aussi citer :
- GetStartupInfoW
- ExitProcess
- GetProcessTimes
- IsWow64Process
- IsWow64Process2
- QueryFullProcessImageNameW
- SetEnvironmentVariableW
- GetEnvironmentVariableW
QueryFullProcessImageNameW est très utile pour récupérer proprement le chemin complet d’un processus ouvert.
Erreurs classiques
Comme souvent en Win32, beaucoup de bugs ou de mauvaises habitudes viennent d’un mauvais raisonnement sur les bases.
Parmi les erreurs fréquentes :
- oublier CloseHandle sur les handles réels obtenus
- confondre PID, handle de processus et pseudo-handle
- penser qu’un processus exécute du code sans thread
- demander PROCESS_ALL_ACCESS par réflexe
- utiliser TerminateProcess trop facilement
- ne pas vérifier les retours de CreateProcess ou OpenProcess
- mal gérer l’héritage de handles
- croire qu’un parent contrôle automatiquement l’enfant
Le processus est le cadre d’exécution d’un programme sous Windows. Il fournit l’espace mémoire, la table de handles, le contexte de sécurité, les modules et les ressources associées à une instance d’exécution. Mais ce sont toujours les threads qui exécutent effectivement le code.
Les points vraiment importants à garder en tete sont les suivants :
- un processus est un conteneur, pas l’unité d’exécution elle-meme
- chaque processus est isolé par défaut
- CreateProcessW est l’API centrale de création
- OpenProcess permet d’ouvrir un processus existant selon des droits précis
- les handles doivent etre fermés proprement
- les modules, le token et les handles font partie de la réalité concrète d’un processus
- la sécurité et les droits conditionnent énormément ce qu’on peut faire
Pour avoir déjà une base solide, il faut reconnaitre au minimum :
- STARTUPINFOW
- STARTUPINFOEXW
- PROCESS_INFORMATION
- SECURITY_ATTRIBUTES
- PROCESSENTRY32W
- MODULEINFO
- TOKEN_PRIVILEGES
- LUID
- TOKEN_USER
- PROCESS_MEMORY_COUNTERS
- FILETIME
Pour le processus courant et l’identification :
- GetCurrentProcess
- GetCurrentProcessId
- GetCurrentThread
- GetCurrentThreadId
- GetProcessId
- CreateProcessW
- ResumeThread
- GetStartupInfoW
- ExitProcess
- WaitForSingleObject
- WaitForMultipleObjects
- GetExitCodeProcess
- TerminateProcess
- OpenProcess
- ReadProcessMemory
- WriteProcessMemory
- VirtualAllocEx
- VirtualFreeEx
- VirtualProtectEx
- VirtualQueryEx
- CreateRemoteThread
- DuplicateHandle
- GetModuleHandleW
- GetModuleHandleExW
- GetModuleFileNameW
- LoadLibraryW
- LoadLibraryExW
- FreeLibrary
- EnumProcessModules
- GetModuleBaseNameW
- GetModuleInformation
- OpenProcessToken
- GetTokenInformation
- AdjustTokenPrivileges
- LookupPrivilegeValueW
- DuplicateTokenEx
- CreateToolhelp32Snapshot
- Process32FirstW
- Process32NextW
- QueryFullProcessImageNameW
- GetProcessTimes
- GetProcessMemoryInfo
- IsWow64Process
- IsWow64Process2
- CreateJobObjectW
- AssignProcessToJobObject
- SetInformationJobObject
- QueryInformationJobObject
- TerminateJobObject
Comprendre les processus, c’est comprendre comment Windows structure la vie d’un programme du début à la fin. C’est aussi comprendre que mémoire, handles, modules, sécurité, threads et I/O ne flottent pas dans le vide : tout cela s’inscrit dans le cadre d’un processus précis.
Si tu maitrises déjà correctement :
- GetCurrentProcess et GetCurrentProcessId
- CreateProcessW
- STARTUPINFOW et PROCESS_INFORMATION
- WaitForSingleObject et GetExitCodeProcess
- OpenProcess avec des droits adaptés
- les grandes lignes du token de sécurité
- les modules et les handles
A la prochaine pour le prochain tuto
