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

问题描述:

我正在编写一个技术支持工具套件的C#远程控制应用程序。一切正常,除了我无法使用SendInput到Winlogon桌面。我成功地检测到从默认到Winlogon的变化,并且我可以切换到它并截取屏幕截图。它只是不接受SendInput函数。我知道这是可能的,因为TeamViewer会这样做,并且他们的清单中没有uiAccess = true。他们似乎正在使用与我相同的流程。如何发送输入到WinSta0 Winlogon桌面使用C#/ pinvoke

以下是我在简单介绍的内容:安装服务。服务侦听连接请求。服务在用户会话中使用CreateProcessAsUser和winlogon.exe中的重复访问令牌启动新进程。查看器连接到新的进程。

任何人都可以识别什么是缺失给新的进程访问到Winlogon SendInput?以下是我用来从服务启动新流​​程的代码。接下来是我用来检测对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的新桌面中启动了另一个进程。然后我发信号通知观看者切换并关闭当前进程。