2017-01-29 55 views
0

C#으로 원격 제어 응용 프로그램을 작성하여 기술 지원 도구 세트를 작성합니다. Winlogon 데스크톱에 SendInput을 사용할 수 없다는 점을 제외하고는 모두 잘 작동합니다. 필자는 Default에서 Winlogon으로의 변경을 성공적으로 감지하고 있으며이를 전환하여 스크린 샷을 캡처 할 수 있습니다. 그것은 단지 SendInput 함수를 받아들이지 않습니다. 나는 TeamViewer가 그것을하기 때문에 가능하다는 것을 알고 있으며, 매니페스트에 uiAccess = true가 없다. 그들은 내가하는 것과 같은 과정을 사용하고있는 것처럼 보입니다.C#/pinvoke를 사용하여 WinSta0 Winlogon 데스크톱으로 SendInput하는 방법

다음은 제가 한 마디로하고있는 것입니다 : 서비스를 설치하십시오. 서비스가 연결 요청을 수신합니다. 서비스는 사용자 프로세스에서 CreateProcessAsUser와 winlogon.exe에서 중복 된 액세스 토큰으로 새로운 프로세스를 시작합니다. 뷰어가 새 프로세스에 연결됩니다.

누가 새로운 프로세스가 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); 

답변

0

나는 로그온 바탕 화면에서 작동하도록 SendInput을 얻었습니다 (밝혀진대로, UAC 보안 데스크탑). SetThreadDesktop은 처음에 대상 데스크탑에서 프로세스를 시작한 것과 동일한 권한을 부여해서는 안됩니다.

바탕 화면 변경이 감지되면 SetThreadDesktop을 호출하는 대신 CreateProcessAsUser를 사용하여 새 데스크톱에서 다른 프로세스를 시작했습니다. 그런 다음 뷰어가 전환하도록 신호를 보내고 현재 프로세스를 종료했습니다.