Salut l'équipe
Aujourd’hui on attaque un sujet fondamental pour tout développeur système Windows : WinDbg, le kernel debugging et le tracing.
Ce cours n’est pas juste une liste de commandes. Le but est de comprendre comment raisonner devant un programme, un driver, une pile d’appels, un registre, un IRP ou un message de debug.
Quand on commence le développement Win32 bas niveau, on peut souvent s’en sortir avec Visual Studio, des
Code: Select all
printfMais dès qu’on touche à :
- la Native API
- les transitions user mode vers kernel mode
- les drivers
- les IRP
- les threads bloqués
- les BSOD
- les dumps mémoire
- les symboles
- les traces système
Cet outil, c’est WinDbg.
1) Pourquoi WinDbg est important
WinDbg est le debugger système de Windows.
Il permet de travailler à plusieurs niveaux :
- debug user-mode
- debug kernel-mode
- analyse de crash dump
- analyse de mémoire
- inspection des registres CPU
- inspection des threads
- inspection des stacks
- inspection des modules chargés
- debug de drivers
- utilisation d’extensions spécialisées
C’est surtout son accès direct à la structure du système.
Avec WinDbg, on peut passer d’une vision simple :
Code: Select all
Mon programme plante
Code: Select all
Quel thread exécute quoi ?
Quelle fonction a été appelée ?
Quels arguments ont été passés ?
Quel registre contient quelle valeur ?
Quel module contient cette adresse ?
Quel processus possède ce thread ?
Quel driver a reçu cet IRP ?
Chaque outil a son domaine.
Visual Studio
Visual Studio est très confortable pour développer et déboguer du code applicatif.
Il est utile pour :
- compiler
- mettre des breakpoints classiques
- inspecter des variables
- déboguer du code source
- faire du développement C/C++ classique
x64dbg
x64dbg est très bon pour le reverse user-mode.
Il est pratique pour :
- analyse d’exécutables
- patching
- breakpoints user-mode simples
- interface graphique confortable
- inspection rapide du désassemblage
WinDbg
WinDbg est plus austère, mais plus puissant pour le développement système.
Il est meilleur pour :
- les symboles Microsoft
- les PDB
- les extensions système
- les stacks fiables
- les dumps mémoire
- le kernel debugging
- les drivers
- les structures Windows
- les commandes comme ,
Code: Select all
!process,Code: Select all
!thread,Code: Select all
!handleCode: Select all
!peb
Code: Select all
x64dbg = reverse user-mode confortable
VS = développement et debug source classique
WinDbg = debugger système Windows sérieux
3) Les trois familles de commandes WinDbg
WinDbg se pilote énormément par commandes.
Il faut distinguer plusieurs familles.
3.1) Commandes classiques
Exemples :
Code: Select all
r
k
lm
db
dq
du
bp
g
p
t
3.2) Commandes avec point
Exemples :
Code: Select all
.symfix
.reload
.process
.detach
.cls
3.3) Commandes d’extension
Exemples :
Code: Select all
!process
!thread
!handle
!peb
!teb
!analyze
Code: Select all
!Ces commandes sont extrêmement importantes en kernel debugging, car elles savent interpréter des structures internes de Windows.
4) Les symboles : la base absolue
Sans symboles, WinDbg affiche surtout des adresses.
Exemple sans symboles :
Code: Select all
00007ff7`23cc9c28
fffff804`12345678
Code: Select all
ntdll!NtCreateFile
kernel32!CreateFileW
MyCounter!DriverEntry
MyCounter!DispatchDeviceControl
- des noms de fonctions
- des noms de modules
- des variables globales
- des types
- des offsets
- des informations de debug
Un chemin de symboles courant est :
Code: Select all
srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
- utiliser comme cache local
Code: Select all
C:\Symbols - télécharger les symboles nécessaires depuis le serveur Microsoft
Initialiser un chemin de symboles Microsoft :
Code: Select all
.symfix
Code: Select all
.reload
Code: Select all
.reload /f
Code: Select all
lm
Code: Select all
lm m ntdll
lm m kernel32
lm m MyCounter
Code: Select all
x module!*
x module!fonction*
x ntdll!Nt*
x MyCounter!*
Quand on développe un driver, il faut garder le fichier
Code: Select all
.pdbCode: Select all
.sysSi le
Code: Select all
.pdb- de mauvais noms
- de mauvais offsets
- des stacks incomplètes
- des breakpoints qui ne se résolvent pas
Code: Select all
.sys chargé + .pdb correspondant = debug propre
La commande principale est :
Code: Select all
r
Pour afficher un registre précis :
Code: Select all
r rcx
r rdx
r r8
r r9
r rax
r rsp
r rip
En x64 Windows, certains registres sont particulièrement importants :
- : instruction courante
Code: Select all
RIP - : pointeur de pile
Code: Select all
RSP - : base pointer, selon le code généré
Code: Select all
RBP - : valeur de retour
Code: Select all
RAX - : premier argument
Code: Select all
RCX - : deuxième argument
Code: Select all
RDX - : troisième argument
Code: Select all
R8 - : quatrième argument
Code: Select all
R9
Sur Windows x64, les quatre premiers arguments d’une fonction sont passés dans :
Code: Select all
RCX = 1er argument
RDX = 2e argument
R8 = 3e argument
R9 = 4e argument
Code: Select all
RAX
Exemple avec
Code: Select all
MessageBoxWCode: Select all
MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
Code: Select all
RCX = hWnd
RDX = lpText
R8 = lpCaption
R9 = uType
Code: Select all
MessageBoxWCode: Select all
du rdx
Code: Select all
du r8
WinDbg permet de lire la mémoire de plusieurs façons.
6.1) Affichage brut
Afficher des bytes :
Code: Select all
db adresse
Code: Select all
dd adresse
Code: Select all
dq adresse
Afficher une chaîne ANSI :
Code: Select all
da adresse
Code: Select all
du adresse
Code: Select all
du = Display Unicode
C’est un affichage de mémoire interprétée comme une chaîne Unicode.
Exemple :
Code: Select all
du rcx
Code: Select all
RCX6.3) Exemple pratique
Si un programme appelle :
Code: Select all
MessageBoxW(NULL, L"Appuie sur OK", L"Test", MB_OK);
Code: Select all
MessageBoxWCode: Select all
du rdx
Code: Select all
Appuie sur OK
Code: Select all
du r8
Code: Select all
Test
La commande de recherche est :
Code: Select all
s
Code: Select all
s -u debut taille "texte"
Code: Select all
s -u 0 L?80000000 "TOP_SECRET"
Code: Select all
TOP_SECRETSi on connaît déjà la base du module, on peut limiter la recherche :
Code: Select all
s -u 00007ff7`23cb0000 L?200000 "TOP_SECRET"
Code: Select all
du adresse
Un breakpoint permet d’arrêter l’exécution à un endroit précis.
8.1) Breakpoint classique
Code: Select all
bp module!fonction
Code: Select all
bp user32!MessageBoxW
Code: Select all
bu module!fonction
Exemple pour un driver :
Code: Select all
bu MyCounter!DriverEntry
Code: Select all
MyCounter.sysRègle mentale :
Code: Select all
bp = module déjà chargé
bu = module pas encore chargé ou breakpoint symbolique durable
Lister les breakpoints :
Code: Select all
bl
Code: Select all
bd numéro
Code: Select all
be numéro
Code: Select all
bc numéro
Code: Select all
bc *
Continuer :
Code: Select all
g
Code: Select all
t
Code: Select all
p
Code: Select all
gu
Code: Select all
Break
9) La stack : voir la vérité de l’exécution
La stack est l’un des éléments les plus importants en debug.
Afficher la stack :
Code: Select all
k
Code: Select all
kb
kp
kv
- : stack simple
Code: Select all
k - : stack avec quelques paramètres
Code: Select all
kb - : stack avec paramètres si les symboles le permettent
Code: Select all
kp - : stack verbose
Code: Select all
kv
Exemple user-mode :
Code: Select all
user32!MessageBoxW
secret!main
ucrtbase!invoke_main
kernel32!BaseThreadInitThunk
ntdll!RtlUserThreadStart
Code: Select all
MyCounter!DispatchDeviceControl
nt!IofCallDriver
nt!NtDeviceIoControlFile
ntdll!NtDeviceIoControlFile
KERNELBASE!DeviceIoControl
10) Les threads en user-mode
Afficher les threads :
Code: Select all
~
Code: Select all
~0s
~1s
~2s
Code: Select all
~1k
~2k
Règle mentale :
Code: Select all
un processus contient des threads
un thread contient une exécution
une stack montre le chemin de cette exécution
11.1) TEB
Le TEB est le Thread Environment Block.
Il contient des informations user-mode liées à un thread :
- informations de stack
- TLS
- données spécifiques au thread
- liens vers certaines structures user-mode
Code: Select all
!teb
Le PEB est le Process Environment Block.
Il contient des informations user-mode liées au processus :
- liste des modules chargés
- informations du loader
- chemin image
- paramètres processus
- informations d’environnement
Code: Select all
!peb
Afficher les modules :
Code: Select all
lm
Code: Select all
lm m kernel32
lm m ntdll
lm m user32
lm m MyCounter
Parce que chaque adresse appartient à un module.
Si on voit une adresse :
Code: Select all
00007fff`a9bbec60
Code: Select all
user32!MessageBoxW
13) De Win32 à la Native API
Une application appelle souvent des API Win32 :
Code: Select all
CreateFileW
ReadFile
WriteFile
DeviceIoControl
VirtualAlloc
CreateProcessW
Code: Select all
ntdll.dllExemple :
Code: Select all
CreateFileW
↓
KernelBase / Kernel32
↓
ntdll!NtCreateFile
↓
syscall
↓
kernel
Code: Select all
Win32 API = couche pratique user-mode
Native API = interface plus proche du noyau
syscall = transition user-mode vers kernel-mode
Quand on observe
Code: Select all
CreateFileWCode: Select all
NtCreateFileEn x64, les arguments suivent la convention d’appel Windows :
Code: Select all
RCX, RDX, R8, R9
Code: Select all
NTSTATUSCode: Select all
RAX
Code: Select all
STATUS_SUCCESS
STATUS_OBJECT_NAME_NOT_FOUND
STATUS_ACCESS_DENIED
Code: Select all
RAX15) Debug user-mode : exercice mental
Exemple :
Code: Select all
const wchar_t* secret = L"TOP_SECRET_123";
MessageBoxW(NULL, L"Appuie sur OK", L"Test", MB_OK);
Code: Select all
bp user32!MessageBoxW
g
Code: Select all
r
du rdx
du r8
Code: Select all
s -u 0 L?80000000 "TOP_SECRET"
Code: Select all
du adresse
- breakpoint
- registres
- calling convention x64
- chaînes Unicode
- recherche mémoire
- lecture mémoire
Le kernel debugging ressemble au user-mode debugging, mais avec une différence majeure :
Code: Select all
User-mode debug = un processus
Kernel debug = tout le système
Schéma :
Code: Select all
Host avec WinDbg
↓
connexion de debug
↓
Target Windows / VM
Parce que si le kernel de la target se bloque, crashe ou atteint un breakpoint, le système cible est suspendu.
Il faut donc que le debugger soit ailleurs.
17) Host et target
Host
La machine host exécute WinDbg.
Elle sert à :
- contrôler la session
- recevoir les breakpoints
- afficher les stacks
- analyser les structures kernel
- envoyer les commandes
La machine target est le Windows débogué.
Elle peut être :
- une autre machine physique
- une VM Hyper-V
- une VM VMware
- une VM VirtualBox selon configuration
Code: Select all
PC principal = host
VM Windows = target
Le local kernel debugging consiste à déboguer le kernel de la même machine.
Il est utile pour certaines observations, mais il a des limites.
Avantages :
- mise en place plus simple
- utile pour explorer
- pas besoin de deuxième machine
- moins adapté au debug driver sérieux
- certaines actions ne sont pas possibles
- si le système est bloqué, le debugger est aussi affecté
- Secure Boot peut gêner
Code: Select all
bcdedit /debug on
19) Full kernel debugging avec VM
Le modèle recommandé :
Code: Select all
Host Windows
↓
WinDbg
↓
connexion réseau ou pipe
↓
VM target Windows
↓
driver à déboguer
- de stopper le kernel cible
- de garder le host actif
- de charger et décharger des drivers
- de voir les BSOD
- d’analyser les crash dumps
- de mettre des breakpoints dans DriverEntry
Le kernel debugging réseau est moderne et pratique.
Côté target :
Code: Select all
bcdedit /debug on
bcdedit /dbgsettings net hostip:<IP_HOST> port:<PORT> key:<KEY>
Code: Select all
bcdedit /debug on
bcdedit /dbgsettings net hostip:192.168.1.10 port:50000 key:1.2.3.4
Côté host :
Code: Select all
WinDbg
File
Attach to kernel
NET
- l’adresse IP soit correcte
- le port soit correct
- la key soit correcte
- le firewall ne bloque pas
- host et target puissent communiquer
En VM, on peut aussi utiliser un port série virtuel exposé comme un named pipe.
Exemple de pipe :
Code: Select all
\\.\pipe\debug
Code: Select all
File
Attach to kernel
COM
Port: \\.\pipe\debug
Baud: 115200
22) Première commande kernel : !process
La commande centrale :
Code: Select all
!process 0 0
Signification :
- premier : tous les processus
Code: Select all
0 - deuxième : niveau de détail minimal
Code: Select all
0
Code: Select all
!process 0 1
!process 0 2
!process 0 7
Code: Select all
!process <adresse_EPROCESS> 1
!process <adresse_EPROCESS> 7
En kernel, un processus est représenté par une structure interne souvent appelée EPROCESS.
Quand WinDbg affiche un processus avec
Code: Select all
!process- adresse du processus
- PID / Cid
- ParentCid
- Peb
- ObjectTable
- HandleCount
- Image
- SessionId
Code: Select all
EPROCESS = représentation kernel du processus
PEB = représentation user-mode importante du processus
En kernel debug, on n’est pas automatiquement dans le bon contexte user-mode.
Si on veut lire correctement la mémoire user-mode d’un processus, il faut souvent changer de contexte :
Code: Select all
.process /r /p <adresse_EPROCESS>
.reload /user
Code: Select all
!peb
Code: Select all
Kernel debug ≠ accès automatique correct à toute la mémoire user-mode
Code: Select all
je veux travailler dans ce processus
Afficher le thread courant :
Code: Select all
!thread
Code: Select all
!thread <adresse_ETHREAD>
Dans les sorties WinDbg, on peut voir :
- TID
- TEB
- état
- priorité
- wait reason
- objet attendu
- stack
Code: Select all
ETHREAD = représentation kernel d’un thread
TEB = structure user-mode du thread
Un thread qui ne s’exécute pas attend souvent quelque chose.
WinDbg peut montrer des états comme :
Code: Select all
WAIT
UserRequest
WrQueue
WrLpcReply
Executive
KernelMode
UserMode
Non-Alertable
- un event
- un mutex
- une queue
- un message GUI
- une réponse LPC/ALPC
- une opération d’I/O
- un objet kernel
Un mauvais driver peut bloquer un thread si :
- un IRP n’est jamais complété
- un verrou n’est pas relâché
- une attente est faite au mauvais IRQL
- un événement n’est jamais signalé
Quand on débogue un driver, on veut souvent observer :
- le chargement du driver
- DriverEntry
- DriverUnload
- les dispatch routines
- les IRP reçus
- les IOCTL
- les buffers
- les statuts retournés
- la complétion des IRP
Code: Select all
mainIl est appelé par le système.
Donc les breakpoints importants sont souvent dans :
Code: Select all
DriverEntry
DriverUnload
DispatchCreateClose
DispatchRead
DispatchWrite
DispatchDeviceControl
Pour arrêter au chargement d’un driver :
Code: Select all
bu MyCounter!DriverEntry
g
Quand le driver est chargé, WinDbg s’arrête dans
Code: Select all
DriverEntryPourquoi
Code: Select all
buParce que le driver n’est souvent pas encore chargé au moment où on place le breakpoint.
29) Breakpoints sur dispatch routines
Exemples :
Code: Select all
bu MyCounter!DispatchCreateClose
bu MyCounter!DispatchDeviceControl
bu MyCounter!DispatchRead
bu MyCounter!DispatchWrite
Code: Select all
CreateFileW(L"\\\\.\\MyCounter", ...)
DeviceIoControl(...)
ReadFile(...)
WriteFile(...)
CloseHandle(...)
Code: Select all
IRP_MJ_CREATE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_READ
IRP_MJ_WRITE
IRP_MJ_CLOSE
Côté user-mode, l’application parle souvent au driver avec :
Code: Select all
CreateFileW
DeviceIoControl
ReadFile
WriteFile
CloseHandle
Code: Select all
CreateFileW("\\\\.\\MyCounter")
↓
I/O Manager
↓
IRP_MJ_CREATE
↓
Driver
Code: Select all
DeviceIoControl(...)
↓
I/O Manager
↓
IRP_MJ_DEVICE_CONTROL
↓
Driver
31) IoGetCurrentIrpStackLocation
Dans une dispatch routine, on récupère les paramètres de l’IRP avec :
Code: Select all
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
Mentalité :
Code: Select all
IRP = requête d’I/O
IO_STACK_LOCATION = paramètres de cette requête à ce niveau de la pile
Code: Select all
stack->Parameters.DeviceIoControl.IoControlCode
stack->Parameters.DeviceIoControl.InputBufferLength
stack->Parameters.DeviceIoControl.OutputBufferLength
Code: Select all
stack->Parameters.Write.Length
Code: Select all
stack->Parameters.Read.Length
Un driver doit terminer correctement les requêtes qu’il traite.
Forme classique :
Code: Select all
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = bytes;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
- : statut final
Code: Select all
IoStatus.Status - : nombre d’octets utiles retournés ou traités selon le cas
Code: Select all
IoStatus.Information
33) API kernel importantes vues dans ce contexte
Création du device :
Code: Select all
IoCreateDevice
Code: Select all
IoCreateSymbolicLink
Code: Select all
IoDeleteSymbolicLink
Code: Select all
IoDeleteDevice
Code: Select all
IoGetCurrentIrpStackLocation
Code: Select all
IoCompleteRequest
Code: Select all
DbgPrint
DbgPrintEx
KdPrint
KdPrintEx
Code: Select all
ASSERT
NT_ASSERT
Code: Select all
DriverEntryIl sert généralement à :
- initialiser le driver
- enregistrer les dispatch routines
- créer un device object
- créer un symbolic link si nécessaire
- préparer les structures globales
- définir la routine de déchargement
Code: Select all
DriverObject->DriverUnload = MyUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDeviceControl;
Code: Select all
DriverUnloadIl doit libérer ce que
Code: Select all
DriverEntryExemples :
Code: Select all
IoDeleteSymbolicLink(&symLink);
IoDeleteDevice(DeviceObject);
Code: Select all
ce qui est créé doit être nettoyé
Les assertions servent à vérifier des hypothèses internes.
Exemple :
Code: Select all
ASSERT(DeviceObject != NULL);
ASSERT(Irp != NULL);
En release, certaines assertions peuvent disparaître selon les macros et la configuration.
Utilité :
- attraper les bugs tôt
- documenter les hypothèses du code
- éviter de continuer avec un état incohérent
- une assertion ne remplace pas une vraie validation d’entrée
- une assertion sert surtout à vérifier un invariant interne
Code: Select all
DbgPrintExemple :
Code: Select all
DbgPrint("MyCounter loaded\n");
Code: Select all
KdPrintCode: Select all
KdPrint(("MyCounter loaded\n"));
Code: Select all
KdPrintCes messages peuvent être visibles dans :
- WinDbg kernel
- DebugView selon configuration
- certains outils de capture debug
Code: Select all
DbgPrintExCode: Select all
DbgPrintForme :
Code: Select all
DbgPrintEx(ComponentId, Level, Format, ...);
Code: Select all
DbgPrintEx(DPFLTR_IHVDRIVER_ID,
DPFLTR_INFO_LEVEL,
"MyCounter: Driver loaded\n");
Le
Code: Select all
ComponentIdPour un driver tiers, on utilise souvent :
Code: Select all
DPFLTR_IHVDRIVER_ID
Niveaux courants :
Code: Select all
DPFLTR_ERROR_LEVEL
DPFLTR_WARNING_LEVEL
DPFLTR_INFO_LEVEL
DPFLTR_TRACE_LEVEL
- ERROR : problème sérieux
- WARNING : problème potentiel
- INFO : information normale
- TRACE : détails très verbeux
Pour éviter de répéter
Code: Select all
DbgPrintExExemple :
Code: Select all
#define LOG_COMPONENT DPFLTR_IHVDRIVER_ID
#define LogInfo(fmt, ...) \
DbgPrintEx(LOG_COMPONENT, DPFLTR_INFO_LEVEL, "[MyDriver] " fmt, __VA_ARGS__)
#define LogError(fmt, ...) \
DbgPrintEx(LOG_COMPONENT, DPFLTR_ERROR_LEVEL, "[MyDriver] " fmt, __VA_ARGS__)
- avoir un format uniforme
- filtrer plus facilement
- rendre le code plus lisible
Code: Select all
__VA_ARGS__40) DebugView
DebugView est un outil Sysinternals qui peut afficher des sorties debug.
Il est utile pour :
- voir rapidement des messages
Code: Select all
DbgPrint - déboguer sans garder WinDbg ouvert en permanence
- observer des traces simples
41) Filtres DbgPrintEx
Un point important : tous les messages
Code: Select all
DbgPrintExL’affichage dépend notamment :
- du ComponentId
- du Level
- des filtres configurés
- du debugger attaché
- de la configuration système
Il faut se demander :
- est-ce que le breakpoint est atteint ?
- est-ce que la fonction de log est appelée ?
- est-ce que le filtre autorise ce niveau ?
- est-ce que le debugger capture bien la sortie ?
ETW est le système de tracing avancé de Windows.
C’est plus sérieux, plus structuré et plus puissant que de simples
Code: Select all
DbgPrintSchéma :
Code: Select all
Provider ETW
↓
Session ETW
↓
Consumer / outil d’analyse
Code: Select all
Driver
↓
événements ETW
↓
session de trace
↓
TraceView / WPA / outil de lecture
Provider
Le provider est le composant qui produit les événements.
Cela peut être :
- un driver
- un service
- un composant système
- une application
La session est la capture active.
Elle décide :
- quels providers sont activés
- quels niveaux sont capturés
- quels mots-clés sont activés
- où les traces sont écrites
Le consumer lit ou affiche les événements.
Exemples :
- TraceView
- Windows Performance Analyzer
- outils personnalisés
Un provider ETW est identifié par un GUID.
Le GUID permet à l’outil de trace de savoir quel provider activer.
44) Niveaux ETW
Les événements peuvent avoir des niveaux :
- Critical
- Error
- Warning
- Information
- Verbose
Exemple :
Code: Select all
Information = événements normaux utiles
Verbose = détails nombreux, utiles en diagnostic profond
Error = événements d’échec
Les keywords permettent de catégoriser les événements.
Exemple mental :
Code: Select all
KEYWORD_IO
KEYWORD_INIT
KEYWORD_POWER
KEYWORD_REGISTRY
C’est ce qui rend ETW beaucoup plus propre que du logging brut.
46) TraceView
TraceView est un outil graphique du WDK permettant de configurer et visualiser des traces ETW.
Il permet :
- de créer une session de trace
- d’ajouter un provider
- de choisir un GUID
- de démarrer la capture
- de voir les événements
- d’écrire dans un fichier de log
Pour un usage plus avancé, on peut aussi rencontrer :
Code: Select all
logmanCode: Select all
tracelog- Windows Performance Recorder
- Windows Performance Analyzer
Résumé :
Code: Select all
DbgPrintEx = simple, pratique, rapide pour debug
ETW = structuré, filtrable, plus adapté au diagnostic sérieux
ETW est meilleur pour :
- tracing structuré
- diagnostic long
- production
- analyse de performance
- capture sélective
- corrélation d’événements
48.1) Symboles
Code: Select all
.symfix
.reload
.reload /f
lm
lm m module
x module!*
Code: Select all
r
r rcx
r rdx
r r8
r r9
r rax
db adresse
dd adresse
dq adresse
da adresse
du adresse
s -u debut taille "texte"
Code: Select all
k
kb
kp
kv
~
~0s
~1s
~2k
Code: Select all
bp module!fonction
bu module!fonction
bl
bd numéro
be numéro
bc numéro
bc *
g
p
t
gu
Code: Select all
!process 0 0
!process 0 1
!process 0 7
!process <EPROCESS> 7
!thread
!thread <ETHREAD>
.process /r /p <EPROCESS>
.reload /user
!peb
!teb
Point d’entrée et déchargement :
Code: Select all
DriverEntry
DriverUnload
Code: Select all
IoCreateDevice
IoCreateSymbolicLink
IoDeleteSymbolicLink
IoDeleteDevice
Code: Select all
IRP_MJ_CREATE
IRP_MJ_CLOSE
IRP_MJ_READ
IRP_MJ_WRITE
IRP_MJ_DEVICE_CONTROL
Code: Select all
IoGetCurrentIrpStackLocation
IoCompleteRequest
Code: Select all
DbgPrint
DbgPrintEx
KdPrint
ASSERT
NT_ASSERT
Code: Select all
CreateFileW
DeviceIoControl
ReadFile
WriteFile
CloseHandle
Après ce chapitre, il faut savoir :
- lancer WinDbg sur un programme user-mode
- mettre un breakpoint sur une API Win32
- lire les registres
- comprendre
Code: Select all
RCX/RDX/R8/R9 - afficher une chaîne Unicode avec
Code: Select all
du - chercher une chaîne en mémoire avec
Code: Select all
s -u - lire une stack avec
Code: Select all
k - lister les modules avec
Code: Select all
lm - configurer les symboles
- comprendre le passage Win32 vers Native API
- connecter WinDbg à une VM en kernel debug
- utiliser et
Code: Select all
!processCode: Select all
!thread - mettre un breakpoint différé sur
Code: Select all
DriverEntry - déboguer une dispatch routine
- utiliser
Code: Select all
DbgPrintEx - comprendre l’intérêt d’ETW
Pour le user-mode :
Code: Select all
Programme
↓
API Win32
↓
Native API
↓
syscall
↓
kernel
Code: Select all
Application user-mode
↓
CreateFile / DeviceIoControl
↓
I/O Manager
↓
IRP
↓
Dispatch routine du driver
↓
IoStatus
↓
IoCompleteRequest
↓
retour user-mode
Code: Select all
Breakpoint
↓
Registres
↓
Mémoire
↓
Stack
↓
Modules
↓
Symboles
↓
Structures système
Code: Select all
1. Ouvrir l'exe dans WinDbg
2. Vérifier les symboles
3. Mettre un breakpoint sur l'API intéressante
4. g
5. r
6. Lire RCX/RDX/R8/R9
7. du sur les pointeurs string
8. k pour voir la stack
9. lm pour voir les modules
10. chercher en mémoire si nécessaire
Code: Select all
1. Compiler le driver en Debug
2. Garder le .pdb correspondant
3. Préparer une VM target
4. Activer le kernel debugging
5. Connecter WinDbg depuis le host
6. Configurer les symboles
7. bu Driver!DriverEntry
8. Charger le driver
9. Vérifier DriverEntry
10. Placer des breakpoints sur les dispatch routines
11. Lancer le programme user-mode
12. Observer IRP, stack, registres et logs
13. Corriger le driver
14. Rebuild, reload, retester
- oublier les symboles
- charger un mauvais PDB
- utiliser alors que le driver n’est pas encore chargé
Code: Select all
bp - oublier
Code: Select all
.reload /f - ne pas comprendre
Code: Select all
RCX/RDX/R8/R9 - confondre adresse et contenu pointé
- utiliser au lieu de
Code: Select all
dbpour une chaîne UnicodeCode: Select all
du - faire sans bon contexte process en kernel debug
Code: Select all
!peb - ne pas compléter un IRP
- mettre trop de sans filtrage
Code: Select all
DbgPrint - oublier que le système entier est figé quand le kernel est stoppé
WinDbg est l’outil central du debug système Windows.
Les symboles sont indispensables.
La stack montre le chemin réel d’exécution.
Les registres donnent les arguments et les retours.
En x64 Windows, les quatre premiers arguments sont dans
Code: Select all
RCXCode: Select all
RDXCode: Select all
R8Code: Select all
R9Les chaînes Unicode se lisent avec
Code: Select all
duLes modules se listent avec
Code: Select all
lmLes processus kernel se listent avec
Code: Select all
!process 0 0Les threads kernel s’inspectent avec
Code: Select all
!threadUn driver se débogue souvent avec un breakpoint différé sur
Code: Select all
DriverEntryCode: Select all
buLes IRP se comprennent avec
Code: Select all
IoGetCurrentIrpStackLocationCode: Select all
IoCompleteRequestCode: Select all
DbgPrintExETW sert au tracing propre, structuré et plus professionnel.
Conclusion
Ce chapitre donne les bases nécessaires pour passer d’un développeur Win32 classique à quelqu’un qui commence à raisonner comme un développeur système Windows.
Le point important n’est pas seulement de connaître des commandes.
Le vrai objectif est de savoir répondre à ces questions :
- où suis-je dans l’exécution ?
- quel thread exécute ce code ?
- quelle fonction m’a amené ici ?
- quels arguments ont été passés ?
- quelle mémoire est pointée ?
- quel module contient cette adresse ?
- quel processus possède ce contexte ?
- quel driver reçoit cette requête ?
- est-ce que l’IRP est correctement terminé ?
- est-ce que mes logs sont visibles et filtrables ?
Il devient une fenêtre directe sur Windows.
