如何从具有管理员权限的进程中启动没有管理员权限的新进程?

Posted

技术标签:

【中文标题】如何从具有管理员权限的进程中启动没有管理员权限的新进程?【英文标题】:How to start a new process without administrator privileges from a process with administrator privileges? 【发布时间】:2012-06-25 12:48:26 【问题描述】:

我正在为应用程序创建自动更新程序。该应用程序由用户启动,无需管理员权限即可运行。自动更新程序以管理员权限启动,并在下载新文件之前终止应用程序。

当我想在自动更新程序完成后启动更新的应用程序时,问题就出现了。如果我使用常规 System.Diagnostics.Process.Start(file),应用程序也以管理员权限启动,并且它必须在当前用户上运行才能按预期工作。

那么,如何让自动更新程序以当前用户而不是管理员身份启动应用程序?

我尝试过使用以下方法:

var pSI = new ProcessStartInfo()  
    UseShellExecute = false, 
    UserName = Environment.UserName, 
    FileName = file 
;
System.Diagnostics.Process.Start(pSI);

但这会引发错误“无效的用户名或密码”。我检查了用户名是否正确,并且我知道密码可能无效,因为我没有包含它。但是要求用户输入他/她的密码不是一个选项,因为自动启动应用程序的全部原因是为了让用户更容易。

有什么建议吗?

【问题讨论】:

This answer 描述了一种方法,您获取当前进程令牌的副本,使用 SetTokenInformation 删除高完整性 SID,然后使用 CreateProcessAsUser 使用修改后的令牌启动新进程。 我知道这已经过时了,但我在这里找到了一个非常好的方法,它比这篇文章中的答案更适合我。 CreateProcessWithTokenW 调用对我来说失败了。这有效:code.msdn.microsoft.com/windowsapps/… 本题对应的C++题:***.com/questions/37948064/… 【参考方案1】:

codeplex 上有一个名为 User Account Control Helpers 的项目。

该项目提供了一个与 UAC 机制交互的库。

在库中,您会找到一个名为UserAccountControl 的类。班上 有一个名为CreateProcessAsStandardUser 的静态方法来启动一个 来自具有标准用户权限的提升进程的进程。

简而言之,这些函数打开桌面外壳进程的进程令牌。 然后,它复制该令牌以获得主令牌。然后这个令牌 用于在登录用户下启动新进程。

更多信息请阅读Aaron Margosis的以下博文。

【讨论】:

【参考方案2】:

您可以使用CreateProcessWithLogonW function 或类似的CreateProcessAsUser 和CreateProcessWithTokenW。

【讨论】:

【参考方案3】:

假设您发出信号让应用程序彻底关闭而不是终止它,并且如果您仍然能够在发布更新程序之前对应用程序进行更改,一个简单的解决方案是让应用程序在之前启动一个临时进程退出。您可以在临时位置为临时进程创建可执行文件。更新完成后,向中间进程发出信号以重新启动应用程序并退出。这样一来,一切都会自然发生,您不必乱来。

另一种选择是使用OpenProcessOpenProcessTokenDuplicateToken 在终止应用程序之前获取应用程序安全令牌的副本。然后,您可以使用CreateProcessAsUser 在原始上下文中重新启动应用程序。

即使更新程序在不同的帐户下和/或在与应用程序的不同会话中运行,这两种方法也应该有效。

【讨论】:

【参考方案4】:

您想要实现的目标不能很容易地完成,并且不受支持。但是,可以使用少量的黑客攻击。 Aaron Margosis 写了一封 article 描述了一种技术。

要引用相关部分,您需要执行以下步骤:

    在您当前的令牌中启用 SeIncreaseQuotaPrivilege 获取代表桌面外壳的 HWND (GetShellWindow) 获取与该窗口关联的进程的进程 ID (PID) (GetWindowThreadProcessId) 打开该进程 (OpenProcess) 从该进程获取访问令牌 (OpenProcessToken) 使用该令牌创建一个主令牌 (DuplicateTokenEx) 使用该主令牌 (CreateProcessWithTokenW) 启动新进程

本文包含一些演示 C++ 源代码的下载链接,从中可以简单地转换为 C#。

【讨论】:

你给我的链接很好用!我不知道 c++ 我会尝试将该代码转换为 c#。一旦堆栈溢出,我会接受你的回答。谢谢! @Tono,在此之前一年多发布的the answer 中提到了相同的链接。 C# 移植是否可用? CreateProcessWithTokenW 对我来说失败了。我在这里找到了另一种效果很好的方法code.msdn.microsoft.com/windowsapps/… 如果CreateProcessWithTokenW 失败,则可能是“辅助登录服务”被禁用。不幸的是,许多所谓的“优化”网站建议禁用这种“不必要的”服务。 IShellDispatch2 technique 在这方面似乎更可靠,因为它不需要运行此服务。一个缺点是它对如何创建流程的控制较少。【参考方案5】:

我遇到了类似的问题。在 processstartinfo 中有一个密码字段,问题是您必须将密码作为安全字符串提供。所以代码如下:

System.Security.SecureString password = new System.Security.SecureString();
password.AppendChar('c1');
//append the all characters of your password, you could probably use a loop and then,
Process p =new Process();
p.UseShellExecute = false;
p.UserName = Environment.UserName;
p.FileName = file ;
p.Sassword=password;
p.Start();

【讨论】:

是的,您需要正确的密码。与此论坛上的最佳答案相比,我发现这更容易 如果您不知道密码,这并不容易。除非您拥有盒子,否则您怎么能希望知道密码? 这就是我所说的@DavidHeffernan【参考方案6】:

正如作者所描述的,这个问题没有解决。 我现在有同样的问题,我想描述一下我是如何解决这个问题的。

如您所知,这并不容易,但最好的解决方案是强制计算机通过另一个非管理员进程启动“*.exe”文件。我所做的是在任务计划程序without Highest Privilege 参数上创建一个任务,这意味着要安排时间或手动运行此任务。

看起来很愚蠢,但似乎没有办法。

你可以看到this link描述了如何在windows task scheduler/上创建一个新的Task

【讨论】:

【参考方案7】:

实用的 VB.NET 代码从提升的父进程启动具有默认 shell 权限的进程

#Region "References"

Imports System.Runtime.InteropServices

#End Region

Public Class LaunchProcess

#Region "Native Methods"

    <DllImport("User32.dll", SetLastError:=True)> Private Shared Function GetShellWindow() As IntPtr
    End Function

    <DllImport("advapi32.dll", SetLastError:=True)> Private Shared Function OpenProcessToken(ByVal ProcessHandle As IntPtr, ByVal DesiredAccess As Integer, ByRef TokenHandle As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True)> Private Shared Function GetWindowThreadProcessId(ByVal hwnd As IntPtr, ByRef lpdwProcessId As IntPtr) As Integer
    End Function

    <DllImport("kernel32.dll")> Private Shared Function OpenProcess(ByVal dwDesiredAccess As UInteger, <MarshalAs(UnmanagedType.Bool)> ByVal bInheritHandle As Boolean, ByVal dwProcessId As IntPtr) As IntPtr
    End Function

    <DllImport("advapi32.dll", SetLastError:=True)> _
    Private Shared Function DuplicateTokenEx( _
    ByVal ExistingTokenHandle As IntPtr, _
    ByVal dwDesiredAccess As UInt32, _
    ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
    ByVal ImpersonationLevel As Integer, _
    ByVal TokenType As Integer, _
    ByRef DuplicateTokenHandle As System.IntPtr) As Boolean
    End Function

    <DllImport("advapi32.dll", SetLastError:=True)> Private Shared Function LookupPrivilegeValue(lpSystemName As String, lpName As String, ByRef lpLuid As LUID) As Boolean
    End Function

    <DllImport("advapi32.dll", SetLastError:=True)> _
    Private Shared Function AdjustTokenPrivileges( _
    ByVal TokenHandle As IntPtr, _
    ByVal DisableAllPrivileges As Boolean, _
    ByRef NewState As TOKEN_PRIVILEGES, _
    ByVal BufferLengthInBytes As Integer, _
    ByRef PreviousState As TOKEN_PRIVILEGES, _
    ByRef ReturnLengthInBytes As Integer _
  ) As Boolean
    End Function

    <DllImport("advapi32", SetLastError:=True, CharSet:=CharSet.Unicode)> Private Shared Function CreateProcessWithTokenW(hToken As IntPtr, dwLogonFlags As Integer, lpApplicationName As String, lpCommandLine As String, dwCreationFlags As Integer, lpEnvironment As IntPtr, lpCurrentDirectory As IntPtr, ByRef lpStartupInfo As STARTUPINFO, ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean
    End Function

#End Region

#Region "Structures"

    <StructLayout(LayoutKind.Sequential)> Private Structure SECURITY_ATTRIBUTES

        Friend nLength As Integer
        Friend lpSecurityDescriptor As IntPtr
        Friend bInheritHandle As Integer

    End Structure

    Private Structure TOKEN_PRIVILEGES

        Friend PrivilegeCount As Integer
        Friend TheLuid As LUID
        Friend Attributes As Integer

    End Structure

    Private Structure LUID

        Friend LowPart As UInt32
        Friend HighPart As UInt32

    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> Private Structure STARTUPINFO

        Friend cb As Integer
        Friend lpReserved As String
        Friend lpDesktop As String
        Friend lpTitle As String
        Friend dwX As Integer
        Friend dwY As Integer
        Friend dwXSize As Integer
        Friend dwYSize As Integer
        Friend dwXCountChars As Integer
        Friend dwYCountChars As Integer
        Friend dwFillAttribute As Integer
        Friend dwFlags As Integer
        Friend wShowWindow As Short
        Friend cbReserved2 As Short
        Friend lpReserved2 As Integer
        Friend hStdInput As Integer
        Friend hStdOutput As Integer
        Friend hStdError As Integer

    End Structure

    Private Structure PROCESS_INFORMATION

        Friend hProcess As IntPtr
        Friend hThread As IntPtr
        Friend dwProcessId As Integer
        Friend dwThreadId As Integer

    End Structure

#End Region

#Region "Enumerations"

    Private Enum TOKEN_INFORMATION_CLASS

        TokenUser = 1
        TokenGroups
        TokenPrivileges
        TokenOwner
        TokenPrimaryGroup
        TokenDefaultDacl
        TokenSource
        TokenType
        TokenImpersonationLevel
        TokenStatistics
        TokenRestrictedSids
        TokenSessionId
        TokenGroupsAndPrivileges
        TokenSessionReference
        TokenSandBoxInert
        TokenAuditPolicy
        TokenOrigin
        TokenElevationType
        TokenLinkedToken
        TokenElevation
        TokenHasRestrictions
        TokenAccessInformation
        TokenVirtualizationAllowed
        TokenVirtualizationEnabled
        TokenIntegrityLevel
        TokenUIAccess
        TokenMandatoryPolicy
        TokenLogonSid
        MaxTokenInfoClass

    End Enum

#End Region

#Region "Constants"

    Private Const SE_PRIVILEGE_ENABLED = &H2L
    Private Const PROCESS_QUERY_INFORMATION = &H400
    Private Const TOKEN_ASSIGN_PRIMARY = &H1
    Private Const TOKEN_DUPLICATE = &H2
    Private Const TOKEN_IMPERSONATE = &H4
    Private Const TOKEN_QUERY = &H8
    Private Const TOKEN_QUERY_SOURCE = &H10
    Private Const TOKEN_ADJUST_PRIVILEGES = &H20
    Private Const TOKEN_ADJUST_GROUPS = &H40
    Private Const TOKEN_ADJUST_DEFAULT = &H80
    Private Const TOKEN_ADJUST_SESSIONID = &H100
    Private Const SecurityImpersonation = 2
    Private Const TokenPrimary = 1
    Private Const SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege"

#End Region

#Region "Methods"

    Friend Shared Sub LaunchFile(ByVal FilePath As String, ByVal IsWaitToFinish As Boolean)

        Try

            'Enable the SeIncreaseQuotaPrivilege in current token
            Dim HPrcsToken As IntPtr = Nothing
            OpenProcessToken(Process.GetCurrentProcess.Handle, TOKEN_ADJUST_PRIVILEGES, HPrcsToken)

            Dim TokenPrvlgs As TOKEN_PRIVILEGES
            TokenPrvlgs.PrivilegeCount = 1
            LookupPrivilegeValue(Nothing, SE_INCREASE_QUOTA_NAME, TokenPrvlgs.TheLuid)
            TokenPrvlgs.Attributes = SE_PRIVILEGE_ENABLED

            AdjustTokenPrivileges(HPrcsToken, False, TokenPrvlgs, 0, Nothing, Nothing)

            'Get window handle representing the desktop shell
            Dim HShellWnd As IntPtr = GetShellWindow()

            'Get the ID of the desktop shell process
            Dim ShellPID As IntPtr
            GetWindowThreadProcessId(HShellWnd, ShellPID)

            'Open the desktop shell process in order to get the process token
            Dim HShellPrcs As IntPtr = OpenProcess(PROCESS_QUERY_INFORMATION, False, ShellPID)
            Dim HShellPrcSToken As IntPtr = Nothing
            Dim HPrimaryToken As IntPtr = Nothing

            'Get the process token of the desktop shell
            OpenProcessToken(HShellPrcs, TOKEN_DUPLICATE, HShellPrcSToken)

            'Duplicate the shell's process token to get a primary token
            Dim TokenRights As UInteger = TOKEN_QUERY Or TOKEN_ASSIGN_PRIMARY Or TOKEN_DUPLICATE Or TOKEN_ADJUST_DEFAULT Or TOKEN_ADJUST_SESSIONID
            DuplicateTokenEx(HShellPrcSToken, TokenRights, Nothing, SecurityImpersonation, TokenPrimary, HPrimaryToken)

            Dim StartInfo As STARTUPINFO = Nothing
            Dim PrcsInfo As PROCESS_INFORMATION = Nothing

            StartInfo.cb = Marshal.SizeOf(StartInfo)
            Dim IsSuccessed As Boolean = CreateProcessWithTokenW(HPrimaryToken, 1, FilePath, "", 0, Nothing, Nothing, StartInfo, PrcsInfo)

            If IsSuccessed = True Then

                If IsWaitToFinish = True Then

                    Try

                        Dim Prcs As Process = Process.GetProcessById(PrcsInfo.dwProcessId)
                        Prcs.WaitForExit()

                    Catch ex As Exception
                    End Try

                End If

            Else

                'Could not launch process with shell token may be the process needs admin rights to launch, we will try to launch it with default parent process permissions.

                Dim Prcs As New Process
                Prcs.StartInfo.FileName = FilePath
                Prcs.Start()

                If IsWaitToFinish = True Then Prcs.WaitForExit()

            End If

        Catch ex As Exception
        End Try

    End Sub

#End Region

End Class

LaunchProcess 类用法

LaunchProcess.LaunchFile("C:\Program Files\Test\text.exe", False)

您可以使用 Telerik 代码转换器将代码转换为 C# http://converter.telerik.com/

【讨论】:

【参考方案8】:

Aaron Margosis 的 C# 代码article:

        private static void RunAsDesktopUser(string fileName)
    
        if (string.IsNullOrWhiteSpace(fileName))
            throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName));

        // To start process as shell user you will need to carry out these steps:
        // 1. Enable the SeIncreaseQuotaPrivilege in your current token
        // 2. Get an HWND representing the desktop shell (GetShellWindow)
        // 3. Get the Process ID(PID) of the process associated with that window(GetWindowThreadProcessId)
        // 4. Open that process(OpenProcess)
        // 5. Get the access token from that process (OpenProcessToken)
        // 6. Make a primary token with that token(DuplicateTokenEx)
        // 7. Start the new process with that primary token(CreateProcessWithTokenW)

        var hProcessToken = IntPtr.Zero;
        // Enable SeIncreaseQuotaPrivilege in this process.  (This won't work if current process is not elevated.)
        try
        
            var process = GetCurrentProcess();
            if (!OpenProcessToken(process, 0x0020, ref hProcessToken))
                return;

            var tkp = new TOKEN_PRIVILEGES
            
                PrivilegeCount = 1,
                Privileges = new LUID_AND_ATTRIBUTES[1]
            ;

            if (!LookupPrivilegeValue(null, "SeIncreaseQuotaPrivilege", ref tkp.Privileges[0].Luid))
                return;

            tkp.Privileges[0].Attributes = 0x00000002;

            if (!AdjustTokenPrivileges(hProcessToken, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero))
                return;
        
        finally
        
            CloseHandle(hProcessToken);
        

        // Get an HWND representing the desktop shell.
        // CAVEATS:  This will fail if the shell is not running (crashed or terminated), or the default shell has been
        // replaced with a custom shell.  This also won't return what you probably want if Explorer has been terminated and
        // restarted elevated.
        var hwnd = GetShellWindow();
        if (hwnd == IntPtr.Zero)
            return;

        var hShellProcess = IntPtr.Zero;
        var hShellProcessToken = IntPtr.Zero;
        var hPrimaryToken = IntPtr.Zero;
        try
        
            // Get the PID of the desktop shell process.
            uint dwPID;
            if (GetWindowThreadProcessId(hwnd, out dwPID) == 0)
                return;

            // Open the desktop shell process in order to query it (get the token)
            hShellProcess = OpenProcess(ProcessAccessFlags.QueryInformation, false, dwPID);
            if (hShellProcess == IntPtr.Zero)
                return;

            // Get the process token of the desktop shell.
            if (!OpenProcessToken(hShellProcess, 0x0002, ref hShellProcessToken))
                return;

            var dwTokenRights = 395U;

            // Duplicate the shell's process token to get a primary token.
            // Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
            if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hPrimaryToken))
                return;

            // Start the target process with the new token.
            var si = new STARTUPINFO();
            var pi = new PROCESS_INFORMATION();
            if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, "", 0, IntPtr.Zero, Path.GetDirectoryName(fileName), ref si, out pi))
                return;
        
        finally
        
            CloseHandle(hShellProcessToken);
            CloseHandle(hPrimaryToken);
            CloseHandle(hShellProcess);
        

    

    #region Interop

    private struct TOKEN_PRIVILEGES
    
        public UInt32 PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    

    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    private struct LUID_AND_ATTRIBUTES
    
        public LUID Luid;
        public UInt32 Attributes;
    

    [StructLayout(LayoutKind.Sequential)]
    private struct LUID
    
        public uint LowPart;
        public int HighPart;
    

    [Flags]
    private enum ProcessAccessFlags : uint
    
        All = 0x001F0FFF,
        Terminate = 0x00000001,
        CreateThread = 0x00000002,
        VirtualMemoryOperation = 0x00000008,
        VirtualMemoryRead = 0x00000010,
        VirtualMemoryWrite = 0x00000020,
        DuplicateHandle = 0x00000040,
        CreateProcess = 0x000000080,
        SetQuota = 0x00000100,
        SetInformation = 0x00000200,
        QueryInformation = 0x00000400,
        QueryLimitedInformation = 0x00001000,
        Synchronize = 0x00100000
    

    private enum SECURITY_IMPERSONATION_LEVEL
    
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    

    private enum TOKEN_TYPE
    
        TokenPrimary = 1,
        TokenImpersonation
    

    [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;
    

    [DllImport("kernel32.dll", ExactSpelling = true)]
    private static extern IntPtr GetCurrentProcess();

    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    private static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LookupPrivilegeValue(string host, string name, ref LUID pluid);

    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    private static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TOKEN_PRIVILEGES newst, int len, IntPtr prev, IntPtr relen);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);


    [DllImport("user32.dll")]
    private static extern IntPtr GetShellWindow();

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr phNewToken);

    [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool CreateProcessWithTokenW(IntPtr hToken, int dwLogonFlags, string lpApplicationName, string lpCommandLine, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

    #endregion

【讨论】:

这是一个很好的开始,谢谢!将其扩展为返回 Process 对象并传递参数相当容易:CreateProcessWithTokenW(hPrimaryToken, 0, fileName, $"\"fileName\" arguments",...return Process.GetProcessById(pi.dwProcessId); 请注意,您可以等待进程退出,但不要检查其错误代码。 如何将此解决方案应用于运行我自己的 Windows 应用程序? 您好,我正在尝试使用此方案以提升用户身份运行该进程。示例:我正在从管理员帐户(或从 SYSTEM 帐户)运行代码,并且需要以桌面用户(不属于管理员组)的身份运行新进程,但我需要使用管理员运行新进程权利。【参考方案9】:

这是我多年前看到的一个非常古老的问题,现在又重新审视了。由于它是第一个谷歌搜索结果......我会在这里发布我的答案。

我发现的所有解决方案都极其复杂和荒谬。多年来,我偶然发现了一个我在任何地方都没有看到文档的解决方案,直到现在也没有真正分享过。

代码非常简单...基本上我们正在编写一个批处理文件,其中包含您要运行的进程的可执行文件的名称/路径,以及您想要的任何参数。然后我们用批处理文件的路径生成一个 explorer.exe 进程......

File.WriteAllText(@"C:\test.bat", @"C:\test.exe -randomArgs");

var proc = new Process

    StartInfo = new ProcessStartInfo
    
        FileName = "explorer.exe",
        Arguments = @"C:\test.bat",
        UseShellExecute = true,
        Verb = "runas",
        WindowStyle = ProcessWindowStyle.Hidden
    
;
proc.Start();

我们生成的资源管理器进程立即被操作系统杀死,但是!根 explorer.exe 进程运行运行批处理文件!您可以为 explorer.exe 指定可执行文件的名称,它会执行相同的操作,但是该方法不支持参数。

据我所知,这是一个错误或未记录的功能。但是我无法想象它是如何被恶意使用的,因为它允许降低权限......这适用于 Windows 7/8/8.1/10。

【讨论】:

这如何解决不以管理员身份而是以不同用户身份启动进程的问题? @user3700562 因为它使用 explorer.exe 作为网关来启动 bat 文件。 这在 Windows 10 上对我不起作用。“此应用程序无法在您的电脑上运行。”。 Windows 拒绝启动 .CMD.BAT 这是一个安全屏幕错误。如果 Safescreen 阻止批处理文件,那是一个完全不同的问题。刚刚在没有安全屏的情况下进行了测试,它仍然有效。我维护的一个应用程序也使用了这个,所以如果它失败了,这对我来说将是一个主要的 PIA。 .bat可以在运行后自行删除,如果你添加del %0这一行。【参考方案10】:

在我的应用程序中,我最初使用了user3122201 的解决方案,效果很好。但是,我希望提升的应用程序使用匿名管道与低权限应用程序进行通信。为此,我需要一个允许继承管道句柄的解决方案。

我想出了下面允许句柄继承的解决方案。

请注意此解决方案与 user3122201 的另一个小区别:以下方法作为同一用户的进程运行,但访问受限,而 user3122201 的方法作为桌面用户的进程运行。

    public static class ProcessHelper
    
        /// 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();
                var pi = new PROCESS_INFORMATION();
                var cmd = new StringBuilder();
                cmd.Append('"').Append(fileName).Append('"');
                if (!string.IsNullOrWhiteSpace(args))
                
                    cmd.Append(' ').Append(args);
                

                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);
            
        

        // based on https://***.com/a/16110126/862099
        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
        
            /// <summary>
            /// The buffer receives a TOKEN_USER structure that contains the user account of the token.
            /// </summary>
            TokenUser = 1,

            /// <summary>
            /// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token.
            /// </summary>
            TokenGroups,

            /// <summary>
            /// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token.
            /// </summary>
            TokenPrivileges,

            /// <summary>
            /// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects.
            /// </summary>
            TokenOwner,

            /// <summary>
            /// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects.
            /// </summary>
            TokenPrimaryGroup,

            /// <summary>
            /// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects.
            /// </summary>
            TokenDefaultDacl,

            /// <summary>
            /// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information.
            /// </summary>
            TokenSource,

            /// <summary>
            /// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token.
            /// </summary>
            TokenType,

            /// <summary>
            /// 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.
            /// </summary>
            TokenImpersonationLevel,

            /// <summary>
            /// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics.
            /// </summary>
            TokenStatistics,

            /// <summary>
            /// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token.
            /// </summary>
            TokenRestrictedSids,

            /// <summary>
            /// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token.
            /// </summary>
            TokenSessionId,

            /// <summary>
            /// 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.
            /// </summary>
            TokenGroupsAndPrivileges,

            /// <summary>
            /// Reserved.
            /// </summary>
            TokenSessionReference,

            /// <summary>
            /// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag.
            /// </summary>
            TokenSandBoxInert,

            /// <summary>
            /// Reserved.
            /// </summary>
            TokenAuditPolicy,

            /// <summary>
            /// The buffer receives a TOKEN_ORIGIN value.
            /// </summary>
            TokenOrigin,

            /// <summary>
            /// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token.
            /// </summary>
            TokenElevationType,

            /// <summary>
            /// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token.
            /// </summary>
            TokenLinkedToken,

            /// <summary>
            /// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated.
            /// </summary>
            TokenElevation,

            /// <summary>
            /// The buffer receives a DWORD value that is nonzero if the token has ever been filtered.
            /// </summary>
            TokenHasRestrictions,

            /// <summary>
            /// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token.
            /// </summary>
            TokenAccessInformation,

            /// <summary>
            /// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token.
            /// </summary>
            TokenVirtualizationAllowed,

            /// <summary>
            /// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token.
            /// </summary>
            TokenVirtualizationEnabled,

            /// <summary>
            /// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level.
            /// </summary>
            TokenIntegrityLevel,

            /// <summary>
            /// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set.
            /// </summary>
            TokenUIAccess,

            /// <summary>
            /// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy.
            /// </summary>
            TokenMandatoryPolicy,

            /// <summary>
            /// The buffer receives the token's logon security identifier (SID).
            /// </summary>
            TokenLogonSid,

            /// <summary>
            /// The maximum value for this enumeration
            /// </summary>
            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);
    

【讨论】:

以上是关于如何从具有管理员权限的进程中启动没有管理员权限的新进程?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ADS 中创建具有管理员权限的新用户?

如何创建具有较低完整性级别 (IL) 的新流程?

Vista/7 UAC:如何降低进程权限

如何从 C++ 代码创建具有管理员权限的服务

VC如何将自身进程提升至管理员权限

如何以管理员权限启动进程