aujourd’hui on attaque un très gros chapitre système : le debugging, le diagnostic, ETW et la Debug API Windows.
Ici on ne parle pas juste de mettre un breakpoint dans Visual Studio. On parle d’observer un processus en train de vivre, de mesurer des métriques, de capturer un état cohérent, de tracer ce que fait le système en temps réel, et dans les cas les plus avancés, de devenir soi-meme le debugger du processus cible.
C’est un bloc extrêmement important si tu veux aller vers :
- le développement système Windows
- les outils internes et diagnostics
- la supervision technique
- la sécurité, les EDR, les anti-cheat
- le reverse engineering dynamique
- le debugging avancé
- l’instrumentation de services ou d’applications lourdes
Vision globale du chapitre
En réalité, tout ce chapitre repose sur une progression logique. On commence par observer simplement, puis on mesure, puis on capture un état, puis on trace en profondeur, puis on contrôle activement l’exécution.
Le modèle mental global est celui-ci :
Code: Select all
1. Observer simplement
→ OutputDebugString
2. Mesurer
→ Performance Counters / PDH
3. Capturer un état cohérent
→ Process Snapshots / PSS
→ éventuellement MiniDump dans d’autres scénarios
4. Tracer en profondeur
→ ETW
5. Contrôler activement
→ Debug API
Code: Select all
voir → mesurer → capturer → tracer → contrôler
OutputDebugString : le diagnostic texte le plus simple
Le premier mécanisme de diagnostic est le plus léger : émettre un message de debug.
L’idée générale est très simple :
Code: Select all
Processus
↓
OutputDebugString
↓
Windows
↓
debugger / outil de capture
API principales :
- OutputDebugStringA
- OutputDebugStringW
- OutputDebugString
Code: Select all
OutputDebugStringW(L"Service started");
- afficher l’état d’une opération
- signaler une erreur
- indiquer une progression
- dump une variable formatée
- émettre un message que WinDbg, Visual Studio, DebugView ou un outil maison pourra capturer
OutputDebugString n’est pas un “printf magique vers le vide”. Historiquement, le mécanisme de capture repose sur un schéma de communication bien connu autour de plusieurs objets nommés.
Noms à connaitre :
Code: Select all
DBWIN_BUFFER
DBWIN_BUFFER_READY
DBWIN_DATA_READY
Code: Select all
Process A
↓
OutputDebugString
↓
écrit dans DBWIN_BUFFER
↓
signale DBWIN_DATA_READY
↓
outil de capture attend l'événement
↓
lit le buffer
↓
affiche le message
- PID de l’émetteur
- texte de debug
Pourquoi OutputDebugString reste utile
Parce que c’est :
- très léger
- très simple à utiliser
- capturable par beaucoup d’outils
- parfait pour du diagnostic rapide
- ce n’est pas un vrai système de logging structuré
- ce n’est pas idéal pour de gros volumes
- ce n’est pas adapté aux analyses riches ou historiques
- ce n’est pas une solution complète de supervision
PDH et les Performance Counters : mesurer le système
Après l’observation texte, on passe à la mesure.
Windows expose énormément de compteurs système : CPU, mémoire, disque, réseau, processus, threads, objets noyau, etc. Ces compteurs sont visibles dans Performance Monitor et accessibles par API via PDH, Performance Data Helper.
Modèle mental :
Code: Select all
Kernel / services / sous-systèmes
↓
Performance Counters
↓
PDH
↓
ton programme ou PerfMon
Code: Select all
\Objet(Instance)\Compteur
Code: Select all
\Processor(_Total)\% Processor Time
\Process(*)\% Processor Time
\Process(*)\ID Process
\Memory\Available MBytes
\PhysicalDisk(_Total)\Disk Reads/sec
Les types centraux de PDH sont :
- PDH_HQUERY
- PDH_HCOUNTER
- PDH_STATUS
APIs les plus importantes :
- PdhOpenQuery
- PdhCloseQuery
- PdhAddCounter
- PdhAddEnglishCounter
- PdhRemoveCounter
- PdhCollectQueryData
- PdhCollectQueryDataEx
- PdhGetFormattedCounterValue
- PdhGetFormattedCounterArray
- PdhGetRawCounterValue
- PdhGetCounterInfo
- PdhValidatePath
- PdhExpandWildCardPath
- PdhLookupPerfNameByIndex
- PdhLookupPerfIndexByName
Exemple :
Code: Select all
PDH_HQUERY query = NULL;
PDH_STATUS status = PdhOpenQueryW(
NULL,
0,
&query
);
if (status != ERROR_SUCCESS)
return 1;
Ajouter un compteur
Exemple :
Code: Select all
PDH_HCOUNTER counter = NULL;
PDH_STATUS status = PdhAddEnglishCounterW(
query,
L"\\Processor(_Total)\\% Processor Time",
0,
&counter
);
if (status != ERROR_SUCCESS)
{
PdhCloseQuery(query);
return 1;
}
- PdhAddCounter dépend de la langue du système
- PdhAddEnglishCounter prend un chemin anglais stable
Le double collect : règle fondamentale
Beaucoup de compteurs ne donnent pas une valeur instantanée brute, mais une valeur dérivée d’une variation dans le temps. C’est particulièrement vrai pour des compteurs comme :
Code: Select all
% Processor Time
Code: Select all
PdhCollectQueryData(query);
Sleep(1000);
PdhCollectQueryData(query);
C’est l’un des pièges les plus classiques en PDH.
Lire une valeur simple
API :
- PdhGetFormattedCounterValue
- PDH_FMT_COUNTERVALUE
Code: Select all
PDH_FMT_COUNTERVALUE value = { 0 };
DWORD type = 0;
PDH_STATUS status = PdhGetFormattedCounterValue(
counter,
PDH_FMT_DOUBLE,
&type,
&value
);
if (status != ERROR_SUCCESS)
return 1;
/* lire value.doubleValue */
- PDH_FMT_DOUBLE → doubleValue
- PDH_FMT_LONG → longValue
- PDH_FMT_LARGE → largeValue
Compteurs multi-instance
Quand un compteur contient une wildcard comme :
Code: Select all
\Process(*)\% Processor Time
Dans ce cas, il faut utiliser :
- PdhGetFormattedCounterArray
- PDH_FMT_COUNTERVALUE_ITEM
- le nom de l’instance
- la valeur formatée associée
Autres structures PDH à connaitre
Parmi les structures utiles :
- PDH_FMT_COUNTERVALUE
- PDH_FMT_COUNTERVALUE_ITEM
- PDH_COUNTER_INFO
- PDH_RAW_COUNTER
- PDH_TIME_INFO
- PDH_BROWSE_DLG_CONFIG
PDH_BROWSE_DLG_CONFIG est intéressante pour un outil graphique qui laisse l’utilisateur choisir ses compteurs.
Pièges classiques de PDH
Les erreurs les plus fréquentes sont :
- confondre code de retour et vrai handle
- oublier le double collect
- utiliser PdhGetFormattedCounterValue sur un compteur multi-instance
- lire le mauvais champ de l’union
- oublier PdhCloseQuery
- utiliser des chemins dépendants de la langue sans y faire attention
On passe maintenant à un sujet plus avancé : les Process Snapshots, souvent abrégés PSS.
L’idée de PSS est de capturer une vue cohérente d’un processus, puis de travailler sur cette vue capturée plutôt que sur un processus vivant qui continue de changer sous nos yeux.
Modèle mental :
Code: Select all
Processus cible
↓
PssCaptureSnapshot
↓
snapshot cohérent
↓
PssQuerySnapshot / PssWalkSnapshot
- un processus vivant change sans cesse
- ses threads peuvent naitre ou mourir
- ses handles peuvent varier
- sa mémoire peut bouger
- ses contextes CPU changent en permanence
Handles et types PSS à connaitre
Les types centraux sont :
- HPSS
- HPSSWALK
APIs centrales :
- PssCaptureSnapshot
- PssFreeSnapshot
- PssQuerySnapshot
- PssWalkMarkerCreate
- PssWalkMarkerFree
- PssWalkMarkerGetPosition
- PssWalkMarkerSetPosition
- PssWalkSnapshot
- PssDuplicateSnapshot
API principale :
Code: Select all
DWORD PssCaptureSnapshot(
HANDLE ProcessHandle,
PSS_CAPTURE_FLAGS CaptureFlags,
DWORD ThreadContextFlags,
HPSS* SnapshotHandle
);
Code: Select all
HPSS snapshot = NULL;
DWORD status = PssCaptureSnapshot(
hProcess,
PSS_CAPTURE_THREADS | PSS_CAPTURE_HANDLES,
CONTEXT_ALL,
&snapshot
);
if (status != ERROR_SUCCESS)
return 1;
Flags de capture importants
Il faut connaitre au minimum :
- PSS_CAPTURE_NONE
- PSS_CAPTURE_VA_CLONE
- PSS_CAPTURE_VA_SPACE
- PSS_CAPTURE_HANDLES
- PSS_CAPTURE_HANDLE_NAME_INFORMATION
- PSS_CAPTURE_HANDLE_BASIC_INFORMATION
- PSS_CAPTURE_HANDLE_TYPE_SPECIFIC_INFORMATION
- PSS_CAPTURE_HANDLE_TRACE
- PSS_CAPTURE_THREADS
- PSS_CAPTURE_THREAD_CONTEXT
- PSS_CAPTURE_THREAD_CONTEXT_EXTENDED
- PSS_CAPTURE_VA_SPACE_SECTION_INFORMATION
- PSS_CAPTURE_IPT_TRACE
- PSS_CAPTURE_THREADS → capture les threads
- PSS_CAPTURE_HANDLES → capture les handles
- PSS_CAPTURE_VA_SPACE → capture l’espace virtuel
- PSS_CAPTURE_VA_CLONE → clone mémoire, très puissant
- PSS_CAPTURE_THREAD_CONTEXT → capture le contexte CPU des threads
Autres flags utiles à connaitre :
- PSS_CREATE_BREAKAWAY
- PSS_CREATE_FORCE_BREAKAWAY
- PSS_CREATE_BREAKAWAY_OPTIONAL
- PSS_CREATE_USE_VM_ALLOCATIONS
- PSS_CREATE_RELEASE_SECTION
Interroger un snapshot
APIs :
- PssQuerySnapshot
- PssWalkSnapshot
PssWalkSnapshot sert à parcourir des collections d’entrées, par exemple les threads ou les handles.
Classes importantes côté requêtes :
- PSS_QUERY_PROCESS_INFORMATION
- PSS_QUERY_VA_CLONE_INFORMATION
- PSS_QUERY_AUXILIARY_PAGES_INFORMATION
- PSS_QUERY_VA_SPACE_INFORMATION
- PSS_QUERY_HANDLE_INFORMATION
- PSS_QUERY_THREAD_INFORMATION
- PSS_QUERY_HANDLE_TRACE_INFORMATION
- PSS_QUERY_PERFORMANCE_COUNTERS
Parmi les structures à reconnaitre :
- PSS_PROCESS_INFORMATION
- PSS_VA_CLONE_INFORMATION
- PSS_AUXILIARY_PAGES_INFORMATION
- PSS_VA_SPACE_INFORMATION
- PSS_HANDLE_INFORMATION
- PSS_THREAD_INFORMATION
- PSS_PERFORMANCE_COUNTERS
- PSS_HANDLE_ENTRY
- PSS_THREAD_ENTRY
- PSS_VA_SPACE_ENTRY
Utilisation typique de PSS
Le flux classique ressemble à ceci :
Code: Select all
PssCaptureSnapshot
↓
PssQuerySnapshot
↓
PssWalkMarkerCreate
↓
PssWalkSnapshot dans une boucle
↓
PssWalkMarkerFree
↓
PssFreeSnapshot
MiniDump : à connaitre même si ce n’est pas PSS
Même si ce n’est pas exactement la meme chose que PSS, il faut au moins connaitre un autre grand mécanisme de capture d’état de processus dans le monde du debug et du diagnostic :
- MiniDumpWriteDump
Autres types utiles à connaitre de nom dans ce domaine :
- MINIDUMP_TYPE
- MINIDUMP_EXCEPTION_INFORMATION
- MINIDUMP_USER_STREAM_INFORMATION
On arrive maintenant au bloc le plus massif : ETW.
ETW est le système de tracing haute performance de Windows. C’est un mécanisme générique, très scalable, capable de tracer aussi bien des événements d’application que des événements noyau, avec des sessions dédiées, des consumers, des providers, des mots-clés, des niveaux, des métadonnées, etc.
Modèle mental général :
Code: Select all
Provider
↓
Event
↓
Session ETW
↓
Consumer
Les composants d’ETW
Les grandes notions sont :
- Provider
- Event
- Session
- Consumer
- Manifest ou metadata
- TraceLogging dans les scénarios modernes
- source des événements
- peut être une application, un service, un composant noyau, un provider TraceLogging, etc.
- unité de trace
- contient un provider, un descriptor, des données utilisateur, un timestamp, souvent PID/TID
- capture en cours
- choisit quels providers sont activés
- détermine temps réel ou fichier
- outil ou code qui lit les événements
- WPA, logman, un parseur maison, un agent, etc.
Une première API importante est :
- TdhEnumerateProviders
Structures à connaitre :
- PROVIDER_ENUMERATION_INFO
- TRACE_PROVIDER_INFO
Code: Select all
1. appel avec buffer null / taille insuffisante
2. récupération de la taille
3. allocation
4. second appel
Enumérer les événements d’un provider
Une fois un provider identifié, on peut vouloir connaitre les événements qu’il expose.
API :
- TdhEnumerateManifestProviderEvents
- PROVIDER_EVENT_INFO
Décrire un événement ETW
Deux APIs importantes :
- TdhGetManifestEventInformation
- TdhGetEventInformation
Structure vraiment centrale :
- TRACE_EVENT_INFO
- le GUID du provider
- le descriptor de l’événement
- des offsets vers les chaines de nom
- le nombre de propriétés
- les descriptions de propriétés
- EVENT_PROPERTY_INFO
Propriétés importantes :
- nom
- type d’entrée
- type de sortie
- taille
- count
- flags
Parmi les flags importants :
Code: Select all
PropertyStruct
PropertyParamLength
PropertyParamCount
PropertyWBEMXmlFragment
PropertyParamFixedLength
PropertyParamFixedCount
PropertyHasTags
PropertyHasCustomSchema
Parsing des propriétés ETW
C’est l’un des points les plus techniques du modèle ETW. Le champ UserData d’un EVENT_RECORD n’est qu’un buffer brut. Sans métadonnées, on ne sait pas où commencent les champs, quel type lire, quelle longueur utiliser, ni comment formatter la valeur.
Modèle mental :
Code: Select all
EVENT_RECORD
↓
EventHeader + UserData
↓
TdhGetEventInformation
↓
TRACE_EVENT_INFO + EVENT_PROPERTY_INFO[]
↓
TdhFormatProperty ou parsing manuel
- TdhFormatProperty
- TdhGetProperty
- TdhGetPropertySize
- TdhGetEventMapInformation
- EVENT_MAP_INFO
Créer une session ETW
Une session est créée avec :
- StartTrace
- EVENT_TRACE_PROPERTIES
- Wnode
- LogFileMode
- EnableFlags
- BufferSize
- MinimumBuffers
- MaximumBuffers
- FlushTimer
- LoggerNameOffset
- LogFileNameOffset
Autres APIs autour des sessions :
- ControlTrace
- StopTrace
- FlushTrace
Flags importants :
Code: Select all
EVENT_TRACE_REAL_TIME_MODE
EVENT_TRACE_FILE_MODE_SEQUENTIAL
EVENT_TRACE_FILE_MODE_CIRCULAR
EVENT_TRACE_FILE_MODE_APPEND
EVENT_TRACE_SYSTEM_LOGGER_MODE
- session vers fichier .etl
- session temps réel
- les événements sont livrés au consumer en direct
- les événements sont écrits dans un .etl pour analyse ultérieure
Créer une session ne suffit pas. Il faut encore activer les providers à écouter.
APIs importantes :
- EnableTraceEx
- EnableTraceEx2
On y configure notamment :
- le GUID du provider
- le niveau
- les keywords match any
- les keywords match all
- des filtres éventuels
- ENABLE_TRACE_PARAMETERS
- EVENT_FILTER_DESCRIPTOR
Lire une trace ETW
Une fois la trace produite, il faut la lire.
APIs centrales :
- OpenTrace
- ProcessTrace
- CloseTrace
- EVENT_TRACE_LOGFILE
- LogFileName
- LoggerName
- ProcessTraceMode
- EventCallback
- EventRecordCallback
- LogfileHeader
- Context
Code: Select all
PROCESS_TRACE_MODE_EVENT_RECORD
PROCESS_TRACE_MODE_REAL_TIME
PROCESS_TRACE_MODE_RAW_TIMESTAMP
EVENT_RECORD et structures liées
Quand ProcessTrace livre un événement moderne, on reçoit un :
- EVENT_RECORD
- EventHeader
- BufferContext
- ExtendedDataCount
- UserDataLength
- ExtendedData
- UserData
- UserContext
- EVENT_HEADER
- PID
- TID
- timestamp
- provider GUID
- event descriptor
- ActivityId
- EVENT_DESCRIPTOR
- Id
- Version
- Channel
- Level
- Opcode
- Task
- Keyword
- EVENT_HEADER_EXTENDED_DATA_ITEM
- SID
- stack trace
- activity id
- autres méta-infos étendues
Le provider noyau, souvent associé à :
Code: Select all
SystemTraceControlGuid
Flags très importants à connaitre :
Code: Select all
EVENT_TRACE_FLAG_PROCESS
EVENT_TRACE_FLAG_THREAD
EVENT_TRACE_FLAG_IMAGE_LOAD
EVENT_TRACE_FLAG_DISK_IO
EVENT_TRACE_FLAG_DISK_FILE_IO
EVENT_TRACE_FLAG_REGISTRY
EVENT_TRACE_FLAG_FILE_IO
EVENT_TRACE_FLAG_FILE_IO_INIT
EVENT_TRACE_FLAG_NETWORK_TCPIP
EVENT_TRACE_FLAG_CSWITCH
EVENT_TRACE_FLAG_DPC
EVENT_TRACE_FLAG_INTERRUPT
EVENT_TRACE_FLAG_SYSTEMCALL
EVENT_TRACE_FLAG_VAMAP
EVENT_TRACE_FLAG_VIRTUAL_ALLOC
EVENT_TRACE_FLAG_PROFILE
- créations et fins de processus
- créations et fins de threads
- chargements d’images
- I/O disque
- I/O fichiers
- registre
- réseau
- context switches
- interruptions
- appels système
- allocations virtuelles
Interroger les sessions et providers actifs
Autres APIs importantes :
- TraceQueryInformation
- EnumerateTraceGuidsEx
- QueryAllTraces
- TRACE_QUERY_INFO_CLASS
Code: Select all
TraceGuidQueryList
TraceGuidQueryInfo
TraceGuidQueryProcess
TraceStackTracingInfo
TraceSystemTraceEnableFlagsInfo
TraceProfileSourceConfigInfo
TraceLastBranchConfigurationInfo
TraceMaxPmcCounterQuery
- QueryAllTraces → voir les sessions actives
- EnumerateTraceGuidsEx → lister des GUID de providers
- TraceQueryInformation → interroger des infos plus fines du sous-système ETW
Le monde ETW classique peut etre lourd, notamment avec les manifests. TraceLogging apporte une manière plus simple et moderne d’instrumenter son propre code.
Macros et APIs importantes :
- TRACELOGGING_DEFINE_PROVIDER
- TraceLoggingRegister
- TraceLoggingUnregister
- TraceLoggingWrite
- TraceLoggingValue
- TraceLoggingWideString
- TraceLoggingLevel
- TraceLoggingOpcode
Code: Select all
Ton code
↓
TraceLoggingWrite
↓
provider ETW maison
↓
session ETW
↓
consumer
Code: Select all
TraceLoggingWrite(
g_Provider,
"ProcessStarted",
TraceLoggingValue(pid, "PID"),
TraceLoggingWideString(path, "Path")
);
La Debug API : contrôler activement un processus
On arrive maintenant à un autre monde : la Debug API.
Ici, on ne se contente plus d’observer passivement. On devient le debugger du processus cible.
Il existe deux grands modes :
- lancer un processus en mode debug
- s’attacher à un processus existant
- CreateProcess avec DEBUG_PROCESS
- CreateProcess avec DEBUG_ONLY_THIS_PROCESS
- DebugActiveProcess
- DebugActiveProcessStop
Le cœur d’un debugger Windows user-mode repose sur une boucle d’événements.
APIs centrales :
- WaitForDebugEvent
- WaitForDebugEventEx
- ContinueDebugEvent
Code: Select all
process debuggué
↓
Windows suspend / notifie
↓
WaitForDebugEvent récupère un DEBUG_EVENT
↓
ton code traite l'événement
↓
ContinueDebugEvent relance le process / thread
- si tu oublies ContinueDebugEvent, le processus ou le thread concerné reste bloqué
La structure critique est :
- DEBUG_EVENT
- dwDebugEventCode
- dwProcessId
- dwThreadId
- une union décrivant l’événement réel
Code: Select all
CREATE_PROCESS_DEBUG_EVENT
CREATE_THREAD_DEBUG_EVENT
EXIT_PROCESS_DEBUG_EVENT
EXIT_THREAD_DEBUG_EVENT
LOAD_DLL_DEBUG_EVENT
UNLOAD_DLL_DEBUG_EVENT
OUTPUT_DEBUG_STRING_EVENT
EXCEPTION_DEBUG_EVENT
RIP_EVENT
Les structures à connaitre absolument sont :
- EXCEPTION_DEBUG_INFO
- CREATE_PROCESS_DEBUG_INFO
- CREATE_THREAD_DEBUG_INFO
- EXIT_PROCESS_DEBUG_INFO
- EXIT_THREAD_DEBUG_INFO
- LOAD_DLL_DEBUG_INFO
- UNLOAD_DLL_DEBUG_INFO
- OUTPUT_DEBUG_STRING_INFO
- RIP_INFO
- EXCEPTION_RECORD
- CONTEXT
- EXCEPTION_RECORD
- dwFirstChance
- hFile
- hProcess
- hThread
- lpBaseOfImage
- lpStartAddress
- lpImageName
- hFile
- lpBaseOfDll
- lpImageName
Gestion des exceptions dans le debugger
Quand un événement d’exception arrive, le debugger doit choisir quoi faire.
Constantes importantes :
Code: Select all
DBG_CONTINUE
DBG_EXCEPTION_NOT_HANDLED
- DBG_CONTINUE → “j’ai géré l’événement”
- DBG_EXCEPTION_NOT_HANDLED → “laisse le processus gérer cette exception”
- les breakpoints
- le single-step
- les exceptions first chance / second chance
- certains mécanismes anti-debug
APIs importantes :
- ReadProcessMemory
- WriteProcessMemory
- lire une chaine dans le processus cible
- lire une structure PE
- récupérer un nom d’image
- inspecter une donnée à une adresse donnée
- écrire un breakpoint logiciel
- GetThreadContext
- SetThreadContext
- SuspendThread
- ResumeThread
- FlushInstructionCache
- VirtualProtectEx
- VirtualQueryEx
Breakpoints et contrôle d’exécution : notions à connaitre
Même si ce cours reste introductif, il faut au moins connaitre quelques briques du debugger réel :
- breakpoint logiciel classique via 0xCC
- single-step via trap flag
- lecture / écriture mémoire dans le processus cible
- lecture / écriture du contexte de thread
- ReadProcessMemory
- WriteProcessMemory
- GetThreadContext
- SetThreadContext
- FlushInstructionCache
Autres APIs de debugging utiles
Parmi les APIs importantes :
- IsDebuggerPresent
- CheckRemoteDebuggerPresent
- DebugBreakProcess
- DebugSetProcessKillOnExit
- détecter un debugger sur le processus courant
- détecter si un processus distant est debuggué
- forcer un breakpoint dans un autre processus
- choisir si les processus debuggués meurent quand le debugger se termine
- DebugBreak
- OutputDebugString
- ContinueDebugEvent
- WaitForDebugEventEx
Le modèle final d’un debugger très simple est :
Code: Select all
création / attachement
↓
WaitForDebugEvent
↓
switch(evt.dwDebugEventCode)
↓
traitement spécifique
↓
ContinueDebugEvent
↓
boucle
- afficher les DLL chargées
- afficher les threads créés ou terminés
- voir les messages OutputDebugString
- voir les exceptions
- surveiller la vie entière du processus
C’est un point très important.
ETW :
- observation plutôt passive
- très scalable
- très utilisé en production
- très fort pour la performance et la supervision
- contrôle actif
- suspension / reprise
- événements de debug
- accès direct au cycle de vie du processus
Code: Select all
ETW = j'observe le système
Debug API = je deviens le debugger du processus
Même si le cœur du chapitre tourne autour de PDH, PSS, ETW et Debug API, il est bon de connaitre aussi quelques APIs souvent rencontrées dans les outils réels :
- GetProcessMemoryInfo
- GetProcessTimes
- QueryFullProcessImageNameW
- GetThreadTimes
- MiniDumpWriteDump
- SymInitialize
- SymFromAddr
- StackWalk64
Les APIs vraiment importantes à connaitre
Pour le debug texte :
- OutputDebugStringA
- OutputDebugStringW
- OutputDebugString
- PdhOpenQuery
- PdhCloseQuery
- PdhAddCounter
- PdhAddEnglishCounter
- PdhRemoveCounter
- PdhCollectQueryData
- PdhCollectQueryDataEx
- PdhGetFormattedCounterValue
- PdhGetFormattedCounterArray
- PdhGetRawCounterValue
- PdhGetCounterInfo
- PssCaptureSnapshot
- PssFreeSnapshot
- PssQuerySnapshot
- PssWalkMarkerCreate
- PssWalkMarkerFree
- PssWalkMarkerGetPosition
- PssWalkMarkerSetPosition
- PssWalkSnapshot
- TdhEnumerateProviders
- TdhEnumerateManifestProviderEvents
- TdhGetManifestEventInformation
- TdhGetEventInformation
- TdhGetEventMapInformation
- TdhFormatProperty
- TdhGetProperty
- TdhGetPropertySize
- StartTrace
- ControlTrace
- EnableTraceEx
- EnableTraceEx2
- OpenTrace
- ProcessTrace
- CloseTrace
- TraceQueryInformation
- EnumerateTraceGuidsEx
- QueryAllTraces
- TraceLoggingRegister
- TraceLoggingUnregister
- TraceLoggingWrite
- CreateProcess
- DebugActiveProcess
- DebugActiveProcessStop
- WaitForDebugEvent
- WaitForDebugEventEx
- ContinueDebugEvent
- ReadProcessMemory
- WriteProcessMemory
- GetThreadContext
- SetThreadContext
- IsDebuggerPresent
- CheckRemoteDebuggerPresent
- DebugBreakProcess
- DebugSetProcessKillOnExit
- FlushInstructionCache
Pour PDH :
- PDH_FMT_COUNTERVALUE
- PDH_FMT_COUNTERVALUE_ITEM
- PDH_COUNTER_INFO
- PDH_RAW_COUNTER
- PDH_BROWSE_DLG_CONFIG
- HPSS
- HPSSWALK
- PSS_PROCESS_INFORMATION
- PSS_HANDLE_INFORMATION
- PSS_HANDLE_ENTRY
- PSS_THREAD_ENTRY
- PSS_VA_SPACE_ENTRY
- PROVIDER_ENUMERATION_INFO
- TRACE_PROVIDER_INFO
- PROVIDER_EVENT_INFO
- TRACE_EVENT_INFO
- EVENT_PROPERTY_INFO
- EVENT_MAP_INFO
- EVENT_TRACE_PROPERTIES
- ENABLE_TRACE_PARAMETERS
- EVENT_TRACE_LOGFILE
- EVENT_RECORD
- EVENT_HEADER
- EVENT_DESCRIPTOR
- EVENT_HEADER_EXTENDED_DATA_ITEM
- EVENT_FILTER_DESCRIPTOR
- TRACE_QUERY_INFO_CLASS
- DEBUG_EVENT
- EXCEPTION_DEBUG_INFO
- CREATE_PROCESS_DEBUG_INFO
- CREATE_THREAD_DEBUG_INFO
- EXIT_PROCESS_DEBUG_INFO
- EXIT_THREAD_DEBUG_INFO
- LOAD_DLL_DEBUG_INFO
- UNLOAD_DLL_DEBUG_INFO
- OUTPUT_DEBUG_STRING_INFO
- RIP_INFO
- EXCEPTION_RECORD
- CONTEXT
Si on résume ce chapitre en vision système, on obtient quelque chose comme :
Code: Select all
OutputDebugString
→ debug texte simple
PDH
→ métriques système
PSS
→ snapshot cohérent d’un processus
ETW
→ tracing haute performance du système
Debug API
→ contrôle actif d’un processus par un debugger
Vision terrain
Pour du développement applicatif simple, ce chapitre peut sembler exotique. Mais pour du développement système, il est énorme.
On retrouve directement ces concepts dans :
- des outils de supervision
- des Process Explorer / ProcMon-like
- des antivirus / EDR
- des anti-cheat
- des outils internes de diagnostic
- des debuggers
- des composants de télémétrie
- de l’instrumentation d’applications complexes
Comme souvent en système, les erreurs viennent surtout d’un mauvais modèle mental.
Parmi les fautes fréquentes :
- penser qu’OutputDebugString remplace un vrai logging
- oublier le double collect en PDH
- utiliser la mauvaise API PDH pour un compteur multi-instance
- croire qu’un processus vivant peut être inspecté “cohérent” sans snapshot ni logique de capture
- sous-estimer la complexité du parsing ETW
- confondre observation ETW et contrôle via Debug API
- oublier ContinueDebugEvent
- croire que certaines adresses reçues dans un DEBUG_EVENT sont directement lisibles sans ReadProcessMemory
- ignorer le cycle de vie des sessions ETW
Ce chapitre te fait passer d’une logique :
Code: Select all
"je code une appli Win32"
Code: Select all
"je sais observer, mesurer, capturer, tracer et contrôler ce que fait Windows"
Si tu maitrises déjà correctement :
- OutputDebugString
- PdhOpenQuery, PdhAddEnglishCounter, PdhCollectQueryData
- PdhGetFormattedCounterValue et PdhGetFormattedCounterArray
- PssCaptureSnapshot, PssQuerySnapshot, PssWalkSnapshot
- TdhEnumerateProviders, TdhGetEventInformation, TdhFormatProperty
- StartTrace, EnableTraceEx2, OpenTrace, ProcessTrace
- TraceLoggingWrite
- DebugActiveProcess, WaitForDebugEvent, ContinueDebugEvent
- ReadProcessMemory, WriteProcessMemory, GetThreadContext
Et surtout, tu commences à voir qu’en développement système Windows, savoir écrire du code ne suffit pas. Il faut aussi savoir regarder ce que fait le système, comprendre ce qu’il te montre, et parfois prendre le contrôle du processus pour l’analyser ou le guider.
A la prochaine pour un nouveau cours
