Object Handles
Last updated
Last updated
The Object Manager deals with pointers to kernel memory
User-mode applications can't directly read or write to kernel memory
To access an object, the application uses the handle returned by a system call
Each running process has an associated handle table that contains the following three things
Then handle's numeric identifier
The granted access to the handle (read or write)
The pointer to the object structure in kernel memory
Before the kernel is able to use a handle, the system call has to look up the kernel object pointer from the handle table using a kernel API like ObReferenceObjectByHandle
By providing this handle, the kernel component can return the handle number to the user-mode application without exposing the kernel object directly
User process wants to use a handle, it first passes the handle's value to the system call
System call implementation then calls a kernel API to convert the handle to a kernel pointer by referencing the handle's value in the process's handle table (12 in this case)
To figure out if the user should be granted access, the conversion API takes into consideration the type of access the user requested and the type of object being accessed.
If the requested access doesn't match the granted access the API will return STATUS_ACCESS_DENIED
and the operation will fail
If the object types don't match the API will return STATUS_OBJECT_TYPE_MISMATCH
These checks are crucial for security
The access check makes sure the user can't do an operation on a handle to which they don't have access ( things like writing to a file for which they only have read permission)
The type check makes sure the user hasn't passed an unrelated kernel object type which can lead to type confusion in the kernel, causing things like memory corruption.
If the handle conversion succeeds, the system call now has a kernel pointer to the object, which it can use to do the user's requested operation
The granted access value in the handle table is a 32-bit bitfield
This is the same bitfield used for the DesiredAccess
parameter specified in the system call
Access mash has four components:
Type-specific access component
16 bit
Defines the operations that are allowed on a particular kernel object type
A File
might have bits specifying if the file is allowed to be read or written to when using the handle
A synchronization event might only have a single bit that allows the event to be signaled
standard access
Defines operations that can apply to any object type
Operations:
Delete
- Removes the object; for example, by deleting it from disk or from the registry
ReadControl
- Reads the security descriptor information from the object
WriteDac
- Writes the security descriptor's discretionary access control (DAC) to the object
WriteOwner
- Writes the owner information to the object
Synchronize
- Waits on the object; for example, waits for a process to exit or a mutant to be unlocked
reserved and special access
Most of these are reserved but they include two access values:
AccessSystemSecurity
- Reads or writes audit information on the object
MaximumAllowed
- Requests the max access to an object when performing an access check
generic access component
Four broad categories of access:
GenericRead
GenericWrite
GenericExecute
GenericAll
When you request one of these generic access rights, the SRM first converts the access into the corresponding type-specific access
Because of this, you will never receive access to a handle with GenericRead
but instead you be granted access to the specific access mask that represents read operations for that type
To make the conversion easier, each type has a generic mapping table, which maps the four generic categories to a type-specific access
NtDuplicateObject
can be used to duplicate handles
Reasons for doing this:
Allow a process to take an additional reference to a kernel object
Kernel object won't be destroyed until all handles to it are closed, so creating a new handle maintains the kernel object
Duplication can be used to transfer handles between processes if the source and destination process handles have DupHandle
access
Can be used to reduce the access rights on a handle
For example, when you pass a file handle to a new process, you could grant the duplicated handle only read access, preventing the new process from writing to the object
This shouldn't be relied on to reduce the handle's granted access because if the process with the handle has access to the resource, it can just reopen it to get write access
Initial duplication, keeping the same granted access
First column shows that the handles are different but the call to Compare-NtObject
returns True. This means that the two handles refer to the same underlying kernel object.
The output shows the granted access is no only ModifyState
Other handle attributes relevant to duplication
Inherit
- Allows a new process to inherit the handle when it's created, this let's you pass handles to a new process to perform tasks such as redirecting console output text to a file.
ProtectFromClose
- protects the handle from being closed.