从 C# 调用 CreateProcessAsUser

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/668389/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 12:36:20  来源:igfitidea点击:

Calling CreateProcessAsUser from C#

c#winapiprocessprivilegescreateprocessasuser

提问by Noldorin

I've been attempting to create a new process under the context of a specific user using the CreateProcessAsUserfunction of the Windows API, but seem to be running into a rather nasty security issue...

我一直在尝试使用CreateProcessAsUserWindows API的功能在特定用户的上下文中创建一个新进程,但似乎遇到了一个相当讨厌的安全问题......

Before I explain any further, here's the code I'm currently using to start the new process (a console process - PowerShell to be specific, though it shouldn't matter).

在我进一步解释之前,这是我目前用于启动新进程的代码(控制台进程 - 具体来说是 PowerShell,但它应该无关紧要)。

    private void StartProcess()
    {
        bool retValue;

        // Create startup info for new console process.
        var startupInfo = new STARTUPINFO();
        startupInfo.cb = Marshal.SizeOf(startupInfo);
        startupInfo.dwFlags = StartFlags.STARTF_USESHOWWINDOW;
        startupInfo.wShowWindow = _consoleVisible ? WindowShowStyle.Show : WindowShowStyle.Hide;
        startupInfo.lpTitle = this.ConsoleTitle ?? "Console";

        var procAttrs = new SECURITY_ATTRIBUTES();
        var threadAttrs = new SECURITY_ATTRIBUTES();
        procAttrs.nLength = Marshal.SizeOf(procAttrs);
        threadAttrs.nLength = Marshal.SizeOf(threadAttrs);

        // Log on user temporarily in order to start console process in its security context.
        var hUserToken = IntPtr.Zero;
        var hUserTokenDuplicate = IntPtr.Zero;
        var pEnvironmentBlock = IntPtr.Zero;
        var pNewEnvironmentBlock = IntPtr.Zero;

        if (!WinApi.LogonUser("UserName", null, "Password",
            LogonType.Interactive, LogonProvider.Default, out hUserToken))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Error logging on user.");

        var duplicateTokenAttrs = new SECURITY_ATTRIBUTES();
        duplicateTokenAttrs.nLength = Marshal.SizeOf(duplicateTokenAttrs);
        if (!WinApi.DuplicateTokenEx(hUserToken, 0, ref duplicateTokenAttrs,
            SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary,
            out hUserTokenDuplicate))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Error duplicating user token.");

        try
        {
            // Get block of environment vars for logged on user.
            if (!WinApi.CreateEnvironmentBlock(out pEnvironmentBlock, hUserToken, false))
                throw new Win32Exception(Marshal.GetLastWin32Error(),
                    "Error getting block of environment variables for user.");

            // Read block as array of strings, one per variable.
            var envVars = ReadEnvironmentVariables(pEnvironmentBlock);

            // Append custom environment variables to list.
            foreach (var var in this.EnvironmentVariables)
                envVars.Add(var.Key + "=" + var.Value);

            // Recreate environment block from array of variables.
            var newEnvironmentBlock = string.Join("
retValue = WinApi.CreateProcessWithTokenW(hUserToken, LogonFlags.WithProfile, null,
    this.CommandLine, CreationFlags.CREATE_NEW_CONSOLE |
    CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
    pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);
", envVars.ToArray()) + "
retValue = WinApi.CreateProcessWithLogonW("Alex", null, "password",
    LogonFlags.WithProfile, null, this.CommandLine,
    CreationFlags.CREATE_NEW_CONSOLE | CreationFlags.CREATE_SUSPENDED |
    CreationFlags.CREATE_UNICODE_ENVIRONMENT, pNewEnvironmentBlock,
    null, ref startupInfo, out _processInfo);
"; pNewEnvironmentBlock = Marshal.StringToHGlobalUni(newEnvironmentBlock); // Start new console process. retValue = WinApi.CreateProcessAsUser(hUserTokenDuplicate, null, this.CommandLine, ref procAttrs, ref threadAttrs, false, CreationFlags.CREATE_NEW_CONSOLE | CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT, pNewEnvironmentBlock, null, ref startupInfo, out _processInfo); if (!retValue) throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to create new console process."); } catch { // Catch any exception thrown here so as to prevent any malicious program operating // within the security context of the logged in user. // Clean up. if (hUserToken != IntPtr.Zero) { WinApi.CloseHandle(hUserToken); hUserToken = IntPtr.Zero; } if (hUserTokenDuplicate != IntPtr.Zero) { WinApi.CloseHandle(hUserTokenDuplicate); hUserTokenDuplicate = IntPtr.Zero; } if (pEnvironmentBlock != IntPtr.Zero) { WinApi.DestroyEnvironmentBlock(pEnvironmentBlock); pEnvironmentBlock = IntPtr.Zero; } if (pNewEnvironmentBlock != IntPtr.Zero) { Marshal.FreeHGlobal(pNewEnvironmentBlock); pNewEnvironmentBlock = IntPtr.Zero; } throw; } finally { // Clean up. if (hUserToken != IntPtr.Zero) WinApi.CloseHandle(hUserToken); if (hUserTokenDuplicate != IntPtr.Zero) WinApi.CloseHandle(hUserTokenDuplicate); if (pEnvironmentBlock != IntPtr.Zero) WinApi.DestroyEnvironmentBlock(pEnvironmentBlock); if (pNewEnvironmentBlock != IntPtr.Zero) Marshal.FreeHGlobal(pNewEnvironmentBlock); } _process = Process.GetProcessById(_processInfo.dwProcessId); }

For the sake of the issue here, ignore the code dealing with the environment variables (I've tested that section independently and it seems to work.)

为了这里的问题,忽略处理环境变量的代码(我已经独立测试了该部分,它似乎有效。)

Now, the error I get is the following (thrown at the line following the call to CreateProcessAsUSer):

现在,我得到的错误如下(在调用之后的行抛出CreateProcessAsUSer):

"A required privilege is not held by the client" (error code 1314)

“客户端没有所需的权限”(错误代码 1314)

(The error message was discovered by removing the message parameter from the Win32Exception constructor. Admittedly, my error handling code here may not be the best, but that's a somewhat irrelevant matter. You're welcome to comment on it if you wish, however.) I'm really quite confused as to the cause of this vague error in this situation. MSDN documentation and various forum threads have only given me so much advice, and especially given that the causes for such errors appear to be widely varied, I have no idea which section of code I need to modify. Perhaps it is simply a single parameter I need to change, but I could be making the wrong/not enough WinAPI calls for all I know. What confuses me greatly is that the previous version of the code that uses the plain CreateProcessfunction (equivalent except for the user token parameter) worked perfectly fine. As I understand, it is only necessary to call the Logon user function to receive the appropriate token handle and then duplicate it so that it can be passed to CreateProcessAsUser.

(错误消息是通过从 Win32Exception 构造函数中删除 message 参数发现的。诚然,我这里的错误处理代码可能不是最好的,但这是一个无关紧要的问题。不过,如果您愿意,欢迎您对此发表评论。 ) 我真的很困惑在这种情况下这个模糊错误的原因。MSDN 文档和各种论坛帖子只给了我这么多建议,尤其是考虑到此类错误的原因似乎千差万别,我不知道我需要修改哪一部分代码。也许这只是我需要更改的单个参数,但我可能会做出错误/不够的 WinAPI 调用,据我所知。令我非常困惑的是,之前版本的代码使用了普通的CreateProcess函数(等效于用户令牌参数除外)工作得很好。据我了解,只需要调用登录用户函数来接收适当的令牌句柄,然后复制它,以便可以将其传递给CreateProcessAsUser.

Any suggestions for modifications to the code as well as explanations would be very welcome.

任何修改代码的建议以及解释将非常受欢迎。

Notes

笔记

I've been primarily referring to the MSDN docs (as well as PInvoke.netfor the C# function/strut/enum declarations). The following pages in particular seem to have a lot of information in the Remarks sections, some of which may be important and eluding me:

我主要参考了 MSDN 文档(以及用于 C# 函数/strut/enum 声明的PInvoke.net)。特别是以下页面的备注部分似乎有很多信息,其中一些可能很重要并且我无法理解:

Edit

编辑

I've just tried out Mitch's suggestion, but unfortunately the old error has just been replaced by a new one: "The system cannot find the file specified." (error code 2)

我刚刚尝试了 Mitch 的建议,但不幸的是,旧错误刚刚被一个新错误取代:“系统找不到指定的文件。” (错误代码 2)

The previous call to CreateProcessAsUserwas replaced with the following:

之前的调用CreateProcessAsUser被替换为以下内容:

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessWithLogonW(string principal, string authority,
    string password, LogonFlags logonFlags, string appName, string cmdLine,
    CreationFlags creationFlags, IntPtr environmentBlock, string currentDirectory,
    ref STARTUPINFO startupInfo, out PROCESS_INFORMATION processInfo);

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

Note that this code no longer uses the duplicate token but rather the original, as the MSDN docs appear to suggest.

请注意,此代码不再使用重复的令牌,而是使用原始令牌,正如 MSDN 文档所建议的那样。

And here's another attempt using CreateProcessWithLogonW. The error this time is "Logon failure: unknown user name or bad password" (error code 1326)

这是使用CreateProcessWithLogonW. 这次的错误是“登录失败:未知用户名或密码错误”(错误代码1326)

##代码##

I've also tried specifying the username in UPN format ("Alex@Alex-PC") and passing the domain independently as the second argument, all to no avail (identical error).

我还尝试以 UPN 格式(“Alex@Alex-PC”)指定用户名并将域作为第二个参数独立传递,但都无济于事(相同的错误)。

采纳答案by Noldorin

Ahh... seems liker I've been caught out by one of the biggest gotchas in WinAPI interop programming. Also, Posting the code for my function declarations would have been a wise idea in this case.

啊...似乎更像是我被 WinAPI 互操作编程中最大的陷阱之一所吸引。此外,在这种情况下,为我的函数声明发布代码将是一个明智的主意。

Anyway, all that I needed to do was add an argument to the DllImport attribute of the function specifying CharSet = CharSet.Unicode. This did the trick for both the CreateProcessWithLogonWand CreateProcessWithTokenWfunctions. I guess it finally just hit me that the W suffix of the function names referred to Unicode and that I needed to explicitly specify this in C#! Here are the correctfunction declarations in case anyone is interested:

无论如何,我需要做的就是向函数的 DllImport 属性添加一个参数,指定CharSet = CharSet.Unicode. 这对CreateProcessWithLogonWCreateProcessWithTokenW函数都起到了作用。我想最后我突然想到函数名称的 W 后缀引用了 Unicode,我需要在 C# 中明确指定它!如果有人感兴趣,这里是正确的函数声明:

##代码##

回答by Mitch Wheat

From here:

这里

Typically, the process that calls the CreateProcessAsUser function must have the SE_ASSIGNPRIMARYTOKEN_NAME and SE_INCREASE_QUOTA_NAME privileges. If this function fails with ERROR_PRIVILEGE_NOT_HELD (1314), use the CreateProcessWithLogonW function instead. CreateProcessWithLogonW requires no special privileges, but the specified user account must be allowed to log on interactively. Generally, it is best to use CreateProcessWithLogonW to create a process with alternate credentials.

通常,调用 CreateProcessAsUser 函数的进程必须具有 SE_ASSIGNPRIMARYTOKEN_NAME 和 SE_INCREASE_QUOTA_NAME 权限。如果此函数因 ERROR_PRIVILEGE_NOT_HELD (1314) 而失败,请改用 CreateProcessWithLogonW 函数。CreateProcessWithLogonW 不需要特殊权限,但必须允许指定的用户帐户交互登录。通常,最好使用 CreateProcessWithLogonW 创建具有备用凭据的进程。

See this blog post How to call CreateProcessWithLogonW & CreateProcessAsUser in .NET

请参阅此博客文章如何在 .NET 中调用 CreateProcessWithLogonW 和 CreateProcessAsUser

回答by ermagana