Windows文件对象

Windows I/O系统提供给应用程序的I/O操作的目标对象是文件对象(File Object)。文件对象代表了设备对象的已打开实例,也就是说,内核或应用程序每打开(open)一个设备对象,就将得到一个文件对象。文件对象也是一个内核对象,在用户模式下可以通过句柄来引用文件对象。文件对象表达的是文件的已打开实例,而并非文件本身,所以它并不承担设备对象的数据存储和状态变迁的能力。真正的文件数据位于设备对象而非文件对象中。

文件对象也是对象管理器中的对象,其类型为IoFileObjectType。下面先看一下Windows中对文件对象的定义:

typedefstruct_FILE_OBJECT {

    CSHORTType;

    CSHORTSize;

    PDEVICE_OBJECTDeviceObject;                     // 指向文件所在的设备对象

    PVPBVpb;                                        // 指向文件对象所在卷的卷参数块(VPB

    PVOIDFsContext;                                 // 指向驱动程序为该文件对象维护的状态信息

    PVOIDFsContext2;                                // 指向驱动程序为该文件对象维护的额外状态信息

    PSECTION_OBJECT_POINTERSSectionObjectPointer;   // 文件对象的内存区对象指针

    PVOIDPrivateCacheMap;                           // 文件对象的私有缓存表

    NTSTATUSFinalStatus;                            // 文件对象I/O请求的最终状态

    struct_FILE_OBJECT *RelatedFileObject;          // 相关的文件对象

    BOOLEANLockOperation;                           // 是否已在文件对象上执行了锁(Lock)操作

    BOOLEANDeletePending;                           // 正在执行一个删除与文件对象关联的文件的操作

    BOOLEANReadAccess;                              // 以读访问方式打开该文件

    BOOLEANWriteAccess;                             // 以写访问方式打开该文件

    BOOLEANDeleteAccess;                            // 以删除访问方式打开该文件

    BOOLEANSharedRead;                              // 以读共享访问方式打开该文件

    BOOLEANSharedWrite;                             // 以写共享访问方式打开该文件

    BOOLEANSharedDelete;                            // 以删除共享访问方式打开该文件

    ULONGFlags;                                     // 标志,以FO_作为前缀定义的一组常量,可以组合

    UNICODE_STRINGFileName;                         // 文件名,仅在IRP_MJ_CREATE请求中有效

    LARGE_INTEGERCurrentByteOffset;                 // 文件中的当前偏移位置,以字节为单位

    ULONGWaiters;                                   // 有多少个线程在等待该文件对象,以进行同步访问

    ULONGBusy;                                      // 当前是否有线程在以同步方式访问该文件对象

    PVOIDLastLock;                                  // 指向上一个应用在该文件对象上的字节范围锁

    KEVENTLock;                                     // 文件对象锁,用于同步访问该文件对象

    KEVENTEvent;                                    // 文件对象锁,用于I/O请求的完成通知

    PIO_COMPLETION_CONTEXTCompletionContext;        // 指向与文件对象关联的完成端口信息

} FILE_OBJECT;

FILE_OBJECT包含了指向设备对象的指针、由驱动程序为文件对象维护的状态环境、当前位置信息、访问方式和文件对象标志,以及当多个线程访问一个文件对象时所需要的各种锁。

下图(引用自Microsoft.Press.Windows.Internals)演示了当一个文件被打开时发生的情形:

一个C程序调用运行时库函数fopen,该函数依次调用Windows的CreateFile函数。然后调用Ntdll.dll中的原生函数,在WINDBG工具可以看出,Ntdll.dll中的ZwCreateFile和NtCreateFile都有相同的函数主体,也就是说,它们是同一个函数。

0:001> u ntdll!ZwCreateFile

ntdll!ZwCreateFile:

7c92d090 b825000000      mov    eax,25h

7c92d095 ba0003fe7f      mov    edx,offset SharedUserData!SystemCallStub (7ffe0300)

7c92d09a ff12            call   dword ptr [edx]

7c92d09c c22c00          ret     2Ch

7c92d09f 90              nop

 

0:001> u ntdll!NtCreateFile

ntdll!ZwCreateFile:

7c92d090 b825000000      mov    eax,25h

7c92d095 ba0003fe7f      mov    edx,offset SharedUserData!SystemCallStub (7ffe0300)

7c92d09a ff12            call    dword ptr [edx]

7c92d09c c22c00          ret     2Ch

7c92d09f 90              nop

其中,25h是系统服务号(也就是SSDT中的索引号),地址7ffe0300处存放的是函数的地址,使用命令dd 7ffe0300可以得知,函数的地址是7c92e4f0,

0:001> dd 7ffe0300

7ffe0300 7c92e4f0 7c92e4f4 00000000 00000000

使用命令u 7c92e4f0可以看出,这个函数就是函数KiFastSystemCall

0:001> u 7c92e4f0

ntdll!KiFastSystemCall:

7c92e4f0 8bd4            mov     edx,esp

7c92e4f2 0f34            sysenter

至此,执行线程切换到内核模式中,并进入系统服务分发。系统服务分发根据系统服务号,定位SDT(Service Descriptor Table,服务描述符表)中的对应的系统服务项。其中25h(十进制37)对应的系统服务是ntkrnlpa模块中的函数NtCreateFile。

以上就是用户层利用ntdll!ZwCreateFile来实现对系统服务的调用。在ntkrnalpa模块中也有一个ZwCreateFile函数,下面是该函数的声明:

NTSTATUS

NTAPI

ZwCreateFile (

    __outPHANDLEFileHandle,                     // 指向句柄变量的指针。如果这个函数调用返回成成功(STATUS_SUCCESS),句柄变量接收文件句柄

    __inACCESS_MASKDesiredAccess,               // 指定一个access_mask值确定所请求的访问对象。除了为所有类型的对象定义的访问权限外,调用者还可以指定下列任何访问权限,这些权限都是针对文件的

    __inPOBJECT_ATTRIBUTESObjectAttributes,     // 指向OBJECT_ATTRIBUTES结构的指针

    __outPIO_STATUS_BLOCKIoStatusBlock,         // IoStatusBlock也是一个结构。这个结构在内核开发中经常使用。它往往用于表示一个操作的结果

    __in_optPLARGE_INTEGERAllocationSize,      // 这个参数很少使用,请设置为NULL

    __inULONGFileAttributes,                    // 这个参数控制新建立的文件的属性。一般的说,设置为FILE_ATTRIBUTE_NORMAL即可

    __inULONGShareAccess,                       // 共享访问。一共有三种共享标记可以设置:FILE_SHARE_READ、FILE_SHARE_WRITE、FILE_SHARE_DELETE。

    __inULONGCreateDisposition,                 // 指定当文件确实或不存在时所要执行的动作

    __inULONGCreateOptions,                     // 指定当驱动程序创建或打开文件时应用的选项

    __in_bcount_opt(EaLength) PVOIDEaBuffer,     // 对于设备和中间驱动程序,此参数必须是空指针

    __inULONGEaLength                           // 对于设备和中间驱动程序,此参数必须为零

    );

在Windbg中看看这个函数的代码:

lkd> u nt!ZwCreateFile

nt!ZwCreateFile:

80501010 b825000000      mov    eax,25h

80501015 8d542404        lea    edx,[esp+4]

80501019 9c              pushfd

8050101a 6a08            push    8

8050101c e830140400      call   nt!KiSystemService (80542451)

80501021 c22c00          ret     2Ch

可以看出,它也是通过系统服务号来实现对系统服务的调用。在内核编程中,经常使用这个函数来完成对文件的操作。

综上所述,不管是ntdll!ZwCreateFile还是nt!ZwCreateFile都是通过调用系统服务的方式来实现的,也就是最后都会调用内核函数NtCreateFile。下面是函数NtCreateFile的实现:

NTSTATUS

NtCreateFile (

    __outPHANDLEFileHandle,

    __inACCESS_MASKDesiredAccess,

    __inPOBJECT_ATTRIBUTESObjectAttributes,

    __outPIO_STATUS_BLOCKIoStatusBlock,

    __in_optPLARGE_INTEGERAllocationSize,

    __inULONGFileAttributes,

    __inULONGShareAccess,

    __inULONGCreateDisposition,

    __inULONGCreateOptions,

    __in_bcount_opt(EaLength) PVOIDEaBuffer,

    __inULONGEaLength

    )

{

    PAGED_CODE();

 

    returnIoCreateFile( FileHandle,

                         DesiredAccess,

                         ObjectAttributes,

                         IoStatusBlock,

                         AllocationSize,

                         FileAttributes,

                         ShareAccess,

                         CreateDisposition,

                         CreateOptions,

                         EaBuffer,

                         EaLength,

                         CreateFileTypeNone,

                         (PVOID)NULL,

                         0 );

}

函数NtCreateFile的调用参数与函数ZwCreateFile完全一样。基本上什么都没干,然后又原封不动地调用了函数IoCreateFile,下面是函数IoCreateFile的定义:

NTSTATUS

IoCreateFile(

    OUTPHANDLEFileHandle,

    INACCESS_MASKDesiredAccess,

    INPOBJECT_ATTRIBUTESObjectAttributes,

    OUTPIO_STATUS_BLOCKIoStatusBlock,

    INPLARGE_INTEGERAllocationSizeOPTIONAL,

    INULONGFileAttributes,

    INULONGShareAccess,

    INULONGDisposition,

    INULONGCreateOptions,

    INPVOIDEaBufferOPTIONAL,

    INULONGEaLength,

    INCREATE_FILE_TYPECreateFileType,

    INPVOIDExtraCreateParametersOPTIONAL,

    INULONGOptions

)

注:除了NtCreateFile,NtCreateNamedPipeFile和NtCreateMailslotFile也是调用IoCreateFile函数来创建命名管道和邮件槽对象。

IoCreateFile函数又进一步调用IopCreateFile函数,下面是函数IopCreateFile的定义:

NTSTATUS

IopCreateFile(

    OUTPHANDLEFileHandle,

    INACCESS_MASKDesiredAccess,

    INPOBJECT_ATTRIBUTESObjectAttributes,

    OUTPIO_STATUS_BLOCKIoStatusBlock,

    INPLARGE_INTEGERAllocationSizeOPTIONAL,

    INULONGFileAttributes,

    INULONGShareAccess,

    INULONGDisposition,

    INULONGCreateOptions,

    INPVOIDEaBufferOPTIONAL,

    INULONGEaLength,

    INCREATE_FILE_TYPECreateFileType,

    INPVOIDExtraCreateParametersOPTIONAL,

    INULONGOptions,

    INULONGInternalFlags,

    INPVOIDDeviceObject

    )

IopCreateFile函数的执行主要有以下几个部分

1、获取之前的操作模式

    requestorMode = KeGetPreviousMode();

 

    if (Options & IO_NO_PARAMETER_CHECKING) {

        requestorMode = KernelMode;

    }

2、为OpenPacket分配内存。

    openPacket = IopAllocateOpenPacket();

    if (openPacket == NULL) {

        returnSTATUS_INSUFFICIENT_RESOURCES;

    }

3、针对不同的模式进行不同的参数和标志的检查。

4、填充OpenPacket (OP)

    openPacket->Type = IO_TYPE_OPEN_PACKET;

    openPacket->Size = sizeof( OPEN_PACKET );

    openPacket->ParseCheck = 0L;

    openPacket->AllocationSize = initialAllocationSize;

    openPacket->CreateOptions = CreateOptions;

    openPacket->FileAttributes = (USHORT) FileAttributes;

    openPacket->ShareAccess = (USHORT) ShareAccess;

    openPacket->Disposition = Disposition;

    openPacket->Override = FALSE;

    openPacket->QueryOnly = FALSE;

    openPacket->DeleteOnly = FALSE;

    openPacket->Options = Options;

    openPacket->RelatedFileObject = (PFILE_OBJECT) NULL;

    openPacket->CreateFileType = CreateFileType;

    openPacket->ExtraCreateParameters = ExtraCreateParameters;

    openPacket->TraversedMountPoint = FALSE;

    openPacket->InternalFlags = InternalFlags;

    openPacket->TopDeviceObjectHint = DeviceObject;

    openPacket->FinalStatus = STATUS_SUCCESS;

    openPacket->FileObject = (PFILE_OBJECT) NULL;

5、调用ObOpenObjectByName函数来创建文件对象,它是对象管理器的函数,下面是该函数的声明:

NTSTATUS

ObOpenObjectByName (

    __inPOBJECT_ATTRIBUTESObjectAttributes,

    __in_optPOBJECT_TYPEObjectType,

    __inKPROCESSOR_MODEAccessMode,

    __inout_optPACCESS_STATEAccessState,

    __in_optACCESS_MASKDesiredAccess,

    __inout_optPVOIDParseContext,

    __outPHANDLEHandle

    )

ObOpenObjectByName函数执行的结果是一个指向所创建对象的句柄,只要ObOpenObjectByName返回成功,IopCreateFile函数即成功返回。

ObOpenObjectByName是对象管理器的函数,它实际又是利用对象管理器的另一个函数ObpLookupObjectName来打开一个对象,ObpLookupObjectName从指定的根目录或者系统全局根目录开始,调用ObpLookupDirectoryEntry函数,一层层地进入子目录,直至解析完成,或者碰到实现了Parse方法的对象,从而把余下的路径名称交给该对象进一步解析。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值