Detection engineers are frequently beset with the challenge of detecting a technique for which optics are poor, non-existent, or difficult to collect at scale. Access Token Manipulation (T1134) is a great example of a technique where one such telemetry gap or challenge exists.
In this installment of Better Know a Data Source, we’re going to dive deep into access tokens, exploring the potential value they provide, the challenges they present, why it isn’t reasonable to collect all token-related telemetry, and how practitioners or vendors can best instrument their tools to enable new detection optics.
Access tokens are securable objects that specify the security context of Windows processes and threads. The security context of a securable object consists of the user’s security identifier (SID), group membership, and privileges. These can all be thought of as token attributes.
Let’s break down two types of access tokens:
Primary tokens are applied to a process. By default, when a thread attempts to interact with a securable object, this token is checked to validate the authorization attributes of that user. This token belongs to the user account that created the process.
Impersonation tokens are applied at the thread level, allowing a thread to interact with a securable object under a different security context than that of the primary token.
Unless a token is explicitly set for a thread, all threads inherit the token of the primary thread (i.e., the first thread started in a process) and all actions the process takes will fall under the security context of the primary token.
Why do we care?
Everything a user account does on a system ties back to an access token. These tokens then relate back to the logon sessions where the access token was generated in the first place. In other words, nearly all activity in Windows can be tied back to an identity using access tokens, and therefore, having the ability to track a token back to its source provides invaluable visibility for incident response, detection, and more.
Adversaries commonly impersonate, steal, or otherwise abuse tokens. Token impersonation in particular comes in many different flavors, so let’s examine the behavior in its simplest form: a source process obtains a handle to a target process’s token (running under a different security context) and applies it to a thread within the source process.
This allows an attacker to act on behalf of the target’s more highly privileged token and perform activities they wouldn’t have been able to otherwise.
As far as we know, no vendor currently has explicit telemetry that can definitively determine that token impersonation is happening at scale. To that point, it’s disappointing when you identify an optic that could help detect an attack or adversary technique, but you can’t find a log source that readily collects the telemetry you want. However, as defenders, we’re equipped to understand why optics gaps exist and, ideally, what would be required to obtain the desired optics.
What would it take to obtain access token telemetry?
The majority of telemetry we see from vendors comes from kernel notification routines (e.g., PsSetCreateProcessNotifyRoutine), consuming events from ETW providers, function hooking, and mini-filter drivers (typical for file system monitoring), to name a few sources.
There are two primary data collection strategies: pull or push methods. A pull method involves functions being called on a periodic basis to pull the desired data. A push method gathers data on-demand in response to a specific trigger. Both strategies have their respective pros and cons, so we’ll examine both.
The pull method
The first thing we want to understand is which attributes offer the most potential value to a detection engineer. There are a couple of ways to see what token attributes are available, including by parsing symbols using WinDbg (a popular debugging tool), but Microsoft also provides documentation within the TOKEN_INFORMATION_CLASS enumeration type. Unfortunately, it’s not always the case that Microsoft supplies documentation of complex kernel data structures.
Within WinDbg, however, the address of the token can be accessed within EPROCESS structures. Every user-mode process is backed by a kernel data structure known as an EPROCESS. There are various fields that make up this structure. These fields are basically the attributes that make up a process. These attributes can be values, embedded structures, or pointers to other structures. The attribute that we are interested in is Token. Within the WinDbg output, we can see that Token is accessed via an _EX_FAST_REF structure. This can be seen with the output below:
This EX_FAST_REF structure is made up of 3 members:
Object points to a kernel object
RefCnt tracks active references to an object
Value states the value of the token structure
To find the token structure, we’ll want to look at the Object attribute. Within WinDbg, if you target a specific process and print its attributes using the !process command, it will show you the virtual memory address of the token object for that process. However, since we care about how a vendor would go about getting these optics, we are going to step through it with debugger commands.
Let’s look at this in real time and walk through what it would take to get one token attribute.
We attached the kernel-mode (kd) variety of the WinDbg debugger to a target host in order to enumerate the token attributes of a spawned process. The token attribute we’re looking for is known as the LogonID (TOKEN!AuthenticationId), which relates back to a logon session of a user who was successful. This is a common attribute we see within a lot of telemetry sensors. Here are the steps you can follow to collect it:
Obtain the virtual address of the EPROCESS structure.
Parse the EPROCESS structure for that process.
Dereference the values within the EPROCESS token attribute structure: _EX_FAST_REF.
Dereference the TOKEN structure from the Object member of the _EX_FAST_REF.
Extract the TOKEN attribute of choice ≈
Obtain the virtual address of the target process
If the PID of the target process is 6320, you would run the following within WinDbg:
kd> !process 0n6320
This will spit out a lot of great information, but the thing we care most about is PROCESS <Virtual Memory Address>, the address of the corresponding EPROCESS structure.
Parse the EPROCESS structure for that process
We then take the virtual address of the EPROCESS structure and look for the token attribute that exists within the EPROCESS structure by using dt (display type) in WinDbg. The output informs us that _EX_FAST_REF is stored at an offset of 0x4b8 from the beginning of the EPROCESS structure.
Note: these offsets are subject to change across Windows versions.
Dereference the values within the EPROCESS token attribute structure: _EX_FAST_REF
We will use dt again to pull the information from _EX_FAST_REF
.dt nt!_EX_FAST_REF ffffc087622cd0c0+0x4b8
Within the following output, we have three values: Object, RefCnt, and Value. Again, we care the most about Object, but we can’t just pull this value because it isn’t the true pointer address to the token structure. _EX_FAST_REF uses unions that enable memory conservation by allowing all members to share the same memory location. This is easy to tell because all of the offsets are 0.
Here we can see the primary token attributes provided to us. The majority of them are backed by other enums or structures that we would have to dereference to get the proper value. The LogonId is one of those attributes where the actual attribute is called AuthenticationId, and it’s backed by the local identifier (LUID) structure. Fortunately, this is relatively easy to enumerate.
Extract the TOKEN attribute of choice
To enumerate the AuthenticationId attribute, take the value of the TOKEN structure and the offset of the AuthenticationId attribute and call dt to display the LUID structure value.
Above we can see Windows Security Event 4624 pulling the AuthenticationId attribute (called LogonId within the event) with NtObjectManager. Both have the same value as the output we got within WinDbg.
There are other token attributes that are even harder to gather due to the nested enums and structures. Obtaining the IntegrityLevel is one. Yarden Shafir wrote a good blog on enumerating this value: Exploiting a “Simple” Vulnerability, Part 2 – What If We Made Exploitation Harder?
I mentioned at the beginning of this section that in order to pull this information, vendors would have to do a combination of API hooking and data structure parsing. We have seen the relevant data structures at this point, but what API could we hook? In all likelihood, a vendor would want to hook the NtQueryInformationToken kernel-mode function. This is one of the few native functions that Microsoft documents for us.
This function uses the TOKEN_INFORMATION_CLASS enumeration to pull token attributes, which ultimately still requires you to go through all the same structures we went through to get the attribute values above.
However, for those who are curious, there’s a way to do this in user-mode (although it shouldn’t be used for realtime collection, but is useful for point-in-time collection). The Win32 API version of NtQueryInformationToken is GetTokenInformation.
Here’s the script I wrote to pull token attribute information:
PS C:\> Invoke-TokenCollection -ProcessId 5764
[*] Impersonating SYSTEM
[*] System impersonation passed
[*] Current User: NT AUTHORITY\SYSTEM
Title : Displaying Process/Primary Information
ProcessName : powershell
SessionId : 1
PID : 5764
ProcessTokenUserName : DESKTOP-T4KGJQR\TestUser
ProcessTokenSid : S-1-5-21-3526241117-3673060432-1951554585-1000
ProcessTokenOwnerSid : S-1-5-21-3526241117-3673060432-1951554585-1000
ProcessTokenOwner : DESKTOP-T4KGJQR\TestUser
ProcessTokenType : TokenPrimary
ProcessTokenId : 10923619
TokenIntegrityLevel : MEDIUM_MANDATORY_LEVEL
[*] Reverting back to original user
[*] Current User: DESKTOP-T4KGJQR\TestUser
Here’s what it would look like to retrieve token attributes from a process:
Note: This diagram shows the process flow for obtaining TOKEN information starting from user-mode. If a driver was implemented, then the process would start in kernel-mode where the first function called is ZwQueryInformationToken (functionally the same as NtQueryInformationToken).
The above only shows what it would take to get information from the perspective of a process primary token, but to see impersonation tokens, we need insight on a thread level. So imagine enumerating all of those structures AND enumerating each thread within a process to see if it was impersonating or not. If it is impersonating, then you have to go and find the user it’s impersonating, which is even more intensive.
From a high level, here’s what it would take:
Enumerate a process’s information.
Enumerate active threads of that process (a list of all threads that belong to a process is stored in the ThreadListHead attribute and these values are linked to the ETHREAD.ThreadListEntry attribute).
If the thread is impersonating, then check the ActiveImpersonationInfo attribute within ETHREAD, which will have a value of 1.
If the thread is impersonating, examine the ClientSecurity attribute (backed by the PS_CLIENT_SECURITY_CONTEXT structure), which will point to the impersonation token.
Enumerate the TOKEN structure and attributes with the passed-in token address to figure out what security context the thread is running under.
The push method
This strategy will have the components of the pull method above but will start with a trigger. The process will look something like this:
Driver is implemented.
ObRegisterCallbacks routine registers a notification routine (pre-operation callback—ObPreOperationCallback, for example). This routine is put in place so that the kernel notifies this function when a handle is requested to a process, thread, or desktop (trigger) prior to issuing or denying the handle.
ObPreOperationCallback will take action on Process Type handle operations and then implement all the code needed to obtain the desired information (call to Se/ZwQueryInformationToken). The steps here will be similar to the above pull method, but since it’s in the kernel, only kernel functions will be called to complete the enumeration of the tokens related to source and target process and threads.
Note: The pre-operation callback is used so that some type of action (logging, in this example) can take place before the handle is granted. There are post-operation callbacks, but the desired action will happen after the handle operation has been completed.
Both of these strategies would require evaluating the token structure for every process and every active thread that performs an open handle operation to another process or thread-type object (assuming no exclusion logic is applied). That will cause a lot of noise and f ingestion and/or storage issues. Thus, as of today,it isn’t necessarily feasible to collect these token-related optics at scale.
What token attributes are already collected for us?
Although token information from a thread level is hard to collect in a cost-effective fashion, a lot of data sources provide token attributes in general. Refer to the metadata below from various events.
Although the optics listed above are great, they will not give insight into token impersonation techniques. To truly detect token impersonation, you have to collect token telemetry at the thread level.
As threat researchers, we should continually strive to find new optics—or improve existing ones—if they can help us better observe, detect, and prevent emergent or existing adversary tradecraft. When we identify poor optics or optics that aren’t readily available, we need to figure out why that is, so we can work toward a better solution. This access token research makes it abundantly clear why relevant telemetry is hard to come by. Even so, the methodology described in this article offers important insight into the inner workings of Windows internals and the interplay between detection value and cost.
As I researched this blog, Matt Hand was a tremendous resource for all things Windows Internals and WinDbg. A thank you to him for his mentorship during this process.
Privacy & Cookies Policy
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.