2017-01-27 6 views
3

Я уже несколько часов стучал головой об этом, поэтому решил, что пришло время спросить. Я начну с описания ситуации на высоком уровне. Вы можете найти весь исходный код: https://github.com/Jay-Rad/InstaTech_Client. Этот вопрос относится только к проекту в «/ InstaTech_Service /».Как запустить новый сеанс входа в систему Windows (RDP или консоль) программно

Обзор

Клиент InstaTech является приложением дистанционного управления, который использует WebSockets и делает исходящее соединение с сервером ASP.NET для реле со зрителем. У меня разные версии, но все они работают примерно одинаково (версия Electron пытается использовать WebRTC прежде, чем использовать сырые websockets). Часть зрителя приложения находится на веб-сайте, а демо-версию можно найти здесь: https://instatech.org/Demo/Remote_Control

В версиях WPF (C#) и Electron представлен графический интерфейс с произвольным идентификатором, который они должны предоставить человеку, удаленному на свой компьютер (аналогично TeamViewer). Когда сеанс запущен, они захватывают экран по-разному. Для C# я использую pinvoke для BitBlt, чтобы скопировать изображение в графику в памяти, которая затем отправляется через websocket. Последующие снимки экрана сравниваются с предыдущим, чтобы создать ящик, который включает в себя измененные пиксели, затем отправляется обрезанная секция. Входы мыши и клавиатуры принимаются клиентом и выполняются через pinvoke на keybd_event и mouse_event. Они отлично работают.

Служба, которую я создал, работает аналогичным образом, но вот отличия. Сама служба запускается в сеансе 0 в разделе «Системная учетная запись». Он подключается к серверу и прослушивает websocket. Когда выполняется соединение и выполняется просмотр экрана, он запускает отдельный интерактивный процесс в сеансе пользователя в WinSta0 \ Default. Как только веб-узел нового процесса подключается, сервер начинает ретранслировать сообщения между ним и зрителем вместо службы и зрителя.

Хотя новый процесс запускается интерактивно в сеансе пользователя, он запускается под учетной записью системы. Это достигается с помощью pinvoke для CreateProcessAsUser и дублирования токена доступа winlogon.exe.

Проблема

Это решение работает хорошо, если кто-то уже вошел в систему, даже если через RDP. Однако, если никто не зарегистрирован или компьютер не заблокирован, я не могу взаимодействовать с экраном входа в систему. Когда я делаю захват экрана, я обнаруживаю, что захват завершился неудачно, что означало бы, что рабочий стол WinSta0 \ Default больше не активен. Поскольку я использую CreateProcessAsUser, я могу просто переключить настольные компьютеры на WinSta0 \ Winlogon. Я все еще вижу это (даже если никто не вошел в систему), но он не будет принимать никаких входов. Я понимаю, что это по соображениям безопасности. Ну, странно, некоторые движения мыши «проскальзывают», если я перемещаю их и заставляю курсор перемещаться, а остальные отправляются на рабочий стол по умолчанию и выполняются после входа в систему.

Таким образом, проблема в том, что Я не могу получить учетную запись, зарегистрированную на компьютере с этой настройкой. Если это имеет значение, я не хочу взаимодействовать с рабочим столом Winlogon, если кто-то еще уже вошел в систему и заблокировал компьютер. Я только хочу, чтобы иметь возможность войти в систему, если никто другой не использует его, или это моя учетная запись, которая вошла в систему и на экране блокировки.

Покушение Solutions

Я предполагаю, что нет никакого способа, чтобы обойти невозможность отправить смоделированные входы на рабочий стол Winlogon. (Исправление: То есть, используя функции mouse_event и keybd_event. Я видел, как это делают другие приложения, такие как TeamViewer и Microsoft SCCM Remote Control. Я не уверен, как они это делают.) Если это возможно, я думаю, что это был бы самый прямой маршрут. Но вот некоторые вещи, которые я изучил, сосредоточились на том, чтобы начать новый сеанс входа в систему.

Pinvoke to LsaLogonUser. Я не уверен, выполнит ли это то, что мне нужно, но я все равно попытался. Однако, хотя вызов LsaLogonUser сообщает об успехе, дескриптор, который я получаю от LsaRegisterLogonProcess (вне до lsaHan), равен 0. Я не уверен, что я делаю неправильно. Я не слишком хорошо знаком с вызовами Win32 и пытаюсь забрать его, когда я иду. Возможно, вызывающий процесс не имеет необходимых прав. Я пробовал называть это из службы в сеансе 0 и из процесса, выполняемого в интерактивном сеансе. Пример того, что я делаю, ниже.

Клиентская библиотека COM-клиентов Microsoft Terminal Services. Я не слишком глубоко вникнул в это, но мне интересно, можно ли использовать это для запуска сеанса входа в RDP. После сеанса входа в систему RDP создайте новый процесс InstaTech в этом сеансе и подключитесь к нему. Я сомневаюсь, что это сработает, если попытка RDP будет пытаться с того же компьютера.

Credential Provider. Я сталкивался с доверенными поставщиками во время исследования. Я не уверен, что если вы создадите, вы решите проблему, но похоже, что это было бы очень сложное начинание.

Есть ли у кого-нибудь предложения? Или я чего-то не хватает?

Если вы хотите перекомпилировать сервис и проверить его, я создал временную учетную запись администратора на сервере. Там будет показан любой компьютер с установленной службой, и вы можете войти в систему, используя эту учетную запись. Пожалуйста, имейте в виду, что любой, кто прочитает это сообщение, сможет получить доступ к любым компьютерам, на которых запущена служба, поэтому убедитесь, что они находятся в изолированной среде.

Имя пользователя: администратор
Пароль: plzh @ lpm3purdyplz

услуга является самоустанавливаемом. Передайте ключ -install для установки, -uninstall для удаления. EXE копируется в% programdata% \ InstaTech, и служба запускает его оттуда.

Спасибо!

Код ссылки

public static void CreateNewSession() 
{ 
    var kli = new SECUR32.KERB_INTERACTIVE_LOGON() 
    { 
     MessageType = SECUR32.KERB_LOGON_SUBMIT_TYPE.KerbInteractiveLogon, 
     UserName = "[email protected]", 
     Password = "superencryptedstring" 
    }; 
    IntPtr pluid; 
    IntPtr lsaHan; 
    ulong secMode; 
    uint authPackID; 
    IntPtr kerbLogInfo; 
    SECUR32.LSA_STRING logonProc = new SECUR32.LSA_STRING() 
    { 
     Buffer = Marshal.StringToHGlobalAuto("InstaLogon"), 
     Length = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon")), 
     MaximumLength = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon")) 
    }; 
    SECUR32.LSA_STRING originName = new SECUR32.LSA_STRING() 
    { 
     Buffer = Marshal.StringToHGlobalAuto("InstaLogon"), 
     Length = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon")), 
     MaximumLength = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("InstaLogon")) 
    }; 
    SECUR32.LSA_STRING authPackage = new SECUR32.LSA_STRING() 
    { 
     Buffer = Marshal.StringToHGlobalAuto("MICROSOFT_KERBEROS_NAME_A"), 
     Length = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("MICROSOFT_KERBEROS_NAME_A")), 
     MaximumLength = (ushort)Marshal.SizeOf(Marshal.StringToHGlobalAuto("MICROSOFT_KERBEROS_NAME_A")) 
    }; 
    IntPtr hLogonProc = Marshal.AllocHGlobal(Marshal.SizeOf(logonProc)); 
    Marshal.StructureToPtr(logonProc, hLogonProc, false); 
    ADVAPI32.AllocateLocallyUniqueId(out pluid); 
    SECUR32.LsaRegisterLogonProcess(hLogonProc, out lsaHan, out secMode); 
    SECUR32.LsaLookupAuthenticationPackage(lsaHan, ref authPackage, out authPackID); 

    kerbLogInfo = Marshal.AllocHGlobal(Marshal.SizeOf(kli)); 
    Marshal.StructureToPtr(kli, kerbLogInfo, false); 

    var ts = new SECUR32.TOKEN_SOURCE("Insta"); 
    IntPtr profBuf; 
    uint profBufLen; 
    long logonID; 
    IntPtr logonToken; 
    SECUR32.QUOTA_LIMITS quotas; 
    SECUR32.WinStatusCodes subStatus; 
    SECUR32.LsaLogonUser(lsaHan, ref originName, SECUR32.SecurityLogonType.Interactive, authPackID, kerbLogInfo, (uint)Marshal.SizeOf(kerbLogInfo), IntPtr.Zero, ref ts, out profBuf, out profBufLen, out logonID, out logonToken, out quotas, out subStatus); 
} 

Это метод, что служба в сессии 0 используется для запуска другого экземпляра в интерактивной сессии. Я получил большинство из этой статьи: https://www.codeproject.com/kb/vista-security/subvertingvistauac.aspx. Я только добавил поиск сеанса RDP.

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; 
    } 
+0

Вы когда-нибудь находили решение этого вопроса? Не могли бы вы поделиться? – LorneCash

+0

Да. Я дал краткий ответ ниже. Полную реализацию можно найти здесь: https://github.com/Jay-Rad/InstaTech_Client/blob/master/InstaTech_Service/Socket.cs Соответствующая часть начинается с строки, которая имеет «случай» ConnectUnattended «: '(строка 468). Класс ADVAPI32, который он использует, можно найти по адресу: https://github.com/Jay-Rad/InstaTech_Client/blob/master/Win32_Classes/ADVAPI32.cs – Jared

ответ

1

У меня есть SendInput для работы на рабочем столе для входа в систему (и, как оказалось, защищенного рабочего стола UAC). SetThreadDesktop не должен предоставлять вам те же привилегии, что и исходный процесс на целевом компьютере.

Итак, когда я обнаружил изменение рабочего стола, вместо вызова SetThreadDesktop, я запустил еще один процесс на новом рабочем столе с помощью CreateProcessAsUser. Затем я подал сигнал, чтобы зритель переключил и закрыл текущий процесс.