Comment démarrer un processus à partir du service Windows dans la session de l’utilisateur actuellement connecté

Je dois démarrer un programme à partir du service Windows. Ce programme est une application utilisateur utilisateur. En outre, cette application doit être lancée sous un compte utilisateur spécifique.

Le problème est que Windows Services s’exécute dans la session n ° 0, mais que les sessions utilisateur connectées sont 1,2, etc.

La question est donc la suivante: comment démarrer un processus à partir d’un service de fenêtre de manière à ce qu’il soit exécuté dans la session de l’utilisateur actuellement connecté?

Je mettrais l’accent sur le fait que la question ne concerne pas comment démarrer un processus sous un compte spécifique (c’est évident – Process.Start (new ProcessStartInfo (“..”) {UserName = .., Password = ..})). Même si j’installe mes fenêtres pour s’exécuter sous le compte d’utilisateur actuel, le service s’exécutera de toute façon dans la session n ° 0. Paramétrer “Autoriser le service à interagir avec le bureau” n’aide pas.

Mon service Windows est basé sur .net.

MISE À JOUR : tout d’abord, .NET n’a rien à faire ici, c’est en fait une chose Win32 pure. Voici ce que je fais Le code suivant se trouve dans mon service Windows (C # utilisant la fonction win32 via P / Inkove, j’ai ignoré les signatures d’importation, elles sont toutes là – http://www.pinvoke.net/default.aspx/advapi32/CreateProcessWithLogonW.html ):

var startupInfo = new StartupInfo() { lpDesktop = "WinSta0\\Default", cb = Marshal.SizeOf(typeof(StartupInfo)), }; var processInfo = new ProcessInformation(); ssortingng command = @"c:\windows\Notepad.exe"; ssortingng user = "Administrator"; ssortingng password = "password"; ssortingng currentDirectory = System.IO.Directory.GetCurrentDirectory(); try { bool bRes = CreateProcessWithLogonW(user, null, password, 0, command, command, 0, Convert.ToUInt32(0), currentDirectory, ref startupInfo, out processInfo); if (!bRes) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } catch (Exception ex) { writeToEventLog(ex); return; } WaitForSingleObject(processInfo.hProcess, Convert.ToUInt32(0xFFFFFFF)); UInt32 exitCode = Convert.ToUInt32(123456); GetExitCodeProcess(processInfo.hProcess, ref exitCode); writeToEventLog("Notepad has been started by WatchdogService. Exitcode: " + exitCode); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); 

Le code va à la ligne “Notepad a été démarré par WatchdogService. Exitcode:” + exitCode. Le code de sortie est 3221225794. Et il n’y a pas de nouveau bloc-notes démarré. Où est-ce que je me trompe?

Le problème avec la réponse de Shrike est qu’il ne fonctionne pas avec un utilisateur connecté via RDP.
Voici ma solution, qui détermine correctement la session de l’utilisateur actuel avant de créer le processus. Il a été testé pour fonctionner sur XP et 7.

https://github.com/murrayju/CreateProcessAsUser

Tout ce dont vous avez besoin est intégré dans une seule classe .NET avec une méthode statique:

 public static bool StartProcessAsCurrentUser(ssortingng appPath, ssortingng cmdLine, ssortingng workDir, bool visible) 

Un blog sur MSDN décrit une solution

C’est un article utile sur le démarrage d’un nouveau processus en session interactive à partir du service Windows sur Vista / 7.

Pour les services non-LocalSystem, l’idée de base est la suivante:

  • Énumérez le processus pour obtenir le handle de l’explorateur.

  • OpenProcessToken devrait vous donner le jeton d’access. Remarque: Le compte sous lequel votre service est exécuté doit disposer des privilèges appropriés pour appeler cette API et obtenir un jeton de processus.

  • Une fois que vous avez le jeton, appelez CreateProcessAsUser avec ce jeton. Ce jeton a déjà le bon identifiant de session.

C’est une mauvaise idée de le faire. Bien que ce ne soit probablement pas totalement impossible, Microsoft a tout fait pour rendre cela aussi difficile que possible, car il permet des attaques dites Shatter . Voir ce que Larry Osterman a écrit à ce sujet en 2005 :

La principale raison de cette mauvaise idée est que les services interactifs permettent une classe de menaces connues sous le nom d’attaques “Shatter” (parce qu’elles “brisent les fenêtres”, je crois).

Si vous effectuez une recherche sur “Shatter attack”, vous pouvez voir quelques détails sur le fonctionnement de ces menaces de sécurité. Microsoft a également publié l’ article 327618 de la base de connaissances, qui étend la documentation sur les services interactifs, et Michael Howard a écrit un article sur les services interactifs pour la bibliothèque MSDN. Au départ, les attaques par effraction ont suivi les composants Windows dotés de pompes de messages de fenêtre d’arrière-plan (qui ont été corrigés depuis longtemps), mais ils ont également été utilisés pour attaquer des services tiers qui font apparaître une interface utilisateur.

La deuxième raison pour laquelle c’est une mauvaise idée est que l’indicateur SERVICE_INTERACTIVE_PROCESS ne fonctionne tout simplement pas correctement. L’interface utilisateur du service apparaît dans la session système (normalement session 0). Si, par contre, l’utilisateur exécute une autre session, l’utilisateur ne voit jamais l’interface utilisateur. Il existe deux scénarios principaux dans lesquels un utilisateur se connecte à une autre session – Services Terminal Server et Changement rapide d’utilisateur. TS n’est pas très courant, mais dans les scénarios à domicile où il y a plusieurs personnes utilisant un seul ordinateur, FUS est souvent activé (nous avons par exemple 4 personnes connectées à l’ordinateur, par exemple).

La troisième raison pour laquelle les services interactifs sont une mauvaise idée est que les services interactifs ne sont pas garantis pour fonctionner avec Windows Vista 🙂 Dans le cadre du processus de renforcement de la sécurité de Windows Vista, les utilisateurs interactifs se connectent à des sessions autres que la session système. le premier utilisateur interactif s’exécute dans la session 1, et non la session 0. Cela a pour effet de réduire totalement les attaques brisées – les applications utilisateur ne peuvent pas interagir avec les fenêtres à privilèges élevés exécutées dans les services.

La solution proposée serait d’utiliser une application dans la barre d’état système de l’utilisateur.

Si vous pouvez ignorer en toute sécurité les problèmes et avertissements ci-dessus, vous pouvez suivre les instructions données ici:

Developing for Windows: Isolation de la session 0

Voici comment je l’ai implémenté. Il essaiera de démarrer un processus en tant qu’utilisateur actuellement connecté (à partir d’un service). Ceci est basé sur plusieurs sources rassemblées pour quelque chose qui fonctionne.

Il s’agit en réalité de REALY PURE WIN32 / C ++, ce qui pourrait ne pas être utile à 100% pour les questions originales. Mais j’espère que cela peut faire gagner du temps aux autres en cherchant quelque chose de similaire.

Il nécessite Windows XP / 2003 (ne fonctionne pas avec Windows 2000). Vous devez créer un lien vers Wtsapi32.lib

 #define WINVER 0x0501 #define _WIN32_WINNT 0x0501 #include  #include  bool StartInteractiveProcess(LPTSTR cmd, LPCTSTR cmdDir) { STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.lpDesktop = TEXT("winsta0\\default"); // Use the default desktop for GUIs PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); HANDLE token; DWORD sessionId = ::WTSGetActiveConsoleSessionId(); if (sessionId==0xffffffff) // Noone is logged-in return false; // This only works if the current user is the system-account (we are probably a Windows-Service) HANDLE dummy; if (::WTSQueryUserToken(sessionId, &dummy)) { if (!::DuplicateTokenEx(dummy, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &token)) { ::CloseHandle(dummy); return false; } ::CloseHandle(dummy); // Create process for user with desktop if (!::CreateProcessAsUser(token, NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) { // The "new console" is necessary. Otherwise the process can hang our main process ::CloseHandle(token); return false; } ::CloseHandle(token); } // Create process for current user else if (!::CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) // The "new console" is necessary. Otherwise the process can hang our main process return false; // The following commented lines can be used to wait for the process to exit and terminate it //::WaitForSingleObject(pi.hProcess, INFINITE); //::TerminateProcess(pi.hProcess, 0); ::CloseHandle(pi.hProcess); ::CloseHandle(pi.hThread); return true; } 

J’ai trouvé la solution ici:

http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

Je pense que c’est un excellent lien.

Je ne sais pas comment le faire dans .NET, mais en général, vous devez utiliser la fonction CreateProcessAsUser() API Win32 (ou son équivalent .NET), en spécifiant le nom du bureau et le jeton d’access de l’utilisateur souhaité. C’est ce que j’utilise dans mes services C ++ et cela fonctionne bien.

Implémentation du code @murrayju dans un service Windows sur W10. L’exécution d’un exécutable à partir de Program Files a toujours généré l’erreur -2 dans VS. Je crois que cela était dû au chemin de démarrage du service défini sur System32. La spécification de workDir n’a pas résolu le problème tant que j’ai ajouté la ligne suivante avant CreateProcessAsUser dans StartProcessAsCurrentUser :

 if (workDir != null) Directory.SetCurrentDirectory(workDir); 

PS: Cela aurait dû être un commentaire plutôt qu’une réponse, mais je n’ai pas encore la réputation requirejse. Il m’a fallu un peu de temps pour déboguer, espérer que cela fera gagner du temps à quelqu’un.

La réponse acceptée ne fonctionnait pas dans mon cas car l’application que je commençais nécessitait des privilèges d’administrateur. J’ai créé un fichier de commandes pour lancer l’application. Il contenait les éléments suivants:

 C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe "%~dp0MySoft.exe" 

Ensuite, j’ai transmis l’emplacement de ce fichier à la méthode StartProcessAsCurrentUser (). Fait le tour