作为当前用户,在Windows服务中运行进程

10

我目前有一个在System账户下运行的Windows服务。我的问题是,我需要在服务内部以当前登录用户身份启动某些进程。我已经有了所有代码等来获取当前登录的用户/活动会话。

我的问题是,我需要将一个进程作为已登录用户生成,但不知道用户凭据等信息。

该服务是.net编译服务,我预计需要使用一些Pinvoke方法来获取当前用户进程中的一个句柄,以便复制它并使用该句柄作为进程启动。

不幸的是,我找不到任何好的文档/解决方案来实现它?

如果有人能给我一些指导/示例,我将非常感激。

* 更新 * 我认为我解释有误,并需要根据我实际需要进行重新调整。我并不一定想启动一个新进程,我只想模拟已登录用户。我一直盯着CreateProcess等东西看,把自己引入了生成一个新进程作为当前登录用户的路径(这不是我特别想做的)。

因此,我只想在当前用户上下文中运行一些代码(模拟当前登录的用户)?


如果没有用户登录怎么办? - Jacob Seleznev
我认为你尝试使用管理员帐户运行Windows服务。 - Pranav1688
2个回答

16

一种选择是拥有后台应用程序,在用户登录时自动启动,并通过WCF、thrift或仅监视某个文件并从那里读取命令来侦听您的服务的命令。

另一个选择是按照您最初要求的方式使用Windows API进行启动。但代码相当可怕。这是一个示例,您可以使用它。它将使用CreateProcessInConsoleSession方法执行当前活动用户会话下的任何命令行:

internal class ApplicationLauncher
{
    public enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        MaxTokenInfoClass // MaxTokenInfoClass should always be the last enum
    }

    public const int READ_CONTROL = 0x00020000;

    public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;

    public const int STANDARD_RIGHTS_READ = READ_CONTROL;
    public const int STANDARD_RIGHTS_WRITE = READ_CONTROL;
    public const int STANDARD_RIGHTS_EXECUTE = READ_CONTROL;

    public const int STANDARD_RIGHTS_ALL = 0x001F0000;

    public const int SPECIFIC_RIGHTS_ALL = 0x0000FFFF;

    public const int TOKEN_ASSIGN_PRIMARY = 0x0001;
    public const int TOKEN_DUPLICATE = 0x0002;
    public const int TOKEN_IMPERSONATE = 0x0004;
    public const int TOKEN_QUERY = 0x0008;
    public const int TOKEN_QUERY_SOURCE = 0x0010;
    public const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
    public const int TOKEN_ADJUST_GROUPS = 0x0040;
    public const int TOKEN_ADJUST_DEFAULT = 0x0080;
    public const int TOKEN_ADJUST_SESSIONID = 0x0100;

    public const int TOKEN_ALL_ACCESS_P = (STANDARD_RIGHTS_REQUIRED |
                                           TOKEN_ASSIGN_PRIMARY |
                                           TOKEN_DUPLICATE |
                                           TOKEN_IMPERSONATE |
                                           TOKEN_QUERY |
                                           TOKEN_QUERY_SOURCE |
                                           TOKEN_ADJUST_PRIVILEGES |
                                           TOKEN_ADJUST_GROUPS |
                                           TOKEN_ADJUST_DEFAULT);

    public const int TOKEN_ALL_ACCESS = TOKEN_ALL_ACCESS_P | TOKEN_ADJUST_SESSIONID;

    public const int TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;

    public const int TOKEN_WRITE = STANDARD_RIGHTS_WRITE |
                                   TOKEN_ADJUST_PRIVILEGES |
                                   TOKEN_ADJUST_GROUPS |
                                   TOKEN_ADJUST_DEFAULT;

    public const int TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE;

    public const uint MAXIMUM_ALLOWED = 0x2000000;

    public const int CREATE_NEW_PROCESS_GROUP = 0x00000200;
    public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;

    public const int IDLE_PRIORITY_CLASS = 0x40;
    public const int NORMAL_PRIORITY_CLASS = 0x20;
    public const int HIGH_PRIORITY_CLASS = 0x80;
    public const int REALTIME_PRIORITY_CLASS = 0x100;

    public const int CREATE_NEW_CONSOLE = 0x00000010;

    public const string SE_DEBUG_NAME = "SeDebugPrivilege";
    public const string SE_RESTORE_NAME = "SeRestorePrivilege";
    public const string SE_BACKUP_NAME = "SeBackupPrivilege";

    public const int SE_PRIVILEGE_ENABLED = 0x0002;

    public const int ERROR_NOT_ALL_ASSIGNED = 1300;

    private const uint TH32CS_SNAPPROCESS = 0x00000002;

    public static int INVALID_HANDLE_VALUE = -1;

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpname,
        [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);

    [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, int dwCreationFlags, IntPtr lpEnvironment,
        String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool DuplicateToken(IntPtr ExistingTokenHandle,
        int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

    [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
    public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
        int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges,
        ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,
        ref uint TokenInformation, uint TokenInformationLength);

    [DllImport("userenv.dll", SetLastError = true)]
    public static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

    public static bool CreateProcessInConsoleSession(String CommandLine, bool bElevate)
    {

        PROCESS_INFORMATION pi;

        bool bResult = false;
        uint dwSessionId, winlogonPid = 0;
        IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;

        Debug.Print("CreateProcessInConsoleSession");
        // Log the client on to the local computer.
        dwSessionId = WTSGetActiveConsoleSessionId();

        // Find the winlogon process
        var procEntry = new PROCESSENTRY32();

        uint hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnap == INVALID_HANDLE_VALUE)
        {
            return false;
        }

        procEntry.dwSize = (uint) Marshal.SizeOf(procEntry); //sizeof(PROCESSENTRY32);

        if (Process32First(hSnap, ref procEntry) == 0)
        {
            return false;
        }

        String strCmp = "explorer.exe";
        do
        {
            if (strCmp.IndexOf(procEntry.szExeFile) == 0)
            {
                // We found a winlogon process...make sure it's running in the console session
                uint winlogonSessId = 0;
                if (ProcessIdToSessionId(procEntry.th32ProcessID, ref winlogonSessId) &&
                    winlogonSessId == dwSessionId)
                {
                    winlogonPid = procEntry.th32ProcessID;
                    break;
                }
            }
        }
        while (Process32Next(hSnap, ref procEntry) != 0);

        //Get the user token used by DuplicateTokenEx
        WTSQueryUserToken(dwSessionId, ref hUserToken);

        var si = new STARTUPINFO();
        si.cb = Marshal.SizeOf(si);
        si.lpDesktop = "winsta0\\default";
        var tp = new TOKEN_PRIVILEGES();
        var luid = new LUID();
        hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

        if (
            !OpenProcessToken(hProcess,
                TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY
                | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, ref hPToken))
        {
            Debug.Print(String.Format("CreateProcessInConsoleSession OpenProcessToken error: {0}",
                Marshal.GetLastWin32Error()));
        }

        if (!LookupPrivilegeValue(IntPtr.Zero, SE_DEBUG_NAME, ref luid))
        {
            Debug.Print(String.Format("CreateProcessInConsoleSession LookupPrivilegeValue error: {0}",
                Marshal.GetLastWin32Error()));
        }

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

        if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
                (int) SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int) TOKEN_TYPE.TokenPrimary,
                ref hUserTokenDup))
        {
            Debug.Print(
                String.Format(
                    "CreateProcessInConsoleSession DuplicateTokenEx error: {0} Token does not have the privilege.",
                    Marshal.GetLastWin32Error()));
            CloseHandle(hProcess);
            CloseHandle(hUserToken);
            CloseHandle(hPToken);
            return false;
        }

        if (bElevate)
        {
            //tp.Privileges[0].Luid = luid;
            //tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

            tp.PrivilegeCount = 1;
            tp.Privileges = new int[3];
            tp.Privileges[2] = SE_PRIVILEGE_ENABLED;
            tp.Privileges[1] = luid.HighPart;
            tp.Privileges[0] = luid.LowPart;

            //Adjust Token privilege
            if (
                !SetTokenInformation(hUserTokenDup, TOKEN_INFORMATION_CLASS.TokenSessionId, ref dwSessionId,
                    (uint) IntPtr.Size))
            {
                Debug.Print(
                    String.Format(
                        "CreateProcessInConsoleSession SetTokenInformation error: {0} Token does not have the privilege.",
                        Marshal.GetLastWin32Error()));
                //CloseHandle(hProcess);
                //CloseHandle(hUserToken);
                //CloseHandle(hPToken);
                //CloseHandle(hUserTokenDup);
                //return false;
            }
            if (
                !AdjustTokenPrivileges(hUserTokenDup, false, ref tp, Marshal.SizeOf(tp), /*(PTOKEN_PRIVILEGES)*/
                    IntPtr.Zero, IntPtr.Zero))
            {
                int nErr = Marshal.GetLastWin32Error();

                if (nErr == ERROR_NOT_ALL_ASSIGNED)
                {
                    Debug.Print(
                        String.Format(
                            "CreateProcessInConsoleSession AdjustTokenPrivileges error: {0} Token does not have the privilege.",
                            nErr));
                }
                else
                {
                    Debug.Print(String.Format("CreateProcessInConsoleSession AdjustTokenPrivileges error: {0}", nErr));
                }
            }
        }

        uint dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
        IntPtr pEnv = IntPtr.Zero;
        if (CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true))
        {
            dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
        }
        else
        {
            pEnv = IntPtr.Zero;
        }
        // Launch the process in the client's logon session.
        bResult = CreateProcessAsUser(hUserTokenDup, // client's access token
            null, // file to execute
            CommandLine, // command line
            ref sa, // pointer to process SECURITY_ATTRIBUTES
            ref sa, // pointer to thread SECURITY_ATTRIBUTES
            false, // handles are not inheritable
            (int) dwCreationFlags, // creation flags
            pEnv, // pointer to new environment block 
            null, // name of current directory 
            ref si, // pointer to STARTUPINFO structure
            out pi // receives information about new process
            );
        // End impersonation of client.

        //GetLastError should be 0
        int iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error();

        //Close handles task
        CloseHandle(hProcess);
        CloseHandle(hUserToken);
        CloseHandle(hUserTokenDup);
        CloseHandle(hPToken);

        return (iResultOfCreateProcessAsUser == 0) ? true : false;
    }

    [DllImport("kernel32.dll")]
    private static extern int Process32First(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll")]
    private static extern int Process32Next(uint hSnapshot, ref PROCESSENTRY32 lppe);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hSnapshot);

    [DllImport("kernel32.dll")]
    private static extern uint WTSGetActiveConsoleSessionId();

    [DllImport("Wtsapi32.dll")]
    private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);

    [DllImport("kernel32.dll")]
    private static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);

    [DllImport("kernel32.dll")]
    private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

    [DllImport("advapi32", SetLastError = true)]
    [SuppressUnmanagedCodeSecurity]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, // handle to process
        int DesiredAccess, // desired access to process
        ref IntPtr TokenHandle);

    #region Nested type: LUID

    [StructLayout(LayoutKind.Sequential)]
    internal struct LUID
    {
        public int LowPart;
        public int HighPart;
    }

    #endregion

    //end struct

    #region Nested type: LUID_AND_ATRIBUTES

    [StructLayout(LayoutKind.Sequential)]
    internal struct LUID_AND_ATRIBUTES
    {
        public LUID Luid;
        public int Attributes;
    }

    #endregion

    #region Nested type: PROCESSENTRY32

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESSENTRY32
    {
        public uint dwSize;
        public readonly uint cntUsage;
        public readonly uint th32ProcessID;
        public readonly IntPtr th32DefaultHeapID;
        public readonly uint th32ModuleID;
        public readonly uint cntThreads;
        public readonly uint th32ParentProcessID;
        public readonly int pcPriClassBase;
        public readonly uint dwFlags;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public readonly string szExeFile;
    }

    #endregion

    #region Nested type: PROCESS_INFORMATION

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

    #endregion

    #region Nested type: SECURITY_ATTRIBUTES

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

    #endregion

    #region Nested type: SECURITY_IMPERSONATION_LEVEL

    private enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous = 0,
        SecurityIdentification = 1,
        SecurityImpersonation = 2,
        SecurityDelegation = 3,
    }

    #endregion

    #region Nested type: STARTUPINFO

    [StructLayout(LayoutKind.Sequential)]
    public struct STARTUPINFO
    {
        public int cb;
        public String lpReserved;
        public String lpDesktop;
        public String lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    #endregion

    #region Nested type: TOKEN_PRIVILEGES

    [StructLayout(LayoutKind.Sequential)]
    internal struct TOKEN_PRIVILEGES
    {
        internal int PrivilegeCount;
        //LUID_AND_ATRIBUTES
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        internal int[] Privileges;
    }

    #endregion

    #region Nested type: TOKEN_TYPE

    private enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation = 2
    }

    #endregion

    // handle to open access token
}

这看起来就是我要找的东西。我会仔细阅读代码,它有很好的注释并且看起来很直接。 - user1403598
3
有趣的是,示例代码恰好做了Windows SDK支持团队博客上这篇文章说"绝对不要去做"的事情:窃取在交互式用户桌面上运行的资源管理器进程的访问令牌,打开该进程的访问令牌,并使用此令牌创建一个新进程。 - Cody Gray
非常感谢!我只是将“explorer.exe”替换为“winlogon.exe”,这样我的执行应用程序就会跳到其他窗口的顶部,否则不会。 - Rkshvch
这还能用吗?我只能以“管理员”身份打开应用程序...我想让应用程序作为已登录用户运行。可能吗? - MrLister
1
@alex,这个函数CreateProcessInConsoleSession(String CommandLine, bool bElevate)中的Elevate参数有什么作用? - Mohammad Fneish
显示剩余2条评论

13

像这种关于Windows服务的问题很常见,你处于单用户操作系统的思维模式中。你决定将你的应用程序编写为服务是因为你在单用户操作系统的思维模型与多用户操作系统的现实之间遇到了冲突。不幸的是,服务并没有解决你所有的问题,现在你正在尝试找出如何完成最终注定失败的修改设计的第二个步骤。

事实上,你不能保证有一个“已登录用户”。如果没有人登录工作站,就不会有任何用户登录,但是你的服务仍将在运行。

即使你以某种方式通过确保总有人登录(不可能),然后你将遇到多个用户同时登录的情况。然后你的服务应该选择哪个作为进程启动的用户呢?应该随机选其中一个吗?

在你的情况下,是否有必要区分本地控制台登录用户和远程登录用户?请记住,远程用户将没有本地控制台。

如果你可以以某种方式克服所有这些障碍(不幸的是,可能是通过埋头做事并继续假装Windows是单用户操作系统),那么你可以使用WTSGetActiveConsoleSessionId函数获取当前会话ID,使用WTSQueryUserToken函数获取与该会话ID相对应的用户令牌,最后使用CreateProcessAsUser函数以该用户的上下文启动你的进程。如果有一个用户在登录且他们拥有适当的权限,而且物理控制台未连接到虚拟会话,而且你没有运行允许多个活动控制台会话的服务器SKU,等等。

如果你需要决定使用特定用户账户来启动辅助进程,可以登录该用户,操作他们的用户令牌,执行该进程,最后关闭进程并注销该用户。CreateProcessWithLogonUser 函数 将大量此类繁琐工作封装在一起,使代码更加简洁。但是外表可能会欺骗人,如果你首次提出这个问题,你可能并不完全理解这仍然具有一些巨大的安全隐患。而且,你真的不能承受不理解这种安全风险。
此外,使用 LogonUser 登录的用户(当您使用 CreateProcessWithLogonUser 函数时,它会自动完成)缺少可启动交互式进程的窗口站和桌面。因此,如果您希望在该用户的上下文中启动的进程将显示任何类型的用户界面,则无法成功。Windows 将在您尝试访问所缺乏必要权限的桌面时立即终止应用程序。从 Windows 服务中,没有办法获取对您有用的桌面句柄(这很大程度上解释了您可能已经知道的一般规则,即服务无法显示任何类型的用户界面)。

我真的很喜欢“ultimately-doomed”。但是CreateProcessWithLogonW对他没有帮助,因为他没有用户凭据。 - Grzegorz W
Windows服务最初设计用于接收传入的消息并按请求处理它们(使用来自远程应用程序到服务的消息系统)。该服务正在有意运行,但现在需要额外的规范。不幸的是,某些API需要作为当前用户调用,这就是现在成为要求的原因。我理解安全风险,但这只会调用为CU /活动用户定义的几个API。如果没有用户登录到计算机上,我可以确保服务会报告回来。 - user1403598
它要“报告”什么?没有用户登录!那么这些你需要调用的API是什么?为什么他们需要作为特定的用户被调用?看起来这才是你问题的真正解决方案。 - Cody Gray
远程应用程序在任何域计算机上运行,可以连接到服务,因此服务将根据需要向应用程序报告。服务作为客户端应用程序,允许按请求调用提升的命令。正如我之前所说,应用程序的规格说明略有变化,现在需要按当前用户调用某些要求。例如,根据MSDN文档,某些SCCM Api需要按当前用户运行。另一个想法是像Alex建议的那样,在用户登录时作为独立进程运行。 - user1403598
或者,您可以使用这段代码,它非常易于使用并且运行良好。http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-and-bit-Archite - Wedge

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