L'appel de CreateProcessAsUser à partir de C#

J'ai essayé de créer un nouveau processus dans le contexte d'un utilisateur spécifique en utilisant la CreateProcessAsUser fonction de l'API Windows, mais semblent être en cours d'exécution dans un assez méchant problème de sécurité...

Avant que j'explique plus loin, voici le code que j'utilise actuellement pour démarrer le nouveau processus (un processus console - PowerShell pour être plus précis, mais il ne devrait pas).

    private void StartProcess()
{
bool retValue;
//Create startup info for new console process.
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
startupInfo.dwFlags = StartFlags.STARTF_USESHOWWINDOW;
startupInfo.wShowWindow = _consoleVisible ? WindowShowStyle.Show : WindowShowStyle.Hide;
startupInfo.lpTitle = this.ConsoleTitle ?? "Console";
var procAttrs = new SECURITY_ATTRIBUTES();
var threadAttrs = new SECURITY_ATTRIBUTES();
procAttrs.nLength = Marshal.SizeOf(procAttrs);
threadAttrs.nLength = Marshal.SizeOf(threadAttrs);
//Log on user temporarily in order to start console process in its security context.
var hUserToken = IntPtr.Zero;
var hUserTokenDuplicate = IntPtr.Zero;
var pEnvironmentBlock = IntPtr.Zero;
var pNewEnvironmentBlock = IntPtr.Zero;
if (!WinApi.LogonUser("UserName", null, "Password",
LogonType.Interactive, LogonProvider.Default, out hUserToken))
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error logging on user.");
var duplicateTokenAttrs = new SECURITY_ATTRIBUTES();
duplicateTokenAttrs.nLength = Marshal.SizeOf(duplicateTokenAttrs);
if (!WinApi.DuplicateTokenEx(hUserToken, 0, ref duplicateTokenAttrs,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary,
out hUserTokenDuplicate))
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error duplicating user token.");
try
{
//Get block of environment vars for logged on user.
if (!WinApi.CreateEnvironmentBlock(out pEnvironmentBlock, hUserToken, false))
throw new Win32Exception(Marshal.GetLastWin32Error(),
"Error getting block of environment variables for user.");
//Read block as array of strings, one per variable.
var envVars = ReadEnvironmentVariables(pEnvironmentBlock);
//Append custom environment variables to list.
foreach (var var in this.EnvironmentVariables)
envVars.Add(var.Key + "=" + var.Value);
//Recreate environment block from array of variables.
var newEnvironmentBlock = string.Join("\0", envVars.ToArray()) + "\0";
pNewEnvironmentBlock = Marshal.StringToHGlobalUni(newEnvironmentBlock);
//Start new console process.
retValue = WinApi.CreateProcessAsUser(hUserTokenDuplicate, null, this.CommandLine,
ref procAttrs, ref threadAttrs, false, CreationFlags.CREATE_NEW_CONSOLE |
CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);
if (!retValue) throw new Win32Exception(Marshal.GetLastWin32Error(),
"Unable to create new console process.");
}
catch
{
//Catch any exception thrown here so as to prevent any malicious program operating
//within the security context of the logged in user.
//Clean up.
if (hUserToken != IntPtr.Zero)
{
WinApi.CloseHandle(hUserToken);
hUserToken = IntPtr.Zero;
}
if (hUserTokenDuplicate != IntPtr.Zero)
{
WinApi.CloseHandle(hUserTokenDuplicate);
hUserTokenDuplicate = IntPtr.Zero;
}
if (pEnvironmentBlock != IntPtr.Zero)
{
WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);
pEnvironmentBlock = IntPtr.Zero;
}
if (pNewEnvironmentBlock != IntPtr.Zero)
{
Marshal.FreeHGlobal(pNewEnvironmentBlock);
pNewEnvironmentBlock = IntPtr.Zero;
}
throw;
}
finally
{
//Clean up.
if (hUserToken != IntPtr.Zero)
WinApi.CloseHandle(hUserToken);
if (hUserTokenDuplicate != IntPtr.Zero)
WinApi.CloseHandle(hUserTokenDuplicate);
if (pEnvironmentBlock != IntPtr.Zero)
WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);
if (pNewEnvironmentBlock != IntPtr.Zero)
Marshal.FreeHGlobal(pNewEnvironmentBlock);
}
_process = Process.GetProcessById(_processInfo.dwProcessId);
}

Pour le bien de la question ici, ignorer le code de traiter avec les variables d'environnement (je n'ai testé que la section de façon indépendante et il semble fonctionner.)

Maintenant, l'erreur que j'obtiens est le suivant (renvoyée à la ligne suite à l'appel à CreateProcessAsUSer):

"Un privilège requis n'est pas tenue par le client" (code d'erreur 1314)

(Le message d'erreur a été découvert en supprimant le paramètre de message à partir de la Win32Exception constructeur. Certes, mon code de gestion d'erreur ici, peut-être pas le meilleur, mais c'est un peu hors de propos de la matière. Vous êtes invités à commenter si vous le souhaitez, cependant.) Je suis vraiment confus quant à la cause de cette vague d'erreur dans cette situation. La documentation MSDN et les différentes discussions sur le forum ont seulement me donne beaucoup de conseils, et surtout étant donné que les causes de ces erreurs semblent être très variées, je n'ai aucune idée de quelle section de code que j'ai besoin de modifier. C'est peut-être simplement d'un seul paramètre, j'ai besoin de changer, mais je pourrais faire le mal/pas assez de WinAPI appels pour tout ce que je sais. Ce qui me confond fortement, c'est que la version précédente du code qui utilise la plaine CreateProcess fonction (équivalent sauf pour le jeton de l'utilisateur en paramètre) a parfaitement fonctionné très bien. Ce que je comprends, il est seulement nécessaire de faire appel à l'ouverture de session de l'utilisateur en fonction de recevoir la marque appropriée de la poignée, puis dupliquez-le de sorte qu'il peut être transmis à CreateProcessAsUser.

Toutes les suggestions pour modifier le code, ainsi que des explications seraient les bienvenues.

Notes

J'ai été principalement référence à la MSDN docs (ainsi que PInvoke.net pour le C# fonction/montant/enum déclarations). Les pages suivantes, en particulier, semblent avoir beaucoup d'informations dans les notes de sections, de certains de ce qui peut être important de se soustraire à moi:

Modifier

J'ai juste essayé Mitch de la suggestion, mais, malheureusement, la vieille erreur a simplement été remplacé par un nouveau: "Le système ne peut pas trouver le fichier spécifié." (code d'erreur 2)

De l'appel précédent de CreateProcessAsUser a été remplacé par le suivant:

retValue = WinApi.CreateProcessWithTokenW(hUserToken, LogonFlags.WithProfile, null,
this.CommandLine, CreationFlags.CREATE_NEW_CONSOLE |
CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);

Noter que ce code n'utilise plus le double jeton mais plutôt l'original, comme le MSDN docs semblent le suggérer.

Et voici une autre tentative à l'aide de CreateProcessWithLogonW. L'erreur cette fois est "échec d'ouverture de session: nom d'utilisateur inconnu ou mot de passe incorrect" (code d'erreur 1326)

retValue = WinApi.CreateProcessWithLogonW("Alex", null, "password",
LogonFlags.WithProfile, null, this.CommandLine,
CreationFlags.CREATE_NEW_CONSOLE | CreationFlags.CREATE_SUSPENDED |
CreationFlags.CREATE_UNICODE_ENVIRONMENT, pNewEnvironmentBlock,
null, ref startupInfo, out _processInfo);

J'ai aussi essayé de spécifier le nom d'utilisateur au format UPN ("Alex@Alex-PC") et en passant le domaine de façon indépendante en tant que deuxième argument, en vain (identique d'erreur).

Ne jamais passer votre propre description de Win32Exception. Au lieu de cela, passer un défaut construit Win32Exception comme le InnerException d'une exception de type plus approprié pour votre fonction, votre description va à l'extérieur d'exception. De cette façon, l'appelant obtient à la fois vos informations (ce qui a échoué) ainsi que le code d'erreur Win32 (comment se fait-il échouer) et de la pure correspondance de message d'erreur.
Est-il possible d'obtenir un stdin/stdout de ce processus?

OriginalL'auteur Noldorin | 2009-03-20