如何降低子进程的权限

33

我知道如何从一个进程中使用以下方法以管理员权限启动进程:

proc.StartInfo.UseShellExecute = true;
proc.StartInfo.Verb = "runas";

proc 是一个 System.Diagnostics.Process。但是如何进行相反的操作呢?

如果你运行的进程已经被提升了权限,那么如何在没有管理员权限的情况下启动新进程?更准确地说,我们需要以与 Windows Explorer 相同的权限级别启动新进程,因此如果 UAC 被禁用,则不需要更改权限级别,但如果 UAC 启用且我们的进程正在以提升的方式运行,则需要执行某些未提升的操作,因为我们正在创建虚拟驱动器,如果使用提升的权限创建,并且 Windows Explorer 正在以未提升的方式运行,则不会显示它。

随意更改标题以得到更好的描述,我想不出更好的描述了。


3
同样的问题:https://dev59.com/XXVC5IYBdhLWcg3wvT7g 尽管答案看起来有点可怕。 - weiqure
3
它不仅令人害怕,而且是错误的。UAC 提升只是一种方式,没有记录的方法可以返回(注入 explorer.exe 或使用任务计划程序是一种非常粗暴的方法,可能不是一个好主意)。 - Anders
6个回答

23

对您的解决方案是使用EXPLORER.exe进程。

这个想法是在未提升的模式下运行进程,使用Windows的文件浏览器进程explorer.exe (信息)。 假设我们要启动的进程位于$TEMP\MyUnElevatedProcess.exe中。

因此,对于NSIS代码,我只需编写如下内容:(但可以用任何语言运行)

 Exec '"$WINDIR\explorer.exe" "$TEMP\MyUnElevatedProcess.exe"'

示例代码(使用 NSIS 安装程序)

Exec '"$WINDIR\explorer.exe" "$TEMP\MyUnElevatedProcess.exe"'

***代码源自http://mdb-blog.blogspot.com/2013/01/nsis-lunch-program-as-user-from-uac.html


1
这是我见过的启动非提升进程的最佳答案之一...不幸的是,我无法捕获MyUnElevatedProcess.exe的stdout或stderr,因此无法使用它。 - Kenn
2
我正在尝试使用这个解决方案,但应用程序在Windir文件夹下运行,而不是Temp文件夹。有什么解决办法吗?(即文件相对于windir文件夹) - sebas
2
要在现代UI安装程序的完成页面上使用此复选框,请使用两行。第一行:!define MUI_FINISHPAGE_RUN "$WINDIR\explorer.exe",第二行:!define MUI_FINISHPAGE_RUN_PARAMETERS "$TEMP\MyUnElevatedProcess.exe" - Uwe Keim
15
在链接的页面上,来自微软员工?的评论是:“不幸的是,Windows Shell团队已经回复说,“Explorer.exe AppName.exe”的当前行为是一个错误,并且在未来的更新/版本中可能无法使用。应用程序不应该依赖它。” - Uwe Keim
1
最后一点补充:如果您的参数包含空格,您可以将它们嵌入到 escaped 引号中。要将参数嵌入引号中(如果它们包含空格) $\",例如:!define MUI_FINISHPAGE_RUN_PARAMETERS '$\"$TEMP\MyUnElevatedProcess.exe$\"' - Uwe Keim
显示剩余2条评论

9
我们最终使用了这篇Code Project文章中的示例:高权限可能对您的应用程序有害:如何在安装结束时启动非高权限进程 到目前为止,它似乎很有效,我认为它是注入到RunDll32.exe中的,我的C++/Win32能力相当薄弱,所以我并没有太深入地研究实际实现,只是使用了它。已确认它在Vista和Win7上都可以工作,无论是x86还是x64(至少对我们来说是这样,因为x86和x64需要不同的dll,在安装时进行检查并使用正确的dll)。

7

雷蒙德·陈在他的博客中解决了这个问题:

如何从我的已提升进程启动未提升进程,反之亦然?

GitHub上搜索C#版本的此代码,我在微软的Visual Studio Node.js工具库中找到了以下实现: SystemUtilities.cs (其中包含ExecuteProcessUnElevated函数)。

以防万一文件消失,这里是文件的内容:

// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Runtime.InteropServices;

namespace Microsoft.NodejsTools.SharedProject
{
    /// <summary>
    /// Utility for accessing window IShell* interfaces in order to use them to launch a process unelevated
    /// </summary>
    internal class SystemUtility
    {
        /// <summary>
        /// We are elevated and should launch the process unelevated. We can't create the
        /// process directly without it becoming elevated. So to workaround this, we have
        /// explorer do the process creation (explorer is typically running unelevated).
        /// </summary>
        internal static void ExecuteProcessUnElevated(string process, string args, string currentDirectory = "")
        {
            var shellWindows = (IShellWindows)new CShellWindows();

            // Get the desktop window
            object loc = CSIDL_Desktop;
            object unused = new object();
            int hwnd;
            var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref loc, ref unused, SWC_DESKTOP, out hwnd, SWFO_NEEDDISPATCH);

            // Get the shell browser
            var serviceGuid = SID_STopLevelBrowser;
            var interfaceGuid = typeof(IShellBrowser).GUID;
            var shellBrowser = (IShellBrowser)serviceProvider.QueryService(ref serviceGuid, ref interfaceGuid);

            // Get the shell dispatch
            var dispatch = typeof(IDispatch).GUID;
            var folderView = (IShellFolderViewDual)shellBrowser.QueryActiveShellView().GetItemObject(SVGIO_BACKGROUND, ref dispatch);
            var shellDispatch = (IShellDispatch2)folderView.Application;

            // Use the dispatch (which is unelevated) to launch the process for us
            shellDispatch.ShellExecute(process, args, currentDirectory, string.Empty, SW_SHOWNORMAL);
        }

        /// <summary>
        /// Interop definitions
        /// </summary>
        private const int CSIDL_Desktop = 0;
        private const int SWC_DESKTOP = 8;
        private const int SWFO_NEEDDISPATCH = 1;
        private const int SW_SHOWNORMAL = 1;
        private const int SVGIO_BACKGROUND = 0;
        private readonly static Guid SID_STopLevelBrowser = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");

        [ComImport]
        [Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
        [ClassInterfaceAttribute(ClassInterfaceType.None)]
        private class CShellWindows
        {
        }

        [ComImport]
        [Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        private interface IShellWindows
        {
            [return: MarshalAs(UnmanagedType.IDispatch)]
            object FindWindowSW([MarshalAs(UnmanagedType.Struct)] ref object pvarloc, [MarshalAs(UnmanagedType.Struct)] ref object pvarlocRoot, int swClass, out int pHWND, int swfwOptions);
        }

        [ComImport]
        [Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IServiceProvider
        {
            [return: MarshalAs(UnmanagedType.Interface)]
            object QueryService(ref Guid guidService, ref Guid riid);
        }

        [ComImport]
        [Guid("000214E2-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellBrowser
        {
            void VTableGap01(); // GetWindow
            void VTableGap02(); // ContextSensitiveHelp
            void VTableGap03(); // InsertMenusSB
            void VTableGap04(); // SetMenuSB
            void VTableGap05(); // RemoveMenusSB
            void VTableGap06(); // SetStatusTextSB
            void VTableGap07(); // EnableModelessSB
            void VTableGap08(); // TranslateAcceleratorSB
            void VTableGap09(); // BrowseObject
            void VTableGap10(); // GetViewStateStream
            void VTableGap11(); // GetControlWindow
            void VTableGap12(); // SendControlMsg
            IShellView QueryActiveShellView();
        }

        [ComImport]
        [Guid("000214E3-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellView
        {
            void VTableGap01(); // GetWindow
            void VTableGap02(); // ContextSensitiveHelp
            void VTableGap03(); // TranslateAcceleratorA
            void VTableGap04(); // EnableModeless
            void VTableGap05(); // UIActivate
            void VTableGap06(); // Refresh
            void VTableGap07(); // CreateViewWindow
            void VTableGap08(); // DestroyViewWindow
            void VTableGap09(); // GetCurrentInfo
            void VTableGap10(); // AddPropertySheetPages
            void VTableGap11(); // SaveViewState
            void VTableGap12(); // SelectItem

            [return: MarshalAs(UnmanagedType.Interface)]
            object GetItemObject(UInt32 aspectOfView, ref Guid riid);
        }

        [ComImport]
        [Guid("00020400-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        private interface IDispatch
        {
        }

        [ComImport]
        [Guid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        private interface IShellFolderViewDual
        {
            object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
        }

        [ComImport]
        [Guid("A4C6892C-3BA9-11D2-9DEA-00C04FB16162")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        public interface IShellDispatch2
        {
            void ShellExecute([MarshalAs(UnmanagedType.BStr)] string File, [MarshalAs(UnmanagedType.Struct)] object vArgs, [MarshalAs(UnmanagedType.Struct)] object vDir, [MarshalAs(UnmanagedType.Struct)] object vOperation, [MarshalAs(UnmanagedType.Struct)] object vShow);
        }
    }
}

2
Raymond还发布了一篇跟进文章:如何从我的提权进程中启动一个非提权进程,redux,使用 CreateProcess 而不是 ShellExecute - Remy Lebeau

1
如果您想从一个以管理员权限运行的进程开始一个非管理员权限的进程,您可以复制当前 Shell 进程的访问令牌,并使用该令牌启动一个新的进程。
public static class UnelevatedProcessStarter
{
    public static void Start(string cmdArgs)
    {
        // 1. Get the shell
        var shell = NativeMethods.GetShellWindow();
        if (shell == IntPtr.Zero)
        {
            throw new Exception("Could not find shell window");
        }

        // 2. Copy the access token of the process
        NativeMethods.GetWindowThreadProcessId(shell, out uint shellProcessId);
        var hShellProcess = NativeMethods.OpenProcess(0x00000400 /* QueryInformation */, false, (int)shellProcessId);
        if (!NativeMethods.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out IntPtr hShellToken))
        {
            throw new Win32Exception();
        }

        // 3. Duplicate the acess token
        uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
        var securityAttributes = new SecurityAttributes();

        NativeMethods.DuplicateTokenEx(
            hShellToken,
            tokenAccess,
            ref securityAttributes,
            2 /* SecurityImpersonation */,
            1 /* TokenPrimary */,
            out IntPtr hToken);

        // 4. Create a new process with the copied token
        var si = new Startupinfo();
        si.cb = Marshal.SizeOf(si);

        if (!NativeMethods.CreateProcessWithTokenW(
            hToken,
            0x00000002 /* LogonNetcredentialsOnly */,
            null,
            cmdArgs,
            0x00000010 /* CreateNewConsole */,
            IntPtr.Zero,
            null,
            ref si,
            out ProcessInformation _))
        {
            throw new Win32Exception();
        }
    }

    public class NativeMethods
    {
        [DllImport("user32.dll")]
        public static extern IntPtr GetShellWindow();
        [DllImport("user32.dll", SetLastError = true)]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId);
        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool OpenProcessToken(IntPtr processHandle, UInt32 desiredAccess, out IntPtr tokenHandle);
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess,
            ref SecurityAttributes lpTokenAttributes,
            int impersonationLevel,
            int tokenType,
            out IntPtr phNewToken);
        [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CreateProcessWithTokenW(
            IntPtr hToken, int dwLogonFlags,
            string lpApplicationName, string lpCommandLine,
            int dwCreationFlags, IntPtr lpEnvironment,
            string lpCurrentDirectory, [In] ref Startupinfo lpStartupInfo, out ProcessInformation lpProcessInformation);
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ProcessInformation
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SecurityAttributes
    {
        public int nLength;
        public IntPtr lpSecurityDescriptor;
        public int bInheritHandle;
    }
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public 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;
    }
}

0

基于@NtFreX的解决方案,我创建了另一个解决方案,它:

  1. 从当前标识中获取令牌(似乎有效且需要较少的代码), - 抱歉,这并不总是有效的
  2. 处理从已经取消提升的进程运行的情况(因为在这种情况下,CreateProcessWithTokenW将失败,并显示错误代码1314-“客户端未持有所需特权”)和
  3. 返回进程ID,以便调用代码可以等待它等。
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class UnelevatedProcessStarter
{
    public static int Start(string cmdArgs)
    {
        // 1. Get the shell
        var shell = NativeMethods.GetShellWindow();
        if (shell == IntPtr.Zero)
        {
            throw new Exception("Could not find shell window");
        }

        // 2. Copy the access token of the process
        uint shellProcessId;
        NativeMethods.GetWindowThreadProcessId(shell, out shellProcessId);
        var hShellProcess = NativeMethods.OpenProcess(0x00000400 /* QueryInformation */, false, (int)shellProcessId);
        IntPtr hShellToken;
        if (!NativeMethods.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out hShellToken))
        {
            throw new Win32Exception();
        }

        // 3. Duplicate the access token
        uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
        var securityAttributes = new SecurityAttributes();
        IntPtr hToken;
        if (!NativeMethods.DuplicateTokenEx(
            hShellToken,
            tokenAccess,
            ref securityAttributes,
            2 /* SecurityImpersonation */,
            1 /* TokenPrimary */,
            out hToken))
        {
            throw new Win32Exception();
        }

        // 4. Create a new process with the copied token
        var si = new Startupinfo();
        si.cb = Marshal.SizeOf(si);
        ProcessInformation processInfo;
        if (!NativeMethods.CreateProcessWithTokenW(
            hToken,
            0x00000002 /* LogonNetcredentialsOnly */,
            null,
            cmdArgs,
            0x00000010 /* CreateNewConsole */,
            IntPtr.Zero,
            null,
            ref si,
            out processInfo))
        {
            // Can't do that when not elevated (see https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw)
            // -> start the process as usual
            if (Marshal.GetLastWin32Error() == 1314)
            {
                SecurityAttributes processSecurityAttributes = new SecurityAttributes();
                SecurityAttributes threadSecurityAttributes = new SecurityAttributes();
                if (!NativeMethods.CreateProcessAsUser(
                    IntPtr.Zero,
                    null,
                    cmdArgs,
                    ref processSecurityAttributes,
                    ref threadSecurityAttributes,
                    true,
                    0x00000010 /* CreateNewConsole */,
                    IntPtr.Zero,
                    null,
                    ref si,
                    out processInfo))
                {
                    throw new Win32Exception();
                }
            }
            else
            {
                throw new Win32Exception();
            }
        }

        return processInfo.dwProcessId;
    }

    public class NativeMethods
    {
        [DllImport("user32.dll")]
        public static extern IntPtr GetShellWindow();
        [DllImport("user32.dll", SetLastError = true)]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId);
        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool OpenProcessToken(IntPtr processHandle, UInt32 desiredAccess, out IntPtr tokenHandle);
        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess,
            ref SecurityAttributes lpTokenAttributes,
            int impersonationLevel,
            int tokenType,
            out IntPtr phNewToken);
        [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CreateProcessWithTokenW(
            IntPtr hToken, int dwLogonFlags,
            string lpApplicationName, string lpCommandLine,
            int dwCreationFlags, IntPtr lpEnvironment,
            string lpCurrentDirectory,
            [In] ref Startupinfo lpStartupInfo,
            out ProcessInformation lpProcessInformation);

        [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
        public static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            ref SecurityAttributes lpProcessAttributes,
            ref SecurityAttributes lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref Startupinfo lpStartupInfo,
            out ProcessInformation lpProcessInformation);
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ProcessInformation
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SecurityAttributes
    {
        public int nLength;
        public IntPtr lpSecurityDescriptor;
        public int bInheritHandle;
    }
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public 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;
    }
}

-2

您可以使用ProcessStartInfo.UserNameProcessStartInfo.Password来指定您希望进程在哪个账户下运行。

class Program
{
    static void Main(string[] args)
    {
        var psi = new ProcessStartInfo(@"c:\windows\system32\whoami.exe");
        var password = new SecureString();
        password.AppendChar('s');
        password.AppendChar('e');
        password.AppendChar('c');
        password.AppendChar('r');
        password.AppendChar('e');
        password.AppendChar('t');
        psi.Password = password;
        psi.UserName = "username";
        psi.UseShellExecute = false;
        psi.RedirectStandardOutput = true;

        var p = new Process();
        p.StartInfo = psi;
        p.Start();
        p.WaitForExit();

        Console.WriteLine(p.StandardOutput.ReadToEnd());
    }
}

14
如果用户名和密码未知,你会如何操作?需要适用于任何未知的计算机。 - Davy8
@Davy8,“每个人”用户似乎在所有Windows机器上都可用。我尝试过了,但不确定为什么它仍然抱怨用户名/密码不正确。也许即使“每个人”用户也有一些隐藏的密码?很奇怪。 - Hopeless

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接