CreateProcessAsUser 在活动会话中创建窗口

Posted

技术标签:

【中文标题】CreateProcessAsUser 在活动会话中创建窗口【英文标题】:CreateProcessAsUser Creating Window in Active Session 【发布时间】:2012-02-01 12:35:47 【问题描述】:

我正在使用 Windows 服务中的 CreateProcessAsUser(请我们保持主题并假设我有充分的理由这样做)。与其他人在这里问的相反,我在我的活动终端会话(会话 1)中得到了一个窗口,而不是与服务相同的会话(会话 0)——这是不可取的。

我挪用了Scott Allen's code;并想出了以下内容。显着的变化是“revert to self”、“CREATE_NO_WINDOW”和命令行参数支持。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Security.Principal;
using System.ComponentModel;
using System.IO;

namespace SourceCode.Runtime.ChildProcessService

    [SuppressUnmanagedCodeSecurity]
    class NativeMethods
    
        [StructLayout(LayoutKind.Sequential)]
        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 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)]
        public struct PROCESS_INFORMATION
        
            public IntPtr hProcess;
            public IntPtr hThread;
            public Int32 dwProcessID;
            public Int32 dwThreadID;
        

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        

        public enum SECURITY_IMPERSONATION_LEVEL
        
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        

        public enum TOKEN_TYPE
        
            TokenPrimary = 1,
            TokenImpersonation
        

        public const int GENERIC_ALL_ACCESS = 0x10000000;
        public const int CREATE_NO_WINDOW = 0x08000000;

        [
           DllImport("kernel32.dll",
              EntryPoint = "CloseHandle", SetLastError = true,
              CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)
        ]
        public static extern bool CloseHandle(IntPtr handle);

        [
           DllImport("advapi32.dll",
              EntryPoint = "CreateProcessAsUser", SetLastError = true,
              CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
        ]
        public static extern bool
           CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
                               ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
                               bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
                               string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
                               ref PROCESS_INFORMATION lpProcessInformation);

        [
           DllImport("advapi32.dll",
              EntryPoint = "DuplicateTokenEx")
        ]
        public static extern bool
           DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
                            ref SECURITY_ATTRIBUTES lpThreadAttributes,
                            Int32 ImpersonationLevel, Int32 dwTokenType,
                            ref IntPtr phNewToken);

        public static Process CreateProcessAsUser(string filename, string args)
        
            var hToken = WindowsIdentity.GetCurrent().Token;
            var hDupedToken = IntPtr.Zero;

            var pi = new PROCESS_INFORMATION();
            var sa = new SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa);

            try
            
                if (!DuplicateTokenEx(
                        hToken,
                        GENERIC_ALL_ACCESS,
                        ref sa,
                        (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                        (int)TOKEN_TYPE.TokenPrimary,
                        ref hDupedToken
                    ))
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                var si = new STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = "";

                var path = Path.GetFullPath(filename);
                var dir = Path.GetDirectoryName(path);

                // Revert to self to create the entire process; not doing this might
                // require that the currently impersonated user has "Replace a process
                // level token" rights - we only want our service account to need
                // that right.
                using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero))
                
                    if (!CreateProcessAsUser(
                                            hDupedToken,
                                            path,
                                            string.Format("\"0\" 1", filename.Replace("\"", "\"\""), args),
                                            ref sa, ref sa,
                                            false, 0, IntPtr.Zero,
                                            dir, ref si, ref pi
                                    ))
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                

                return Process.GetProcessById(pi.dwProcessID);
            
            finally
            
                if (pi.hProcess != IntPtr.Zero)
                    CloseHandle(pi.hProcess);
                if (pi.hThread != IntPtr.Zero)
                    CloseHandle(pi.hThread);
                if (hDupedToken != IntPtr.Zero)
                    CloseHandle(hDupedToken);
            
        
    

现在假设该服务在“Domain\MyService”下运行,并且我当前以“Domain\Administrator”身份登录 - 我正在将控制台应用程序作为工作进程启动。当我使用客户端应用程序访问服务(服务未以控制台模式启动,即它处于会话 0 中)并执行调用 CreateProcessAsUser 的方法时,工作进程出现在我的桌面上。

现在我可以让它成为一个没有窗口的 Windows 应用程序来回避控制台窗口的创建;但是,归根结底,它仍在会话 1 中创建。

知道为什么控制台应用程序没有在与服务相同的会话中创建吗?

【问题讨论】:

它看起来可能与this dark magic 有关,但我不知道如何跳过它。 尝试使用“Service-0×0-3e7$\Default”作为桌面 - 这会导致应用程序崩溃。 什么版本的 Windows?您是否尝试过将 lpDeskTop 设为 null? @HansPassant Server 2008 R2。我确实尝试将lpDeskTop 设置为null(这会导致工作进程崩溃) - 我还尝试将其设置为各种硬编码值,例如内置会话 0 桌面名称。 如果有这么多情况导致崩溃,则可能存在编组问题。你能发布你的结构和外部的代码吗? 【参考方案1】:

您可能已经知道,Session 0 的隔离是出于安全原因,您可以在此处阅读更多相关信息 http://msdn.microsoft.com/en-us/windows/hardware/gg463353.aspx

关于为什么在活动会话(例如会话 1)中创建控制台应用程序,这实际上直接链接回您的用户令牌。当您请求当前用户令牌时,此令牌会自动携带会话 id 信息 - 在这种情况下,它是登录终端服务会话(会话 1)。此会话 id 由令牌引用,然后在 DuplicateTokenEx 中复制,然后在 CreateProcessAsUser 调用中使用。为了强制在会话 0 中创建控制台应用程序,您需要显式调用 SetTokenInformation API (advapi32.dll),在调用 CreateProcessAsUser 之前传入您的 hDupedToken,如下所示

..................
UInt32 dwSessionId = 0;  // set it to session 0
SetTokenInformation(hDupedToken, TokenInformationClass.TokenSessionId, ref dwSessionId, (UInt32) IntPtr.Size);
.................
CreateProcessAsUser(hDupedToken, ....)

这里有更多关于 SetTokenInformation 的信息http://msdn.microsoft.com/en-us/library/windows/desktop/aa379591(v=vs.85).aspx

【讨论】:

我认为这可能是一些愚蠢的事情。我要试一试。 非常感谢您的帮助 - 我只是在获取 SE_TCB_NAME 权限时遇到问题(SetTokenInformation 需要)。我已授予我的服务帐户“作为操作系统的一部分”的权利(以及用于猎枪保险的服务)。我尝试使用OpenThreadTokenTOKEN_ALL_ACCESS - 无济于事。你已经获得了 +250 - 我只是希望你在这方面有更多的专业知识来帮助我。 你得到了赏金。是时候开始另一个赏金了;)。 感谢您的赏金。如果您的服务是使用本身具有有限操作系统权限的用户帐户启动的,我认为没有太多解决办法。您不能使用 LocalSystem 帐户启动您的服务吗? LocalSystem 可以启动您的进程并自动拥有该安全权限并访问您在 TS 环境中可能需要的其他 WTS API。 我曾考虑过这一点,但它对安装程序的影响非常大(我们的主要服务需要在域帐户下运行)——我还是会把它放在包里,看看管理层是否会咬人考虑到风险。感谢您的帮助。【参考方案2】:

我能够将最初的帖子作为我的工作解决方案实施,但是,我似乎无法找到隐藏我的控制台窗口的方法。我尝试了 STARTF_USESHOWWINDOW 和 SW_HIDE 但我的命令窗口仍然弹出。知道为什么吗?

    public const int STARTF_USESHOWWINDOW = 0x0000000;
    public const int SW_HIDE = 0;


   public static Process CreateProcessAsUser(string filename, string args)
    
        var hToken = WindowsIdentity.GetCurrent().Token;
        var hDupedToken = IntPtr.Zero;

        var pi = new PROCESS_INFORMATION();
        var sa = new SECURITY_ATTRIBUTES();
        sa.Length = Marshal.SizeOf(sa);

        try
        
            if (!DuplicateTokenEx(
                    hToken,
                    GENERIC_ALL_ACCESS,
                    ref sa,
                    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (int)TOKEN_TYPE.TokenPrimary,
                    ref hDupedToken
                ))
                throw new Win32Exception(Marshal.GetLastWin32Error());

            var si = new STARTUPINFO();
            si.cb = Marshal.SizeOf(si);
            si.lpDesktop = String.Empty; 

            si.dwFlags = STARTF_USESHOWWINDOW;
            si.wShowWindow = SW_HIDE;

            var path = Path.GetFullPath(filename);
            var dir = Path.GetDirectoryName(path);

            // Revert to self to create the entire process; not doing this might
            // require that the currently impersonated user has "Replace a process
            // level token" rights - we only want our service account to need
            // that right.
            using (var ctx = WindowsIdentity.Impersonate(IntPtr.Zero))
            
                UInt32 dwSessionId = 1;  // set it to session 0
                SetTokenInformation(hDupedToken, TOKEN_INFORMATION_CLASS.TokenSessionId,
                    ref dwSessionId, (UInt32)IntPtr.Size);
                if (!CreateProcessAsUser(
                                        hDupedToken,
                                        path,
                                        string.Format("\"0\" 1", filename.Replace("\"", "\"\""), args),
                                        ref sa, ref sa,
                                        false, 0, IntPtr.Zero,
                                        dir, ref si, ref pi
                                ))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            

            return Process.GetProcessById(pi.dwProcessID);
        
        finally
        
            if (pi.hProcess != IntPtr.Zero)
                CloseHandle(pi.hProcess);
            if (pi.hThread != IntPtr.Zero)
                CloseHandle(pi.hThread);
            if (hDupedToken != IntPtr.Zero)
                CloseHandle(hDupedToken);
        
    

【讨论】:

【参考方案3】:

尝试使用 MarshalAsStructLayoutDllImport 的 CharSet 命名参数。您可能需要将MarshalAs 添加到各种字符串中才能执行此操作。不要为 Unicode 烦恼:你没有使用它。我建议首先将它们全部设置为CharSet.Ansi。运行您已经尝试过的所有测试——即设置桌面和所有有趣的东西。如果它崩溃,将它们全部切换为自动。如果仍然不起作用,请将它们全部删除。

假设这些都不起作用,请切换到CreateUserProcessWCharSet.Unicode,这样你就知道你得到了什么。再三考虑,直接跳到这一步。

如果您需要为字符串设置UnmanagedTypeMarshalAs,则需要UnmanagedType.LPStr 用于Ansi,UnmanagedType.LPTStr 用于Auto,UnmanagedType.LPWStr 用于Unicode。实际上,无论如何都要对所有字符串执行此操作。

【讨论】:

我从来不需要为这些函数设置 CharSet,除非它们是 A 或 W 变体,所以我将赌注押在 Auto 上。 祝你好运!如果您有野心,请直接跳到 Unicode。

以上是关于CreateProcessAsUser 在活动会话中创建窗口的主要内容,如果未能解决你的问题,请参考以下文章

CreateProcessAsUser hToken 为零

无密码的 CreateProcessAsUser 和 LogonUser

“更改用户”时 CreateProcessAsUser 不起作用

CreateProcessAsUser 生成错误 5

为每个进程、用户或会话获取非交互式用户的 Window Station?

使用 CreateProcessAsUser 从服务运行应用程序