10

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

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

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

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

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

* 更新 * 我认为我的解释有误,需要根据我的实际需要重新调整。我不一定想启动一个新进程,我只想模拟登录用户。我已经非常专注于查看 CreateProcess 等,我带领自己走上了一条作为当前登录用户创建新进程的路径(这不是我特别想做的)。

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

4

2 回答 2

13

一种选择是让后台应用程序在用户登录时自动启动,并通过 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
}
于 2013-03-13T11:30:50.630 回答
11

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

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

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

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

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

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

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

于 2013-03-13T11:31:17.723 回答