(3)Windows系统总述

一、说明

这是《Windows内核原理与实现》的第二章,主要是对一些概念进行科普,其中关于注册表和系统引导部分,我看着像在看天书,也没有写什么笔记。

这篇博客主要是记录一些我大概能看懂的,以便日后参考。其实也只是把书上的话自己总结了一下,实在没什么特别的,写博客已经是一种习惯了。

二、用户空间和内核空间

低2G是用户空间,高2G是内核空间,他们之间有一块特殊的内存。

0x7fff0000~0x7fffffff 这块特殊的64KB的地址空间,用户模式和内核模式都不能访问。

ProbeForWrite 函数用于检查用户代码是否访问了高2G内存或传递一个无效的指针,它常用于检测3环调用系统函数时的参数有效性检查。
只有当调用系统服务的是3环代码,才需要检查参数有效性,如果是另一个内核函数调用的,就无须检查。

PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
    try {
        ProbeForWrite(InputInformation,
            InputInformationLength,
            sizeof(ULONG));
        if (ARGUMENT_PRESENT(ReturnLength)) {
        	ProbeForWriteUlong(ReturnLength);
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
    	return GetExceptionCode();
    }
}

首先检查目标内存地址是否越过了 0x7fff0000~0x7fffffff,如果是,就抛出非法访问内存异常;
然后,把目标内存的值写入到目标内存,如果不可写,就会触发异常,从而被 except 捕获。

ProbeForWrite 源码:

VOID
ProbeForWrite (
    __inout_bcount(Length) PVOID Address,
    __in SIZE_T Length,
    __in ULONG Alignment
    )

/*++

Routine Description:

    This function probes a structure for write accessibility and ensures
    correct alignment of the structure. If the structure is not accessible
    or has incorrect alignment, then an exception is raised.

Arguments:

    Address - Supplies a pointer to the structure to be probed.

    Length - Supplies the length of the structure.

    Alignment - Supplies the required alignment of the structure expressed
        as the number of bytes in the primitive datatype (e.g., 1 for char,
        2 for short, 4 for long, and 8 for quad).

Return Value:

    None.

--*/

{

    ULONG_PTR EndAddress;
    ULONG_PTR StartAddress;

#define PageSize PAGE_SIZE

    //
    // If the structure has zero length, then do not probe the structure for
    // write accessibility or alignment.
    //

    if (Length != 0) {

        //
        // If the structure is not properly aligned, then raise a data
        // misalignment exception.
        //

        ASSERT((Alignment == 1) || (Alignment == 2) ||
               (Alignment == 4) || (Alignment == 8) ||
               (Alignment == 16));

        StartAddress = (ULONG_PTR)Address;
        if ((StartAddress & (Alignment - 1)) == 0) {

            //
            // Compute the ending address of the structure and probe for
            // write accessibility.
            //

            EndAddress = StartAddress + Length - 1;

            
            //DbgPrint("Hbg: MM_USER_PROBE_ADDRESS = %x\n", MM_USER_PROBE_ADDRESS);
            // MM_USER_PROBE_ADDRESS = 7fff0000
            // 如果越过了 MM_USER_PROBE_ADDRESS ,就抛出非法访问内存异常

            if ((StartAddress <= EndAddress) &&
                (EndAddress < MM_USER_PROBE_ADDRESS)) {

                //
                // N.B. Only the contents of the buffer may be probed.
                //      Therefore the starting byte is probed for the
                //      first page, and then the first byte in the page
                //      for each succeeding page.
                //
                // If this is a Wow64 process, then the native page is 4K, which
                // could be smaller than the native page size/
                //

                EndAddress = (EndAddress & ~(PageSize - 1)) + PageSize;
                do {
                    // 目标内存的值写入到目标内存,volatile 用于关闭编译器优化,确保写入发生
                    *(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;
                    // 一次检查一个字节,一个字节能写入,说明整个页都能写入
                    StartAddress = (StartAddress & ~(PageSize - 1)) + PageSize;
                } while (StartAddress != EndAddress);

                return;

            } else {
                // 非法访问内存
                ExRaiseAccessViolation();
            }

        } else {
            // 内存对齐异常
            ExRaiseDatatypeMisalignment();
        }
    }

    return;
}

三、系统线程,PsCreateSystemThread

驱动程序可以调用 PsCreateSystemThread 来创建系统线程,默认属于 System 进程,也可以指定所属进程从而能访问该进程的内存。

四、中断和异常

中断和异常都会打断正常指令流,中断是CPU产生的,异常的产生则是指令执行后的结果。所以中断是异步的,异常是同步的。

五、内核对象,名称系统

对象头

windows 提供了多种类型的内核对象,设计遵循面向对象思想,每个对象都由对象头和对象体构成。

_OBJECT_HEADER 对象头的定义在 base\ntos\inc\ob.h ,存储了对象管理所需的基本信息,如引用计数,句柄计数,对象名,对象类型等。

typedef struct _OBJECT_HEADER {
    LONG_PTR PointerCount;                      // 引用计数
    union {
        LONG_PTR HandleCount;                   // 指向该对象的句柄数
        PVOID NextToFree;                       // 对象呗延迟删除时加入到一条链中
    };
    POBJECT_TYPE Type;                          // 指向对象的类型对象
    UCHAR NameInfoOffset;                       // 名称信息的内存偏移
    UCHAR HandleInfoOffset;                     // 句柄信息的内存偏移
    UCHAR QuotaInfoOffset;                      // 配额信息的内存偏移
    UCHAR Flags;

    union {
        POBJECT_CREATE_INFORMATION ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };

    PSECURITY_DESCRIPTOR SecurityDescriptor;    // 安全描述符
    QUAD Body;                                  // 对象体开始
} OBJECT_HEADER, *POBJECT_HEADER;

其中,对象类型 POBJECT_TYPE Type 是一个指针,指向的类型就叫 OBJECT_TYPE ,它同样定义在 ob.h ,OBJECT_TYPE 存储了对象类型的信息,包括在全局对象类型数组中的索引,拷贝自对象头的对象名等。

对象类型

#define OBJECT_LOCK_COUNT 4

typedef struct _OBJECT_TYPE {
    ERESOURCE Mutex;
    LIST_ENTRY TypeList;
    UNICODE_STRING Name;            // Copy from object header for convenience
    PVOID DefaultObject;
    ULONG Index;                    // 此类型对象在全局数组中的索引
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    OBJECT_TYPE_INITIALIZER TypeInfo;
#ifdef POOL_TAGGING
    ULONG Key;
#endif //POOL_TAGGING
    ERESOURCE ObjectLocks[ OBJECT_LOCK_COUNT ];
} OBJECT_TYPE, *POBJECT_TYPE;

(3)Windows系统总述

WRK提供了31种对象类型,例如我们在中级班接触过的 PsThreadType, PsProcessType ,IoDriverObjectType 等。这些对象类型是系统初始化时调用 ObCreateObjectType 函数创建的。

ObCreateObjectType 函数原型如下:

NTSTATUS
ObCreateObjectType (
    __in PUNICODE_STRING TypeName,
    __in POBJECT_TYPE_INITIALIZER ObjectTypeInitializer,
    __in_opt PSECURITY_DESCRIPTOR SecurityDescriptor,
    __out POBJECT_TYPE *ObjectType
);

第二个参数用到了 OBJECT_TYPE_INITIALIZER 类型,它除了指定了此种类型对象的一些数据特性,还指定了该类型对象的一些基本操作方法,如 Dump, Open, Close 等。

typedef struct _OBJECT_TYPE_INITIALIZER {
    USHORT Length;
    BOOLEAN UseDefaultObject;
    BOOLEAN CaseInsensitive;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccessMask;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    BOOLEAN MaintainTypeList;
    POOL_TYPE PoolType;
    ULONG DefaultPagedPoolCharge;
    ULONG DefaultNonPagedPoolCharge;
    OB_DUMP_METHOD DumpProcedure;
    OB_OPEN_METHOD OpenProcedure;
    OB_CLOSE_METHOD CloseProcedure;
    OB_DELETE_METHOD DeleteProcedure;
    OB_PARSE_METHOD ParseProcedure;
    OB_SECURITY_METHOD SecurityProcedure;
    OB_QUERYNAME_METHOD QueryNameProcedure;
    OB_OKAYTOCLOSE_METHOD OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;

对象类型数组 ObpObjectTypes

系统使用static全局数组 ObpObjectTypes 记录所有已创建的类型,限定不超过48种对象类型,OBJECT_TYPE 中的 Index 就是对象类型在此数组中的下标。

#define OBP_MAX_DEFINED_OBJECT_TYPES 48
POBJECT_TYPE ObpObjectTypes[ OBP_MAX_DEFINED_OBJECT_TYPES ];

创建对象

类型创建后,就可以调用 ObCreateObject 函数创建对象了。

NTSTATUS
ObCreateObject (
    __in KPROCESSOR_MODE ProbeMode,
    __in POBJECT_TYPE ObjectType,
    __in POBJECT_ATTRIBUTES ObjectAttributes,
    __in KPROCESSOR_MODE OwnershipMode,
    __inout_opt PVOID ParseContext,
    __in ULONG ObjectBodySize,
    __in ULONG PagedPoolCharge,
    __in ULONG NonPagedPoolCharge,
    __out PVOID *Object
);

其中,ObjectBodySize 指定了对象体的大小,输出参数 Object 指向对象体的地址。

名称系统

windows提供了一套以名称来管理对象的机制,这对于跨模块,跨进程共享对象提供了便利。windows维护了一套全局的名称查询机制,ObpDirectoryObjectType 类型对象是实现这一机制的关键。下面来简单分析一下这套名称查询机制。

全局变量 ObpRootDirectoryObject 是根目录对象:

#define NUMBER_HASH_BUCKETS 37
#define OBJ_INVALID_SESSION_ID 0xFFFFFFFF

typedef struct _OBJECT_DIRECTORY {
    struct _OBJECT_DIRECTORY_ENTRY *HashBuckets[ NUMBER_HASH_BUCKETS ];
    EX_PUSH_LOCK Lock;
    struct _DEVICE_MAP *DeviceMap;
    ULONG SessionId;
} OBJECT_DIRECTORY, *POBJECT_DIRECTORY;

...

POBJECT_DIRECTORY ObpRootDirectoryObject;

根目录之下,系统内置了一些子目录,通过分析 NtCreateDirectoryObject 函数被调用的情况,我们可以看到 Callback、ArcName、Device、Driver、FileSystem、KernelObjects、ObjectTypes、GLOBAL?? 和 Security
子目录的创建过程。例如,阅读 IopCreateRootDirectories 函数,发现这里创建了 Driver 子目录:

//
// Create the root directory object for the \Driver directory.
//

RtlInitUnicodeString( &nameString, L"\\Driver" );
InitializeObjectAttributes( &objectAttributes,
                           &nameString,
                           OBJ_PERMANENT|OBJ_KERNEL_HANDLE,
                           (HANDLE) NULL,
                           (PSECURITY_DESCRIPTOR) NULL );

status = NtCreateDirectoryObject( &handle,
                                 DIRECTORY_ALL_ACCESS,
                                 &objectAttributes );
if (!NT_SUCCESS( status )) {
    return FALSE;
} else {
    (VOID) ObCloseHandle( handle , KernelMode);
}

对象管理器内部有一套函数用于插入、查询和删除目录项或目录。对外提供了 ObOpenObjectByName 和ObReferenceObjectByName 函数用来根据名称打开或引用对象。这些函数我暂时不打算分析,猜测其内部应该是哈希表实现的。

对象体

分析 ObpAllocateObject 函数发现,对象体和对象头在同一块连续内存:

ObjectHeader = ExAllocatePoolWithTag( PoolType,
                                     HeaderSize + ObjectBodySize,
                                     (ObjectType == NULL ? 'TjbO' : ObjectType->Key) |
                                     PROTECTED_POOL );

通过对象头获取对象体的方法是这样的:

#define OBJECT_TO_OBJECT_HEADER( o ) \
	CONTAINING_RECORD( (o), OBJECT_HEADER, Body )

内核对象生命周期,内核计数

内核对象通过引用计数管理生命周期,当引用计数为0,对象就被销毁。引用计数来源于两方面,一个是内核的指针引用该对象,增加引用计数和减少引用计数的函数是 ObReferenceObjectByPointer
和 ObDereferenceObject;另一个是进程打开内核对象并获得一个句柄,增加减少句柄的函数是 ObpIncrementHandleCount 和ObpDecrementHandleCount 。

上一篇:C#几行代码让windows蓝屏


下一篇:C/C++ 实现文件透明加解密