从 C# 调用 CreateProcessAsUser

发布于 2024-07-14 23:52:19 字数 6901 浏览 15 评论 0 原文

我一直在尝试使用 Windows API 的 CreateProcessAsUser 函数在特定用户的上下文中创建一个新进程,但似乎遇到了相当严重的安全问题...

在我解释之前更进一步,这是我当前用于启动新进程的代码(控制台进程 - 具体来说是 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("\0", envVars.ToArray()) + "\0";
            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);
    }

为了解决这里的问题,请忽略处理环境变量的代码(我已经独立测试了该部分,它似乎可以工作。)

现在,我得到的错误如下(抛出在调用 <代码>CreateProcessAsUSer):

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

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

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

我主要参考了 MSDN 文档(以及 C# function/strut/enum 声明的 PInvoke.net )。 特别是以下几页的“备注”部分似乎包含大量信息,其中一些可能很重要但我却无法理解:

编辑

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

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

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

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

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

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

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

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

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

    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("\0", envVars.ToArray()) + "\0";
            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):

"A required privilege is not held by the client" (error code 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 CreateProcess function (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.

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.net for 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:

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)

The previous call to CreateProcessAsUser was replaced with the following:

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

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

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

薆情海 2024-07-21 23:52:19

啊...似乎我遇到了 WinAPI 互操作编程中最大的陷阱之一。 另外,在这种情况下,发布我的函数声明的代码是一个明智的主意。

不管怎样,我所需要做的就是向指定 CharSet = CharSet.Unicode 的函数的 DllImport 属性添加一个参数。 这对 CreateProcessWithLogonW 和 CreateProcessWithTokenW 函数都起到了作用。 我想我终于意识到函数名称的 W 后缀引用了 Unicode,并且我需要在 C# 中显式指定这一点! 以下是正确的函数声明,以防有人感兴趣:

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

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.

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 CreateProcessWithLogonW and CreateProcessWithTokenW functions. 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 correct function declarations in case anyone is interested:

[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);
少女情怀诗 2024-07-21 23:52:19

来自此处

通常,调用
CreateProcessAsUser函数必须有
SE_ASSIGNPRIMARYTOKEN_NAME 和
SE_INCREASE_QUOTA_NAME 权限。 如果
该函数失败并显示
ERROR_PRIVILEGE_NOT_HELD (1314),使用
CreateProcessWithLogonW 函数
反而。 使用登录创建进程
不需要特殊特权,但是
指定的用户帐户必须是
允许交互式登录。
一般来说,最好使用
CreateProcessWithLogonW 创建一个
使用备用凭据进行处理。

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

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.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文