c# winapi process privileges createprocessasuser

Llamar a CreateProcessAsUser desde C#



winapi privileges (3)

He estado intentando crear un nuevo proceso en el contexto de un usuario específico que usa la función CreateProcessAsUser de la API de Windows, pero parece que se está ejecutando en un problema de seguridad bastante desagradable ...

Antes de seguir explicando, aquí está el código que estoy usando actualmente para comenzar el nuevo proceso (un proceso de consola: PowerShell es específico, aunque no debería importar).

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); }

Por el bien del problema aquí, ignore el código que trata con las variables de entorno (he probado esa sección de forma independiente y parece que funciona).

Ahora, el error que obtengo es el siguiente (arrojado en la línea que sigue a la llamada a CreateProcessAsUSer ):

"El cliente no tiene un privilegio requerido" (código de error 1314)

(El mensaje de error se descubrió al eliminar el parámetro de mensaje del constructor Win32Exception. Es cierto que mi código de manejo de errores aquí puede no ser el mejor, pero eso es un asunto un tanto irrelevante. Sin embargo, puede comentarlo si lo desea. ) Realmente estoy bastante confundido sobre la causa de este vago error en esta situación. La documentación de MSDN y varios hilos del foro me han dado tan pocos consejos, y especialmente dado que las causas de tales errores parecen ser muy variadas, no tengo idea de qué sección del código necesito modificar. Tal vez sea simplemente un parámetro único que necesito cambiar, pero podría estar haciendo las llamadas WinAPI incorrectas / no suficientes para todo lo que sé. Lo que me confunde mucho es que la versión anterior del código que usa la función simple CreateProcess (equivalente a excepción del parámetro token del usuario) funcionó perfectamente bien. Según tengo entendido, solo es necesario llamar a la función de usuario de Inicio de sesión para recibir el identificador de token apropiado y luego duplicarlo para que se pueda pasar a CreateProcessAsUser .

Cualquier sugerencia para modificaciones al código así como explicaciones sería muy bienvenido.

Notas

Me he referido principalmente a los documentos de MSDN (así como a PInvoke.net para las declaraciones de la función C / strut / enum). Las siguientes páginas en particular parecen tener mucha información en las secciones de Comentarios, algunas de las cuales pueden ser importantes y me pueden eludir:

Editar

Acabo de probar la sugerencia de Mitch, pero desafortunadamente el antiguo error acaba de ser reemplazado por uno nuevo: "El sistema no puede encontrar el archivo especificado". (código de error 2)

La llamada anterior a CreateProcessAsUser se reemplazó por lo siguiente:

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);

Tenga en cuenta que este código ya no utiliza el token duplicado, sino el original, como parecen sugerir los documentos de MSDN.

Y aquí hay otro intento de usar CreateProcessWithLogonW . El error esta vez es "Error de inicio de sesión: nombre de usuario desconocido o contraseña incorrecta" (código de error 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);

También intenté especificar el nombre de usuario en formato UPN ("Alex @ Alex-PC") y pasar el dominio de forma independiente como el segundo argumento, todo en vano (error idéntico).


Desde aquí :

Normalmente, el proceso que llama a la función CreateProcessAsUser debe tener los privilegios SE_ASSIGNPRIMARYTOKEN_NAME y SE_INCREASE_QUOTA_NAME. Si esta función falla con ERROR_PRIVILEGE_NOT_HELD (1314), use la función CreateProcessWithLogonW en su lugar. CreateProcessWithLogonW no requiere privilegios especiales, pero la cuenta de usuario especificada debe poder iniciar sesión de forma interactiva. En general, es mejor usar CreateProcessWithLogonW para crear un proceso con credenciales alternativas.

Vea esta publicación en el blog Cómo llamar a CreateProcessWithLogonW y CreateProcessAsUser en .NET


Ahh ... Parece que me ha pillado uno de los mayores problemas en la programación de interoperabilidad de WinAPI. Además, publicar el código para mis declaraciones de funciones hubiera sido una buena idea en este caso.

De todos modos, todo lo que tenía que hacer era agregar un argumento al atributo DllImport de la función que especifica CharSet = CharSet.Unicode . Este fue el truco para las funciones CreateProcessWithLogonW y CreateProcessWithTokenW . ¡Supongo que finalmente me di cuenta de que el sufijo W de los nombres de las funciones hacía referencia a Unicode y que necesitaba especificarlo explícitamente en C #! Aquí están las declaraciones de funciones correctas en caso de que alguien esté interesado:

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool CreateProcessWithLogonW(string principal, string authority, string password, LogonFlags logonFlags, string appName, string cmdLine, CreationFlags creationFlags, IntPtr environmentBlock, string currentDirectory, ref STARTUPINFO startupInfo, out PROCESS_INFORMATION processInfo); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool CreateProcessWithTokenW(IntPtr hToken, LogonFlags dwLogonFlags, string lpApplicationName, string lpCommandLine, CreationFlags dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);