Analyze Windows NT/2000 Kernel Object Organization
WebSphere (http://webcrazy.yeah.net /)
The object manager occupies an extremely important position in the Windows NT/2000 kernel. One of its main functions is to organize and manage system kernel objects. In Windows NT/2000, the kernel object manager introduces a large number of C ++ object-oriented ideas, that is, all kernel objects are encapsulated in the object manager, except the object manager itself, all other subsystems that want to reference the kernel object structure members are not transparent, that is, they must be accessed through the object manager. Microsoft strongly recommends that the kernel driver code follow this principle (User-mode code cannot directly access the data), and she provides a series of routines starting with OB for us to use. However, this does not mean that only the object manager (that is, these functions) can access the data. The Code provided below directly accesses these structures. In this case, we can only pray that Microsoft will always keep these object headers and the Object Body Structure unchanged, which is generally unlikely, therefore, the code I provide is only for learning the Windows NT/2000 kernel and has no practical application significance.
Let's talk about the kernel named objects. The named objects are stored in the system's global named kernel zone. They are similar to the traditional dos directory and file organization methods. The object manager also manages these objects in a tree structure, this allows you to quickly retrieve kernel objects. This advantage is easily reflected in the fast object retrieval speed, which will greatly improve the system performance. For example, process a has used a file at a certain time point, that is, A has a kernel object (file_object) pointing to this file in the kernel. If another process B tries to access this file, when B opens this file through the createfile API, the system guides the object manager to find the object bucket. If this object already exists, check whether the object header has exclusive access, if yes, it will fail. Otherwise, the reference count or handle count of this object will be increased. This kind of search object is everywhere in the system, because in Windows NT/2000, all the operable data structures that require protection such as security_descriptor are treated as objects, for example, common process objects (eprocess/kpeb), thread objects (ETHREAD/kteb), and driver objects (driver_object. Of course, this tree structure organizes the kernel named objects. Another advantage is that all named objects are organized in a very organized manner, such as the device object is under/device, the object type name is in/objecttypes. In addition, the user-mode process can only access /?? And/basenamedobjects objects, while kernel-State Code has no restrictions. As for how to organize and manage these named objects in the system, in fact, in Windows NT/2000, the directory object pointed to by the kernel variable obprootdirectoryobject points to the root directory and uses a hash table (hashtable) to organize and manage these named kernel objects. Let's take a look at the i386kd analysis. Let's take a look at the code I wrote.
Kd>! Object/
Object: 8148e210 type: (814c5820) Directory
Objectheader: 8148e1f8
Handlecount: 0 pointercount: 39
Directory object: 00000000 name :/
99 symbolic links snapped through this directory
Hashbucket [00]: 8148a350 directory 'arcname'
814a8f10 device 'ntfs'
Hashbucket [01]: e2390040 port 'selsacommandport'
Hashbucket [03]: e1012030 key '/Registry'
Hashbucket [06]: e1394560 port 'xactsrvlpcport'
Hashbucket [07]: e13682e0 port 'dbguiapipport'
Hashbucket [09]: 84305760 directory 'nls'
.
.
.
Kd>! Object 814a8f10
Object: 814a8f10 type: (814b5ac0) Device
Objectheader: 814a8ef8
Handlecount: 0 pointercount: 2
Directory object: 8148e210 name: NTFS
The implementation code is as follows:
//-----------------------------------------------
//
// Dump Windows 2000 kernel object
// Only test on Windows 2000 Server Chinese edition
// Build 2195 (free )! Programmed by webcrazy
// (Tsu00@263.net) on!
// Welcome to http://webcrazy.yeah.net!
//
//-----------------------------------------------
Ulong obprootdirectoryobject = 0x8148e210; // fetch from symbol File
Void dumpdirectoryobject (pvoid directoryobject)
{
Ulong hashbucket;
Ulong * hash;
For (hashbucket = 0; hashbucket <= 0x24; hashbucket ++)
{
Hash = (ulong *) (ulong) directoryobject + hashbucket * 4 );
If (* hash = 0) continue;
Dbuplint ("/n hashbucket [% 02x]/n", hashbucket );
Do
{
Punicode_string obname, obtypename;
Pvoid object, obpreheader, obstandardheader;
Hash = (ulong *) (* hash );
Object = (pvoid) (* (ulong *) (ulong) hash + 4 ));
Obpreheader = (pvoid) (ulong) Object-0x28 );
Obstandardheader = (pvoid) (ulong) Object-0x18 );
Obname = (punicode_string) (ulong) obpreheader + 4 );
Dbuplint ("/tdump object: % 08x/N", (ulong) object );
Dbuplint ("/T name: % s", obname-> buffer );
Obtypename = (punicode_string) (ulong) (* (ulong *) (ulong) obstandardheader + 8) + 0x40 );
Dbuplint ("/T type: % s (% 08x)/n", obtypename-> buffer,
* (Ulong *) (ulong) obstandardheader + 8 ));
Dbuplint ("/T pointercount: % d handlecount: % d/N", * (ulong *) obstandardheader,
* (Ulong *) (ulong) obstandardheader + 4 ));
} While (* hash! = 0 );
}
}
Void dumpobject (pvoid object)
{
Punicode_string obname, obtypename;
Unicode_string temp;
Pvoid obpreheader = (pvoid) (ulong) Object-0x28 ),
Obstandardheader = (pvoid) (ulong) Object-0x18 );
If (ushort) ntbuildnumber )! = 2195 ){
Dbuplint ("only test on Windows 2000 Server build 2195! /N ");
Return;
}
Obname = (punicode_string) (ulong) obpreheader + 4 );
Dbuplint ("Dump object: % 08x/n name: % s", (ulong) object, obname-> buffer );
Obtypename = (punicode_string) (ulong) (* (ulong *) (ulong) obstandardheader + 8) + 0x40 );
Dbuplint ("type: % s (% 08x)/n", obtypename-> buffer, * (ulong *) (ulong) obstandardheader + 8 ));
Dbuplint ("pointercount: % d handlecount: % d/N", * (ulong *) obstandardheader,
* (Ulong *) (ulong) obstandardheader + 4 ));
Rtlinitunicodestring (& temp, l "directory ");
If (! Rtlcompareunicodestring (& temp, obtypename, false ))
Dumpdirectoryobject (object );
}
Void dumprootdirectoryobject ()
{
Dumpobject (pvoid) obprootdirectoryobject );
}
Those who have read the windbg output or used the SoftICE objdir command above should know the usefulness of the code, so I will not introduce it. This Code directly reads the system's global named object area without considering synchronization, and does not add one to pointercount When referencing object members. In addition, the callback function defined in object_type is not considered currently, this may cause errors when the user-driven code or the operating system mounts some additional applications, but I have clearly defined the data structure of the object area in the code, it is helpful to understand the Kernel Object Organization of Windows NT/2000, so I will list it (this code was written after I understood it for a long time ). In the code, I use the pvoid pointer and convert the pointer to the ulong type to directly add the structure offset address. Do not use the syntax such as (ulong *) ob ++, so that you can extract the offsets of some important structural members from the Code directly, but this also causes the code to be less concise. For the obprootdirectoryobject, the most important Windows NT/2000 kernel variable, I extract it directly from the symbols file and you must adjust it according to the actual situation. Alternatively, you can use obreferenceobjectbyname to obtain it.
In fact, there are already many applications that can implement this object listing function, except for the above windbg/i1_kd! Object‑softice's objdircommand, And the objdir.exe and Mark russinovich's winobj.exe in ddkare also provided. If you search for the named object storage area, you can enumerate System Device objects and driver objects to implement the functions of the device and driver commands in SoftICE. It must be noted that not all Device objects are located under/device, and not all driver objects are located under/driver. Therefore, you must call dumpdirectoryobject recursively to find the entire space, I don't know why Microsoft does not limit the storage location of device and driver objects.
The naming kernel objects mentioned above can be shared among processes, which is also the most basic principle of LPC (Local Procedure Call) in Windows NT/2000, LPC uses a kernel object called port in the kernel. Why can naming objects be shared among processes, but the unnaming objects mentioned below cannot be shared among processes? In my understanding, I think that since both are at the high end of 4G memory in the system kernel, both of them can be shared between processes theoretically. Microsoft only provides objects such as obreferenceobjectbyname to search for Objects Based on their names, while the obreferenceobjectbyhandle provided by Microsoft does not specify the process parameters. Therefore, unnamed objects cannot be shared to this extent. In addition, another major reason Microsoft does not provide such a parameter is to make the system more robust and stable. Next I will talk about the "handle" mechanism for managing unnamed kernel objects.
Why should we introduce the "handle" concept? In Windows NT/2000, the "handle" mechanism is used to prevent the kernel object pointer from being directly returned to the user process, in addition to this function, the "handle" also manages all kernel objects owned by a specific process, including unnamed objects. In SoftICE, the proc command and the-O option can get all the handles owned by a specific process. However, SoftICE is inconvenient to use when retrieving a specified handle. I tried to use kd2sys provided by numbench. EXE converts windbg extension DLL to SoftICE. SYS file, but problems often occur during use. Later, after analyzing the obreferenceobjectbyhandle kernel routine, I defined the following Macros in SoftICE (the obreferenceobjectbyhandle analysis process and Code are not listed here, so you can follow up carefully ):
// Define the macro handle
// Handle <kpeb> // The kpeb parameter specifies a specific process. The handle index is the handle index.
: Macro handle = "ADDR % 1; what (@ (% 1 + 128) + 8) + (% 2> 2) * 8) | 80000000 ;."
MACRO: 'handle' defined
// Display the handle object pointer and object type name whose handle index is 4 in the system process
: Handle DWORD (@ psinitialsystemprocess) 4
The value 8148ebc8 is (a) kernel process object (handle = 0004) for system (08)
----------------------------------------------
| _ HANDLE header address | _ HANDLE object type | _ indicates that the handle belongs to the system process.
| _ HANDLE Index
This macro is only tested on Microsoft Windows 2000 Server build 2195 Chinese edition. As shown above, only the object pointer and object type name are displayed. To view more detailed information about objects, you can only go deep into the kernel object header and Object Body Structure or use windbg! Handle command. This macro has clearly explained the structure of the Process Handle table, and many object structures are also applied in the Code provided above, I will not list the specific implementation code. We suggest you look at Mark russinovich's handleex.
There is a lot of content about the Windows NT/2000 kernel object organization mentioned in the title, such as the security settings of kernel objects. I cannot write it out here because of the limited level and effort. In addition, it should be proposed that this article does not involve the user mode's GDI object, and there is another way in the Organization. During the analysis of Windows NT/2000, I initially felt that many advanced ideas about Microsoft's design of Windows NT/2000 were becoming more and more difficult to analyze. Hope you have some experience, can communicate with you, or contact me (tsu00@263.net )!
References:
1. David solomom inside Windows NT, 2nd Edition
2. Mark russinovich documents