I am currently struggling on the challenge to run some Powershell code under "local system" which should launch a new detached process in the context of the currently logged-in user.
It all seems to work fine except the new process is always a sub-process and NOT really detached. Is is just because of a wrong value for dwCreationFlags? Can someone help here to make this work? Or is this ONLY possible via the task scheduler?
Here the test-code I have so far:
cls
remove-variable * -ea 0
$ErrorActionPreference = 'stop'
#requires -runasadmin
# pinvoke-code to switch into users context:
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO {
public uint 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;
}
public static class Kernel32 {
[DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
public static extern bool WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public static extern bool DuplicateTokenEx(
IntPtr ExistingTokenHandle,
uint dwDesiredAccess,
IntPtr lpThreadAttributes,
int TokenType,
int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern uint CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
System.Text.StringBuilder lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandle,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hSnapshot);
}
"@
# get the users token:
$userToken = [IntPtr]::Zero
$session = [kernel32]::WTSGetActiveConsoleSessionId()
$null = [kernel32]::WTSQueryUserToken($session, [ref]$userToken) # runs as "local system" only
# copy the token:
$newToken = [intPtr]::Zero
$null = [kernel32]::DuplicateTokenEx($userToken, 0xF01FF, [IntPtr]::Zero, 2, 1, [ref]$newToken);
$null = [kernel32]::CloseHandle($userToken)
# define startup-conditions for new process:
$startInfo = new-object STARTUPINFO
$marshal = [System.Runtime.InteropServices.Marshal]
$startInfo.cb = $marshal::SizeOf($startInfo)
$startInfo.lpDesktop = "winsta0\default"
$startInfo.dwFlags = 1 # to respect wShowWindow-value
$startInfo.wShowWindow = 1 # 0=hidden
$appPath = "C:\Windows\System32\notepad.exe"
$cmdLine = $apppath
$dir = [System.IO.Directory]::GetParent($appPath).Fullname
$dwCreationFlags = 0x08000400 # this works and can interact with the user, but is not detached
$dwCreationFlags = 0x00000008 # in theory this should start a detached process(?)
# start a new user-process:
$procInfo = new-object PROCESS_INFORMATION
$result = [kernel32]::CreateProcessAsUser($newToken, $appPath, $cmdLine, 0, 0, $false, $dwCreationFlags, 0, $dir, [ref]$startInfo, [ref]$procInfo)
write-host "result: $result"
write-host $procInfo.dwProcessId
# clean-up:
$null = [kernel32]::CloseHandle($newToken)
$null = [kernel32]::CloseHandle($procInfo.hThread)
$null = [kernel32]::CloseHandle($procInfo.hProcess)
# check, if the new process is REALLY detached or not:
try {
$child = [System.Management.ManagementObjectSearcher]::new("select * from Win32_Process where ProcessId = $($procInfo.dwProcessId)").Get()
$parent = ([System.Management.ManagementObjectSearcher]::new("select * FROM Win32_Process WHERE ProcessId = $($child.ParentProcessId)").Get()).Name
write-host "Parent is '$parent'"
Stop-Process -Id $child.ProcessId -Force
} catch {}
I am currently struggling on the challenge to run some Powershell code under "local system" which should launch a new detached process in the context of the currently logged-in user.
It all seems to work fine except the new process is always a sub-process and NOT really detached. Is is just because of a wrong value for dwCreationFlags? Can someone help here to make this work? Or is this ONLY possible via the task scheduler?
Here the test-code I have so far:
cls
remove-variable * -ea 0
$ErrorActionPreference = 'stop'
#requires -runasadmin
# pinvoke-code to switch into users context:
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO {
public uint 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;
}
public static class Kernel32 {
[DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
public static extern bool WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
public static extern bool DuplicateTokenEx(
IntPtr ExistingTokenHandle,
uint dwDesiredAccess,
IntPtr lpThreadAttributes,
int TokenType,
int ImpersonationLevel,
ref IntPtr DuplicateTokenHandle);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern uint CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
System.Text.StringBuilder lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandle,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hSnapshot);
}
"@
# get the users token:
$userToken = [IntPtr]::Zero
$session = [kernel32]::WTSGetActiveConsoleSessionId()
$null = [kernel32]::WTSQueryUserToken($session, [ref]$userToken) # runs as "local system" only
# copy the token:
$newToken = [intPtr]::Zero
$null = [kernel32]::DuplicateTokenEx($userToken, 0xF01FF, [IntPtr]::Zero, 2, 1, [ref]$newToken);
$null = [kernel32]::CloseHandle($userToken)
# define startup-conditions for new process:
$startInfo = new-object STARTUPINFO
$marshal = [System.Runtime.InteropServices.Marshal]
$startInfo.cb = $marshal::SizeOf($startInfo)
$startInfo.lpDesktop = "winsta0\default"
$startInfo.dwFlags = 1 # to respect wShowWindow-value
$startInfo.wShowWindow = 1 # 0=hidden
$appPath = "C:\Windows\System32\notepad.exe"
$cmdLine = $apppath
$dir = [System.IO.Directory]::GetParent($appPath).Fullname
$dwCreationFlags = 0x08000400 # this works and can interact with the user, but is not detached
$dwCreationFlags = 0x00000008 # in theory this should start a detached process(?)
# start a new user-process:
$procInfo = new-object PROCESS_INFORMATION
$result = [kernel32]::CreateProcessAsUser($newToken, $appPath, $cmdLine, 0, 0, $false, $dwCreationFlags, 0, $dir, [ref]$startInfo, [ref]$procInfo)
write-host "result: $result"
write-host $procInfo.dwProcessId
# clean-up:
$null = [kernel32]::CloseHandle($newToken)
$null = [kernel32]::CloseHandle($procInfo.hThread)
$null = [kernel32]::CloseHandle($procInfo.hProcess)
# check, if the new process is REALLY detached or not:
try {
$child = [System.Management.ManagementObjectSearcher]::new("select * from Win32_Process where ProcessId = $($procInfo.dwProcessId)").Get()
$parent = ([System.Management.ManagementObjectSearcher]::new("select * FROM Win32_Process WHERE ProcessId = $($child.ParentProcessId)").Get()).Name
write-host "Parent is '$parent'"
Stop-Process -Id $child.ProcessId -Force
} catch {}
Here are the flags. You want to set the DETACHED_PROCESS flag
[Flags]
enum CreateProcessFlags : uint
{
DEBUG_PROCESS = 0x00000001,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
CREATE_SUSPENDED = 0x00000004,
DETACHED_PROCESS = 0x00000008,
CREATE_NEW_CONSOLE = 0x00000010,
NORMAL_PRIORITY_CLASS = 0x00000020,
IDLE_PRIORITY_CLASS = 0x00000040,
HIGH_PRIORITY_CLASS = 0x00000080,
REALTIME_PRIORITY_CLASS = 0x00000100,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_FORCEDOS = 0x00002000,
BELOW_NORMAL_PRIORITY_CLASS = 0x00004000,
ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000,
INHERIT_PARENT_AFFINITY = 0x00010000,
INHERIT_CALLER_PRIORITY = 0x00020000,
CREATE_PROTECTED_PROCESS = 0x00040000,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
PROCESS_MODE_BACKGROUND_BEGIN = 0x00100000,
PROCESS_MODE_BACKGROUND_END = 0x00200000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NO_WINDOW = 0x08000000,
PROFILE_USER = 0x10000000,
PROFILE_KERNEL = 0x20000000,
PROFILE_SERVER = 0x40000000,
CREATE_IGNORE_SYSTEM_DEFAULT = 0x80000000,
}
explorer.exe
process the parent process. When a process is launched from a noninteractive context such as a service, e.g. the Task Scheduler or PowerShell remoting, the parent process is an instance of asvchost
process. Launching a process from an application process makes the former a child process of the latter by default. The question at hand is how not to make the launched process a child process of the calling one. – mklement0 Commented Jan 30 at 2:29