c# - 如何使用 C#/pinvoke 将输入发送到 WinSta0\Winlogon 桌面

标签 c# sendinput winlogon teamviewer

我正在用 C# 为一套技术支持工具编写一个远程控制应用程序。一切正常,除了我无法使用 SendInput 到 Winlogon 桌面。我成功地检测到从 Default 到 Winlogon 的变化,并且我能够切换到它并捕获屏幕截图。它只是不接受 SendInput 函数。我知道这是可能的,因为 TeamViewer 做到了,而且他们的 list 中没有 uiAccess=true 。他们似乎在使用与我相同的过程。

简而言之,这是我正在做的事情:安装服务。服务监听连接请求。服务使用 CreateProcessAsUser 和来自 winlogon.exe 的复制访问 token 在用户 session 中启动新进程。查看器连接到新进程。

任何人都可以确定缺少什么来让新进程访问 SendInput 到 winlogon 吗?这是我用来从服务启动新进程的代码。接下来是我用来检测 Winlogon 桌面更改并切换到它的代码。

public static bool OpenProcessAsSystem(string applicationName, out PROCESS_INFORMATION procInfo)
{
    try
    {

        uint winlogonPid = 0;
        IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
        procInfo = new PROCESS_INFORMATION();

        // Obtain session ID for active session.
        uint dwSessionId = Kernel32.WTSGetActiveConsoleSessionId();

        // Check for RDP session.  If active, use that session ID instead.
        var rdpSessionID = GetRDPSession();
        if (rdpSessionID > 0)
        {
            dwSessionId = rdpSessionID;
        }

        // Obtain the process ID of the winlogon process that is running within the currently active session.
        Process[] processes = Process.GetProcessesByName("winlogon");
        foreach (Process p in processes)
        {
            if ((uint)p.SessionId == dwSessionId)
            {
                winlogonPid = (uint)p.Id;
            }
        }

        // Obtain a handle to the winlogon process.
        hProcess = Kernel32.OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

        // Obtain a handle to the access token of the winlogon process.
        if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
        {
            Kernel32.CloseHandle(hProcess);
            return false;
        }

        // Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser.
        SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
        sa.Length = Marshal.SizeOf(sa);

        // Copy the access token of the winlogon process; the newly created token will be a primary token.
        if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
        {
            Kernel32.CloseHandle(hProcess);
            Kernel32.CloseHandle(hPToken);
            return false;
        }

        // By default, CreateProcessAsUser creates a process on a non-interactive window station, meaning
        // the window station has a desktop that is invisible and the process is incapable of receiving
        // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 
        // interaction with the new process.
        STARTUPINFO si = new STARTUPINFO();
        si.cb = (int)Marshal.SizeOf(si);
        si.lpDesktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop

        // flags that specify the priority and creation method of the process
        uint dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

        // create a new process in the current user's logon session
        bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token
                                        null,                   // file to execute
                                        applicationName,        // command line
                                        ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                                        ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                                        false,                  // handles are not inheritable
                                        dwCreationFlags,        // creation flags
                                        IntPtr.Zero,            // pointer to new environment block 
                                        null,                   // name of current directory 
                                        ref si,                 // pointer to STARTUPINFO structure
                                        out procInfo            // receives information about new process
                                        );

        // invalidate the handles
        Kernel32.CloseHandle(hProcess);
        Kernel32.CloseHandle(hPToken);
        Kernel32.CloseHandle(hUserTokenDup);

        return result;
    }
    catch
    {
        procInfo = new PROCESS_INFORMATION() { };
        return false;
    }
}
public static uint GetRDPSession()
{
    IntPtr ppSessionInfo = IntPtr.Zero;
    Int32 count = 0;
    Int32 retval = WTSAPI32.WTSEnumerateSessions(WTSAPI32.WTS_CURRENT_SERVER_HANDLE, 0, 1, ref ppSessionInfo, ref count);
    Int32 dataSize = Marshal.SizeOf(typeof(WTSAPI32.WTS_SESSION_INFO));
    var sessList = new List<WTSAPI32.WTS_SESSION_INFO>();
    Int64 current = (int)ppSessionInfo;

    if (retval != 0)
    {
        for (int i = 0; i < count; i++)
        {
            WTSAPI32.WTS_SESSION_INFO sessInf = (WTSAPI32.WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTSAPI32.WTS_SESSION_INFO));
            current += dataSize;
            sessList.Add(sessInf);
        }
    }
    uint retVal = 0;
    var rdpSession = sessList.Find(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0);
    if (sessList.Exists(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0))
    {
        retVal = (uint)rdpSession.SessionID;
    }
    return retVal;
}

这是我用来捕获屏幕、检测桌面变化并切换到它的工具。

var hWnd = User32.GetDesktopWindow();
var hDC = User32.GetWindowDC(hWnd);
var graphDC = graphic.GetHdc();
var copyResult = GDI32.BitBlt(graphDC, 0, 0, totalWidth, totalHeight, hDC, 0, 0, GDI32.TernaryRasterOperations.SRCCOPY | GDI32.TernaryRasterOperations.CAPTUREBLT);
// Change to input desktop if copy fails.
if (!copyResult)
{
     var inputDesktop = User32.OpenInputDesktop();
     if (User32.SetThreadDesktop(inputDesktop) == false)
     {
         graphic.Clear(System.Drawing.Color.White);
         var font = new Font(FontFamily.GenericSansSerif, 30, System.Drawing.FontStyle.Bold);
         graphic.DrawString("Waiting for screen capture...", font, Brushes.Black, new PointF((totalWidth / 2), totalHeight / 2), new StringFormat() { Alignment = StringAlignment.Center });
         var error = Marshal.GetLastWin32Error();
         writeToErrorLog(new Exception("Failed to open input desktop.  Error: " + error.ToString()));
    }
    var dw = User32.GetDesktopWindow();
    User32.SetActiveWindow(dw);
    User32.SetForegroundWindow(dw);
    User32.CloseDesktop(inputDesktop);
 }
 graphic.ReleaseHdc(graphDC);
 User32.ReleaseDC(hWnd, hDC);

最佳答案

我让 SendInput 在登录桌面(以及 UAC 安全桌面)上工作。 SetThreadDesktop 不得授予您与最初在目标桌面上启动进程相同的权限。

因此,当我检测到桌面发生变化时,我没有调用 SetThreadDesktop,而是使用 CreateProcessAsUser 在新桌面中启动了另一个进程。然后我示意查看器切换并关闭当前进程。

编辑(多年后):我最终错了。您只需要确保当前线程在当前桌面上没有任何打开的窗口或 Hook 。由于这仅为调用线程(而非进程)设置桌面,其他线程也需要调用它。

关于c# - 如何使用 C#/pinvoke 将输入发送到 WinSta0\Winlogon 桌面,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41924863/

相关文章:

c++ - 将输入发送到后台窗口

c# - 使用 SendInput 时,如何判断接收窗口何时收到并处理了消息?

c++ - 如何设置在 Windows 登录屏幕上打开应用程序的按钮

c# - 使用相同的方法名称调用多个 dll 导入

c# - 如何选择使用默认命名空间的节点?

c# - PartialClassGenerationTask 和 MarkupCompilePass1 如何一起工作/属于

c# - SerialPort 类偶尔卡在 Dispose 上

c++ - SendInput 不适用于保持键 C++

c# - 如何以编程方式与 winlogon 交互?