以当前用户身份从 Windows 服务运行进程

本文关键字:Windows 服务 运行 进程 身份 用户 | 更新日期: 2023-09-27 18:03:45

我目前有一个在系统帐户下运行的Windows服务。我的问题是我需要以当前登录用户的身份从服务中启动某些进程。我拥有获取当前登录用户/活动会话的所有代码等。

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

该服务是 .net 编译的服务,我希望我需要使用一些 Pinvoke 方法来获取当前用户进程之一的句柄,以便复制它并使用句柄作为进程午餐。

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

如果有人能够给我一些指导/例子,我将不胜感激。

*更新*我想我解释得不正确,需要根据我的实际要求进行重新调整。我不一定想启动新进程,我只想模拟登录用户。我一直沉迷于查看CreateProcess等,我已经引导自己走上了一条以当前登录用户的身份创建新进程的道路(这不是我特别想做的(。

反过来,我只想在当前用户上下文下运行一些代码(模拟当前登录用户(?

以当前用户身份从 Windows 服务运行进程

一种选择是让后台应用程序在用户登录时自动启动,并通过 WCF 或节俭侦听来自服务的命令,或者只是监视某些文件并从那里读取命令。

另一种选择是执行您最初要求的操作 - 使用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
}

与有关 Windows 服务的这些类型的问题一样,您是在单用户操作系统的思维方式中操作的。您决定将应用程序编写为服务的全部原因是,您在单用户操作系统的心智模型与多用户操作系统的现实之间遇到了冲突。 不幸的是,服务并没有解决您的所有问题,现在您正在尝试弄清楚如何在最终注定要失败的黑客设计中完成第二步。

事实是,您无法保证存在"登录用户"。如果没有人登录到工作站,则没有人登录,但您的服务仍将运行。

即使您通过确保某人始终登录(不可能(以某种方式克服了这一点,那么您也会遇到多个用户登录的情况。那么,您的服务应该以哪一个开始该过程?它应该随机选择其中之一吗?

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

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

如果可以决定要使用其帐户启动辅助进程的特定用户,则可以登录该用户,操作其用户令牌,执行进程,最后关闭进程并注销用户。CreateProcessWithLogonUser函数为您包装了很多苦差事,使代码更加苗条。但是外表可能是欺骗性的,这仍然有一些巨大的安全隐患,如果你首先问这个问题,你可能并不完全理解。而且您真的不能不了解这样的安全风险。

此外,使用LogonUser登录的用户(当您使用CreateProcessWithLogonUser功能时会自动为您完成(缺少可以启动交互进程的窗口站和桌面。因此,如果您希望在该用户的上下文中启动的进程将显示任何类型的 UI,那么您就不走运了。Windows 会在您的应用尝试访问缺少必要权限的桌面时立即终止您的应用。无法从 Windows 服务获取对您有用的桌面句柄(这对于解释您可能已经知道的一般规则大有帮助,即服务不能显示任何类型的 UI(。