From 945fa9ea590de5f009ccab94139cc4110d869116 Mon Sep 17 00:00:00 2001 From: Serge <5920850+seerge@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:32:26 +0200 Subject: [PATCH] Custom bindings tweak --- app/Helpers/RestrictedProcessHelper.cs | 376 +++++++++++++++++++++++++ app/Input/InputDispatcher.cs | 24 +- 2 files changed, 380 insertions(+), 20 deletions(-) create mode 100644 app/Helpers/RestrictedProcessHelper.cs diff --git a/app/Helpers/RestrictedProcessHelper.cs b/app/Helpers/RestrictedProcessHelper.cs new file mode 100644 index 00000000..af45a2f2 --- /dev/null +++ b/app/Helpers/RestrictedProcessHelper.cs @@ -0,0 +1,376 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +public static class RestrictedProcessHelper +{ + /// Runs a process as a non-elevated version of the current user. + public static Process? RunAsRestrictedUser(string fileName, string? args = null) + { + if (string.IsNullOrWhiteSpace(fileName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName)); + + if (!GetRestrictedSessionUserToken(out var hRestrictedToken)) + { + return null; + } + + try + { + var si = new STARTUPINFO(); + si.cb = Marshal.SizeOf(si); + si.dwFlags = 0x1; + si.wShowWindow = 0x00; // Set the window to be hidden + + var pi = new PROCESS_INFORMATION(); + var cmd = new StringBuilder(); + cmd.Append('"').Append(fileName).Append('"'); + if (!string.IsNullOrWhiteSpace(args)) + { + cmd.Append(' ').Append(args); + } + + Logger.WriteLine($"Launching {cmd}"); + + if (!CreateProcessAsUser( + hRestrictedToken, + null, + cmd, + IntPtr.Zero, + IntPtr.Zero, + true, // inherit handle + 0, + IntPtr.Zero, + Path.GetDirectoryName(fileName), + ref si, + out pi)) + { + return null; + } + + return Process.GetProcessById(pi.dwProcessId); + } + finally + { + CloseHandle(hRestrictedToken); + } + } + + private static bool GetRestrictedSessionUserToken(out IntPtr token) + { + token = IntPtr.Zero; + if (!SaferCreateLevel(SaferScope.User, SaferLevel.NormalUser, SaferOpenFlags.Open, out var hLevel, IntPtr.Zero)) + { + return false; + } + + IntPtr hRestrictedToken = IntPtr.Zero; + TOKEN_MANDATORY_LABEL tml = default; + tml.Label.Sid = IntPtr.Zero; + IntPtr tmlPtr = IntPtr.Zero; + + try + { + if (!SaferComputeTokenFromLevel(hLevel, IntPtr.Zero, out hRestrictedToken, 0, IntPtr.Zero)) + { + return false; + } + + // Set the token to medium integrity. + tml.Label.Attributes = SE_GROUP_INTEGRITY; + tml.Label.Sid = IntPtr.Zero; + if (!ConvertStringSidToSid("S-1-16-8192", out tml.Label.Sid)) + { + return false; + } + + tmlPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tml)); + Marshal.StructureToPtr(tml, tmlPtr, false); + if (!SetTokenInformation(hRestrictedToken, + TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, + tmlPtr, (uint)Marshal.SizeOf(tml))) + { + return false; + } + + token = hRestrictedToken; + hRestrictedToken = IntPtr.Zero; // make sure finally() doesn't close the handle + } + finally + { + SaferCloseLevel(hLevel); + SafeCloseHandle(hRestrictedToken); + if (tml.Label.Sid != IntPtr.Zero) + { + LocalFree(tml.Label.Sid); + } + if (tmlPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(tmlPtr); + } + } + + return true; + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct STARTUPINFO + { + public Int32 cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public Int32 dwX; + public Int32 dwY; + public Int32 dwXSize; + public Int32 dwYSize; + public Int32 dwXCountChars; + public Int32 dwYCountChars; + public Int32 dwFillAttribute; + public Int32 dwFlags; + public Int16 wShowWindow; + public Int16 cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + private struct TOKEN_MANDATORY_LABEL + { + public SID_AND_ATTRIBUTES Label; + } + + public enum SaferLevel : uint + { + Disallowed = 0, + Untrusted = 0x1000, + Constrained = 0x10000, + NormalUser = 0x20000, + FullyTrusted = 0x40000 + } + + public enum SaferScope : uint + { + Machine = 1, + User = 2 + } + + [Flags] + public enum SaferOpenFlags : uint + { + Open = 1 + } + + [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)] + private static extern bool SaferCreateLevel(SaferScope scope, SaferLevel level, SaferOpenFlags openFlags, out IntPtr pLevelHandle, IntPtr lpReserved); + + [DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)] + private static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved); + + [DllImport("advapi32", SetLastError = true)] + private static extern bool SaferCloseLevel(IntPtr hLevelHandle); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool ConvertStringSidToSid(string StringSid, out IntPtr ptrSid); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr hObject); + + private static bool SafeCloseHandle(IntPtr hObject) + { + return (hObject == IntPtr.Zero) ? true : CloseHandle(hObject); + } + + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr LocalFree(IntPtr hMem); + + enum TOKEN_INFORMATION_CLASS + { + /// + /// The buffer receives a TOKEN_USER structure that contains the user account of the token. + /// + TokenUser = 1, + + /// + /// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token. + /// + TokenGroups, + + /// + /// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token. + /// + TokenPrivileges, + + /// + /// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects. + /// + TokenOwner, + + /// + /// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects. + /// + TokenPrimaryGroup, + + /// + /// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects. + /// + TokenDefaultDacl, + + /// + /// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information. + /// + TokenSource, + + /// + /// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token. + /// + TokenType, + + /// + /// The buffer receives a SECURITY_IMPERSONATION_LEVEL value that indicates the impersonation level of the token. If the access token is not an impersonation token, the function fails. + /// + TokenImpersonationLevel, + + /// + /// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics. + /// + TokenStatistics, + + /// + /// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token. + /// + TokenRestrictedSids, + + /// + /// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token. + /// + TokenSessionId, + + /// + /// The buffer receives a TOKEN_GROUPS_AND_PRIVILEGES structure that contains the user SID, the group accounts, the restricted SIDs, and the authentication ID associated with the token. + /// + TokenGroupsAndPrivileges, + + /// + /// Reserved. + /// + TokenSessionReference, + + /// + /// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag. + /// + TokenSandBoxInert, + + /// + /// Reserved. + /// + TokenAuditPolicy, + + /// + /// The buffer receives a TOKEN_ORIGIN value. + /// + TokenOrigin, + + /// + /// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token. + /// + TokenElevationType, + + /// + /// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token. + /// + TokenLinkedToken, + + /// + /// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated. + /// + TokenElevation, + + /// + /// The buffer receives a DWORD value that is nonzero if the token has ever been filtered. + /// + TokenHasRestrictions, + + /// + /// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token. + /// + TokenAccessInformation, + + /// + /// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token. + /// + TokenVirtualizationAllowed, + + /// + /// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token. + /// + TokenVirtualizationEnabled, + + /// + /// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level. + /// + TokenIntegrityLevel, + + /// + /// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set. + /// + TokenUIAccess, + + /// + /// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy. + /// + TokenMandatoryPolicy, + + /// + /// The buffer receives the token's logon security identifier (SID). + /// + TokenLogonSid, + + /// + /// The maximum value for this enumeration + /// + MaxTokenInfoClass + } + + [DllImport("advapi32.dll", SetLastError = true)] + static extern Boolean SetTokenInformation( + IntPtr TokenHandle, + TOKEN_INFORMATION_CLASS TokenInformationClass, + IntPtr TokenInformation, + UInt32 TokenInformationLength); + + const uint SE_GROUP_INTEGRITY = 0x00000020; + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static extern bool CreateProcessAsUser( + IntPtr hToken, + string? lpApplicationName, + StringBuilder? lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + bool bInheritHandles, + uint dwCreationFlags, + IntPtr lpEnvironment, + string? lpCurrentDirectory, + ref STARTUPINFO lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); +} \ No newline at end of file diff --git a/app/Input/InputDispatcher.cs b/app/Input/InputDispatcher.cs index a9003a95..b106aecd 100644 --- a/app/Input/InputDispatcher.cs +++ b/app/Input/InputDispatcher.cs @@ -941,33 +941,17 @@ namespace GHelper.Input static void LaunchProcess(string command = "") { - + if (string.IsNullOrEmpty(command)) return; try { - - //string executable = command.Split(' ')[0]; - //string arguments = command.Substring(executable.Length).Trim(); - ProcessStartInfo startInfo = new ProcessStartInfo("cmd", "/C " + command); - - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - - startInfo.WorkingDirectory = Environment.CurrentDirectory; - //startInfo.Arguments = arguments; - Process proc = Process.Start(startInfo); + RestrictedProcessHelper.RunAsRestrictedUser(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"), "/C " + command); } - catch + catch (Exception ex) { - Logger.WriteLine("Failed to run " + command); + Logger.WriteLine($"Failed to run: {command} {ex.Message}"); } - - } - - static void WatcherEventArrived(object sender, EventArrivedEventArgs e) { if (e.NewEvent is null) return;