Invoke-AsService.ps1

Function Invoke-WithProcessToken {
<#
.SYNOPSIS
 
    Will run a scriptblock with the process token of the specified process.
 
.DESCRIPTION
 
    The function installs a service and runs it under the NT Authority\SYSTEM credentials.
    In this context it will launch powershell.exe with the process token of the specified process.
    This enables us to impersonate any process.
 
#>

    param(
        [Parameter(Mandatory=$true)]
        [Diagnostics.Process]$ProcessObject,
        [Parameter(Mandatory=$true)]
        [scriptblock]$Process={},
        [scriptblock]$Begin={},
        [scriptblock]$End={},
        [int]$Depth = 4
    )
    begin {
        Function Test-Elevated {
            $wid=[System.Security.Principal.WindowsIdentity]::GetCurrent()
            $prp=new-object System.Security.Principal.WindowsPrincipal($wid)
            $adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator
            $prp.IsInRole($adm)
        }
    $code = @"
using System;
using System.Security;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Management;
using System.ComponentModel;
using System.Configuration.Install;
using System.Collections;
using System.ServiceProcess;
namespace GetRandom.Powershell.InvokeAsServiceSvc
{
   class TempPowershellService : ServiceBase
   {
       static void Main()
       {
           ServiceBase.Run(new ServiceBase[] { new TempPowershellService() });
       }
       protected override void OnStart(string[] args)
       {
           string[] clArgs = Environment.GetCommandLineArgs();
           try
           {
               if (clArgs.Length != 8)
               {
                   throw new Exception("Too few command line arguments for the service");
               }
               string argString = String.Format(
                   "{4} -command .{{import-clixml '{0}' | .'{1}' | export-clixml -Path '{2}' -Depth {3}}}",
                   clArgs[1],
                   clArgs[2],
                   clArgs[3],
                   clArgs[5],
                   Environment.ExpandEnvironmentVariables(@"%systemroot%\system32\windowspowershell\v1.0\powershell.exe")
                   );
                
               int pid = int.Parse(clArgs[6]);
               bool forceSta0 = clArgs[7] == "True";
               Process ps = Process.GetProcessById(CreateProcessWithCopiedToken(pid, argString, forceSta0));
               ps.WaitForExit();
               System.IO.File.AppendAllText(clArgs[4], "success");
           }
           catch (Exception e)
           {
               System.IO.File.AppendAllText(clArgs[4], "fail\r\n" + e.Message);
           }
       }
       protected override void OnStop()
       {
       }
  
       [StructLayout(LayoutKind.Sequential)]
       public struct SECURITY_ATTRIBUTES
       {
           public int Length;
           public IntPtr lpSecurityDescriptor;
           public bool bInheritHandle;
       }
  
       [StructLayout(LayoutKind.Sequential)]
       public struct STARTUPINFO
       {
           public int cb;
           public String lpReserved;
           public String lpDesktop;
           public String lpTitle;
           public uint dwX;
           public uint dwY;
           public uint dwXSize;
           public uint dwYSize;
           public uint dwXCountChars;
           public uint dwYCountChars;
           public uint dwFillAttribute;
           public uint dwFlags;
           public short wShowWindow;
           public short cbReserved2;
           public IntPtr lpReserved2;
           public IntPtr hStdInput;
           public IntPtr hStdOutput;
           public IntPtr hStdError;
       }
  
       [StructLayout(LayoutKind.Sequential)]
       public struct PROCESS_INFORMATION
       {
           public IntPtr hProcess;
           public IntPtr hThread;
           public uint dwProcessId;
           public uint dwThreadId;
       }
  
       enum TOKEN_TYPE : int
       {
           TokenPrimary = 1,
           TokenImpersonation = 2
       }
  
       enum SECURITY_IMPERSONATION_LEVEL : int
       {
           SecurityAnonymous = 0,
           SecurityIdentification = 1,
           SecurityImpersonation = 2,
           SecurityDelegation = 3,
       }
  
  
       public const int TOKEN_DUPLICATE = 0x0002;
       public const uint MAXIMUM_ALLOWED = 0x2000000;
       public const int CREATE_NEW_CONSOLE = 0x00000010;
  
       public const int IDLE_PRIORITY_CLASS = 0x40;
       public const int NORMAL_PRIORITY_CLASS = 0x20;
       public const int HIGH_PRIORITY_CLASS = 0x80;
       public const int REALTIME_PRIORITY_CLASS = 0x100;
       public const int CREATE_NO_WINDOW = 0x08000000;
  
  
       [DllImport("kernel32.dll", SetLastError = true)]
       private static extern bool CloseHandle(IntPtr hSnapshot);
  
       [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
       public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
           ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
           String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
  
       [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
       public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
           ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
           int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
  
       [DllImport("kernel32.dll")]
       static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
  
       [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
       static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);
  
       private static int CreateProcessWithCopiedToken(int servicePid, string applicationName, bool forceSta0)
       {
           int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
  
           // obtain a handle to the winlogon process
           IntPtr hUserTokenDup = IntPtr.Zero;
           IntPtr hPToken = IntPtr.Zero;
           IntPtr hProcess = IntPtr.Zero;
           PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
           hProcess = OpenProcess(MAXIMUM_ALLOWED, false, (uint)servicePid);
            
           // obtain a handle to the access token of the winlogon process
           if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
           {
               CloseHandle(hProcess);
               throw new Exception("Failed to open process token");
           }
           SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
           sa.Length = Marshal.SizeOf(sa);
  
           if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
           {
               CloseHandle(hProcess);
               CloseHandle(hPToken);
               throw new Exception("Failed to duplicate token");
           }
  
           STARTUPINFO si = new STARTUPINFO();
           if (forceSta0)
           {
               // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
               si.lpDesktop = @"winsta0\default";
           }
           si.cb = (int)Marshal.SizeOf(si);
  
           bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
                                           null, // file to execute
                                           applicationName, // command line
                                           ref sa, // pointer to process SECURITY_ATTRIBUTES
                                           ref sa, // pointer to thread SECURITY_ATTRIBUTES
                                           false, // handles are not inheritable
                                           dwCreationFlags, // creation flags
                                           IntPtr.Zero, // pointer to new environment block
                                           null, // name of current directory
                                           ref si, // pointer to STARTUPINFO structure
                                           out procInfo // receives information about new process
                                           );
            
           // invalidate the handles
           try { CloseHandle(hProcess); }
           catch (Exception) { }
           try { CloseHandle(hPToken); }
           catch (Exception) { }
           try { CloseHandle(hUserTokenDup); }
           catch (Exception) { }
           if (result)
           {
               return (int)procInfo.dwProcessId;
           } else {
               throw new Exception("Failed to start process");
           }
       }
   }
}
"@

        if( -not (Test-Elevated)) {
            throw "Process is not running as an eleveated process. Please run as elevated."
        }        
        [void][System.Reflection.Assembly]::LoadWithPartialName("System.ServiceProcess")  
        $serviceNamePrefix = "MyTempPowershellSvc"
        $timeStamp = get-date -Format yyyyMMdd-HHmmss
        $serviceName = "{0}-{1}" -f $serviceNamePrefix,$timeStamp
        $tempPSexe   = "{0}.exe" -f $serviceName,$timeStamp
        $tempPSout   = "{0}.out" -f $serviceName,$timeStamp
        $tempPSin    = "{0}.in"  -f $serviceName,$timeStamp
        $tempPSscr   = "{0}.ps1" -f $serviceName,$timeStamp
        $tempPScomplete   = "{0}.end" -f $serviceName,$timeStamp
        $servicePath = Join-Path $env:temp $tempPSexe
        $outPath     = Join-Path $env:temp $tempPSout
        $inPath      = Join-Path $env:temp $tempPSin
        $scrPath     = Join-Path $env:temp $tempPSscr
        $completePath     = Join-Path $env:temp $tempPScomplete
 
        Add-Type $code -ReferencedAssemblies "System.ServiceProcess","System.Configuration.Install" -OutputAssembly $servicePath -OutputType WindowsApplication | Out-Null
        $serviceImagePath = "`"{0}`" `"{1}`" `"{2}`" `"{3}`" `"{4}`" {5} {6} {7}" -f $servicePath,$inPath,$scrPath,$outPath,$completePath,$depth,$ProcessObject.ID,$false
        $objectsFromPipeline = new-object Collections.ArrayList
        $script = "BEGIN {{{0}}}`nPROCESS {{{1}}}`nEND {{{2}}}" -f $Begin.ToString(),$Process.ToString(),$End.ToString()
        $script.ToString() | Out-File -FilePath $scrPath -Force    
    }
 
    process {
        [void]$objectsFromPipeline.Add($_)
    }
 
    end
    {
        if($(Get-Process -id $ProcessObject.Id -ErrorAction SilentlyContinue) -eq $null){
            throw "Faild to find process id $($ProcessObject.Id)"
        }
        $objectsFromPipeline | Export-Clixml -Path $inPath -Depth $Depth
        New-Service -Name $serviceName -BinaryPathName $serviceImagePath -DisplayName $serviceName -Description $serviceName -StartupType Manual | out-null
        $service = Get-Service $serviceName
        $service.Start()
        while ( -not (test-path $completePath)) {
            #write-host $(get-date)
            start-sleep -Milliseconds 100
        }
        $service.Stop() | Out-Null
        do {
            $service = Get-Service $serviceName
        } while($service.Status -ne "Stopped")
        (Get-WmiObject win32_service -Filter "name='$serviceName'").delete() | out-null
        try { Import-Clixml -Path $outPath -ErrorAction SilentlyContinue} catch {}
        try { Remove-Item $servicePath -Force -ErrorAction SilentlyContinue} catch {}
        try { Remove-Item $inPath      -Force -ErrorAction SilentlyContinue} catch {}
        try { Remove-Item $outPath     -Force -ErrorAction SilentlyContinue} catch {}
        try { Remove-Item $scrPath     -Force -ErrorAction SilentlyContinue} catch {}
        try { Remove-Item $completePath -Force -ErrorAction SilentlyContinue} catch {}
    }
}
 
Function Invoke-AsService
{
<#
.SYNOPSIS
 
    Will run a scriptblock with the process token of the specified service.
 
.DESCRIPTION
 
    The function installs a service and runs it under the NT Authority\SYSTEM credentials.
    In this context it will launch powershell.exe with the process token of the specified process.
    This enables impersonation of any process.
 
.PARAMETER Process
 
    The PROCESS script block to invoked as a service.
 
.PARAMETER Begin
 
    The BEGIN script block to invoked as a service.
 
.PARAMETER End
 
    The END script block to invoked as a service.
 
.PARAMETER SourceService
 
    The source service which will be impersonated
 
.PARAMETER Depth
 
    Objects passed on the pipeline to the function will be serialized temporarily to disk with the Export-CliXml cmdlet.
    The parameter specifies how many levels of objects passed in on the pipeline are included in the XML representation.
    The default value is 3.
 
 
.EXAMPLE
 
    Invoke-AsService -Process {whoami /all} -SourceService TrustedInstaller
 
    This gives us the whoami /all result from the execution context as NT SERVICE\TrustedInstaller
 
#>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory=$true)]
        [scriptblock]$Process={},
        [scriptblock]$Begin={},
        [scriptblock]$End={},
        [Parameter(Mandatory=$true)]
        [string]$SourceService,
        [int]$Depth = 3
    )
   
    if($(get-service $SourceService | ? { $_.status -eq "Running" }) -eq $null) {
        get-service $SourceService | start-service
        do {
            $sourceServiceObject = Get-Service $SourceService
        } while($sourceServiceObject.Status -ne "Running")
    }
    $sourceServicePath = Get-WmiObject -Class win32_service | ? { $_.name -eq $SourceService} | select -ExpandProperty pathname
    $sourceServicePid = get-process | ? {$_.path -eq $sourceServicePath} | select -ExpandProperty id
    Write-Verbose "Source Service PID $sourceServicePid"
    Invoke-WithProcessToken -ProcessObject $(get-process -id $sourceServicePid) -Process $Process -Begin $Begin -End $End
}