Bitdefender Hypervisor Memory Introspection
wintoken.c File Reference

This file handles token steal detection and token privilege protection. More...

#include "wintoken.h"
#include "winprocess.h"
#include "winprocesshp.h"
#include "decoder.h"
#include "hook.h"
#include "drivers.h"
#include "alerts.h"
#include "winpool.h"
#include "gpacache.h"

Go to the source code of this file.

Functions

static INTSTATUS IntWinTokenPrivsHandleWrite (void *Context, void *Hook, QWORD Address, INTRO_ACTION *Action)
 EPT callback triggered when a write occurs over the Privileges bitfields in a nt!_TOKEN structure protected through EPT. More...
 
static INTSTATUS IntWinTokenPrivsHandleSwap (void *Context, QWORD VirtualAddress, QWORD OldEntry, QWORD NewEntry, QWORD OldPageSize, QWORD NewPageSize)
 Handles a token swap-in or swap-out, re-applying protection if the token is not assigned anymore to a process. When a token is de-allocated and the whole page becomes free, as we increased on ExAllocatePoolWithTag the size of token allocations to always be one page, for performance purposes, the kernel may use the already freed page for different purposes, sometimes even mapping new physical memory into it. Since we have already hooked the page against writes, we will have, in such cases, translation violations, as the new mapping will not have the same contents as the TOKEN allocation which was freed before. Therefore we have to verify during the translation modifications of tokens if the current token is still assigned to the process and it was just swapped out, or if it has been freed, in which case we should re-establish the hook on the newly assigned token, while removing the old hook, which will solve the possibility of translation violations appearing in this case. More...
 
static void IntWinTokenPrivsSendEptAlert (EXCEPTION_KM_ORIGINATOR *Originator, EXCEPTION_VICTIM_ZONE *Victim, INTRO_ACTION Action, INTRO_ACTION_REASON Reason)
 Sends an EVENT_EPT_VIOLATION for a token privileges violation. More...
 
static void IntWinTokenPrivsSendIntegrityAlert (EXCEPTION_VICTIM_ZONE *Victim, INTRO_ACTION Action, INTRO_ACTION_REASON Reason)
 Sends an EVENT_INTEGRITY_VIOLATION when checks over the token privileges have failed. More...
 
static BOOLEAN IntWinTokenPrivsShouldHook (WIN_PROCESS_OBJECT *Process, QWORD NewTokenPtr)
 Decides if the given token address should be hooked through EPT or not. More...
 
static INTSTATUS IntWinTokenProtectPrivsInternal (WIN_PROCESS_OBJECT *Process, QWORD NewTokenPtr)
 If needed, this function will establish an EPT hook on the given token pointer for privileges protection. Note that, this function might get called in the case where Process->OriginalTokenPtr != NewTokenPtr, in order to re-establish the hook in the case where the token pointer has changed between timer ticks. More...
 
static INTSTATUS IntWinTokenFetchTokenAddress (WIN_PROCESS_OBJECT *Process, QWORD *OldValue, QWORD *NewValue)
 Fetches the token pointer from inside the EPROCESS and returns the old token pointer and the new token pointer which may have changed at the time of the read. More...
 
BOOLEAN IntWinTokenPtrIsStolen (WIN_PROCESS_OBJECT *Process, BOOLEAN Check, WIN_PROCESS_OBJECT **FromProcess, QWORD *OldValue, QWORD *NewValue)
 This function checks if the security token of a given process has been stone from another process. More...
 
INTSTATUS IntWinTokenPtrCheckIntegrityOnProcess (WIN_PROCESS_OBJECT *Process)
 This function checks if the security token of a given process has been stone from another process. More...
 
INTSTATUS IntWinTokenCheckCurrentPrivileges (WIN_PROCESS_OBJECT *Process, QWORD TokenPtr, BOOLEAN IntegrityCheck, BOOLEAN *PresentIncreased, BOOLEAN *EnabledIncreased, QWORD *Present, QWORD *Enabled)
 Verifies the current token if the current Privileges.Present and Privileges.Enabled fields were not altered in a malicious way. More...
 
INTSTATUS IntWinTokenPrivsCheckIntegrityOnProcess (WIN_PROCESS_OBJECT *Process)
 This function checks if the privileges bitfields for the given process have been changed in a malicious manner, sending an alert if needed. More...
 
TIMER_FRIENDLY INTSTATUS IntWinTokenCheckIntegrity (void)
 This function checks the integrity of the security token for all the processes inside gWinProcesses. The checks include both verifying if there are token pointers belonging to multiple processes, indicating a stolen token, and verifying if the Token Privileges have not changed in a malicious way, indicating a privilege escalation. More...
 
INTSTATUS IntWinTokenPrivsProtectOnProcess (WIN_PROCESS_OBJECT *Process)
 Updates the stored original Privileges bitfields (Present and Enabled) and hooks through EPT the Privileges inside the assigned token of the given process, if needed. More...
 
INTSTATUS IntWinTokenPrivsUnprotectOnProcess (WIN_PROCESS_OBJECT *Process)
 
INTSTATUS IntWinTokenProtectPrivs (void)
 Protects all the currently unprotected tokens belonging to processes against privileges manipulation. More...
 
INTSTATUS IntWinTokenUnprotectPrivs (void)
 Unprotects all the currently protected tokens belonging to processes against privileges manipulation. More...
 

Variables

LIST_HEAD gWinProcesses
 The list of all the processes inside the guest. More...
 

Detailed Description

This file handles token steal detection and token privilege protection.

This module will assure, through integrity protection, once every second, that there are no tokens which are assigned to two processes simultaneously. If two processes share the same token, it means that either some rootkit or some kernel exploit leveraging some arbitrary writes has stolen a token (most probably a more privileged one) and assigned it to a malicious process. In this case, the introspection engine will raise an alert. Moreover, the privileges field in the token structures are protected against modifications. Note that, due to some privilege escalation, a process might exploit some vulnerability such that the assigned privileges are increased, for example CVE-2020-0796 leverages such a vulnerability in the srv2.sys driver so that it gains SYSTEM privileges. The privileges protection is available, due to performance reasons, only on dynamically detected processes (e.g. processes that start after introspection engine initializes), or on all processes on systems which support sub page protection (SPP). The performance improvement in this case is provided by the fact that, once introspection engine initializes, all the token allocations are forced to one page, thus there will not be extra exits outside the protected structure. It is also worth mentioning that dynamic processes can have tokens which are allocated before introspection engine initialization. For this purpose we will check that those allocations have the size of a 4kb page. Note that, on static detected processes introspection engine will verify the assigned privileges to every process once every second, through the integrity mechanism, but due to the fact that, on detection, the LPE has already been executed and the vulnerability leveraged, the introspection engine cannot enforce protection in these case, meaning that rewriting the privileges would be futile, since the process had very high privileges for around 1 second, which is more than enough to provoke damage and enforce persistence on the system. The checks performed on integrity on privileges are:

  1. The Present field inside Privileges should not increase. That means, no bits should be 1 if they were previously 0. Note that present privileges may decrease.
  2. Any bit set in the Enabled field of Privileges should also be set in the Present field. As the kernel checks against the Present bitfield when increasing a privilege in the Enabled bitfield, but when checking the privileges before accessing a resource, the kernel only checks the Enabled bitfield, we should ensure that there is a consistency between those fields.

Definition in file wintoken.c.

Function Documentation

◆ IntWinTokenCheckCurrentPrivileges()

INTSTATUS IntWinTokenCheckCurrentPrivileges ( WIN_PROCESS_OBJECT Process,
QWORD  TokenPtr,
BOOLEAN  IntegrityCheck,
BOOLEAN PresentIncreased,
BOOLEAN EnabledIncreased,
QWORD Present,
QWORD Enabled 
)

Verifies the current token if the current Privileges.Present and Privileges.Enabled fields were not altered in a malicious way.

The checks performed on integrity on privileges are:

  1. The Present field inside Privileges should not increase. That means, no bits should be 1 if they were previously 0. Note that present privileges may decrease.
  2. Any bit set in the Enabled field of Privileges should also be set in the Present field. As the kernel checks against the Present bitfield when increasing a privilege in the Enabled bitfield, but when checking the privileges before accessing a resource, the kernel only checks the Enabled bitfield, we should ensure that there is a consistency between those fields. Note: This function might be called in cases where Process->OriginalTokenPtr != TokenPtr (e.g. the current token assigned to the given process has changed, but we have not yet updated Process->OriginalTokenPtr internally), when it is not this case, one might simply call this function with Process->OriginalTokenPtr as the second argument.
Parameters
[in]ProcessThe process for which the checks are done.
[in]TokenPtrThe GVA which points to the assigned token, may be different from Process->OriginalTokenPtr.
[in]IntegrityCheckThis should be set by the caller if this function is called during an integrity check on timer. If this parameter is set, the function will take into account the corner case in which there is a one bit difference between Enabled and Present privileges, due to a race condition between our checks and the privilege removal from the guest.
[out]PresentIncreasedIt will store a boolean representing whether the current privileges violate the first check.
[out]EnabledIncreasedIt will store a boolean representing whether the current privileges violate the second check.
[out]PresentThe current value in the Privileges.Present field.
[out]EnabledThe current value in the Privileges.Enabled field.
Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_INVALID_PARAMETER_1If a NULL Process has been given.
INT_STATUS_INVALID_PARAMETER_3If a NULL PresentIncreased has been given.
INT_STATUS_INVALID_PARAMETER_4If a NULL EnabledIncreased has been given.

Definition at line 811 of file wintoken.c.

Referenced by IntWinDpiValidateTokenPrivs(), and IntWinTokenPrivsCheckIntegrityOnProcess().

◆ IntWinTokenCheckIntegrity()

TIMER_FRIENDLY INTSTATUS IntWinTokenCheckIntegrity ( void  )

This function checks the integrity of the security token for all the processes inside gWinProcesses. The checks include both verifying if there are token pointers belonging to multiple processes, indicating a stolen token, and verifying if the Token Privileges have not changed in a malicious way, indicating a privilege escalation.

Return values
INT_STATUS_SUCCESSOn success.

Definition at line 1097 of file wintoken.c.

Referenced by IntHandleTimer().

◆ IntWinTokenFetchTokenAddress()

static INTSTATUS IntWinTokenFetchTokenAddress ( WIN_PROCESS_OBJECT Process,
QWORD OldValue,
QWORD NewValue 
)
static

Fetches the token pointer from inside the EPROCESS and returns the old token pointer and the new token pointer which may have changed at the time of the read.

Note: the returned old value and new value can be the same, one should check those value after calling this function in order to ensure that the token pointer has changed. If the change is considered alright, one should update Process->OriginalTokenPtr after calling this function.

Parameters
[in]ProcessThe WIN_PROCESS_OBJECT for which new token should be fetched.
[out]OldValueThe old token pointer, stored internally in OriginalTokenPtr field of WIN_PROCESS_OBJECT.
[out]NewValueThe value fetched from the Token field of the given EPROCESS inside the guest.
Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_PAGE_NOT_PRESENTIf the eprocess is not present in memory and the token cannot be read.

Definition at line 564 of file wintoken.c.

Referenced by IntWinTokenPrivsCheckIntegrityOnProcess(), and IntWinTokenPtrIsStolen().

◆ IntWinTokenPrivsCheckIntegrityOnProcess()

INTSTATUS IntWinTokenPrivsCheckIntegrityOnProcess ( WIN_PROCESS_OBJECT Process)

This function checks if the privileges bitfields for the given process have been changed in a malicious manner, sending an alert if needed.

Parameters
[in]ProcessThe WIN_PROCESS_OBJECT for which the privileges are checked in the assigned token.
Return values
INT_STATUS_SUCCESSOn success.

Definition at line 958 of file wintoken.c.

Referenced by IntWinProcCreateProcessObject(), IntWinProcDeleteProcessObject(), and IntWinTokenCheckIntegrity().

◆ IntWinTokenPrivsHandleSwap()

static INTSTATUS IntWinTokenPrivsHandleSwap ( void *  Context,
QWORD  VirtualAddress,
QWORD  OldEntry,
QWORD  NewEntry,
QWORD  OldPageSize,
QWORD  NewPageSize 
)
static

Handles a token swap-in or swap-out, re-applying protection if the token is not assigned anymore to a process. When a token is de-allocated and the whole page becomes free, as we increased on ExAllocatePoolWithTag the size of token allocations to always be one page, for performance purposes, the kernel may use the already freed page for different purposes, sometimes even mapping new physical memory into it. Since we have already hooked the page against writes, we will have, in such cases, translation violations, as the new mapping will not have the same contents as the TOKEN allocation which was freed before. Therefore we have to verify during the translation modifications of tokens if the current token is still assigned to the process and it was just swapped out, or if it has been freed, in which case we should re-establish the hook on the newly assigned token, while removing the old hook, which will solve the possibility of translation violations appearing in this case.

Parameters
[in]ContextThe page for which this handler is invoked.
[in]VirtualAddressThe guest virtual address for which this handler is invoked.
[in]OldEntryThe old page table entry used to translate VirtualAddress.
[in]NewEntryThe new page table entry used to translate VirtualAddress.
[in]OldPageSizeThe old page size.
[in]NewPageSizeThe new page size.
Returns
INT_STATUS_SUCCESS if successful, or an appropriate INTSTATUS error value.

Definition at line 356 of file wintoken.c.

Referenced by IntWinTokenProtectPrivsInternal().

◆ IntWinTokenPrivsHandleWrite()

static INTSTATUS IntWinTokenPrivsHandleWrite ( void *  Context,
void *  Hook,
QWORD  Address,
INTRO_ACTION Action 
)
static

EPT callback triggered when a write occurs over the Privileges bitfields in a nt!_TOKEN structure protected through EPT.

Parameters
[in]ContextThe process for which the given nt!_TOKEN structure has been associated with.
[in]HookThe GPA_HOOK structure which was set on the given token.
[in]AddressThe guest physical address on which the write took place.
[out]ActionThe action decided by the engine for the current violation.
Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_NOT_FOUNDIf a process is not given as Context.

Definition at line 429 of file wintoken.c.

Referenced by IntWinTokenProtectPrivsInternal().

◆ IntWinTokenPrivsProtectOnProcess()

INTSTATUS IntWinTokenPrivsProtectOnProcess ( WIN_PROCESS_OBJECT Process)

Updates the stored original Privileges bitfields (Present and Enabled) and hooks through EPT the Privileges inside the assigned token of the given process, if needed.

Parameters
[in,out]ProcessThe WIN_PROCESS_OBJECT for which the privileges information is stored, and for which a hook would be established on the assigned token, if needed.
Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_INVALID_PARAMETER_1If the given process is NULL.

Definition at line 1163 of file wintoken.c.

Referenced by IntWinProcCreateProcessObject(), and IntWinTokenProtectPrivs().

◆ IntWinTokenPrivsSendEptAlert()

static void IntWinTokenPrivsSendEptAlert ( EXCEPTION_KM_ORIGINATOR Originator,
EXCEPTION_VICTIM_ZONE Victim,
INTRO_ACTION  Action,
INTRO_ACTION_REASON  Reason 
)
static

Sends an EVENT_EPT_VIOLATION for a token privileges violation.

Parameters
[in]OriginatorThe originator driver which has written over the privileges.
[in]VictimDescribes the victim of the given violation.
[in]ActionThe action which was decided to be taken by the exceptions engine.
[in]ReasonThe reason why the given action was given.

Definition at line 73 of file wintoken.c.

Referenced by IntWinTokenPrivsHandleWrite().

◆ IntWinTokenPrivsSendIntegrityAlert()

static void IntWinTokenPrivsSendIntegrityAlert ( EXCEPTION_VICTIM_ZONE Victim,
INTRO_ACTION  Action,
INTRO_ACTION_REASON  Reason 
)
static

Sends an EVENT_INTEGRITY_VIOLATION when checks over the token privileges have failed.

Parameters
[in]VictimThe victim, which is denoted by the process for which the privileges are increased.
[in]ActionThe action taken by the exceptions engine.
[in]ReasonThe reason for which the given action has been taken.

Definition at line 125 of file wintoken.c.

Referenced by IntWinTokenPrivsCheckIntegrityOnProcess().

◆ IntWinTokenPrivsShouldHook()

static BOOLEAN IntWinTokenPrivsShouldHook ( WIN_PROCESS_OBJECT Process,
QWORD  NewTokenPtr 
)
static

Decides if the given token address should be hooked through EPT or not.

The decision is taken based on various considerations. If we have hardware support for sub page protection, we will always hook the tokens through EPT, as the impact will be much lower, even lower that with the "force allocation to page size" trick. On static detected processes, most likely the token was allocated before. On dynamically detected processes we will check if the token has been allocated with one page size, in which case we will hook the given token through EPT.

Parameters
[in]ProcessThe WIN_PROCESS_OBJECT for which the token would be decided if it should be protected or not.
[in]NewTokenPtrThe given token pointer.
Return values
TRUEIf the given token pointer should be hooked through EPT for the privileges protection.
FALSEIf protecting the given token through EPT would induce a high performance impact.

Definition at line 180 of file wintoken.c.

Referenced by IntWinTokenProtectPrivsInternal().

◆ IntWinTokenPrivsUnprotectOnProcess()

INTSTATUS IntWinTokenPrivsUnprotectOnProcess ( WIN_PROCESS_OBJECT Process)

Definition at line 1224 of file wintoken.c.

Referenced by IntWinProcRemoveProcess().

◆ IntWinTokenProtectPrivs()

INTSTATUS IntWinTokenProtectPrivs ( void  )

Protects all the currently unprotected tokens belonging to processes against privileges manipulation.

Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_NOT_NEEDED_HINTIf the option INTRO_OPT_PROT_KM_TOKEN_PRIVS is not activated.

Definition at line 1258 of file wintoken.c.

Referenced by IntGuestUpdateCoreOptions().

◆ IntWinTokenProtectPrivsInternal()

static INTSTATUS IntWinTokenProtectPrivsInternal ( WIN_PROCESS_OBJECT Process,
QWORD  NewTokenPtr 
)
static

If needed, this function will establish an EPT hook on the given token pointer for privileges protection. Note that, this function might get called in the case where Process->OriginalTokenPtr != NewTokenPtr, in order to re-establish the hook in the case where the token pointer has changed between timer ticks.

Parameters
[in]ProcessThe process for which the token should be protected or not.
[in]NewTokenPtrThe address of the token for which the privileges would be protected.
Return values
INT_STATUS_SUCCESSOn success.

Definition at line 276 of file wintoken.c.

Referenced by IntWinTokenPrivsCheckIntegrityOnProcess(), IntWinTokenPrivsHandleSwap(), IntWinTokenPrivsHandleWrite(), and IntWinTokenPrivsProtectOnProcess().

◆ IntWinTokenPtrCheckIntegrityOnProcess()

INTSTATUS IntWinTokenPtrCheckIntegrityOnProcess ( WIN_PROCESS_OBJECT Process)

This function checks if the security token of a given process has been stone from another process.

Parameters
[in]ProcessThe process whose token has to be verified.
Return values
INT_STATUS_SUCCESSOn success.

Definition at line 719 of file wintoken.c.

Referenced by IntWinProcCreateProcessObject(), IntWinProcDeleteProcessObject(), IntWinTokenCheckIntegrity(), IntWinTokenPrivsHandleSwap(), and IntWinTokenPrivsHandleWrite().

◆ IntWinTokenPtrIsStolen()

BOOLEAN IntWinTokenPtrIsStolen ( WIN_PROCESS_OBJECT Process,
BOOLEAN  Check,
WIN_PROCESS_OBJECT **  FromProcess,
QWORD OldValue,
QWORD NewValue 
)

This function checks if the security token of a given process has been stone from another process.

Parameters
[in]ProcessThe process who`s token has to be verified.
[in]CheckIf TRUE, gWinProcesses will be iterated to see if the token value is the same for another process (same thing happens if the original token pointer has been modified).
[out]FromProcessThe process where the token has been stolen from.
[out]OldValueThe old token.
[out]NewValueThe new token.
Return values
TRUEThe given process has a stolen token.
FALSEThe given process has its original token.

Definition at line 625 of file wintoken.c.

Referenced by IntWinDpiValidateParentProcessToken(), and IntWinTokenPtrCheckIntegrityOnProcess().

◆ IntWinTokenUnprotectPrivs()

INTSTATUS IntWinTokenUnprotectPrivs ( void  )

Unprotects all the currently protected tokens belonging to processes against privileges manipulation.

Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_NOT_NEEDED_HINTIf the option INTRO_OPT_PROT_KM_TOKEN_PRIVS is in fact activated.

Definition at line 1299 of file wintoken.c.

Referenced by IntGuestUpdateCoreOptions().

Variable Documentation

◆ gWinProcesses

LIST_HEAD gWinProcesses

The list of all the processes inside the guest.

Definition at line 11 of file winprocesshp.c.