Bitdefender Hypervisor Memory Introspection
|
#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... | |
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.
INT_STATUS_SUCCESS | if successful. |
INT_STATUS_NOT_FOUND | if an error was encountered early in the search process. |
Definition at line 1855 of file winobj.c.
Referenced by IntWinProcCreateProcessObject().
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().
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.
[in] | ObjectGva | The guest virtual address at which the object is located. |
[out] | PoolHeader | On success, will contain the contents of the _POOL_HEADER. The caller must allocate a large enough buffer for this information. |
INT_STATUS_SUCCESS | in case of success. |
INT_STATUS_INVALID_PARAMETER_1 | if ObjectGva is not a valid kernel address. |
INT_STATUS_INVALID_PARAMETER_2 | if PoolHeader is NULL |
INT_STATUS_NOT_FOUND | if 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().
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:
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.
[in] | Gva | Guest virtual address to check. |
INT_STATUS_SUCCESS | if this is a valid kernel object. |
INT_STATUS_NOT_FOUND | if this is not a valid kernel object. |
Definition at line 1503 of file winobj.c.
Referenced by IntWinObjFindRootDirectory().