En Windows: ¿cómo inicia programáticamente un proceso en modo administrador en otro contexto de usuario?

Guión

Tengo una computadora remota que quiero ejecutar instaladores (ejecutables arbitrarios) en progtwigtically. Estos instaladores requieren dos cosas:

  • Deben ejecutarse en modo Administrador.
  • Deben ejecutarse bajo un contexto de usuario específico (Específicamente, un usuario local que es miembro del grupo Administradores).

Esto ha demostrado ser muy desafiante.

Parece que hay algunas herramientas externas que existen que hacen esto, pero estoy buscando una solución que viene con Windows.

Cómo sería una solución válida para este problema

Desde un contexto elevado (por ejemplo, un archivo por lotes elevado o un progtwig ejecutable), una solución válida debería poder iniciar un proceso programáticamente en modo Administrador en otro contexto de usuario. Supongamos que la identificación y la contraseña del otro usuario están disponibles y que el otro usuario es miembro del grupo Administradores. Restricciones adicionales:

  • Una solución válida no puede depender de una herramienta externa. Como las versiones más nuevas de Windows vienen con .NET y PowerShell de manera predeterminada, estas son herramientas válidas para usar.
  • Una solución válida no puede requerir interacciones del usuario. Esto significa que si aparece una ventana de UAC o si se requiere confirmación del usuario, la solución no es válida.

¡Pruebe su solución antes de publicarla para asegurarse de que funcione! Si va a proporcionar un enlace a otra solución, verifique que la solución vinculada funcione antes de publicarla. Muchas personas que afirman tener soluciones de trabajo para este problema de hecho no lo hacen.

Lo que he intentado

He intentado usar Batch Scripts, PowerShell y C #. Por lo que puedo decir, ninguna de estas tecnologías logrará la tarea. Todos ellos sufren el mismo problema fundamental: ejecutar una tarea como otro usuario y en modo Administrador son procesos mutuamente excluyentes. Déjame ser más específico:

Por qué no lote

El comando que se usaría para ejecutar en un contexto de usuario diferente es Runas, que no inicia el proceso elevado. Existen varias herramientas externas que pretenden evitar esto, pero como se dijo anteriormente, no están permitidas.

Por qué no PowerShell

El comando para iniciar un nuevo proceso, Start-Process, puede elevar un nuevo proceso y ejecutarlo como un usuario diferente, pero no al mismo tiempo. Tengo una pregunta abierta que se refiere a este tema. Desafortunadamente, nadie ha proporcionado una solución, lo que me lleva a creer que es imposible.

Por qué no C #

Esto también parece ser imposible, ya que la clase Process no parece ser compatible con el inicio de un proceso en modo Administrador y con credenciales de usuario diferentes.

¿Por qué no una herramienta externa?

Esto me obliga a confiar en el código de otra persona para hacer lo correcto, y prefiero codificarlo yo mismo que hacer eso. De hecho, tengo una solución que es un paso mejor que confiar en otra persona, pero es bastante hackish :

  • Cree una tarea usando el Progtwigdor de tareas para iniciar el ejecutable en modo administrador en la cuenta especificada en algún momento en un futuro muy lejano.
  • Forzar la tarea para ejecutar de inmediato.
  • Espere a ver si la tarea ha terminado. Respondido aqui .

Gracias de antemano a cualquiera que intente ayudar! Es muy apreciado y espero que, si nada más, otras personas pueden encontrarlo para el Progtwigdor de tareas.

OK, resulta que la función CreateProcessWithLogonW filtra el token del usuario, y también lo hace LogonUser . Esto parece dejarnos atascados, ya que no tenemos los privilegios correctos para corregir el problema (ver nota al pie) pero resulta que LogonUser no filtra el token si usa LOGON32_LOGON_BATCH lugar de LOGON32_LOGON_INTERACTIVE .

Aquí hay un código que realmente funciona. Usamos la función CreateProcessAsTokenW para iniciar el proceso, ya que esta variante particular solo requiere el privilegio SE_IMPERSONATE_NAME , que se otorga a las cuentas de administrador de manera predeterminada.

Este progtwig de ejemplo inicia un subproceso que crea un directorio en c:\windows\system32 , que no sería posible si el subproceso no se elevara.

 #define _WIN32_WINNT 0x0501 #include  #include  #include  #include  wchar_t command[] = L"c:\\windows\\system32\\cmd.exe /c md c:\\windows\\system32\\proof-that-i-am-an-admin"; int main(int argc, char **argv) { HANDLE usertoken; STARTUPINFO sinfo; PROCESS_INFORMATION pinfo; ZeroMemory(&sinfo, sizeof(sinfo)); sinfo.cb = sizeof(sinfo); if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken)) { printf("LogonUser: %u\n", GetLastError()); return 1; } if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\\windows\\system32\\cmd.exe", command, 0, NULL, NULL, &sinfo, &pinfo)) { printf("CreateProcess: %u\n", GetLastError()); return 1; } return 0; } 

Sin embargo, si el proceso de destino es un proceso de GUI (incluido un proceso con una consola visible), no se mostrará correctamente. Aparentemente CreateProcessWithTokenW solo asigna los permisos mínimos de escritorio y estación de ventana necesarios para que se ejecute un proceso, lo que no es suficiente para mostrar realmente una GUI.

Incluso si no necesita ver la salida, existe el riesgo de que la GUI rota ocasione problemas funcionales con el progtwig.

Entonces, a menos que el proceso objective se ejecute en segundo plano, probablemente deberíamos asignar permisos de manera apropiada. En general, es mejor crear una nueva estación de ventana y un escritorio nuevo para aislar el proceso objective; en este caso, sin embargo, el proceso de destino se ejecutará como administrador de todos modos, por lo que no tiene sentido, podemos hacer la vida más fácil simplemente cambiando los permisos en la estación de ventana existente y en el escritorio.

Editar 24 de noviembre de 2014: derechos de acceso corregidos en la estación de ventana ACE para que funcionen para usuarios no administrativos. Tenga en cuenta que hacer esto puede permitir que el usuario no administrador en cuestión comprometa los procesos en la sesión de destino.

 #define _WIN32_WINNT 0x0501 #include  #include  #include  #include  wchar_t command[] = L"c:\\windows\\system32\\notepad.exe"; int main(int argc, char **argv) { HANDLE usertoken; STARTUPINFO sinfo; PROCESS_INFORMATION pinfo; HDESK desktop; EXPLICIT_ACCESS explicit_access; BYTE buffer_token_user[SECURITY_MAX_SID_SIZE]; PTOKEN_USER token_user = (PTOKEN_USER)buffer_token_user; PSECURITY_DESCRIPTOR existing_sd; SECURITY_DESCRIPTOR new_sd; PACL existing_dacl, new_dacl; BOOL dacl_present, dacl_defaulted; SECURITY_INFORMATION sec_info_dacl = DACL_SECURITY_INFORMATION; DWORD dw, size; HWINSTA window_station; if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken)) { printf("LogonUser: %u\n", GetLastError()); return 1; } if (!GetTokenInformation(usertoken, TokenUser, buffer_token_user, sizeof(buffer_token_user), &dw)) { printf("GetTokenInformation(TokenUser): %u\n", GetLastError()); return 1; } window_station = GetProcessWindowStation(); if (window_station == NULL) { printf("GetProcessWindowStation: %u\n", GetLastError()); return 1; } if (!GetUserObjectSecurity(window_station, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { printf("GetUserObjectSecurity(window_station) call 1: %u\n", GetLastError()); return 1; } existing_sd = malloc(size); if (existing_sd == NULL) { printf("malloc failed\n"); return 1; } if (!GetUserObjectSecurity(window_station, &sec_info_dacl, existing_sd, size, &dw)) { printf("GetUserObjectSecurity(window_station) call 2: %u\n", GetLastError()); return 1; } if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted)) { printf("GetSecurityDescriptorDacl(window_station): %u\n", GetLastError()); return 1; } if (!dacl_present) { printf("no DACL present on window station\n"); return 1; } explicit_access.grfAccessMode = SET_ACCESS; explicit_access.grfAccessPermissions = WINSTA_ALL_ACCESS | READ_CONTROL; explicit_access.grfInheritance = NO_INHERITANCE; explicit_access.Trustee.pMultipleTrustee = NULL; explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER; explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid; dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl); if (dw != ERROR_SUCCESS) { printf("SetEntriesInAcl(window_station): %u\n", dw); return 1; } if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION)) { printf("InitializeSecurityDescriptor(window_station): %u\n", GetLastError()); return 1; } if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE)) { printf("SetSecurityDescriptorDacl(window_station): %u\n", GetLastError()); return 1; } if (!SetUserObjectSecurity(window_station, &sec_info_dacl, &new_sd)) { printf("SetUserObjectSecurity(window_station): %u\n", GetLastError()); return 1; } free(existing_sd); LocalFree(new_dacl); desktop = GetThreadDesktop(GetCurrentThreadId()); if (desktop == NULL) { printf("GetThreadDesktop: %u\n", GetLastError()); return 1; } if (!GetUserObjectSecurity(desktop, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { printf("GetUserObjectSecurity(desktop) call 1: %u\n", GetLastError()); return 1; } existing_sd = malloc(size); if (existing_sd == NULL) { printf("malloc failed\n"); return 1; } if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, size, &dw)) { printf("GetUserObjectSecurity(desktop) call 2: %u\n", GetLastError()); return 1; } if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, 4096, &dw)) { printf("GetUserObjectSecurity: %u\n", GetLastError()); return 1; } if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted)) { printf("GetSecurityDescriptorDacl: %u\n", GetLastError()); return 1; } if (!dacl_present) { printf("no DACL present\n"); return 1; } explicit_access.grfAccessMode = SET_ACCESS; explicit_access.grfAccessPermissions = GENERIC_ALL; explicit_access.grfInheritance = NO_INHERITANCE; explicit_access.Trustee.pMultipleTrustee = NULL; explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER; explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid; dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl); if (dw != ERROR_SUCCESS) { printf("SetEntriesInAcl: %u\n", dw); return 1; } if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION)) { printf("InitializeSecurityDescriptor: %u\n", GetLastError()); return 1; } if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE)) { printf("SetSecurityDescriptorDacl: %u\n", GetLastError()); return 1; } if (!SetUserObjectSecurity(desktop, &sec_info_dacl, &new_sd)) { printf("SetUserObjectSecurity(window_station): %u\n", GetLastError()); return 1; } free(existing_sd); LocalFree(new_dacl); ZeroMemory(&sinfo, sizeof(sinfo)); sinfo.cb = sizeof(sinfo); if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\\windows\\system32\\notepad.exe", command, 0, NULL, NULL, &sinfo, &pinfo)) { printf("CreateProcess: %u\n", GetLastError()); return 1; } return 0; } 

Tenga en cuenta el uso de LOGON_WITH_PROFILE. Esto no es necesario para mostrar una GUI, y ralentiza el inicio del proceso considerablemente, por lo que debe eliminarlo si no lo necesita, pero si es un administrador, la razón más probable por la que está iniciando un proceso como administrador diferente es que necesitas algo en el perfil de usuario de ese administrador. (Otro escenario podría ser que necesita usar una cuenta de dominio específica para acceder a recursos en otra máquina).


Nota:

Específicamente, necesita SeTcbPrivilege para utilizar GetTokenInformation y TokenLinkedToken para obtener un identificador utilizable para el token elevado que genera LogonUser . Desafortunadamente, este privilegio generalmente solo está disponible si se está ejecutando como sistema local.

Si no tiene SeTcbPrivilege , puede obtener una copia del token vinculado, pero en este caso se trata de un token de suplantación al nivel de SecurityIdentification , por lo que no sirve de nada cuando se crea un nuevo proceso. Gracias a RbMm por ayudarme a aclarar esto.