Breaking out from stripped tokens with duplication

This is just a short complementary follow-up after the previous, much longer article (https://hackingiscool.pl/breaking-out-from-stripped-tokens-using-process-injection/) about moving code execution to a process with a more privileged token (in terms of token privileges and/or group memberships). In addition to the main method (process injection) and the second method I came across later described by another author (new task creation using task scheduling service as a proxy), I was asked by Mateusz Garncarek (so shoutout to him for making me test this) if duplicating the source token process and creating a new one with that token would do the trick, without the current process having any special privileges such as SeAssignPrimaryTokenPrivilege and SeImpersonatePrivilege.

The answer is yes, although honestly I was little surprised that it worked. The full code is here:

https://gist.githubusercontent.com/ewilded/acc8ab992a70ef080f87abaed751a62d/raw/91063d5da2e01c9d3285cb2bbfa2a503007e7601/duplicate_token.cpp

To easily test it, let's use the NtObjectManager powershell module, as it provides a set of convenient functions, including Remove-NtTokenPrivilege, which allows us to remove privileges from the current process token. By operating from a process that has no privileges whatsoever (on the right), we'll jump back into code execution with the privileges of another process of that same user and same integrity level (on the left) by duplicating its primary token and spawning a new child process with it:

As we can see, the second call of whoami /priv not did not even print out any privileges, while it also printed out an error (most likely resulting from the lack of the SeIncreaseWorkingSet privilege) - either way demonstrating that privilege removal for the purpose of the test was successful.

Then, the POC process (duplicate.exe) inheriting the restricted token was run. That process, in turn, successfully opened and duplicated the primary token from the neighbor process with the PID 9524, as confirmed by the newly created whoami_test.txt contents generated by running another child process - cmd.exe /c whoami > whoami_test.txt - which was spawned with the duplicated source process (9524) token as its primary one, using CreateProcessAsUser().

The MSDN page for the CreateProcessAsUser() function states that the token passed as the first argument must have the TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY access rights:

These are granted to the hToken in result of the
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, &sa, SecurityImpersonation, TokenPrimary, &hDupToken) call. The DuplicateTokenEx() function MSDN entry says this about the second argument (desiredAccess):

Since the hDupToken was obtained with OpenProcessToken(hProcess, TOKEN_DUPLICATE, &hToken), from a hProcess descriptor opened only with the PROCESS_QUERY_INFORMATION access right, my surprise came from the fact that the combination of the two sufficed to result in TOKEN_ASSIGN_PRIMARY access right on the hToken descriptor after requesting MAXIMUM_ALLOWED, despite the caller's token not holding any special privileges.

The CreateProcessAsUser() also mentions, further in the first expected parameter's description, that "If hToken is a restricted version of the caller's primary token, the SE_ASSIGNPRIMARYTOKEN_NAME privilege is not required":

In this case, however, technically it's the caller's primary token being a restricted version of the hToken, but it still works. This little nuance can become handy as an alternative to the two earlier described methods of migrating to more privileged processes of the same user and integrity.

No one really cares about cookies and neither do I