从C#调用CreateProcessAsUser

Nol*_*rin 9 c# privileges winapi process createprocessasuser

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

在我进一步解释之前,这里是我正在使用的代码来启动新进程(一个控制台进程 - 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);
    }
Run Code Online (Sandbox Code Playgroud)

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

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

"客户不持有所需的特权"(错误代码1314)

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

任何修改代码的建议以及解释都是非常受欢迎的.

笔记

我主要是指MSDN文档(以及C#function/strut/enum声明的PInvoke.net).以下几页似乎在备注部分中提供了大量信息,其中一些可能很重要并且让我不知所措:

编辑

我刚刚尝试了Mitch的建议,但不幸的是旧的错误刚被新的错误所取代:"系统无法找到指定的文件." (错误代码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);
Run Code Online (Sandbox Code Playgroud)

请注意,此代码不再使用重复令牌,而是使用原始代码,正如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);
Run Code Online (Sandbox Code Playgroud)

我也试过用UPN格式指定用户名("Alex @ Alex-PC")并独立地传递域名作为第二个参数,都无济于事(相同的错误).

Nol*_*rin 7

啊...似乎是骗子我被WinAPI互操作编程中最大的问题所困扰.此外,在这种情况下,发布我的函数声明的代码将是一个明智的想法.

无论如何,我需要做的就是为函数指定的DllImport属性添加一个参数CharSet = CharSet.Unicode.这为功能CreateProcessWithLogonWCreateProcessWithTokenW功能提供了技巧.我想最后只是告诉我,函数名称的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);
Run Code Online (Sandbox Code Playgroud)


Mit*_*eat 6

这里:

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

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


小智 5

Jonathan Peppers 提供了这段很棒的代码来解决我的问题

http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb?ppud=4

  • 应始终避免仅链接的答案。SO 有一项政策反对这一点。如果该链接被破坏(存档等),如果没有对其中内容和代码示例的摘要,这个答案将变得毫无用处。 (2认同)