Cours sur les processus en win32

Développement système natif en c/c++ avec win32 ...

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 46
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Cours sur les processus en win32

Post by Hydraxx »

Salut, :D

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
Autrement dit, le processus n’est pas juste “le programme en cours”. C’est une structure d’exécution complète, avec son identité, ses ressources, ses contraintes, et son contexte système.

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
Mais il faut insister sur un point essentiel : un processus n’exécute rien par lui-meme. Ce sont toujours ses threads qui exécutent les instructions. Le processus fournit le cadre, les ressources, l’identité et l’environnement. Le thread fournit le flux d’exécution.

Autrement dit :
  • le processus contient
  • le thread exécute
Sans thread, un processus est une structure inerte.

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
Pour qu’une interaction entre processus soit possible, il faut passer par des mécanismes explicites, par exemple :
  • OpenProcess
  • ReadProcessMemory
  • WriteProcessMemory
  • DuplicateHandle
  • pipes
  • sockets
  • mémoire partagée
  • RPC ou autres mécanismes IPC
Cette isolation est une base majeure de la stabilité et de la sécurité du système.

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();
Il faut bien connaitre la particularité de GetCurrentProcess. Cette fonction ne renvoie pas un vrai handle obtenu via l’allocateur noyau de handles au sens habituel, mais un pseudo-handle. Il représente le processus courant dans le contexte du thread appelant.

Conséquence importante :
  • le pseudo-handle retourné par GetCurrentProcess ne doit pas etre fermé avec CloseHandle
De la meme manière, il existe aussi :
  • GetCurrentThread
  • GetCurrentThreadId
qui permettent de raisonner sur le thread courant.

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;
Cette fonction crée plusieurs choses à la fois :
  • un nouveau processus
  • un thread principal initial
  • les structures noyau associées
  • les handles de processus et de thread retournés dans PROCESS_INFORMATION
PROCESS_INFORMATION contient :
  • hProcess : handle du processus créé
  • hThread : handle du thread principal
  • dwProcessId : PID
  • dwThreadId : TID du thread principal
Il ne faut jamais oublier que CreateProcess crée immédiatement un thread principal en plus du processus. Les deux sont liés au démarrage.

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
Les champs souvent utiles à connaitre sont :
  • cb
  • dwFlags
  • hStdInput
  • hStdOutput
  • hStdError
  • wShowWindow
  • lpDesktop
Quand on veut rediriger les flux standard, on utilise généralement :
  • STARTF_USESTDHANDLES
Quand on veut influencer l’état initial de la fenêtre, on rencontre souvent :
  • STARTF_USESHOWWINDOW
STARTUPINFOEX et les attributs étendus

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
Cela permet notamment de gérer des scénarios plus avancés comme :
  • 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
On ne l’utilise pas dans tous les programmes, mais c’est une brique très importante du CreateProcess moderne avancé.

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);
Si on oublie de le faire, on fuit des handles. C’est une erreur classique. Meme si le système récupérera tout à la fin du processus appelant, ce n’est pas une raison pour laisser trainer les ressources.

Il faut bien distinguer :
  • le processus créé lui-meme
  • le handle que ton programme possède vers ce processus
Fermer le handle n’arrête pas le processus. Cela libère seulement ta référence sur lui.

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
CREATE_SUSPENDED est particulièrement intéressant. Il crée le processus et son thread principal, mais laisse ce dernier suspendu. Cela permet d’effectuer certaines opérations avant la reprise.

Pour reprendre le thread principal :
  • ResumeThread
Arguments, environnement et répertoire courant

CreateProcess permet aussi de contrôler :
  • la ligne de commande
  • le bloc d’environnement
  • le répertoire courant du nouveau processus
Cela se fait via les paramètres lpCommandLine, lpEnvironment et lpCurrentDirectory.

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
et pour parser selon les règles usuelles du shell Windows :
  • CommandLineToArgvW
Parent et enfant sous Windows

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
Tout ce qui doit etre fait doit l’etre explicitement.

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);
Quand le processus se termine, l’objet passe à l’état signalé.

Pour récupérer son code de sortie :

Code: Select all

DWORD exitCode = 0;

if (!GetExitCodeProcess(pi.hProcess, &exitCode))
    return 1;
Tant que le processus n’est pas terminé, GetExitCodeProcess peut renvoyer STILL_ACTIVE comme code.

Il est aussi possible d’attendre plusieurs objets, par exemple un processus et un event d’arrêt, avec :
  • WaitForMultipleObjects
Ouvrir un processus existant avec OpenProcess

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;
Le point crucial ici, ce sont les droits demandés. Sous Windows, demander trop peu de droits peut rendre l’usage du handle inutile. Demander trop de droits peut faire échouer l’ouverture à cause des restrictions de sécurité.

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
PROCESS_ALL_ACCESS est souvent une mauvaise habitude. Mieux vaut demander exactement ce qui est nécessaire.

Terminer un processus

L’API la plus directe pour arrêter un processus est :

Code: Select all

TerminateProcess(hProc, 0);
Mais il faut comprendre ce que cela implique. TerminateProcess est une terminaison brutale. Ce n’est pas un “arrêt propre” comparable à une sortie normale du programme.

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
C’est donc une API de dernier recours, utile parfois, mais à manipuler avec prudence.

Suspendre, reprendre, interroger

Autour des processus et de leurs threads, plusieurs API sont importantes à connaitre :
  • SuspendThread
  • ResumeThread
  • GetPriorityClass
  • SetPriorityClass
  • GetProcessId
  • GetExitCodeProcess
  • GetProcessVersion
SuspendThread et ResumeThread agissent sur un thread, pas directement sur “tout le processus”. Si on veut suspendre réellement tous les threads d’un processus, il faut une logique plus large.

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
REALTIME_PRIORITY_CLASS doit etre manié avec beaucoup de prudence.

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
Exemple conceptuel : un pipe créé avec des handles héritables peut etre transmis à un enfant si CreateProcess est appelé avec bInheritHandles à TRUE.

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
Exemple simple :

Code: Select all

HMODULE hMod = GetModuleHandleW(NULL);
NULL permet ici de récupérer le module principal du processus courant.

Pour lister les modules d’un autre processus, on rencontre souvent des API PSAPI comme :
  • EnumProcessModules
  • EnumProcessModulesEx
  • GetModuleBaseNameW
  • GetModuleInformation
Les structures importantes ici incluent notamment :
  • MODULEINFO
Lire ou écrire la mémoire d’un autre processus

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
Ces fonctions sont importantes pour :
  • les outils de diagnostic
  • certains débogueurs
  • l’inspection mémoire
  • des scénarios d’injection ou d’outillage système
Elles demandent naturellement des droits adaptés via OpenProcess.

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
Il faut au moins connaitre le nom de ces API, car elles apparaissent souvent dans les outils système, les injecteurs, les débogueurs ou certaines analyses sécurité.

Autres fonctions proches à connaitre :
  • CreateRemoteThread
  • CreateRemoteThreadEx
  • QueueUserAPC
Les tokens de sécurité

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
Les structures clés dans ce domaine incluent souvent :
  • TOKEN_PRIVILEGES
  • LUID
  • TOKEN_USER
  • TOKEN_GROUPS
  • SID_AND_ATTRIBUTES
Cela détermine une grande partie de ce que le processus a le droit de faire.

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
PROCESS_MEMORY_COUNTERS, issue notamment de PSAPI, est utile pour interroger certaines statistiques mémoire du processus, par exemple avec :
  • GetProcessMemoryInfo
Jobs et regroupement de processus

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
Les jobs permettent par exemple :
  • de limiter certaines ressources
  • de tuer tout un groupe de processus
  • de structurer un environnement d’exécution plus contrôlé
C’est une notion importante dès qu’on parle de supervision ou de sandboxing léger.

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
La structure associée est :
  • PROCESSENTRY32W
Exemple conceptuel :

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);
C’est une base très utile pour les outils système.

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
GetProcessTimes donne accès à plusieurs FILETIME décrivant par exemple les temps noyau et user du processus.

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
Ce qu’il faut retenir

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
Résumé des structures à connaitre

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
Résumé des API à connaitre absolument

Pour le processus courant et l’identification :
  • GetCurrentProcess
  • GetCurrentProcessId
  • GetCurrentThread
  • GetCurrentThreadId
  • GetProcessId
Pour la création et le lancement :
  • CreateProcessW
  • ResumeThread
  • GetStartupInfoW
  • ExitProcess
Pour l’attente et la terminaison :
  • WaitForSingleObject
  • WaitForMultipleObjects
  • GetExitCodeProcess
  • TerminateProcess
Pour l’ouverture et la manipulation :
  • OpenProcess
  • ReadProcessMemory
  • WriteProcessMemory
  • VirtualAllocEx
  • VirtualFreeEx
  • VirtualProtectEx
  • VirtualQueryEx
  • CreateRemoteThread
  • DuplicateHandle
Pour les modules :
  • GetModuleHandleW
  • GetModuleHandleExW
  • GetModuleFileNameW
  • LoadLibraryW
  • LoadLibraryExW
  • FreeLibrary
  • EnumProcessModules
  • GetModuleBaseNameW
  • GetModuleInformation
Pour la sécurité :
  • OpenProcessToken
  • GetTokenInformation
  • AdjustTokenPrivileges
  • LookupPrivilegeValueW
  • DuplicateTokenEx
Pour l’énumération et les infos système :
  • CreateToolhelp32Snapshot
  • Process32FirstW
  • Process32NextW
  • QueryFullProcessImageNameW
  • GetProcessTimes
  • GetProcessMemoryInfo
  • IsWow64Process
  • IsWow64Process2
Pour les jobs :
  • CreateJobObjectW
  • AssignProcessToJobObject
  • SetInformationJobObject
  • QueryInformationJobObject
  • TerminateJobObject
Conclusion

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
alors tu possèdes déjà une bonne base en développement système Windows autour des processus.

A la prochaine pour le prochain tuto :D

Who is online

Users browsing this forum: No registered users and 0 guests