小生想知道内核对象都有什么样的区别,如使用Winobj看到的:
Arcname, basenamedobjects, callback, device 这些里面的内核对象都有什么区别啊??要全面的,比较详细的!语言不限~~
都用的一个共用的OBJ_HEAD结构,树形存放,只是类型不一样。长度也不一样。
Inside WINDOWS NT Object Manager--1--(好文章,如果懂俄文就好了)
目录
==========
Inside WINDOWS NT Object Manager
00."对象化的" Windows NT
01.Windows NT对象管理器
02.对象类型
03.Object Directory 与 SymbolicLink
04.对象的创建与对象的结构
05.Object Type 结构体
06.Дальнейшая жизнь объекта - простейший случай
07.句柄数据库
08.对象的安全性
09.命名对象
0a.对象的消亡
0b.其它管理器函数
0c.结语
附录
13.从用户模式下获取对象信息
14.某些与对象管理相关的系统服务
00."对象化的" Windows NT
=========================
如果您熟悉Windows NT体系结构,当然会知道在这个系统中资源都是以对象的形式存在的。这样集中了对资源的管理。实际上
,所有的Windows NT子系统无论如何都要使用资源,也就是说都要经过对象管理器。对象管理器是Windows NT的子系统,它对
对象提供支持。对对象的使用贯穿了整个操作系统,真正的“对象化”的NT对应用程序隐藏了Win32子系统,甚至于部分的隐藏
了系统调用接口。
通常,当提到NT的体系结构时,都会说到各种管理器(对象管理器、内存管理器等等)。所有这些管理器都是Windows NT内核
(ntoskrnl.exe)的一部分。NT的内核主要使用C语言编写的,每一个管理器本身都是一组函数,这些函数都被封装在相应的模
块中。并不是所有的都定义了接口。管理器之间借助于函数调用相互协作(在内核内部)。一部分函数从内核中导出的并公开
了使用方法以在内核模式下使用。
对象管理器是一组形如ObXXXX的函数。实际上并不是所有的函数都是从内核中导出的。一般说来,对象管理器主要在内核中实
现。例如,许多系统调用(NtCreateProcess、NtCreateEvent...)的处理都与这样或是那样的对象相联系。但是服务隐藏了对
象管理器,不让我们的程序知道。甚至于驱动程序的设计人员未必需要大量直接的与对象管理器打交道。尽管如此,了解内核
是如何实际工作的无疑非常重要,更为重要的是对象管理器还与安全管理器密切相关。理解了对象管理器就揭开了NT中许多起
初并不明显,但潜在具有的可能性……
01.Windows NT对象管理器
================================
对象管理器是NT内核中许多形如ObXXX的函数。某些函数并未导出并只在内核内部由子系统使用。对于在内核之外的使用,主要
是通过未公开的函数。
ObAssignSecurity, ObCheckCreateObjectAccess , ObCheckObjectAccess,
ObCreateObject, ObDereferenceObject, ObFindHandleForObject,
ObGetObjectPointerCount, ObGetObjectSecurity, ObInsertObject,
ObMakeTemporaryObject, ObOpenObjectByName, ObOpenObjectByPointer,
ObQueryNameString, ObQueryObjectAuditingByHandle,
ObReferenceObjectByHandle, ObReferenceObjectByName,
ObReferenceObjectByPointer, ObReleaseObjectSecurity,
ObSetSecurityDescriptorInfo, ObfDereferenceObject, ObfReferenceObject
为了描述对象管理器,我们先来看一下什么是对象。
02.对象类型
================
对象管理器本身就工作在某些对象之上。其中之一就是object type。object type用于描述所有对象的共同属性。object type
由函数ObCreateObjectType(...)在内核初始化子系统的时候创建(也就是说是在系统初始化的时候)。我们所感兴趣的并不是
ObCreateObjectType(...)函数,因为其并未由内核导出。但是,在object type中的信息是非常重要的。关于这些我们少后再
说。
大概的讲,任何对象都可以分为两个部分:一部分包含管理上必需的信息(我们称之为首部),另一部分填充并确定创建该对
象的子系统的必需信息(这是对象的 body)。对象管理器对首部进行操作,而实际上并不对对象的内容感兴趣,还有一些对象
是对象管理器本身使用的。object type正如其名字所示的那样,定义了对象的类型。每一个对象都有对其object type的引用
,对象管理器也会非常频繁的使用对象的body。object type结构体的一个域是类型的名字。在NT下有以下各类型:
Type, Directory, SymbolicLink, Event, EventPair, Mutant, Semaphore,
Windows, Desktop, Timer, File, IoCompletion, Adapter, Controller, Device,
Driver, Key, Port, Section, Process, Thread, Token, Profile
Type类型的object type不足一惧。要知道扼要的讲object type是用于管理器的对象,其和所有其它的对象是一样的,也有自
己的Type类型(它自己的Type类型对象)。如果好好的想一下的话,这样做似乎也并不奇怪。下面,在Type这个话题下,主要
来讲几个与object type相关的名词。
03.Object Directory与SymbolicLink
===================================
所有列举出的类型都是由不同的子系统创建的。我们现在讨论Directory和SymbolicLink类型,因为在对象管理器本身的工作中
要用到这些类型的对象(这些类型是其在其自己初始化时创建的)。
NT的对象可以有名字,还可以组织成树型的结构。Directory对象正是用来组织这种结构的。它的用途非常简单——保存对其它
对象的引用,例如,另外一个Directory对象。Directory的body的结构非常简单:
typedef _DIR_ITEM{
PDIR_ITEM Next;
PVOID Object;
}DIR_ITEM,*PDIR_ITEM;
typedef struct _DIRECTORY{
PDIR_ITEM HashEntries[37];
PDIR_ITEM LastHashAccess; //94h
DWORD LastHashResult; //98h
}DIRECTORY,*PDIRECTORY;
路径用于保存命名对象。路径本身是37个entry的哈希表。表中的元素除保存着指向后面元素的指针之外,还保存着指向属于该
路径对象的body的指针。哈希函数形式如下:
DWORD Hash(const char* str);
{
char *ptr=str;
int str_len=strlen(str);
char Sym;
int hash=0;
while(str_len--)
{
Sym=*ptr++;
ToUpperCase(&Sym);
Sym-=' ';
hash=hash*3+(hash>>1)+Sym;// умножение знаковое
}
return hash%37;
}
当然,这里的代码只是给出程序的逻辑,并不是真正的实现。这个例子主要是基于对ObpLookupDirectoryEntry(...)函数的分
析写出的。最后的两个域反映出对哈希表的最后访问。LastHashAccess是指向最后访问的entry的指针。LastHashResult包含成
功查找到的单元。NT想优化对路径的查找,而且如果在查找的结果中找到的entries中的一个entry不在表的开头,则这个表的
元素就要被移到表的开头(认为后面对该对象的访问的几率最大)。
Directory是对象,和其它的对象一样,它也可以有名字。如果再考虑到对象还有对自己所在目录的引用,则对可以画出树型的
层级结构这个问题就能够理解了。
在Windows NT中包含着根目录ObpRootDirectoryObject,Windows NT的命名对象树就从这里开始。同时,还有
ObpTypeDirectoryObject目录,它包含所有的object type(使用这个树是为了防止object type重名)。内核表
ObpObjectTypes中保存着指向object type的指针。再有,对于每一个类型的创建,子系统都保存着单独的指针。
SymbolicLink这个object type用于保存字符串。在树中进行查找时它们被用作引用。例如,对象\??\C:(这表示,在
ObpRootDirectoryObject中有对路径 “??”的引用,在这个路径下包含着对象“C:”)就是SymbolicLink对象并包含字符串“
\Device\Harddisk0\ Partition1”。该对象格式如下:
typedef struct _SYMBOLIC_LINK{
LARGE_INTEGER CreationTime; // время создания от 1601 года
UNICODE_STRING Link;
}SYMBOLIC_LINK,*PSYMBOLIC_LINK;
现在有了对管理器用到的类型和对象的一般概念,我们可以来研究更为具体的信息了。
04.对象的创建与对象的结构
===============================
对象由ObCreateObject函数创建。这个函数由ntoskrnl.exe导出,下面给出其原型:
NTSTATUS NTOSKRNL
ObCreateObject
(
KPROCESSOR_MODE bMode, // kernel / user
POBJECT_TYPE Type, // 对象类型
POBJECT_ATTRIBUTES Attributes, // 属性
BOOLEAN bObjectMode, // kernel/user对象类型
DWORD Reserved, // 函数未使用
DWORD BodySize, // 对象body的大小
DWORD PagedPoolQuota OPTIONAL, // 如果为0
DWORD NonPagedPoolQuota OPTIONAL,// 则回收
PVOID* pObjectBody // 指向body的指针
);
参数Reserved可以简单的忽略。bObjectMode定义了是否要从用户模式访问。PagedPollQuota和 NonPagedPollQuota是与这些对
象相关联的分页池和非分页池的限额。限额记录了进程每一次其打开句柄的情况。进程有一个限额的阈值,其不能超越。
POBJECT_ATTRIBUTES结构体是公开的(ntdef.h中有),但我这里还要将其给出,因为后面会经常引用其中的域。
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
现在来集中讨论重要的域RootDirectory和ObjectName。RootDirectory是什么,现在我想已经能猜到了——将要包含对象(如
果需要)的目录。ObjectName——见名知意。顺便说一下,可以不指明RootDirectory,但在ObjectName中要指定对象的完整路
径,这时对象将在起始于ObpRootDirectoryObject的树中创建。
关于安全方面的话我们单独讲,所以Security域嘛……暂时留下不提。剩下Attributes域。ntdef.h中描述了各位的意义。下面
给出这些属性。
#define OBJ_INHERIT 0x00000002L
#define OBJ_PERMANENT 0x00000010L
#define OBJ_EXCLUSIVE 0x00000020L
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_OPENIF 0x00000080L
#define OBJ_OPENLINK 0x00000100L
结果,函数返回指向对象body的指针。
到了描述对象结构的时候啦。
+---------------+<------------------------------> -??h
| |
| ........ | --长度可变的结构体 (到 30h)
| |
+---------------+<--对象首部--------------> 00h
| |
| Header | --标准的首部18h字节
| |
+---------------+<--对象body----------------> 18h
| |
| |
| Body | - тело объекта желаемого размера
| |
+---------------+<------------------------------> 18h+BodySize
管理器经常要操作指向对象首部的指针。它返回给我们的是指向body的指针。首部之上还有一块区域,这块区域长度可变并依
赖于所建对象的参数和类型。这个“帽子”可以保存下面这个结构体:
typedef struct _POLLED_QUOTA // 如果限额不等于default则增加
{ // 保存限额的消耗量
DWORD PagedPoolQuota;
DWORD NonPagedPoolQuota;
DWORD QuotaInformationSize; // PagedPollQuota的总和
PPROCESS_OBJECT pProcess; // 拥有此对象的进程
}POLLED_QUOTA;
typedef struct _HANDLE_DB // 关于打开句柄的信息
{
union {
PPROCESS_OBJECT pProcess;// 如果只有一个进程打开了
// 指针
PVOID HandlesDBInfo; //-------------+--------
}HanInfo; // |dd Num;2
// +-----------
// |dd pProcess1
// |dd Counter2
// +----------
// |dd pProcess2
// |dd Counter2
// +-----------
// |....
DWORD Counter; // 句柄总数.
}HANDLE_DB;
typedef struct _OBJECT_NAME
{
PDIRECTORY Directory; // 对象所属的路径
UNICODE_STRING ObjectName; //对象名
DWORD Reserved; //对齐
}OBJECT_NAME;
typedef struct _CREATOR_INFO
{
LIST_ENTRY Creator; // 闭合的链表 (包含着类型)
DWORD UniqueProcessId;// 对象父进程的ID
DWORD Reserved; // 对齐
}CREATOR_INFO;
这个或是其它的结构体可以存在,也可以不存在,但顺序不能变。在注释中写明了这些结构体都是作什么用的了。它们中最后
一个(哪怕只有一个)的后面就是标准的对象首部了。
typedef struct _OBJECT_HEADER
{
DWORD RefCounter; // 对象的引用计数 00
DWORD HandleCounter; // 句柄数目 04
POBJECT_TYPE ObjectType; // 对象类型 08
DWORD SubHeaderInfo; // 后面会讲 0c
UNION // 10
{
POBJECT_INFO ObjectInfo;
PQUOTA_BLOCK pQuotaBlock;
} a;
PSECURITY_DESCRIPTOR SecurityDescriptor; // 14 Optional
} OBJECT_HEADER;
先不讲这个联合体,以后再说。其它域的作用都很显然。对象管理器将统计打开的对象句柄以及对对象的引用,以此来知道何
时删除对象的名字和body(如果对象不是permanent的,也就是说没有设置OBJ_PERMANENT位)。还有对对象类型的引用和有关
安全的信息。首部前面的结构体中的域在 SubHeaderInfo域中也有体现。
SubHeaderInfo中各个位的含义如下:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+---------------+-------------+---------------+-----------------+
|U|H|S|P|E|I|M|Q|Quota offset |HandleDB offset| Name Offset |
++-+-+-+-+-+-+-++-----+-------+------+--------+-------+---------+
| | | | | | | | | | |
| | | | | | | | | | OBJECT_NAME距首部起始的正偏移(向上)
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
| | | | | | | | | HANDLE_DB距首部起始的正偏移(向上)
| | | | | | | | |
| | | | | | | | |
| | | | | | | | |
| | | | | | | | POLLED_QUOTA距首部起始的正偏移(向上)
| | | | | | | |
| | | | | | | |
| | | | | | | +- QuotaBlock/ObjectInfo (Quota charged)
| | | | | | +-- 对象的User/Kernel模式
| | | | | +---- 是否有CREATOR_INFO
| | | | +------ 对应于位OBJ_EXCLUSIVE
| | | +-------- 对应于位OBJ_PERMANENT.
| | | 用于该对象的创建,必须对应相应的权限
| | |
| | +---------- 存在SecurityDescriptor
| +------------ HandleInfo未初始化
+-------------- Unused/Reserved
*在SubHeaderInfo没有CREATOR_INFO的字节偏移,但因为这个结构体(如果有)总是最后一个,故指向它的指针可以获得,只
需从首部指针中减去sizeof(CREATOR_INFO)就可以了。
当我第一次开始反汇编对象管理器函数并看到ObCreateObject(...)的时候,我断定这个函数做了使对象开始其“生活”的所有
必需的工作。但是一个普遍的原则——直到最后再下定论(很好的原则),在这里也成立。实际上,对于前面描述的各域,这
个函数只填充了结构体的最低限度的域。倒不如说这个函数在内存中把对象折腾了一番,捎带脚儿地填充了几个域。
再详细些。SubHeaderInfo中的位:Q=1 总是,S=0 总是,H=1 如果存在HANDLE_DB。OBJECT_NAME中 Directory = NULL。
CREATOR_INFO中的LIST_ENTRY指向自己。POLLED_QUOTA中 pProcess = NULL。HANDLE_DB清零。HandleCounter = 0;
RefCounter = 1; 其余的域以相应的information entries和对象类型中的信息来填充。除此之外,分配并填充OBJECT_INFO结
构体,首部的UNION {..}a中的相应指针(达,当然所有的内存都是从NonPaged pool里分配的)。
typedef struct _OBJECT_INFO{
DWORD Attributes; //00h OBJECT_ATTRIBUTES.Attributes
HANDLE RootDirectory; //04h
DWORD Reserved; //08h - Unknown or Res.
KPROCESSOR_MODE bMode; //0ch
BYTE Reserved[3]; //0dh - Alignment
DWORD PagedPoolQuota; //10h
DWORD NonPagedPoolQuota; //14h
DWORD QotaInformationSize;//18h - 组SID的大小
//+ DACL的大小(圆整后)
PSECURITY_DESCRIPTOR SelfRelSecDescriptor;
//1ch - 指向Self Relativ的指针.
//Non Paed Pool里的安全描述符
PSECURITY_QUALITY_OF_SERVICE pSecQual; //20h
SECURITY_QUALITY_OF_SERVICE SecQuality; //24h
//30h
} OBJECT_INFO,*POBJECT_INFO;
顺便说一句,系统为OBJECT_INFO结构体维护了一个叫ObpCreateInfoLookasideList的Lookaside list(见DDK)。实际上
Reserved域有时也会用到(在对象方法中作参数),但我尚未碰到过这个域的值不为0的情况。 QuotaInformationSize用在从
Paged和NonPaged pool中移除限额用的。
05.Object Type 结构体
=========================
这里我只描述一下object type的body的结构。
typedef struct _OBJECT_TYPE
{
ERESOURCE TypeAsResource; //0x0 可用作资源
//34h
PLIST_ENTRY FirstCreatorInfo;//38h 我注意到了这个结构体
PLIST_ENTRY LastCreatorInfo; //3ch 只是用于object type
UNICODE_STRING TypeName; //40h 类型名
DWORD Unknown2[2]; //48h
DWORD RefCount; //50h 该类型对象的计数
DWORD HanCount; //54h 该类型句柄的计数
DWORD PeakRef; //58h 对象的峰值
DWORD PeakHandles; //5ch 句柄的峰值
DWORD Unknown3; //60h
DWORD AllowedAttributeMask;//64h 可能的属性 0 - 允许所有的
GENERIC_MAPPING GenericMapping;//68 отображение родовых прав на специальные
DWORD AllowedAccessMask; //78h (ACCESS_SYSTEM_SECURITY 总是设置的)
BOOLEAN bInNameSpace; //7ch 这个类型的对象在对象路径中
// 可能我会弄错, 但也类似.
BOOLEAN bHandleDB; //7dh 是否包含对象句柄的信息(HANDLE_DB)
BOOLEAN bCreatorInfo; //7eh ----//---- CreatorInfo + 38h处的链表
BOOLEAN Unknown5; //7fh
DWORD Unknown6; //80h 如果 !=0 则在NpSuccess里创建
DWORD PagedPoollQuota; //84h default
DWORD NonPagedPollQuota; //88h 限额
PVOID DumpProcedure; //8ch 原型未知 (?)
PVOID OpenProcedure; //90h 原型已知
PVOID CloseProcedure; //94h 原型已知
PVOID DeleteProcedure; //98h 原型已知
PVOID ParseProcedure; //9ch 原型已知
PVOID SecurityProcedure; //a0h 原型已知
// 可以有4种调用情况:
//0-set sec_info, 1-query descriptor, 2-delete, 3-assign
PVOID QueryNameProcedure; //a4h 原型已知
PVOID Tag; //a8h 通过高层次信息判断
// 这应该是方法OkayToCloseProcedure;
// 实际上, 对于所有的对象我都发现在这个地址上有四字符的类型Tag,例如Dire (Directory)
} OBJECT_TYPE,*POBJECT_TYPE;
我就不给出已知方法的原型了,因为这里不大用得到。于是,类型种包含了类型名,操作对象的函数,还有属于该类型的用于
对象的一般信息。结构体的域都已有注释,不再细说。
我们将视线转向偏移0处的ERESOURCE结构体(可以在NTDDK.H中找到)。换句话说,这个对象可以用作资源。对象管理器使用
ExAcquireResourceExclusiveLite函数(见DDK)从object type中取得资源。PagedPollQuota和NonPagedPollQuota将限额指定
为默认值。如果我们使用限额值为零来调用 ObCreateObject函数(或与默认的限额值相同)而且如果没有设置OBJ_EXCLUSIVE
且如果QuotaInformationSize 小于0x800,则在创建对象时将不会创建POLLED_QUOTA结构体。AllowedAttributeMask和
AllowedAccessMask域指定了属性的掩码和允许的访问。最后有指向方法的指针,这些方法在定义的时候调用。例如,在增加句
柄的时候,在这个进程句柄的基址里调用OpenProcedure函数。这并不是说这些域就是我们想象的那样(以及我从Хелен
Касер(译注:我想是Helen Custer)的关于WIndows NT的书中读到的那样)是必需的。但是它们能够扩展对象的功能。
下面我来讲调用某些方法的条件。
06.Дальнейшая жизнь объекта - простейший случай
===============================================
ObCreateObject创建对象,但没有什么该对象本质上的东西。没有它的句柄,不能用名字打开它(ObCreateObject函数并不向
对象树中添加对象)。我们回来看一下对象管理器导出的函数。下面的三个函数很令人好奇:ObOpenObjectByName,
ObOpenObjectByPointer,ObInsertObject。第一个函数先不说。那是说第二个呢还是第三个? ObOpenObjectByPointer自然应
该讲,可是ObInsertObject平时更少被提到,所以我们从它开始。
NTSTATUS NTOSKRNL
ObInsertObject(
PVOID pObject, //body
PACCESS_STATE pAccessState OPTIONAL,
ACCESS_MASK Access,
DWORD RefCounterDelta OPTIONAL, //0- default (т.е. 1)
PVOID OUT *ObjectExist OPTIONAL, //如果已经存在
PHANDLE OUT Handle //句柄
);
函数的逻辑十分明显。它从填充好的先前的OBJECT_INFO结构体体得到主要的信息是其即将用到的从对象首部中获得的一个指针
(指向对象首部的指针可以简单的用body指针减去18h)。函数的工作方式有两种:一,对象没有名字且没有在类型中设置
bInNameSpace标志;二,设置了该标志或是有名字。我们来看这两种情况。
如果对象没有名字,则调用ObpCreateUnnamedHandle(..)。后面,我将引用一个内部函数而不给出其原型,其原型是已知的。
这个函数本身是以许多内部函数为基础的,这些内部函数共同完成了该函数的工作。
当前进程记录了限额。在这种情况下,填充a.pQuotaBlock域并清SubHeaderInfo中的Q位。现在对象“填充好了”限额。
a.pQuotaBlock指向与每一个进程相联系的QUOTA_BLOCK结构体。我们现在先不研究对象-进程的结构,我想下面这个结构体值
得一提。
typedef _QUOTA_BLOCK{
DWORD KSPIN_LOCK QuotaLock;
DWORD RefCounter; // 用于计数该block的进程
DWORD PeakNonPagedPoolUsage;
DWORD PeakPagedPoolUsage;
DWORD NonPagedpoolUsage;
DWORD PagedPoolUsage;
DWORD NonPagedPoolLimit;
DWORD PagedPoolLimit;
DWORD PeakPagefileUsage;
DWORD PagefileUsage;
DWORD PageFileLimit;
}QUOTA_BLOCK,*PQUOTA_BLOCK;
我想各域的名字已经很清楚的说明了其用途。行,我们接着往下进行。增加句柄的数目并恢复HANDLE_DB中的信息,如果有的话
。HANDLE_DB本身是个结构体,保存了对打开对象的各进程的引用。它为每一个进程都维护了一个计数器。在第一遍中将
HanInfo与此结构体相联结显示了对“懒惰”原则的遵守。如果对象只是从一个进程中打开,则联结本身就是指向这个进程的指
针。不可命名的对象可以是exclusive的(属性中的OBJ_EXCLUSIVE 位)。这样控制了只能从一个与POLLED_QUOTA中的域对应的
进程中打开对象。我们记得,这个结构体总是存在于exclusive的对象中。这样,进程就独自占据了对象——如此就没有了控制
访问的问题。合情合理的,对象独占的句柄能够继承(OBJ_INHERIT),并被管理器检查。如果 CREATOR_INFO是必需的信息,
则要有CREATOR_INFO链表,object type有相应的域指向它。object type本身也在这个链中。实际上,我发现bCreatorInfo位
只在类型对象“Type”中设置。这表明链表只用于对象类型。对于类型已定的对象,则不含此链表。好了,只剩下描述
ObpIncrementUnnamedHandleCount(..)函数的主要内容的逻辑了。在该函数执行时还会调用 OpenProcedure(如果有)。
当上述操作完成时,会使首部中的计数器RefCounter增加RefCounterDelta+1。最后,使用ExCreateHandle创建句柄。在成功创
建句柄后,就离开相应的OBJECT_INFO结构体,这个结构体役期已满,不再需要……