Bitdefender Hypervisor Memory Introspection
winobj.h File Reference
#include "wddefs.h"

Go to the source code of this file.

Functions

INTSTATUS IntWinObjIsTypeObject (QWORD Gva)
 Checks if the supplied guest memory location holds a valid type object. More...
 
INTSTATUS IntWinObjGetPoolHeaderForObject (QWORD ObjectGva, POOL_HEADER *PoolHeader)
 Reads the _POOL_HEADER structure for a given kernel object. More...
 
INTSTATUS IntWinGuestFindDriversNamespace (void)
 Runs the driver object namespace search. More...
 
void IntWinObjCleanup (void)
 Cleans up any resources allocated by the object search. More...
 

Function Documentation

◆ IntWinGuestFindDriversNamespace()

INTSTATUS IntWinGuestFindDriversNamespace ( void  )

Runs the driver object namespace search.

This starts the search for kernel driver objects, but when this function returns not all objects are found yet, as a big part of the search can (and will, in most situations) be executed asynchronously. The search starts by looking for the root of the object namespace. This is an _OBJECT_DIRECTORY structure used by the kernel with the name "\".

There is not a clear and easy way of figuring out where this exists inside the kernel, especially if Introcore is started long before the guest has booted. In order to obtain it, we look for a series of pointers inside the kernel .data section, as the pointer to this object will be saved in a global inside the kernel. This is based on some weak heuristics and invariants check which will produce a series of candidate pointers. Then, more serious checks are done on those candidates. We do this in order to reduce the search area, as most of the checks need to read from paged memory, and are done by a series of IntSwapMemRead calls that will page-in the needed memory pages, if they are not present. Doing this for all the pointers in the .data section may impact performance and will introduce a large window in which no driver objects are protected. If no candidates are found, the search is tried again, this time ignoring the gGuest.KernelBuffer contents, using the IntWinGuestFindDriversNamespaceNoBuffer function. If the search for candidates fails again, an error of type intErrGuestStructureNotFound is reported and the search stops.

The search starts by reading the allocation tag for each of the candidates and checking it against known-good values. This phase is done by IntWinObjHandleRootDirTagInMemory. Once a valid candidate is found, all the others are ignored and their subsequent IntWinObjHandleRootDirTagInMemory invocations are canceled. If no good allocation tag is found, an error of type intErrGuestStructureNotFound is reported and the search stops.

Next, every entry in the root's HashBuckets fields is parsed. This is an array of OBJECT_DIR_ENTRY_COUNT pointers to _OBJECT_DIRECTORY_ENTRY structures. Some may be NULL and are ignored. These may reside in paged pool, so they might be swapped out, so another round of swap-ins is scheduled using IntWinObjHandleDirectoryEntryInMemory as a handler.

There, we want to obtain the name of the object, but since the the name may also be swapped out, we simply register a new swap-in request, with IntWinObjHandleObjectInMemory as the handler. Also, since every _OBJECT_DIRECTORY_ENTRY may point to another _OBJECT_DIRECTORY_ENTRY, another swap-in is scheduled for the ChainLink member of the _OBJECT_DIRECTORY_ENTRY structure, having the same target, IntWinObjHandleDirectoryEntryInMemory.

Once the name information is obtained, another IntSwapMemRead is issued for the buffer itself, using IntWinObjParseDriverDirectory as the callback.

Once that callback gets invoked, the name is checked against the two directories that contain driver objects: "Driver" and "FileSystem". If the name matches, we iterate the HashBuckets entries of this directory and issue another IntSwapMemRead, this time for the driver object, setting the handler to IntWinObjHandleDriverDirectoryEntryInMemory.

Inside IntWinObjHandleDriverDirectoryEntryInMemory, a _OBJECT_DIRECTORY_ENTRY structure is obtained. The Object field of this structure can point to a _DRIVER_OBJECT or a _DEVICE_OBJECT structure. The object is validated to be a valid driver object using IntWinDrvObjIsValidDriverObject, and if it is it is added to introcore list of driver objects; otherwise, it is ignored. Similar to the other functions that parse a _OBJECT_DIRECTORY_ENTRY. this function sets up another swap-in, for the next entry in the object list, setting itself as the handler for that read.

At any point, any of the IntSwapMemRead callbacks may be the last callback that is invoked in a synchronous manner, so all of the callbacks check for this. If it happens, they finalize the search and check for errors. If errors were encountered, they are reported to the integrator and the introspection engine will be unloaded.

Note, however, that there is no way of ensuring that all driver objects allocated before introcore started are successfully detected. Doing this requires access to the list of all the currently used driver objects, and this is exactly what we are trying to obtain here.

Return values
INT_STATUS_SUCCESSif successful.
INT_STATUS_NOT_FOUNDif an error was encountered early in the search process.
Remarks
Even if this function exits with success, errors may be encountered while the search is conducted, in which case those will be reported with the GLUE_IFACE.NotifyIntrospectionErrorState mechanism and introcore will be unloaded.

Definition at line 1855 of file winobj.c.

Referenced by IntWinProcCreateProcessObject().

◆ IntWinObjCleanup()

void IntWinObjCleanup ( void  )

Cleans up any resources allocated by the object search.

This will cancel any pending swap memory transactions and will free any memory allocated for the contexts used by those reads.

Definition at line 2080 of file winobj.c.

Referenced by IntGuestPrepareUninit(), and IntWinObjCheckDrvDirSearchState().

◆ IntWinObjGetPoolHeaderForObject()

INTSTATUS IntWinObjGetPoolHeaderForObject ( QWORD  ObjectGva,
POOL_HEADER PoolHeader 
)

Reads the _POOL_HEADER structure for a given kernel object.

This function assumes that the object comes from the non paged pool, or it is readable at the moment.

Parameters
[in]ObjectGvaThe guest virtual address at which the object is located.
[out]PoolHeaderOn success, will contain the contents of the _POOL_HEADER. The caller must allocate a large enough buffer for this information.
Return values
INT_STATUS_SUCCESSin case of success.
INT_STATUS_INVALID_PARAMETER_1if ObjectGva is not a valid kernel address.
INT_STATUS_INVALID_PARAMETER_2if PoolHeader is NULL
INT_STATUS_NOT_FOUNDif the address at which the pool header should be located is not a kernel address.

Definition at line 1648 of file winobj.c.

Referenced by IntWinObjIsTypeObject().

◆ IntWinObjIsTypeObject()

INTSTATUS IntWinObjIsTypeObject ( QWORD  Gva)

Checks if the supplied guest memory location holds a valid type object.

Gva must point to a valid _OBJECT_TYPE structure. In order to ensure this the following invariants are checked:

  • the DefaultObject field must be a valid kernel pointer
  • the Name.Buffer field must be a valid kernel pointer
  • the TypeList.Blink field must be a valid kernel pointer
  • the TypeList.Flink must be a valid kernel pointer
  • the pool tag of the allocation must be WIN_POOL_TAG_OBJECT_7 for Windows 7 and WIN_POOL_TAG_OBJECT for newer version
  • the allocation must be from the non paged pool with the type NonPagedPoolMustSucceed
  • the PoolType field of the _POOL_HEADER can not be DontUseThisType
  • the PoolType field of the _POOL_HEADER can not be DontUseThisTypeSession

Since the allocation must be done from the non paged pool, we must be able to read the object and its headers at any time, so if any of the IntKernVirtMemRead calls fails, this can not be a valid type object.

Parameters
[in]GvaGuest virtual address to check.
Return values
INT_STATUS_SUCCESSif this is a valid kernel object.
INT_STATUS_NOT_FOUNDif this is not a valid kernel object.

Definition at line 1503 of file winobj.c.

Referenced by IntWinObjFindRootDirectory().