Examining the Windows NT Filesystem by Mark Russinovich and Bryce Cogswell Listing One typedef struct _FILE_OBJECT { CSHORT Type; CSHORT Size; PDEVICE_OBJECT DeviceObject; PVPB Vpb; PVOID FsContext; PVOID FsContext2; PSECTION_OBJECT_POINTERS SectionObjectPointer; PVOID PrivateCacheMap; NTSTATUS FinalStatus; struct _FILE_OBJECT *RelatedFileObject; BOOLEAN LockOperation; BOOLEAN DeletePending; BOOLEAN ReadAccess; BOOLEAN WriteAccess; BOOLEAN DeleteAccess; BOOLEAN SharedRead; BOOLEAN SharedWrite; BOOLEAN SharedDelete; ULONG Flags; UNICODE_STRING FileName; LARGE_INTEGER CurrentByteOffset; ULONG Waiters; ULONG Busy; PVOID LastLock; KEVENT Lock; KEVENT Event; PIO_COMPLETION_CONTEXT CompletionContext; } FILE_OBJECT; Listing Two typedef struct _IRP { CSHORT Type; USHORT Size; // Define the common fields used to control the IRP. // Define a pointer to the Memory Descriptor List (MDL) for this I/O // request. This field is only used if the I/O is "direct I/O". PMDL MdlAddress; // Flags word - used to remember various flags. ULONG Flags; // The following union is used for one of three purposes: // 1. An associated IRP. Field is a pointer to a master IRP. // 2. The master IRP. Field is the count of the number of IRPs which must // complete (associated IRPs) before the master can complete. // 3. This operation is being buffered and the field is the address of // the system space buffer. union { struct _IRP *MasterIrp; LONG IrpCount; PVOID SystemBuffer; } AssociatedIrp; // Thread list entry - allows queueing the IRP to the thread pending I/O // request packet list. LIST_ENTRY ThreadListEntry; // I/O status - final status of operation. IO_STATUS_BLOCK IoStatus; // Requestor mode - mode of the original requestor of this operation. KPROCESSOR_MODE RequestorMode; // Pending returned - TRUE if pending was initially returned as the // status for this packet. BOOLEAN PendingReturned; // Stack state information. CHAR StackCount; CHAR CurrentLocation; // Cancel - packet has been canceled. BOOLEAN Cancel; // Cancel Irql - Irql at which the cancel spinlock was acquired. KIRQL CancelIrql; // ApcEnvironment - Used to save the APC environment at the time that the // packet was initialized. CCHAR ApcEnvironment; // Zoned - packet was allocated from a zone. BOOLEAN Zoned; // User parameters. PIO_STATUS_BLOCK UserIosb; PKEVENT UserEvent; union { struct { PIO_APC_ROUTINE UserApcRoutine; PVOID UserApcContext; } AsynchronousParameters; LARGE_INTEGER AllocationSize; } Overlay; // CancelRoutine - Used to contain the address of a cancel routine supplied // by a device driver when the IRP is in a cancelable state. PDRIVER_CANCEL CancelRoutine; // Note that the UserBuffer parameter is outside of the stack so that I/O // completion can copy data back into the user's address space without // having to know exactly which service was being invoked. The length // of the copy is stored in the second half of the I/O status block. If // the UserBuffer field is NULL, then no copy is performed. PVOID UserBuffer; // Kernel structures // The following section contains kernel structures which the IRP needs // in order to place various work information in kernel controller system // queues. Because the size and alignment cannot be controlled, they are // placed here at the end so they just hang off and do not affect the // alignment of other fields in the IRP. union { struct { // DeviceQueueEntry - The device queue entry field is used to queue // the IRP to the device driver device queue. KDEVICE_QUEUE_ENTRY DeviceQueueEntry; // Thread - pointer to caller's Thread Control Block. PETHREAD Thread; // Auxillary buffer - pointer to any auxillary buffer that is // required to pass information to a driver that is not contained // in a normal buffer. PCHAR AuxiliaryBuffer; // List entry - queues packet to completion queue, among others. LIST_ENTRY ListEntry; // Current stack location - contains a pointer to the current // IO_STACK_LOCATION structure in the IRP stack. This field // should never be directly accessed by drivers. They should // use the standard functions. struct _IO_STACK_LOCATION *CurrentStackLocation; // Original file object - pointer to the original file object // that was used to open the file. This field is owned by the // I/O system and should not be used by any other drivers. PFILE_OBJECT OriginalFileObject; } Overlay; // APC - This APC control block is used for the special kernel APC as // well as for the caller's APC, if one was specified in the original // argument list. If so, then the APC is reused for the normal APC for // whatever mode the caller was in and the "special" routine that is // invoked before the APC gets control simply deallocates the IRP. KAPC Apc; // CompletionKey - This is the key that is used to distinguish // individual I/O operations initiated on a single file handle. // ULONG CompletionKey; } Tail; } IRP, *PIRP; BOOLEAN HookDrive( IN char Drive, IN PDRIVER_OBJECT DriverObject ) { IO_STATUS_BLOCK ioStatus; HANDLE ntFileHandle; OBJECT_ATTRIBUTES objectAttributes; PDEVICE_OBJECT fileSysDevice; PDEVICE_OBJECT hookDevice; UNICODE_STRING fileNameUnicodeString; WCHAR filename[] = L"\\DosDevices\\A:\\"; NTSTATUS ntStatus; ULONG i; PFILE_OBJECT fileObject; PHOOK_EXTENSION hookExtension; if ( Drive >= 'a' && Drive <= 'z' ) Drive -= 'a'; else Drive -= 'A'; if ( (unsigned char)Drive >= 26 ) return FALSE; if ( LDriveDevices[Drive] == NULL ) { // point to the current logical drive filename[12] = 'A'+Drive; // what device to hook? - first open the root directory RtlInitUnicodeString( &fileNameUnicodeString, filename ); InitializeObjectAttributes( &objectAttributes, &fileNameUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwCreateFile( &ntFileHandle, SYNCHRONIZE|FILE_READ_ACCESS, &objectAttributes, &ioStatus, NULL, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT| FILE_OPEN_FOR_BACKUP_INTENT|FILE_DIRECTORY_FILE, NULL, 0 ); if( !NT_SUCCESS( ntStatus ) ) return FALSE; // got the file handle, so now look-up the file-object ntStatus = ObReferenceObjectByHandle( ntFileHandle, FILE_READ_DATA, NULL, KernelMode,&fileObject, NULL ); if( !NT_SUCCESS( ntStatus )) return FALSE; // now, find out what device is associated with the object fileSysDevice = IoGetRelatedDeviceObject( fileObject ); if ( ! fileSysDevice ) { ZwClose( ntFileHandle ); return FALSE; } // make sure we haven't already attached to this device // (which can happen for directory mounted drives for networks) for( i = 0; i < 26; i++ ) { if( LDriveDevices[i] == fileSysDevice ) { // yep, already watching it so add it to a group ObDereferenceObject( fileObject ); ZwClose( ntFileHandle ); LDriveMap[ Drive ] = LDriveMap[i]; LDriveDevices[ Drive ] = fileSysDevice; return TRUE; } } // create a device to attach to the chain ntStatus = IoCreateDevice( DriverObject, sizeof(HOOK_EXTENSION), NULL, fileSysDevice->DeviceType, 0, FALSE, &hookDevice ); if ( !NT_SUCCESS(ntStatus) ) return FALSE; // clear our init flag as per NT DDK KB article on creating // device objects from a dispatch routine hookDevice->Flagsn &= ~DO_DEVICE_INITIALIZING; // set-up the device extensions hookExtension = hookDevice->DeviceExtension; hookExtension->LogicalDrive = 'A'+Drive; hookExtension->FileSystem = fileSysDevice; // now, attach ourselves to the device ntStatus = IoAttachDeviceByPointer( hookDevice, fileSysDevice ); if ( !NT_SUCCESS(ntStatus) ) { // if we couldn't attach ObDereferenceObject( fileObject ); ZwClose( ntFileHandle ); return FALSE; } else { // assign this drive a drive group if it doesn't have one if( !LDriveMap[ Drive ] ) LDriveMap[ Drive ] = ++LDriveGroup; } // close the file and update our list ObDereferenceObject( fileObject ); ZwClose( ntFileHandle ); LDriveDevices[Drive] = hookDevice; } return TRUE; } Listing Three BOOLEAN HookDrive( IN char Drive, IN PDRIVER_OBJECT DriverObject ) { IO_STATUS_BLOCK ioStatus; HANDLE ntFileHandle; OBJECT_ATTRIBUTES objectAttributes; PDEVICE_OBJECT fileSysDevice; PDEVICE_OBJECT hookDevice; UNICODE_STRING fileNameUnicodeString; WCHAR filename[] = L"\\DosDevices\\A:\\"; NTSTATUS ntStatus; ULONG i; PFILE_OBJECT fileObject; PHOOK_EXTENSION hookExtension; if ( Drive >= 'a' && Drive <= 'z' ) Drive -= 'a'; else Drive -= 'A'; if ( (unsigned char)Drive >= 26 ) return FALSE; if ( LDriveDevices[Drive] == NULL ) { // point to the current logical drive filename[12] = 'A'+Drive; // have to figure out what device to hook - first open the root directory RtlInitUnicodeString( &fileNameUnicodeString, filename ); InitializeObjectAttributes( &objectAttributes, &fileNameUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwCreateFile( &ntFileHandle, SYNCHRONIZE|FILE_READ_ACCESS, &objectAttributes, &ioStatus, NULL, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT| FILE_OPEN_FOR_BACKUP_INTENT|FILE_DIRECTORY_FILE, NULL, 0 ); if( !NT_SUCCESS( ntStatus ) ) return FALSE; // got the file handle, so now look-up the file-object ntStatus = ObReferenceObjectByHandle( ntFileHandle, FILE_READ_DATA, NULL, KernelMode,&fileObject, NULL ); if( !NT_SUCCESS( ntStatus )) return FALSE; // now, find out what device is associated with the object fileSysDevice = IoGetRelatedDeviceObject( fileObject ); if ( ! fileSysDevice ) { ZwClose( ntFileHandle ); return FALSE; } // make sure we haven't already attached to this device // (which can happen for directory mounted drives for networks) for( i = 0; i < 26; i++ ) { if( LDriveDevices[i] == fileSysDevice ) { // yep, already watching it so add it to a group ObDereferenceObject( fileObject ); ZwClose( ntFileHandle ); LDriveMap[ Drive ] = LDriveMap[i]; LDriveDevices[ Drive ] = fileSysDevice; return TRUE; } } // create a device to attach to the chain ntStatus = IoCreateDevice( DriverObject, sizeof(HOOK_EXTENSION), NULL, fileSysDevice->DeviceType, 0, FALSE, &hookDevice ); // did we create a device successfully? if ( !NT_SUCCESS(ntStatus) ) return FALSE; // clear our init flag as per NT DDK KB article on creating // device objects from a dispatch routine hookDevice->Flagsn &= ~DO_DEVICE_INITIALIZING; // set-up the device extensions hookExtension = hookDevice->DeviceExtension; hookExtension->LogicalDrive = 'A'+Drive; hookExtension->FileSystem = fileSysDevice; // now, attach ourselves to the device ntStatus = IoAttachDeviceByPointer( hookDevice, fileSysDevice ); if ( !NT_SUCCESS(ntStatus) ) { // if we couldn't attach ObDereferenceObject( fileObject ); ZwClose( ntFileHandle ); return FALSE; } else { // assign this drive a drive group if it doesn't have one if( !LDriveMap[ Drive ] ) LDriveMap[ Drive ] = ++LDriveGroup; } // close the file and update our list ObDereferenceObject( fileObject ); ZwClose( ntFileHandle ); LDriveDevices[Drive] = hookDevice; } return TRUE; }