系列文章目录
文章目录
- 系列文章目录
- 4.2 对象类型
- OBJECT_TYPE_INITIALIZER
- ExpInitializeTimerImplementation()
- ObpInsertEntryDirectory()
- ObInit()
- IopCreateObjectTypes()
4.2 对象类型
对象是分类的,因而是有“类型(Type)”的,前面列举了许多常用的Windows 对象类型。但是要列举所有的对象类型则不可能,因为Windows对象类型的集合(对于内核)是开放的,内核函数可以向这个集合中加入新的对象类型。一旦增加了一种对象类型之后,用户程序就可以通过(借用文件对象的)系统调用创建或打开并操作属于此种类型的对象。那么用户程序是否可以增加新的(内核)对象类型呢?Windows并没有提供这样的系统调用,所以从用户空间是无法达到这个目标的。但是用户可以把内核模块即.sys模块动态安装到内核中,使其成为内核的一部分。这样,用户就实际上有了增加新对象类型的*。
Windows内核为新对象类型的定义提供了一个全局的OBJECT_TYPE_INITIALIZER 数据结构,作为需要填写和递交的“申请单”:
OBJECT_TYPE_INITIALIZER
//
// Object Type Initialize for ObCreateObjectType
//
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;
字段 Length 说明目标数据结构的长度。OpenProcedure、CloseProcedure、DeleteProcedure 等均为函数指针,都是为目标对象类型所定义的操作。其中ParseProcedure提供了解析路径名以找到目标对象的方法。一般对象的ParseProcedure 都是很简单的,因为都是在对象目录中寻找目标对象,但是文件对象的 ParseProcedure 是个特例,文件对象的寻找涉及文件系统,需要转入文件目录中寻找。
要定义即创建一种新的对象类型时,就填写好一个OBJECT_TYPE_INITIALIZER数据结构,然后调用(内核函数)ObCreateObiectTypeO。这个函数根据“申请单”创建起新对象类型的数据结构,然后将其挂入对象目录。对象类型的数据结构是OBJECT_TYPE,其内部有个成分TypeInfo也是OBJECT_TYPE_INITIALIZER数据结构,“申请单”的内容将被复制到OBJECT_TYPE数据结构内部的Typelnfo中。
下面通过一个实例来说明对象类型的创建,这个过程实际上涉及对象、对象类型以及对象目录这三个方面。这里用做实例的仍是Timer 即“定时器”这个对象类型的创建。
ExpInitializeTimerImplementation()
VOID
INIT_FUNCTION
NTAPI
ExpInitializeTimerImplementation(VOID)
{
OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
UNICODE_STRING Name;
/* Create the Timer Object Type */
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
RtlInitUnicodeString(&Name, L"Timer");//对象类型为Section,L表示Unicode
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(ETIMER);
ObjectTypeInitializer.GenericMapping = ExpTimerMapping;//用于访问控制
ObjectTypeInitializer.PoolType = NonPagedPool;//采用不可倒换页面池
ObjectTypeInitializer.ValidAccessMask = TIMER_ALL_ACCESS;
ObjectTypeInitializer.DeleteProcedure = ExpDeleteTimer;
//删除定时器需要调用的函数。
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ExTimerType);
/* Initialize the Wait List and Lock */
KeInitializeSpinLock(&ExpWakeListLock);
InitializeListHead(&ExpWakeList);
}
函数名前面的INIT_FUNCTION表示这个函数仅在初始化时用到,初始化之后就可以回收其所占的空间另作他用:NTAPI则表示这是由内核“导出”的函数,即可以在可安装模块(.sys模块)
中调用的函数。
程序很简单,填写好一个“申请单”,然后就调用ObCreateObjectTypeO。注意“申请单”中的字段 Poolype 设置成NonPagedPool,说明此类对象的数据结构需常驻(物理)内存,其所在页面不可以被换出到倒换文件中。另一个字段 DefaultNonPagedPoolCharge 则表示这种对象类型的数据结构所消耗的不可倒换虚存实际上就是物理内存,这里说是sizeOf(ETIMER),因为这种类型的数据结构就是 ETIMER。
调用 ObCreateObjectType()时的参数ExTimerType 是个全局的OBJECT_TYPE 结构指针,用来返回所创建的数据结构。这个数据结构成为“定时器”这个对象类型的定义,以后凡是要创建定时器对象即类型为ETIMER的对象时,就要以该指针作为参数之一,以便快速找到这个类型的定义。
但是对象类型的创建并不简单,我们来分段阅读ObCreateObjectTypeO的代码:
NTSTATUS
NTAPI
ObCreateObjectType(IN PUNICODE_STRING TypeName,
IN POBJECT_TYPE_INITIALIZER ObjectTypeInitializer,
IN PVOID Reserved,
OUT POBJECT_TYPE *ObjectType)
{
POBJECT_TYPE LocalObjectType;
....
/* Verify parameters */
if (!(TypeName) ||....)
....
/* Setup a lookup context */
ObpInitializeDirectoryLookup(&Context);
/* Check if we've already created the directory of types */
if (ObpTypeDirectoryObject)
{
/* Acquire the directory lock */
ObpAcquireDirectoryLockExclusive(ObpTypeDirectoryObject, &Context);
/* Do the lookup */
if (ObpLookupEntryDirectory(ObpTypeDirectoryObject,
TypeName,
OBJ_CASE_INSENSITIVE,
FALSE,
&Context))
{
/* We have already created it, so fail */
ObpCleanupDirectoryLookup(&Context);
return STATUS_OBJECT_NAME_COLLISION;
}
}
}
前面讲过,内核中有个对象目录,指针ObpRotDirectoryObject指向对象目录的根。其实内核中还有个“对象类型”目录,这是个单层的目录,除一个目录节点之外,其余的就都是叶节点了。此外,还有个OBJECT_DIRECTORY指针 ObpTypeDirectoryObject,指向对象类型目录的目录节点如果对象类型目录已经建立,就先通过ObpLookupEntryDirectory()在该目录中寻找,如果找到了同名的节点就说明同名的对象类型业已存在,所以失败返回
我们继续往下看。
/* Now make a copy of the object name */
ObjectName.Buffer = ExAllocatePoolWithTag(PagedPool,
TypeName->MaximumLength,
OB_NAME_TAG);
if (!ObjectName.Buffer)
{
/* Out of memory, fail */
ObpCleanupDirectoryLookup(&Context);
return STATUS_INSUFFICIENT_RESOURCES;
}
/* Set the length and copy the name */
ObjectName.MaximumLength = TypeName->MaximumLength;
RtlCopyUnicodeString(&ObjectName, TypeName);
/* Allocate the Object */
Status = ObpAllocateObject(NULL,
&ObjectName,
ObpTypeObjectType,
sizeof(OBJECT_TYPE),
KernelMode,
(POBJECT_HEADER*)&Header);
if (!NT_SUCCESS(Status))
{
/* Free the name and fail */
ObpCleanupDirectoryLookup(&Context);
ExFreePool(ObjectName.Buffer);
return Status;
}
/* Setup the flags and name */
LocalObjectType = (POBJECT_TYPE)&Header->Body;
LocalObjectType->Name = ObjectName;
Header->Flags |= OB_FLAG_KERNEL_MODE | OB_FLAG_PERMANENT;
/* Clear accounting data */
LocalObjectType->TotalNumberOfObjects =
LocalObjectType->TotalNumberOfHandles =
LocalObjectType->HighWaterNumberOfObjects =
LocalObjectType->HighWaterNumberOfHandles = 0;
对于内核而言,新创建的对象类型本身就是个“对象”,只不过是一种名为“Type”的特殊的对象,其数据结构是 OBJECT TYPE。
类型OBJECTTYPE是“元类型”。所有对象类型(的定义)都是作为对象而存在的,它们的类型都是 OBJECT_TYPE。所以这里实际创建的是类型为OBJECT_TYPE,对象名为TypeName 例如“Timer”等的对象,注意这里所说的对象名实际上就是所创建类型的类型名。将Unicode 形式的类型名复制到对象名 ObiectName中之后,就通过 ObpAllocateObiect0为该对象分配数据结构及其对象头。注意这里的第一个参数是NULL,这本来应该是个OBJECT_CREATE_INFORMATION结构指针,但是在创建对象类型时这个指针都是NULL,因为没有关于具体对象属性的信息需要传递。第三个参数为ObpTypeObjectType,这是个全局指针,指向对象类型“Type”的OBJECT_TYPE 数据结构。每一种对象类型都有自己的OBJECTTYPE数据结构,元类型OBJECTTYPE自身也不例外。第四个参数是 size0f(OBJECT TYPE),因为这里要分配的是 OBJECT TYPE 数据结构。最后一个参数则是指针 Header的地址,用来返回所分配的数据结构。ObpAllocateObiectO的代码不难理解,有兴趣或需要的读者可以自己找来阅读。
为所创建的对象类型分配了数据结构之后,下面就是必要的初始化,并将其插入对象类型目录:
/* Check if this is the first Object Type */
if (!ObpTypeObjectType)
{
/* It is, so set this as the type object */
ObpTypeObjectType = LocalObjectType;
Header->Type = ObpTypeObjectType;
/* Set the hard-coded key and object count */
LocalObjectType->TotalNumberOfObjects = 1;
LocalObjectType->Key = TAG('O', 'b', 'j', 'T');
}
else
{
/* Set Tag */
Tag[0] = (CHAR)TypeName->Buffer[0];
Tag[1] = (CHAR)TypeName->Buffer[1];
Tag[2] = (CHAR)TypeName->Buffer[2];
Tag[3] = (CHAR)TypeName->Buffer[3];
LocalObjectType->Key = *(PULONG)Tag;
}
/* Set up the type information */
LocalObjectType->TypeInfo = *ObjectTypeInitializer;
LocalObjectType->TypeInfo.PoolType = ObjectTypeInitializer->PoolType;
/* Check if we have to maintain a type list */
if (NtGlobalFlag & FLG_MAINTAIN_OBJECT_TYPELIST)
{
/* Enable support */
LocalObjectType->TypeInfo.MaintainTypeList = TRUE;
}
/* Calculate how much space our header'll take up */
HeaderSize = sizeof(OBJECT_HEADER) +
sizeof(OBJECT_HEADER_NAME_INFO) +
(ObjectTypeInitializer->MaintainHandleCount ?
sizeof(OBJECT_HEADER_HANDLE_INFO) : 0);
/* Check the pool type */
if (ObjectTypeInitializer->PoolType == NonPagedPool)
{
/* Update the NonPaged Pool charge */
LocalObjectType->TypeInfo.DefaultNonPagedPoolCharge += HeaderSize;
}
else
{
/* Update the Paged Pool charge */
LocalObjectType->TypeInfo.DefaultPagedPoolCharge += HeaderSize;
}
/* All objects types need a security procedure */
if (!ObjectTypeInitializer->SecurityProcedure)
{
LocalObjectType->TypeInfo.SecurityProcedure = SeDefaultObjectMethod;
}
总的来说这一段代码没有多大技术含量,列在这里只是为了保持这部分代码的完整性。注意这里的 HeaderSize是OBJECT _HEADER 的大小加上OBJECT_HEADER_NAME_INFO 的大小,再加上个可选项 OBJECT_HEADER_HANDLE_INFO 的大小。根据所分配缓冲区的来源,这里记下对象头对可倒换页面池或不可倒换页面池的耗用。我们再往下看:
/* Select the Wait Object */
if (LocalObjectType->TypeInfo.UseDefaultObject)
{
/* Add the SYNCHRONIZE access mask since it's waitable */
LocalObjectType->TypeInfo.ValidAccessMask |= SYNCHRONIZE;
/* Use the "Default Object", a simple event */
LocalObjectType->DefaultObject = &ObpDefaultObject;
}
/* The File Object gets an optimized hack so it can be waited on */
else if ((TypeName->Length == 8) && !(wcscmp(TypeName->Buffer, L"File")))
{
/* Wait on the File Object's event directly */
LocalObjectType->DefaultObject = (PVOID)FIELD_OFFSET(FILE_OBJECT,
Event);
}
else if ((TypeName->Length == 24) && !(wcscmp(TypeName->Buffer, L"WaitablePort")))
{
/* Wait on the LPC Port's object directly */
LocalObjectType->DefaultObject = (PVOID)FIELD_OFFSET(LPCP_PORT_OBJECT,
WaitEvent);
}
else
{
/* No default Object */
LocalObjectType->DefaultObject = NULL;
}
/* Initialize Object Type components */
ExInitializeResourceLite(&LocalObjectType->Mutex);
for (i = 0; i < 4; i++)
{
/* Initialize the object locks */
ExInitializeResourceLite(&LocalObjectType->ObjectLocks[i]);
}
InitializeListHead(&LocalObjectType->TypeList);
/* Lock the object type */
ObpEnterObjectTypeMutex(LocalObjectType);
/* Get creator info and insert it into the type list */
CreatorInfo = OBJECT_HEADER_TO_CREATOR_INFO(Header);
if (CreatorInfo) InsertTailList(&ObpTypeObjectType->TypeList,
&CreatorInfo->TypeList);
/* Set the index and the entry into the object type array */
LocalObjectType->Index = ObpTypeObjectType->TotalNumberOfObjects;
if (LocalObjectType->Index < 32)
{
/* It fits, insert it */
ObpObjectTypes[LocalObjectType->Index - 1] = LocalObjectType;
}
/* Release the object type */
ObpLeaveObjectTypeMutex(LocalObjectType);
对某些类型的对象所进行的某些操作常常需要等待,这样的操作可以是同步的也可以是异步的,这就需要在操作的调用者和执行者之间有个同步的手段,这通常是另一个对象,一个专门用于同步即等待的对象。为此,对象类型的OBJECT_TYPE结构中有个指针DefaultObject,指向一个默认的“等待对象”。如果使用时没有特别的安排,用于等待的对象就是作为操作目标的对象所属类型的默认等待对象。后面,读者在系统调用NtWaitForSingleObiect()和NtWaitForMultipleObjectsO)的代码中将会看到对“等待对象”的使用。有些对象类型以全局的默认等待对象ObpDefaultObject作为其默认等待对象,而File对象则以一个专门的Event对象为默认等待对象,这就是FILE_OBJECT数据结构内部作为其结构成分之一的Event对象。同样,WaitablePort 对象也以其数据结构内部的WaitEvent 对象为默认等待对象。最后,还有些对象类型是不存在操作同步的问题的,或者不使用默认的等待对象,这些对象类型的DefaultObject设置成NULL。但是要注意,当对象类型为File或WaitablePort 的时候,DefaultObiect的值并非指针,而是位移,因为默认的等待对象就在File或WaitablePort 对象的数据结构内部。
下面的InsertTailList()将新创建的对象类型挂入 ObpTypeObjectType 队列。如前所述,在对象头部和OBJECTTYPE结构内部都没有用于这个目的的队列头,这样的队列头在OBJECTHEADERCREATOR_INFO结构中。此外,内核中还有个BJECT_TYPE结构指针数组ObpObjectTypes],用来指向系统中的各种对象类型,这个数组的大小只是32,但如果不考虑由可安装内核模块所动态创建的对象类型,光对付内核所固有的对象类型则已经足够了。
我们再往下看:
/* Check if we're actually creating the directory object itself */
if (!(ObpTypeDirectoryObject) ||
(ObpInsertEntryDirectory(ObpTypeDirectoryObject, &Context, Header)))
{
/* Check if the type directory exists */
if (ObpTypeDirectoryObject)
{
/* Reference it */
ObReferenceObject(ObpTypeDirectoryObject);
}
/* Cleanup the lookup context */
ObpCleanupDirectoryLookup(&Context);
/* Return the object type and success */
*ObjectType = LocalObjectType;
return STATUS_SUCCESS;
}
/* If we got here, then we failed */
ObpCleanupDirectoryLookup(&Context);
return STATUS_INSUFFICIENT_RESOURCES;
最后通过 ObpInsertEntryDirectory()将新创建的对象类型插入对象类型目录 ObpTypeDirectoryObject。注意这是以 ObpTypeDirectoryObjec非空即类型目录已经存在为条件的;如果类型目录尚未建立,即ObpTypeDirectoryObject 为空,则只是通过参数ObjectType返回新创建的对象类型。如果ObpInsertEntryDirectory()失败,则ObCreateObjectType()失败返回。
如前所述,OBJECT_HEADER数据结构中没有用来将其挂入某个队列的队列头或指针,OBJECT_TYPE结构中虽然有个队列头,但那只是用来将所有的对象类型连接在一起的。那么,代表对象类型的数据结构又怎样被连接到对象目录中呢?我们继续往下看。
ObpInsertEntryDirectory()
[ObCreateObjectType() > ObpInsertEntryDirectory()]
BOOLEAN
NTAPI
ObpInsertEntryDirectory(IN POBJECT_DIRECTORY Parent,
IN POBP_LOOKUP_CONTEXT Context,
IN POBJECT_HEADER ObjectHeader)
{
POBJECT_DIRECTORY_ENTRY *AllocatedEntry;
POBJECT_DIRECTORY_ENTRY NewEntry;
POBJECT_HEADER_NAME_INFO HeaderNameInfo;
/* Make sure we have a name */
ASSERT(ObjectHeader->NameInfoOffset != 0);
/* Validate the context */
if ((Context->Object) ||
!(Context->DirectoryLocked) ||
(Parent != Context->Directory))
{
/* Invalid context */
DPRINT1("OB: ObpInsertEntryDirectory - invalid context %p %ld\n",
Context, Context->DirectoryLocked);
KEBUGCHECK(0);
return FALSE;
}
/* Allocate a new Directory Entry */
NewEntry = ExAllocatePoolWithTag(PagedPool,
sizeof(OBJECT_DIRECTORY_ENTRY),
OB_DIR_TAG);
if (!NewEntry) return FALSE;
/* Save the hash */
NewEntry->HashValue = Context->HashValue;
/* Get the Object Name Information */
HeaderNameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader);
/* Get the Allocated entry */
AllocatedEntry = &Parent->HashBuckets[Context->HashIndex];
/* Set it */
NewEntry->ChainLink = *AllocatedEntry;
*AllocatedEntry = NewEntry;
/* Associate the Object */
NewEntry->Object = &ObjectHeader->Body;
/* Associate the Directory */
HeaderNameInfo->Directory = Parent;
return TRUE;
}
如前所述,为了把一个对象挂入某个对象目录,需要为其分配一个OBJECT_DIRECTORYENTRY即“对象目录项”数据结构,让该数据结构中的指针Object指向这个对象(的正身),而真正插入队列的则是对象目录项数据结构。在这里,需要挂入目录的是个对象类型,但是对象类型也是对象,所以也需要为其分配一个对象目录项。另一方面,对象类型目录是个单层的目录,其目录节点就是 ObpTypeDirectoryObject。
下面再通过对象目录的初始化过程 ObnitO)进一步说明对象类型的创建。
ObInit()
BOOLEAN
INIT_FUNCTION
NTAPI
ObInit(VOID)
{
....
PKPRCB Prcb = KeGetCurrentPrcb();
....
....
/* Create kernel handle table */
PsGetCurrentProcess()->ObjectTable = ExCreateHandleTable(NULL);
ObpKernelHandleTable = PsGetCurrentProcess()->ObjectTable;
/* Create the Type Type */
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
RtlInitUnicodeString(&Name, L"Type");
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
ObjectTypeInitializer.ValidAccessMask = OBJECT_TYPE_ALL_ACCESS;
ObjectTypeInitializer.UseDefaultObject = TRUE;
ObjectTypeInitializer.MaintainTypeList = TRUE;
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.GenericMapping = ObpTypeMapping;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(OBJECT_TYPE);
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ObpTypeObjectType);
/* Create the Directory Type */
RtlInitUnicodeString(&Name, L"Directory");
ObjectTypeInitializer.ValidAccessMask = DIRECTORY_ALL_ACCESS;
ObjectTypeInitializer.UseDefaultObject = FALSE;
ObjectTypeInitializer.MaintainTypeList = FALSE;
ObjectTypeInitializer.GenericMapping = ObpDirectoryMapping;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(OBJECT_DIRECTORY);
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ObDirectoryType);
/* Create 'symbolic link' object type */
RtlInitUnicodeString(&Name, L"SymbolicLink");
ObjectTypeInitializer.DefaultNonPagedPoolCharge =
sizeof(OBJECT_SYMBOLIC_LINK);
ObjectTypeInitializer.GenericMapping = ObpSymbolicLinkMapping;
ObjectTypeInitializer.ValidAccessMask = SYMBOLIC_LINK_ALL_ACCESS;
ObjectTypeInitializer.ParseProcedure = ObpParseSymbolicLink;
ObjectTypeInitializer.DeleteProcedure = ObpDeleteSymbolicLink;
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ObSymbolicLinkType);
/* Phase 0 initialization complete */
ObpInitializationPhase++;
return TRUE;
函数 Obnit()在系统的初始化阶段被调用两次,第一次是在所谓Phase0阶段,第二次是在Phase1阶段,或者说是“后Phase0”阶段。上面这一段代码是在Phase0中执行的。
系统中首先创建的对象类型是Type,这是所有其他对象类型的模板,是“元类型”。如前所述,ObjectTypeInitializer是个全局的OBJECT_TYPE_INITIALIZER数据结构,是用于创建对象类型的“申请单”。填写好这个数据结构之后就调用 ObCreateObjectType()予以创建。注意这里的最后一个参数是&ObpTypeObjectType,这是因为“Type”是系统中的第一个对象类型,其后在创建其他对象类型的时候都要引用这个指针。在前面我们看到,为创建定时器对象类型而调用ObpAllocateObiect()时的第三个参数就是指针 ObpTypeObjectType。
创建了元类型之后,又创建了Directory和SymbolicLink 两种对象类型。这两种类型是用来构建对象目录的,所以要首先创建。这时候的“申请单”不需要完全从头填写,只要在Type的“申请单”上做一些修改就可以了。
在这三种对象类型中,元类型Type是不对用户空间开放的,所以严格地说不属于我们所说的“对象”类型,而 Directory和 SymbolicLink 则都对用户空间开放,所以是真正意义上的“对象”类型。所以,Phase0完成了创建对象目录的准备。
到系统初始化的Phase1阶段,Obnit()又会被调用,这一次就转到了标签 ObPostPhase0 下面,这一次要创建的是对象目录:
ObPostPhase0:
/* Re-initialize lookaside lists */
ObInit2();
/* Initialize Object Types directory attributes */
RtlInitUnicodeString(&Name, L"\\");
InitializeObjectAttributes(&ObjectAttributes,
&Name,
OBJ_CASE_INSENSITIVE | OBJ_PERMANENT,
NULL,
SePublicDefaultUnrestrictedSd);
/* Create the directory */
Status = NtCreateDirectoryObject(&Handle,
DIRECTORY_ALL_ACCESS,
&ObjectAttributes);
if (!NT_SUCCESS(Status)) return FALSE;
/* Get a handle to it */
Status = ObReferenceObjectByHandle(Handle,
0,
ObDirectoryType,
KernelMode,
(PVOID*)&ObpRootDirectoryObject,
NULL);
if (!NT_SUCCESS(Status)) return FALSE;
/* Close the extra handle */
Status = NtClose(Handle);
if (!NT_SUCCESS(Status