Le filesystem Windows ne se résume pas à ZwCreateFile, ZwReadFile et ZwWriteFile. Derrière une simple ouverture de fichier se trouvent l'I/O Manager, les objets FILE_OBJECT, les pilotes de système de fichiers, les volumes, les chemins NT, les structures FILE_INFORMATION_CLASS et plusieurs formats de buffers variables.
Ce cours regroupe les notions générales nécessaires pour comprendre les fichiers et répertoires côté noyau sans se limiter à un type de driver particulier.
Sommaire
- 1. Architecture générale du chemin I/O
- 2. FILE_OBJECT
- 3. Volumes et Device Objects
- 4. Chemins DOS, NT et Device Paths
- 5. Symbolic Links et lettres de lecteurs
- 6. Reparse Points et résolution de chemin
- 7. Streams NTFS
- 8. IRP_MJ_CREATE
- 9. READ, WRITE et cache
- 10. CLEANUP et CLOSE
- 11. FILE_INFORMATION_CLASS
- 12. FILE_STANDARD_INFORMATION
- 13. FILE_BASIC_INFORMATION
- 14. FILE_NAME_INFORMATION et structures variables
- 15. Suppression de fichiers
- 16. Renommage de fichiers
- 17. IRP_MJ_DIRECTORY_CONTROL
- 18. Structures d'énumération de répertoire
- 19. NextEntryOffset
- 20. Parcours sécurisé d'un buffer variable
- 21. Lien avec SYSTEM_PROCESS_INFORMATION
- 22. Modification d'un buffer d'énumération
- 23. Bonnes pratiques
1. Architecture générale du chemin I/O
Lorsqu'une application ouvre ou manipule un fichier, l'opération traverse plusieurs couches.
Code: Select all
Application
|
v
Win32 / Native API
|
v
I/O Manager
|
v
File System Driver
|
v
Volume
|
v
Storage Stack
|
v
Périphérique
Parmi les opérations importantes :
- IRP_MJ_CREATE
- IRP_MJ_READ
- IRP_MJ_WRITE
- IRP_MJ_QUERY_INFORMATION
- IRP_MJ_SET_INFORMATION
- IRP_MJ_DIRECTORY_CONTROL
- IRP_MJ_CLEANUP
- IRP_MJ_CLOSE
2. FILE_OBJECT
Un FILE_OBJECT représente une ouverture d'un objet géré par l'I/O Manager.
Il peut représenter :
- un fichier
- un répertoire
- un périphérique
- une ouverture particulière d'un objet de type fichier
Code: Select all
DeviceObject
FileName
Flags
FsContext
FsContext2
3. Volumes et Device Objects
Un volume est généralement représenté dans le namespace NT par un nom tel que :
Code: Select all
\Device\HarddiskVolume3
Code: Select all
C:
Un volume est associé à une pile de Device Objects. Une requête peut traverser plusieurs drivers avant d'atteindre le périphérique de stockage.
4. Chemins DOS, NT et Device Paths
Il existe plusieurs représentations d'un même fichier.
Exemples :
Code: Select all
C:\Windows\System32\kernel32.dll
\??\C:\Windows\System32\kernel32.dll
\Device\HarddiskVolume3\Windows\System32\kernel32.dll
Le chemin NT reflète davantage le namespace natif.
Le préfixe :
Code: Select all
\??\
5. Symbolic Links et lettres de lecteurs
Les lettres de lecteur reposent sur des symbolic links.
Conceptuellement :
Code: Select all
C: -> \Device\HarddiskVolume3
Code: Select all
\GLOBAL??
Code: Select all
\Device\MyDriver
\DosDevices\MyDriver
Code: Select all
\\.\MyDriver
6. Reparse Points et résolution de chemin
Les reparse points permettent de modifier la résolution d'un chemin.
Ils sont utilisés notamment pour :
- junctions
- symbolic links NTFS
- mount points
- certains systèmes de fichiers ou services cloud
7. Streams NTFS
NTFS permet plusieurs flux pour un même fichier.
Exemple :
Code: Select all
C:\Test\file.txt
C:\Test\file.txt:metadata
Le code kernel qui analyse des noms de fichiers doit tenir compte de cette possibilité.
8. IRP_MJ_CREATE
Le nom CREATE peut prêter à confusion.
IRP_MJ_CREATE sert à ouvrir un objet, pas uniquement à créer un nouveau fichier.
Il peut correspondre à :
- ouvrir un fichier existant
- créer un fichier
- ouvrir un répertoire
- ouvrir un Device Object
- ouvrir avec des droits spécifiques
- DesiredAccess
- ShareAccess
- CreateDisposition
- CreateOptions
9. READ, WRITE et cache
Une lecture ou écriture n'implique pas nécessairement un accès immédiat au disque.
Windows utilise notamment :
- Cache Manager
- Memory Manager
- paging I/O
- cached I/O
- non-cached I/O
10. CLEANUP et CLOSE
IRP_MJ_CLEANUP et IRP_MJ_CLOSE sont deux étapes différentes.
CLEANUP :
- correspond généralement à la fermeture du dernier handle utilisateur lié à cette ouverture
- est souvent le moment où l'on arrête certaines opérations associées au handle
- arrive plus tard
- intervient lorsque le FILE_OBJECT n'est plus référencé et peut être détruit
11. FILE_INFORMATION_CLASS
Windows utilise FILE_INFORMATION_CLASS pour déterminer la nature des informations demandées ou modifiées.
Exemples :
Code: Select all
FileBasicInformation
FileStandardInformation
FileNameInformation
FileDispositionInformation
FileDispositionInformationEx
FileRenameInformation
12. FILE_STANDARD_INFORMATION
Cette classe retourne notamment :
- AllocationSize
- EndOfFile
- NumberOfLinks
- DeletePending
- Directory
Code: Select all
FILE_STANDARD_INFORMATION info;
ZwQueryInformationFile(
FileHandle,
&IoStatus,
&info,
sizeof(info),
FileStandardInformation
);
13. FILE_BASIC_INFORMATION
Cette classe contient les informations classiques :
- CreationTime
- LastAccessTime
- LastWriteTime
- ChangeTime
- FileAttributes
14. FILE_NAME_INFORMATION et structures variables
Certaines structures se terminent par un tableau de taille variable :
Code: Select all
typedef struct _FILE_NAME_INFORMATION {
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAME_INFORMATION;
Code: Select all
sizeof(FILE_NAME_INFORMATION)
15. Suppression de fichiers
Une suppression n'est pas forcément représentée par une opération unique.
On peut rencontrer :
- FILE_DELETE_ON_CLOSE lors d'une ouverture
- FileDispositionInformation
- FileDispositionInformationEx
Code: Select all
typedef struct _FILE_DISPOSITION_INFORMATION {
BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION;
16. Renommage de fichiers
Le renommage utilise notamment :
Code: Select all
FileRenameInformation
Comme toujours :
- valider la taille du buffer
- vérifier la classe
- ne pas lire au-delà de la longueur annoncée
17. IRP_MJ_DIRECTORY_CONTROL
Les opérations sur les répertoires utilisent :
Code: Select all
IRP_MJ_DIRECTORY_CONTROL
Code: Select all
IRP_MN_QUERY_DIRECTORY
18. Structures d'énumération de répertoire
Le format varie selon FileInformationClass.
Exemples :
- FileDirectoryInformation
- FileFullDirectoryInformation
- FileBothDirectoryInformation
- FileNamesInformation
- FileIdBothDirectoryInformation
- FileIdFullDirectoryInformation
19. NextEntryOffset
Les entrées sont souvent chaînées par un offset :
Code: Select all
ULONG NextEntryOffset;
Code: Select all
Entry A
+ NextEntryOffset
|
v
Entry B
+ NextEntryOffset
|
v
Entry C
NextEntryOffset = 0
20. Parcours sécurisé d'un buffer variable
Pseudo-code :
Code: Select all
PUCHAR current = buffer;
PUCHAR end = current + bufferSize;
while (current < end) {
auto entry = reinterpret_cast<PFILE_DIRECTORY_INFORMATION>(current);
// traiter l'entrée
if (entry->NextEntryOffset == 0)
break;
if (current + entry->NextEntryOffset >= end)
break;
current += entry->NextEntryOffset;
}
21. Lien avec SYSTEM_PROCESS_INFORMATION
Le pattern NextEntryOffset ne concerne pas uniquement les fichiers.
On retrouve la même idée dans :
Code: Select all
SYSTEM_PROCESS_INFORMATION
22. Modification d'un buffer d'énumération
Modifier un buffer doit préserver sa cohérence.
Pour retirer une entrée intermédiaire, l'entrée précédente peut être modifiée pour pointer vers la suivante.
Conceptuellement :
Code: Select all
A -> B -> C
devient
A -----> C
Toute modification incorrecte de taille ou d'offset peut provoquer une corruption ou un crash.
23. Bonnes pratiques
- Valider les tailles avant chaque cast
- Ne jamais faire confiance à un buffer variable sans contrôle
- Distinguer chemin DOS et chemin NT
- Ne pas confondre CLEANUP et CLOSE
- Tenir compte des reparse points
- Ne pas supposer qu'un WRITE correspond à une écriture disque immédiate
- Tester uniquement en VM lors des premières expérimentations kernel
Conclusion
Le filesystem Windows expose plusieurs couches qui se croisent : I/O Manager, FILE_OBJECT, Device Objects, volumes, namespace NT, classes d'informations et buffers variables. Le comprendre comme un ensemble cohérent permet ensuite de manipuler les fichiers proprement depuis le kernel et de lire les structures retournées par Windows sans dépendre d'un framework particulier.
Exercices
- Afficher le DeviceObject et le FileName d'un FILE_OBJECT dans un driver de test.
- Interroger FileStandardInformation puis FileBasicInformation sur un fichier.
- Parser un buffer FILE_DIRECTORY_INFORMATION sans le modifier.
- Comparer un chemin DOS et son Device Path natif dans WinObj.
