In the previous blog-post, we learned about the Tokens and Privileges which are assigned to the user by the system. In this blog series, we will take a look at the technical aspects of the Token Manipulation Attack. Here, we will be using Microsoft Win API’s in C++ for demonstrating the attack process.
TECHNIQUES
Technique 1: Creating a process with SYSTEM level privileges by Duplicating the Primary Token of a process running under the security context of SYSTEM.
Below diagram shows the steps of how to create a process with higher privileges.
As we want to create a process with SYSTEM level privileges, we have to access the process which is running under the SYSTEM account. It is not allowed to access a process of SYSTEM level from another process running under a different account with fewer privileges. In order to access the SYSTEM process, the SE_DEBUG_NAME privilege has to be enabled for the calling processes token.
In the previous blog, we saw that these “God mode” privileges are not available to the standard users. The users who are a part of the Administrator group with the Elevated Token are assigned these “God mode” privileges. So, to achieve this scenario the pre-requisite is that the user is already a part of the administrator group. That means somehow you have to gain the higher-level user access through UAC bypass or any other exploits.
Note: In 2017, Google Project Zero’s James Forshaw did great research and presentation on the topic “Abusing access token for UAC Bypass”. In which he was able to duplicate a token of the elevated process from a non-elevated process. This bug was patched in October 2018 by Microsoft, which was also neatly covered by Forshaw in the blog. Do read the three–part blog series on this topic.
THE CODE
Now, once the user is a part of the Administrator group, we have to enable the SE_DEBUG_NAME privilege. By default, this privilege is disabled for the users of the administrator group, but for the local system, this privilege is enabled by default.
Below is the code for Enabling the privilege of a current process:
BOOL EnableTokenPrivilege(LPTSTR LPrivilege)
{
TOKEN_PRIVILEGES tokenpriv;
BOOL bResult = FALSE;
HANDLE hToken = NULL;
DWORD dwSize;
ZeroMemory(&tokenpriv, sizeof(tokenpriv));
tokenpriv.PrivilegeCount = 1;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken) &&
LookupPrivilegeValue(NULL, LPrivilegeSE_DEBUG_NAME, &tokenpriv.Privileges[0].Luid))
{
tokenprivp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bResult = AdjustTokenPrivileges(hToken, FALSE, &tokenpriv, 0, NULL, NULL);
}
else
{
_tprintf(L"Open Process Token Failed with Error Code: %d\n", GetLastError());
}
CloseHandle(hToken);
return bResult;
}
- Here, we are passing the Privilege we want to enable as an argument. Then we declare a TOKEN_PRIVILEGES struct tokenpriv. This struct contains information about the privileges related to that token.
- Then we open the token of the current process using OpenProcessToken() and receive the handle in the hToken handle (Instead of opening a process handle using OpenProcess() we are directly accessing the current process using GetCurrentProcess() function).
- The LookupPrivilegeValue() function gives the LUID of the specified Privilege using which the local system identifies the privilege. We are retrieving the LUID in the tokenpriv struct which we declared.
- If both the OpenProcessToken() and LookupPrivilegeValue() function succeeds, then we set the Privilege attribute of the tokenpriv struct to SE_PRIVILEGE_ENABLED. This indicates the Privilege is enabled.
- Then using the AdjustTokenPrivileges() function we enable the privilege for that given token. We pass the token handle hToken and the TOKEN_PRIVILEGES struct which has the privileges and the attributes. If this function is successful, then the specified Privilege will be enabled.
After the Debug privilege is enabled, we will open a Process Handle of a SYSTEM level process. Below is the code snippet of opening a process handle:
HANDLE hProcess = NULL;
int pid = _wtoi(argv[1]);
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
if (!hProcess)
{
_tprintf(L"Cannot Open Process. Failed with Error Code: %d\n", GetLastError());
CloseHandle(hProcess);
}
- Here, we declare a HANDLE hProcess for receiving the process handle.
- By using the OpenProcess() function, we will open the handle to the process ID specified in the variable pid. This pid can be given directly or can be accepted by the user. We are accepting the process id from the command line arguments.
- In OpenProcess() function the first parameter is the desired access required on the process. We have used PROCESS_QUERY_INFORMATION access which will give us information like Token, exit code and priority class. Here is the list of process security and access rights which we can use for opening a process handle. These access rights should be used as per requirement.
- If this function is successful, we receive the process handle of the specified process in the HANDLE hProcess.
The next thing to do is opening a process token from the already opened process handle. Our primary task is to open the process token of the target process and duplicate it and create a new process with that duplicated token. Token also has the security rights and access parameters which define the level of access which we want to acquire.
Below is the code snippet of the opening a process token:
HANDLE hToken = NULL;
if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_DUPLICATE, &hToken))
{
_tprintf(L"Cannot Open Process Token. Failed with Error Code: %d\n", GetLastError());
CloseHandle(hToken);
CloseHandle(hProcess);
}
- We are declaring a HANDLE hToken which will give us the token handle. Using the OpenProcessToken() function we will open the token of the specified process. We will pass the hProcess handle in the process handle parameter which was received by the OpenProcess() function.
- Now in the second parameter, we have to mention the access we require on the token to duplicate it. We are specifying TOKEN_QUERY and TOKEN_DUPLICATE access that we will require to duplicating the primary token.
-> TOKEN_QUERY: Required to query an access token.
-> TOKEN_DUPLICATE: Required to duplicate an access token.
- Then we give the token handle hToken in which we will receive the Token Handle. If the OpenProcessToken() function is successful then the hToken handle will receive the token details.
Now the main task is to duplicate a primary token from the token handle which we have opened. For this, we will declare a new HANDLE which will receive the duplicated token. To duplicate the token we will use the DuplicateTokenEx() function.
The Syntax of the DuplicateTokenEx() function from Microsoft Docs.
Here, we have the existing token hToken from the OpenProcessToken() function. For the desired access we will specify MAXIMUM_ALLOWED which will give all the access rights that are valid for the token which we have opened. We will specify NULL for security attributes which will give the default security descriptor.
Now we have to define the Impersonation level which will define at what level we can impersonate the process.
According to the constants, we want to impersonate the security context of the target process on its local system so we will use the SecurityImpersonation constant as the impersonation level.
This can be done using the SECURITY_IMPERSONATION_LEVEL enumeration.
Next, we have to specify the Token Type which we want to duplicate. As we learned in the previous blog post there are two types of token: Primary and Impersonation Tokens. Since we are creating a new process, we have to use the Primary Token. We can also use the Impersonation Token to create a new process as the DuplicateTokenEx() function will convert the impersonation token into Primary Token. But when we impersonate a process rather than creating a new process, we always have to use Impersonation Token. This can be done using the TOKEN_TYPE enumeration.
Below is the code snippet of the DuplicateTokenEx() function:-
HANDLE NewToken = NULL;
BOOL DuplicateTokenResult = FALSE;
SECURITY_IMPERSONATION_LEVEL Sec_Imp_Level = SecurityImpersonation;
TOKEN_TYPE token_type = TokenPrimary;
DuplicateTokenResult = DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, Sec_Imp_Level, token_type, &NewToken);
if (!DuplicateTokenResult)
{
_tprintf(L"Duplicate Token Failed with Error Code: %d\n", GetLastError());
CloseHandle(hToken);
CloseHandle(NewToken);
}
As discussed above we are using Sec_Imp_Level which is SECURITY_IMPERSONATION_LEVEL enumeration to define the impersonation level as SecurityImpersonation. Then we are using the TOKEN_TYPE enumeration token_type to define the type as TokenPrimary. In the last parameter, we are pointing to the new token handle NewToken which will receive the New duplicated Token.
If this function is successful, then it will give us new duplicated token in the HANDLE NewToken.
After the Duplication process of the token is done, we will create a new process using the newly duplicated token. To create a new process with this token we will use the CreateProcessWithTokenW() function.
Let’s look at the syntax of the CreateProcessWithTokenW() function from Microsoft Docs.
As mentioned in the syntax, we will provide the handle NewToken which is the duplicated token. But according to the docs, the token must have the TOKEN_QUERY, TOKEN_DUPLICATE, and TOKEN_ASSIGN_PRIMARY access rights. If we take look at the OpenProcessToken() code above we have only specified the TOKEN_QUERY and TOKEN_DUPLICATE access rights, but in the DuplicateTokenEx() code we are specifying the MAXIMUM_ALLOWED access rights which will give all the access rights of the token which we are duplicating. So, the TOKEN_ASSIGN_PRIMARY access right is included in that. If the TOKEN_ASSIGN_PRIMARY access right is not present in the token handle, then it will give an access denied error and won’t create the process.
Below is the code snippet for CreateProcessWithTokenW() function:-
STARTUPINFOEX startup_info = {};
PROCESS_INFORMATION process_info = {};
BOOL CreateProcTokenRes = FALSE;
CreateProcTokenRes = CreateProcessWithTokenW(NewToken, 0, L"C:\\Windows\\system32\\cmd.exe", NULL, CREATE_NEW_CONSOLE, NULL, NULL, &startup_info, &process_info);
if (!CreateProcTokenRes)
{
_tprintf(L"Cannot Create Process With Token. Failed with Error Code: %d\n", GetLastError());
CloseHandle(NewToken);
}
In the lpApplicationName parameter of the function, we will specify the application name which we want to run under the security context of the new Token. In our case, we will spawn a new process of cmd.exe with the new Token.
We have declared two structure at the start of the code snippet, startup_info and process_info. The STARTUPINFOEX structure specified the attributes of a new process. For eg, we can change the parent of the process, whether the process can have a child process or not and many more. For that two functions we can use InitializeProcThreadAttributeList() and UpdateProcThreadAttribute(). However, we aren’t going to change any attributes of the process for now. The PROCESS_INFORMATION structure is used to receive information like the HANDLE to the Process and Thread, process ID, thread ID etc.
If this function is successful, then a new CMD process will be launched under the security context of the specified New Token which we duplicated from an existing SYSTEM process.
Below is the demo of the above technique: –
Here, 652 is the process ID of lsass.exe which is a non-protected process running under the security context of SYSTEM. As we can see, the caller user is running under the security context of user Subzero0x9 which is a standard user with an elevated token. The SeDebug privilege didn’t enable for the process, so first the debug privilege is enabled the whole process of Token Manipulation is done. On the success of this program, a new CMD shell is spawned which is running under the security context of nt authority\system i.e. SYSTEM.
Let’s take a look at Process Explorer and see what it is showing regarding the newly spawned process.
Here, we can see the process properties of PID 2732 which is a cmd.exe process spawned by our program. As we can see that the user for this process is SYSTEM, which means this process is running under the security context of the SYSTEM. Also, we can see the SID, Logon Session and Privileges enabled for the spawned process, it is same as a SYSTEM level process.
CONCLUSION
That’s it for now. In this blog, we explored the technique in which we created a process under the security context of the SYSTEM user by opening a handle to a SYSTEM level process and duplicating the token and spawning a new CMD process by using that duplicated token. In this scenario, we played around the primary token in the next blog we will look into other techniques exploring the impersonation token.