Building a custom tool to enumerate Windows Access Tokens for red team tradecraft
Learn how to build a simple tool to enumerate Windows Access Tokens during a red team engagement.
Towards the end of the last post, I mentioned that most token enumeration tools use following Windows APIs to retrieve token information:
GetCurrentProcessId() - To retrieve the process ID of the current process. This is required if token information of the current process is to be retrieved.
OpenProcess() - To obtain a handle to the target process. This API needs a process ID. This process ID can be passed during runtime or can be retrieved using APIs like GetProcessId() or GetCurrentProcessId().
OpenProcessToken() - To obtain a handle to the token of the target process.
GetTokenInformation() - To retrieve the token information of the target process.
Before we delve into other tools that can be used for enumerating access tokens, lets write a custom token enumeration tool to understand the underlying functionality. The code given below is written in C++ and can be compiled using Visual Studio 2022.
Lets start by getting a process ID to work with:
unsigned int pid = 0;
std::cout << "\n\nDo you want to retrieve token information for the current process? (y/n): ";
char choice;
std::cin >> choice;
if (choice == 'y' || choice == 'Y')
{
// If the user ansers yes, retrieve the current process ID.
pid = GetCurrentProcessId();
if (pid == 0)
{
std::cerr << "\n\nGetCurrentProcessId failed. Error: " << GetLastError() << std::endl;
return 1;
}
}
else {
// If the user answers no, ask the user to enter a process ID.
std::cout << "\nEnter the process ID: ";
if (!(std::cin >> pid))
{
std::cerr << "\nInvalid input. Please enter a valid process ID.";
return 1;
}
}
std::cout << "Selected Process ID: " << pid << std::endl;
The above code asks the user if they want to retrieve the token information of the current process or not. If the user answers yes, it uses GetCurrentProcessId()
to retrieve the process ID of the current process. If the user answers no, it asks the user to enter a process ID. The selected process ID is then displayed as the output.
Next, we’ll obtain a handle to the selected process.
// Getting the handle for the current process
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (hProcess == NULL)
{
std::cerr << "\n\nOpenProcess failed. Error: " << GetLastError() << std::endl;
return 1;
}
std::cout << "\n\nObtained handle to the current process: " <<hProcess;
The above code uses OpenProcess()
to obtain a handle to the selected process. If the call to OpenProcess()
fails, the user is notified of the same along with the error that occurred. This error is retrieved using GetLastError()
. If the call succeeds, the process handle is displayed as the output.
Once we have the handle to the process, we can use it to retrieve the handle to it’s access token.
HANDLE hToken = NULL;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
{
std::cerr << "\n\nOpenProcessToken failed. Error: " << GetLastError() << std::endl;
CloseHandle(hProcess);
return 1;
}
std::cout << "\n\nObtained handle to the current process token: " << hToken;
The above code uses OpenProcessToken()
to obtain a handle to the access token of the selected process. If the call to OpenProcessToken()
fails, the user is notified of the same along with the error that occurred. This error is retrieved using GetLastError()
. We also close the process handle obtained earlier. If the call succeeds, the token handle is displayed as the output.
Next, we will use the token handle to retrieve token information. Since this is a basic token enumerator, we will just retrieve the username and the domain name.
// Retrieving token information length
DWORD tokenLength = 0;
GetTokenInformation(hToken, TokenUser, NULL, 0, &tokenLength);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
std::cerr << "\n\nFailed to get token information size. Error: " << GetLastError() << std::endl;
CloseHandle(hToken);
CloseHandle(hProcess);
return 1;
}
std::vector<BYTE> tokenInfo(tokenLength);
if (!GetTokenInformation(hToken, TokenUser, tokenInfo.data(), tokenLength, &tokenLength))
{
std::cerr << "\n\nFailed to get token information. Error: " << GetLastError() << std::endl;
CloseHandle(hToken);
CloseHandle(hProcess);
return 1;
}
std::cout << "\n\nRetrieved token information";
Retrieving token information is a two step process.
Get the size of the token information buffer - The
GetTokenInformation()
requires a buffer of appropriate size to be passed as an argument. This buffer will be filled with the required token information. Since, we do not have this information beforehand, we first callGetTokenInformation()
with a value from the TOKEN_INFORMATION_CLASS enum and a NULL buffer. The sepcified value will depend on the type of information we want to retrieve. The size of the buffer will depend on this value. In the above code, we have specified TokenUser as the value fromTOKEN_INFORMATION_CLASS
enum.Retrieve token information - Once we know the buffer size, we can allocate a buffer of appropriate size and pass it to
GetTokenInformation()
along with the selected value fromTOKEN_INFORMATION_CLASS
and the buffer size to retrieve the required token information.
In either step, if the call to GetTokenInformation()
fails the user is notified of the same along with the error that occurred. This error is retrieved using GetLastError()
. We also close the process and token handles obtained earlier. If the call succeeds, the code moves to the next step, extracting the user SID.
// Extracting user SID
TOKEN_USER* tUser = reinterpret_cast<TOKEN_USER*>(tokenInfo.data());
PSID psid = tUser->User.Sid;
// Converting User SID to username and domain name.
char userName[256], domainName[256];
DWORD userNameSize = sizeof(userName);
DWORD domainNameSize = sizeof(domainName);
SID_NAME_USE sidType;
if (!LookupAccountSidA(NULL, psid, userName, &userNameSize, domainName, &domainNameSize, &sidType))
{
std::cerr << "\n\nFailed to lookup account SID. Error: " << GetLastError() << std::endl;
CloseHandle(hToken);
CloseHandle(hProcess);
return 1;
}
std::cout << "\n\nUser Name: " << userName << "\nDomain Name: " << domainName ;
std::cout << "\nUser: " << domainName << "\\" << userName << std::endl;
The above code extracts the user SID from the retrieved token information. It then converts this SID to the username and the domain name with the help of LookupAccountSidA()
. If the call fails the user is notified of the same along with the error that occurred. This error is retrieved using GetLastError()
. We also close the process and token handles obtained earlier. If the call succeeds, the username and domain are displayed as the output.
Finally, we close the process and token handles.
CloseHandle(hToken);
CloseHandle(hProcess);
The entire code is available at 100 Days of Red Team GitHub repository.


As the name implies, we have implemented very basic functionality in this token enumeration tool. It can be developed further to retrieve and display additional token information elements. To get some ideas, check out the Token Dump tool. Gurkirat Singh (tbhaxor), developer of Token Dump, has published a detailed write-up of how Token Dump works behind the scene.
Red Team Notes
- We can use OpenProcess(), OpenProcessToken() and GetTokenInformation() Windows APIs to build a simple token enumeration tool.
- The code for Basic Token Enumerator tool is available here.
Follow my journey of 100 Days of Red Team on WhatsApp, Telegram or Discord.