4 min read

Abusing Windows Token

References

I came across this reference while browsing my twitter feed and thought it was pretty interesting. This blog post just captures some of the highlights from the article.

The gist of the article is explaining how token manipulation can be carried out to impersonate a user without touching the lsass process. The key highlight is how to create a process as another user. The article steps through how to steal and manipulate Access Tokens.

The following contains interesting notes or copied snippets from the first article in the reference.

Todo: I intend to break these down into their individual parts to better understand access tokens :)

Introduction

Primary Token vs. Impersonation Token

  • A primary token is associated to a process while an impersonation token is associated to a thread
  • A primary token is obtained after authenticating interactively while an impersonation token is obtained after authenticating remotely

Listening and Duplicating Tokens

  • use NtQuerySystemInformation to get a list of handleTableInformation
  • loop over all these handles, we are going to check if we can acquire a handle on the process that owns this handle. The reason we are doing this is because in order to manipulate a handle we need to duplicate it and in order to duplicate a handle we need to have access to the process that owns it.
  • if you don't own it, you need the SeDebugPrivilege
  • You can get a duplicate handle, however the dupHandle now contains the duplicated handle that we can use on our own. However we don’t know to which type of Windows’ objects this token is associated to. Thus we will have to check manually using the NtQueryObject function which returns a string (the type of object): `
GetObjectInfo
LPWSTR GetObjectInfo(HANDLE hObject, OBJECT_INFORMATION_CLASS objInfoClass) {
    LPWSTR data = NULL;
    DWORD dwSize = sizeof(OBJECT_NAME_INFORMATION);
    POBJECT_NAME_INFORMATION pObjectInfo = (POBJECT_NAME_INFORMATION)malloc(dwSize);

    NTSTATUS ntReturn = NtQueryObject(hObject, objInfoClass, pObjectInfo, dwSize, &dwSize);
    if ((ntReturn == STATUS_BUFFER_OVERFLOW) || (ntReturn == STATUS_INFO_LENGTH_MISMATCH)) {
        pObjectInfo = (POBJECT_NAME_INFORMATION)realloc(pObjectInfo, dwSize);
        ntReturn = NtQueryObject(hObject, objInfoClass, pObjectInfo, dwSize, &dwSize);
    }
    if ((ntReturn >= STATUS_SUCCESS) && (pObjectInfo->Buffer != NULL)) {
        data = (LPWSTR)calloc(pObjectInfo->Length, sizeof(WCHAR));
        CopyMemory(data, pObjectInfo->Buffer, pObjectInfo->Length);
    }
    free(pObjectInfo);
    return data;
}
  • use GetTokenInformation to filter tokens handle. The purpose is to check whether this is a primary token or an impersonation token, who created this token and to whom it is associated.
  • Tokens can be manipulated via DuplicateTokenEx

Impersonate LoggedOn User

  • This function is probably the most powerful one as it allows you to impersonate a user and run code on their behalf
  • All we need to do is to give it a duplicated token, let’s say the duplicated token of the NT AUTHORITY\SYSTEM account, in order to run code as the NT AUTHORITY\SYSTEM account. Internally this function simply changes the token of our current thread to the one we gave it (the duplicated one).
  • to change back RevertToSelf
  • The only prerequisite you will need is to have the SeImpersonatePrivilege. Luckily, this privilege is always available to local administrator accounts.
  • this approach allows you to run code but not a process because the new process uses the token of the calling process - not the impersonated one

CreateProcessAsUserW and CreateProcessWithTokenW

  • It is not possible to use the CreateProcessAsUser if you do not hold enough privileges. To be more precise, using the CreateProcessAsUser requires having the SeTCBPrivilege which is the highest privilege of the entire Windows operating system. The only account that owns this privilege is the system itself
  • We can over leverage the CreateProcessWithTokenW function without holding particular privileges (other than being a local administrator).
  • First, you need to escalate to System via the DuplicateTokenEx
  • This approach won't work if you try to impersonate a user because

When we tried to manipulate the token of the WHITEFLAG/Administrateur account, we duplicated an object from a user located in session n°2 and tried to use it in the session of the local administrator account (session n°1). However, if we take a deeper look at how the CreateProcessWithTokenW function works, we will see that this function uses the session id to spawn the graphical process. Since the stolen token has a session id that does not match our session the graphic window crashes.

DWORD current_token_session_id;
ProcessIdToSessionId(GetCurrentProcessId(), &current_token_session_id);

Once we have got the ID of our session, we’ll have to overwrite the value contained in the SessionId attribute of the duplicated token. This can be done using the SetInformationToken function:

SetTokenInformation(duplicated_token, TokenSessionId, &current_token_session_id, sizeof(DWORD))
  • When you use the CreateProcessWithTokenW function a new graphical window pops. This won't work in this context as we do not have a graphical window. However, we can leverage CreateProcessAsUserW since it doesn't spawn a graphical window
if (!SetTokenInformation(duplicated_token, TokenSessionId, &current_token_session_id, sizeof(DWORD))) {
    printf("[!] Couldn't change token session id (error: %d)\n", GetLastError());
}
printf("[*] Impersonating %ws and launching command [%ws] via CreateProcessAsUserW\n", found_tokens[k].user_name, command);
CreateProcessAsUserW(duplicated_token, NULL, command, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);