POC IRP Hooking
Vous l’attendiez, le voilà ! Le POC de mon précédent post, ou l’art de planquer des fichiers en utilisant un IRP Hook. Mais avant que vous fassiez des bêtises, on va mettre au clair quelques petites choses.
Pour récupérer le contenu d’un dossier, l’OS va utiliser un appel système sur l’API NtQueryDirectoryFile. Cette API va ensuite forger une requête, un IRP, et l’envoyé au driver Nfts qui se chargera de répondre. La routine du driver Nfts qui est utilisée s’appelle NtfsFsdDirectoryControl de prototype :
NTSTATUS MyNtfsFsdDirectoryControl ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp );
Les arguments sont passés à travers l’IRP. Par exemple, lorsque vous affichez le contenu de votre C:\, voici ce qui se passe.
Breakpoint 1 hit Ntfs!NtfsFsdDirectoryControl: fcb3f2c3 684c010000 push 14Ch kd> kb ChildEBP RetAddr Args to Child f9316cd0 804e3d77 80e7a408 ffacb430 80e8cf38 Ntfs!NtfsFsdDirectoryControl f9316ce0 fcbbb459 f9316d0c 804e3d77 80e7add0 nt!IopfCallDriver+0x31 f9316ce8 804e3d77 80e7add0 ffacb430 806ed070 sr!SrPassThrough+0x31 f9316cf8 8056a9ab f9316d64 00c2de3c 80574dad nt!IopfCallDriver+0x31 f9316d0c 80574e0a 80e7add0 ffacb430 80e39118 nt!IopSynchronousServiceTail+0x60 f9316d30 804df06b 00000544 00000000 00000000 nt!NtQueryDirectoryFile+0x5d f9316d30 7c91eb94 00000544 00000000 00000000 nt!KiFastCallEntry+0xf8 00c2de04 7c91df6a 7c80eec2 00000544 00000000 ntdll!KiFastSystemCallRet 00c2de08 7c80eec2 00000544 00000000 00000000 ntdll!ZwQueryDirectoryFile+0xc 00c2e114 7c9f8afd 00c2e3bc 00000000 001500ac kernel32!FindFirstFileExW+0x3a0 00c2e138 7c9f8a97 00c2e3bc 001500ac 0014fe9c SHELL32!SHFindFirstFile+0x2a 00c2e38c 7c9fa996 00100138 00000000 00c2e3bc SHELL32!SHFindFirstFileRetry+0x5b 00c2e5dc 7c9fa870 00101a88 00101a70 00c2e604 SHELL32!CFileSysEnum::Init+0x14b 00c2e5ec 7c9fab6d 0010c820 00100138 00000060 SHELL32!CFSFolder_CreateEnum+0x37 00c2e604 7c9ff03d 0010c830 00100138 00000060 SHELL32!CFSFolder::EnumObjects+0x30 00c2e638 7c9ffa3e 00100138 0014b7f4 0014b7d8 SHELL32!CDefviewEnumTask::FillObjectsToDPA+0x8b 00c2e68c 7774e201 00000001 00000000 0014b7d8 SHELL32!CDefView::CreateViewWindow2+0x2de WARNING: Frame IP not in any known module. Following frames may be wrong. 00c2e738 777392ca 000dac20 0014b7d8 0014e210 0x7774e201 00c2e848 77f5a77f 00106900 777364e1 0013fac4 0x777392ca 00c2e77c 75f35fa2 000dac24 0014e210 00145798 SHLWAPI!EnumConnectionPointSinks+0xac
Dans la call stack on peut retrouver les arguments passé à la fonction NtQueryDirectoryFile :
kd> dd 00c2de08+8 00c2de10 00000544 00000000 00000000 00000000 00c2de20 00c2de78 00c2dea8 00000268 00000003 00c2de30 00000001 00c2de90 00000000
Ce qui correspond, pour être plus clair à l’appel suivant :
ZwQueryDirectoryFile( IN HANDLE FileHandle, -> 0x544 IN HANDLE Event OPTIONAL, -> NULL IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, -> NULL IN PVOID ApcContext OPTIONAL, -> NULL OUT PIO_STATUS_BLOCK IoStatusBlock, -> 0xc2de78 OUT PVOID Buffer, -> 0xc2dea8 IN ULONG BufferLength,-> 0x268 IN FILE_INFORMATION_CLASS FileInformationClass, -> 0x3=FileBothDirectoryInformation IN BOOLEAN ReturnSingleEntry, -> 1=TRUE IN PUNICODE_STRING FileName, -> 0xc2de90 IN BOOLEAN RestartScan -> NULL );
En analysant un peu les arguments, le FileHandle correspond à :
kd> !handle 544 processor number 0, process ffb5f798 PROCESS ffb5f798 SessionId: 0 Cid: 0524 Peb: 7ffd9000 ParentCid: 0504 DirBase: 009a5000 ObjectTable: e1793b28 HandleCount: 340. Image: explorer.exe Handle table at e1536000 with 340 Entries in use 0544: Object: 80e39118 GrantedAccess: 00100001 Entry: e1536a88 Object: 80e39118 Type: (80e95e70) File ObjectHeader: 80e39100 HandleCount: 1 PointerCount: 3 Directory Object: 00000000 Name: \\ {HarddiskVolume1}
Un handle du process explorer.exe de type File, portant le nom de ‘\’ attaché au HarddiskVolume1. En fait c’est tout simplement le handle sur C:\
Le FileName quant à lui contient :
kd> !ustr 00c2de90 String(2,6) at 00c2de90: *
Le fichier demandé est donc C:\*
Enfin le FileInformationClass est mit à FileBothDirectoryInformation (3). Ce qui veut dire que la fonction va renvoyer dans la Buffer en 0xc2dea8 une liste chaînée de structures FILE_BOTH_DIRECTORY_INFORMATION contenant les descriptions des fichiers contenu dans C:\
Vous voyez que c’est pas compliqué Windows :}
Maitenant qu’on sait ce que NtQueryDirectoryFile doit faire, regardons la forme de l’IRP.
kd> !irp ffacb430 Irp is active with 9 stacks 9 is current (= 0xffacb5c0) No Mdl: No System Buffer: Thread 80d4e020: Irp stack trace. cmd flg cl Device File Completion-Context [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 [ 0, 0] 0 0 00000000 00000000 00000000-00000000 Args: 00000000 00000000 00000000 00000000 >[ c, 1] 2 0 80e7a408 80e39118 00000000-00000000 FileSystemNtfs Args: 00000268 ffb03038 00000003 00000000
[ c, 1] Veut dire qu’on à fait appel à une routines IRP_MJ_DIRECTORY_CONTROL(0xc) avec le code IRP_MN_QUERY_DIRECTORY(1). Dans les arguments de l’IRP on peut retrouver la taille du Buffer (0×268) et le FileInformationClass (3). Le Buffer lui est placé dans le champ UserBuffer de l’IRP.
Maintenant qu’on sait tout ca, on peut définir le fonctionnement du hook. Il s’agit de modifier la table des Dispatch routines du driver Nfts afin d’y mettre notre fonction à la place de NtfsFsdDirectoryControl. Notre fonction va vérifier si les arguments passés à la routine vérifient certaines propriétés, sachant qu’on désire modifier le résultat de la requête afin de supprimer dans la liste chainée un ou plusieurs fichiers, il nous faut donc s’intéresser aux requêtes qui vérifient :
- Le champ MinorFunction de l’IRP vaut IRP_MN_QUERY_DIRECTORY
- Le champ Irp->Parameters.QueryDirectory.FileInformationClass vaut une de ces valeurs :
FileDirectoryInformation
FileFullDirectoryInformation
FileBothDirectoryInformation
FileNamesInformation
Si ces conditions ne sont pas vérifiées alors on return en appelant la fonction originale NtfsFsdDirectoryControl pour compléter la requête.
Dans la cas ou les conditions sont OK, on appel toujours la fonction NtfsFsdDirectoryControl puis on parcourt la liste chainée en modifiant au besoins les résultats.
Enfin, il reste aussi la routine NtfsFsdCreate, qui permet à partir d’un nom de fichier de d’obtenir un handle. Ainsi il est possible d’ouvrir un fichier en même si on ne voit pas « directement ». Il faut donc tenir compte de ce problème en posant un hook sur cette routine et en vérifiant le nom du fichier. Si jamais il correspond à ceux qu’on veut cacher alors on renvoie un STATUS_OBJECT_NAME_INVALID.
Dans mon code, tout les fichiers et dossiers commençant par un ‘_’ sont cachés. De plus si l’utiliser essaye d’ouvrir un handle sur le fichier C:\_bouh.txt la routine NtfsFsdCreate renverra STATUS_OBJECT_NAME_INVALID.
Voici de quoi vous amusez
http://ivanlef0u.fr/repo/IRP.rar
Enjoy !
En cas de soucis n’hesitez pas à me poser des questions.
Références :
http://www.rootkit.com/newsread_print.php?newsid=690
http://www.rootkit.com/newsread.php?newsid=647
http://msdn2.microsoft.com/en-us/library/ms795825.aspx
http://msdn2.microsoft.com/en-us/library/ms795806.aspx
http://www.antirootkit.com/articles/Nailuj-Rootkit-Analysis/index.htm
http://ivanlef0u.fr/repo/windoz/hidingfr.txt
11 comments avril 26th, 2007