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;
}



 

 



