Hello, world! In the previous posts on windows api exploitation for the red-blue team, you have seen that each process has a user and groups associated with it which provides different privileges to each process running in the system and such a system is called a multi-user multi-process operating system.
In the information-gathering phase, the more information you have about the target, the easy it gets to exploit the vulnerabilities on the system. So in this post, I will guide you through how to dump the access token information of the user from the process id.
The source of the topic can be found below 👇
Table of Contents
Overview of Tokens
Before moving forward, let me give you a quick introduction to the tokens. This is will give you a heads up on why I am writing this post and how it can be helpful for you while performing red teaming steps on the target organization.
In simple words – A token is basically an object that holds authorization information about the user and groups and a set of privileges which is then inherited by a process and then its threads. A process then tends to access certain resources and based on this token the OS then verifies whether the process is allowed to perform actions on the protected resources or not.
How does a Token is assigned to the Session?
Note: This information from the bird's eye. For more details and in-depth knowledge, please goto the links mentioned in the resources section
When a user provides their login credentials, they are securely hashed and provided to the Local Security Authority (LSA). The LSA provides a set of APIs to perform these checks from the SAM database which actually contains the passwords and usernames for the local accounts. If this authentication is done for an account connected to a domain, then LSA simply negotiates the authentication process with the Domain Controller (DC).
Once the authentication succeeds, the LSA will generate a login session for the account and also an access token. Every login session has a Locally Unique ID (
LUID) and multiple access tokens that contain Authentication ID which is then linked to the LUID of the session. This means that there is a Many to One relationship between login sessions and access tokens.
The following image explains the token creation process from the LSA and how this is stored with the login session.
This image has been shamelessly copied from the elastic.co blog and might go down in future.
You can get the logon sessions using
query session command in the cmd prompt
How does a Token is Inherited by the Processes?
After the authentication of a user session is successful, a process is used to create the
explorer.exe in an interactive session and then it exists. During the child process creation, it is inherited by the initial process and then
explorer.exe stays running. Then other processes are started from the parent process explorer.exe which inherits the token information and so on.
For instance, in this case, I have created a user name testuser and after login, I have started
powershell.exe as the first interactive process. In the screenshot below, you can see the process details has
explorer.exe as the parent process with process id 3068.
Let's find all processes other than PowerShell that were started by the
explorer.exe process in the login session of the
Now you must be wondering what is the parent process id of the explorer process itself. Well, that process is already finished and our poor explorer.exe is now an orphan process.
Getting Process and Token Handle
The process of dumping tokens starts with opening the process handle and then getting the process token from that handle. So let's start by implementing this
In the case of protected processes, the function will fail when
PROCESS_QUERY_INFORMATION is passed as the desired access right. Therefore to get at least minimal details about the process, let's use
PROCESS_QUERY_LIMITED_INFORMATION access rights. For more details of available access rights, you can check this link.
Once this process handle is opened, print the basic details of the process like its name and process id and parent process id. The code related to this functionality is already implemented in the
ProcessDetails.h file. These details are fetched from the
CreateToolhelp32Snapshot function which is already explained in the Windows Process Listing using ToolHelp32 API.
Let's now get the token from the process using the
OpenProcessToken() function with
TOKEN_QUERY_SOURCE access rights used to query the information from the valid token handle. All the available access rights for the token can be found here.
Dumping Information from the Token Handle
Each part of the token dumping class is defined in the different header files starting with the "Token" in the name. Let's start off in the order of dumping. All the following functions discussed will be using
GetTokenInformation() function with the appropriate value from
Dumping Session ID
As we have discussed each login has a different set of access tokens and each access token has an authentication id field which is the unique identifier of the session id. But in the system, the session is represented as a
DWORD value. Use
TokenSessionId value from the token information enumeration for this information. The source code of this can be found in the
Dumping Statistics (Memory Usage and Token ID)
As you know each token has its unique identity and also contains the information of the login session. Apart from this it also contains the total memory usage and is available to store further information about the access rights of the user. Such details are supposed to be stored somewhere in the token, thus having their place in token statistics. You can find the implementation in the
TokenStatistics.h file. Use
TokenStatistics class from the enumeration which will return the buffer of
AuthenticationId fields are in the form of LUID structure, thus containing the high part and the low part. This is a numerical form but you can print the hexadecimal format with
std::hex and restore the decimal format using
std::dec format specifiers.
DynamicAvailable contains the information of the size of information stored in the token and total space left in the container to hold more information. These values change on the basis of the type of token, user and privileges that exist or are enabled in it. Since these are in the form of bytes, you can use
StrFormatByteSize which is a macro defined for
Dumping Domain Name and Account Name
Tokens also contain the current user who has been logged in and this token associated with. In fact, when we have tried to get the user from the processes via WTS Api, the function internally looked into the process token to obtain this information. The implementation can be found in the
You can use the
TokenUser property from the enumeration which will return the
TOKEN_USER structure in the buffer containing only one field containing information of the user.
The SID structure has pretty weird fields that aren't in the scope of this post. Since this serialization will be used in groups, so I have created a utility function
SIDSerialize(PSID) which internally calls the
To free the memory allocated by the serializing function, use the
Similar to the above case, account name and domain name lookup will be used in the following dumping process. Therefore I have created another utility function
GetDomainAccount() to carry out this workload for us. This function internally calls the
LookupAccountSidW() function to get the details from the SID.
The function also returns the type of SID which is then serialized by the
GetSidType(SID_NAME_USE) function from the local utility file.
Dumping Groups Details
To make things easy, the operating system or the administrator assigns certain groups to the user account that provides special privileges to all the users under that umbrella. Since there can be multiple groups to provide multiple rights to the same user, the
TokenGroups enumeration will return the
TOKEN_GROUPS structure containing information of total groups and their SID information. The implementation can be found in the
GroupCount property will contain the total number of elements in the
Groups array. It can be used to iterate over this list as shown below
Dumping Privileges and Status
We have already discussed privileges in the WTS Api #2, that provides additional access to the processes to do a specific task even though the process is running with an administrator user. It can be dumped this information by providing the
TokenPrivileges enum value in the function, and implemented in the
This will return a buffer of structure
TOKEN_PRIVILEGES, which contains the
PrivilegeCount field, which will contain the total count of the privileges in the
Privileges array. Remember we used the function to get the LUID of the privilege from its name, well there is a function that will give you the name of the privilege from its LUID, that is
LookupPrivilegeNameW. As a bonus, there is a function used to get the short description of the privileges, like what it is primarily used for. You can get this information via
The description and names of this status can be found here – https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-token_privileges
Dumping the Token Owner
Well in windows, you can create the resources with certain security descriptors that provide additional access information for them. Such information can be retrieved via
TokenOwner enum value. The implementation can be found in the
This will return the information in form of the
TOKEN_OWNER structure containing
Owner field as the reference to the SID of the security descriptor's owner. Let's use our utility function to get the serialized value of the SID and its account details.
Dumping Primary Group of the Token
With the owner details of the token security descriptor, you can also find the group details using
TokenPrimaryGroup value from the enumeration and the implementation can be found in the
This will return the buffer of type
TOKEN_PRIMARY_GROUP structure and only one SID reference for the group –
PrimaryGroup. Now call the functions to get the serialised SID string and the account details as shown below.
Dumping Source of the Token
The token is of course issued by the LSA but the actual source can be different. This information is based on the type of user or group is being authenticated and the type of resources that the account is being authenticated for. This information can be retrieved by
TokenSource enum value and is implemented in the
The buffer returned will be of a type
TOKEN_SOURCE that contains two fields, but the field we are interested in —
SourceName, which is used to identify the token sources. For example, one of the most common values is User32 which is for the tokens created by Session Manager for interactive logins.
Since the LUID consists of two fields –
LowPart. Both of these are actually numbers and you can print uppercase hexadecimal format by using
std::hex cout stream formatters. And to reset this you can use
std::dec for the decimal base of numbers and
std::nouppercase to prevent enforcing uppercase letters during formatting.
Dumping Type of the Token and Impersonation Level
In windows, there are basically two types of tokens –
primary which are created by the windows kernel and provide default security descriptions, and
impersonation which are created in the userland and is used to create the resources on behalf of another user. You can get whether the specific token of a process is primary and impersonated, by using
TokenType enum value. This is implemented in the
The buffer will return the
DWORD of enum type
TOKEN_TYPE. Additionally, you can print the level of the impersonation, when the token is of impersonation type. This can be done by providing
TokenImpersonationLevel enum value. This will provide you with the buffer of type
Dumping Elevation Status
If a privileged user logs into the system that can perform privileged actions on the system, then the token must be elevated. You can get this information by providing
TokenElevationType enum value in the function and find its implementation in the
This will return the data of
TOKEN_ELEVATION structure which contains only one field –
TokenIsElevated is a DWORD (an overkill I would say) which only contains a nonzero value if the token has elevated privileges; otherwise, a zero value. Well, if this is a truthy value, you can also get the elevation type from it, which will be one of three values in the
Dumping Restricted Token Status
With an existing access token, you use the CreateRestrictedToken function to create a new token with restricted or limited access to the resources by relinquishing the one allowed in the original token, thus the token is called a restricted token. You can get this status of whether or not the current is a restricted one using the
TokenHasRestrictions value from the enumeration. This is implemented in the
This will return a DWORD which can be either nonzero if the token is restricted, otherwise a zero value.
Dumping Sandboxing Status
While creating the restricted token, you can pass an additional flag
SANDBOX_INERT to it. If this value is used, the system does not check AppLocker rules or apply Software Restriction Policies. You can get this value by using the
TokenSandBoxInert value from the enum and implementation can be found in the
The buffer receives a DWORD value that is nonzero if the token includes the
Code in Action 🚀
The following video contains a demonstration of token information dumping of
procexp64.exe processes and each execution will show a different set of information about users, groups, token privileges etc.
Note: After recording this video the program was changed and so does the output. There will be some difference, but the code works in the similar way.