8b855ec9b9
CreateProcessWithLogonW (which ProcessStartInfo.UserName/Password uses under the hood) refuses to run when the caller is LocalSystem - which is exactly the scenario every hook hits, since the service runs as SYSTEM by default. The hook just got "Access is denied" with no useful context. Switch SpecificUser to the same LogonUser + DuplicateTokenEx + CreateProcessAsUser path that InteractiveUser already uses. The launcher tries LOGON32_LOGON_INTERACTIVE first, falling back to LOGON32_LOGON_BATCH for accounts without interactive-logon rights (typical for service-only users). Domain "." is normalized to the machine name so ".\justin" works. The launcher's two public entry points - LaunchAsActiveConsoleUser and LaunchAsSpecificUser - share the same LaunchWithToken core, so stdio capture and environment-block construction stay identical. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
158 lines
5.0 KiB
C#
158 lines
5.0 KiB
C#
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Versioning;
|
|
|
|
namespace WebhookServer.Core.Execution.Native;
|
|
|
|
/// <summary>
|
|
/// Win32 P/Invoke layer for launching processes in another user's session.
|
|
/// Used by <see cref="InteractiveProcessLauncher"/>; not intended for general use.
|
|
/// </summary>
|
|
[SupportedOSPlatform("windows")]
|
|
internal static class NativeMethods
|
|
{
|
|
public const uint INVALID_SESSION_ID = 0xFFFFFFFF;
|
|
public const int MAXIMUM_ALLOWED = 0x02000000;
|
|
|
|
public const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
|
|
public const uint CREATE_NO_WINDOW = 0x08000000;
|
|
public const uint CREATE_NEW_CONSOLE = 0x00000010;
|
|
public const uint NORMAL_PRIORITY_CLASS = 0x00000020;
|
|
|
|
public const int STARTF_USESTDHANDLES = 0x00000100;
|
|
|
|
public const int HANDLE_FLAG_INHERIT = 1;
|
|
|
|
public const uint INFINITE = 0xFFFFFFFF;
|
|
|
|
public enum SECURITY_IMPERSONATION_LEVEL
|
|
{
|
|
SecurityAnonymous = 0,
|
|
SecurityIdentification = 1,
|
|
SecurityImpersonation = 2,
|
|
SecurityDelegation = 3,
|
|
}
|
|
|
|
public enum TOKEN_TYPE
|
|
{
|
|
TokenPrimary = 1,
|
|
TokenImpersonation = 2,
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct SECURITY_ATTRIBUTES
|
|
{
|
|
public int nLength;
|
|
public IntPtr lpSecurityDescriptor;
|
|
public int bInheritHandle;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
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 ushort wShowWindow;
|
|
public ushort 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 const int LOGON32_LOGON_INTERACTIVE = 2;
|
|
public const int LOGON32_LOGON_BATCH = 4;
|
|
public const int LOGON32_PROVIDER_DEFAULT = 0;
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool LogonUser(
|
|
string lpszUsername,
|
|
string? lpszDomain,
|
|
string lpszPassword,
|
|
int dwLogonType,
|
|
int dwLogonProvider,
|
|
out IntPtr phToken);
|
|
|
|
[DllImport("kernel32.dll")]
|
|
public static extern uint WTSGetActiveConsoleSessionId();
|
|
|
|
[DllImport("wtsapi32.dll", SetLastError = true)]
|
|
public static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken);
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool DuplicateTokenEx(
|
|
IntPtr hExistingToken,
|
|
uint dwDesiredAccess,
|
|
IntPtr lpTokenAttributes,
|
|
SECURITY_IMPERSONATION_LEVEL impersonationLevel,
|
|
TOKEN_TYPE tokenType,
|
|
out IntPtr phNewToken);
|
|
|
|
[DllImport("userenv.dll", SetLastError = true)]
|
|
public static extern bool CreateEnvironmentBlock(
|
|
out IntPtr lpEnvironment,
|
|
IntPtr hToken,
|
|
bool bInherit);
|
|
|
|
[DllImport("userenv.dll", SetLastError = true)]
|
|
public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
public static extern bool CreateProcessAsUser(
|
|
IntPtr hToken,
|
|
string? lpApplicationName,
|
|
string? lpCommandLine,
|
|
IntPtr lpProcessAttributes,
|
|
IntPtr lpThreadAttributes,
|
|
bool bInheritHandles,
|
|
uint dwCreationFlags,
|
|
IntPtr lpEnvironment,
|
|
string? lpCurrentDirectory,
|
|
ref STARTUPINFO lpStartupInfo,
|
|
out PROCESS_INFORMATION lpProcessInformation);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool CreatePipe(
|
|
out IntPtr hReadPipe,
|
|
out IntPtr hWritePipe,
|
|
ref SECURITY_ATTRIBUTES lpPipeAttributes,
|
|
uint nSize);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool SetHandleInformation(IntPtr hObject, int dwMask, int dwFlags);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool CloseHandle(IntPtr hObject);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
|
|
|
|
public static Win32Exception LastError(string what) =>
|
|
new(Marshal.GetLastWin32Error(), $"{what} failed");
|
|
}
|