Ir al contenido



Foto

[MULTILENGUAJE] Lanzar una aplicación GUI desde un servicio


  • Por favor identifícate para responder
3 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.839 mensajes
  • LocationMadrid - España

Escrito 17 septiembre 2012 - 06:50

En ocasiones es necesario lanzar desde un servicio una aplicación GUI con credenciales del usuario dueño de la sesión activa. El problema es que desde un servicio, que corre como usuario SYSTEM, creará procesos también SYSTEM y en una estación de ventana y escritorio diferentes a los del usuario activo con lo que la comunicación con el mismo queda desactivada. Esto es especialmente cierto en Window 7.

Para solventar el problema la estrategia será la siguiente:
1.- Encontrar el Token del usuario de la sesión activa y duplicarlo.
2.- Arrancar el nuevo proceso usando ese Token con la API CreateProcessAsUser.

Para que esto funcione debemos correr dichas API como SYSTEM o fallarán.
El primer paso se puede conseguir con el siguiente código:
 

delphi
  1. WTSQueryUserToken(WtsGetActiveConsoleSessionID, @hToken);

El problema es que la API WtsGetActiveConsoleSessionID no siempre encuentra la sesión, pero afortunadamente podemos enumerarlas para encontrar la que nos interesa. El código se complica un poco pero conseguimos el objetivo:

delphi
  1. function GetCurrentUserToken: THandle;
  2. var
  3.   hToken: THandle;
  4.   pSi: PWTS_SESSION_INFO;
  5.   pSiA: PWTS_SESSION_INFO_ARRAY;
  6.   Count, i: DWORD;
  7. begin
  8.   hToken:= 0;
  9.   Result:= 0;
  10.   pSi:= nil;
  11.   Count:= 0;
  12.  
  13.   WTSEnumerateSessionsA(0, 0, 1, @pSi, @Count);
  14.   pSiA:= PWTS_SESSION_INFO_ARRAY(pSi);
  15.   for i:= 0 to Count-1 do
  16.   begin
  17.     if pSiA.State = 0 then
  18.     begin
  19.       if WTSQueryUserToken(pSiA[i].SessionId, @hToken) then
  20.         DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY or TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, Result);
  21.       break;
  22.     end;
  23.   end;
  24.   WTSFreeMemory(pSi);
  25. end;

Llegados a este punto usamos la API CreateProcessAsUser para crear nuestro proceso GUI.

Pongo el código completo de un ejemplo consistente en aplicación de consola encargara de abrir el Notepad. Dicha aplicación se debe correr como usuario SYSTEM para que funcione.
 

delphi
  1. program RunAsOwnerSesion;
  2.  
  3. uses
  4.   windows;
  5.  
  6. type
  7. WTS_SESSION_INFO = record
  8.   SessionId:      DWORD;
  9.   pWinStationName: PCHAR;
  10.   State:          DWORD;
  11. end;
  12. PWTS_SESSION_INFO = ^WTS_SESSION_INFO;
  13. PPWTS_SESSION_INFO = ^PWTS_SESSION_INFO;
  14. WTS_SESSION_INFO_ARRAY = array [0..0] of WTS_SESSION_INFO;
  15. PWTS_SESSION_INFO_ARRAY = ^WTS_SESSION_INFO_ARRAY;
  16.  
  17. function WTSEnumerateSessionsA(hServer: THandle; Reserved, Version: DWORD; ppSI: PPWTS_SESSION_INFO; pCount: PDWORD): boolean; stdcall external 'Wtsapi32.dll';
  18. function WTSQueryUserToken(SessionId: DWORD; phToken: PHANDLE): boolean; stdcall external 'Wtsapi32.dll';
  19. function WTSFreeMemory(pSi: Pointer): boolean; stdcall external 'Wtsapi32.dll';
  20.  
  21. function GetCurrentUserToken: THandle;
  22. var
  23.   hToken: THandle;
  24.   pSi: PWTS_SESSION_INFO;
  25.   pSiA: PWTS_SESSION_INFO_ARRAY;
  26.   Count, i: DWORD;
  27. begin
  28.   hToken:= 0;
  29.   Result:= 0;
  30.   pSi:= nil;
  31.   Count:= 0;
  32.  
  33.   // Obtener la lista de las sesiones
  34.   WTSEnumerateSessionsA(0, 0, 1, @pSi, @Count);
  35.   pSiA:= PWTS_SESSION_INFO_ARRAY(pSi);
  36.   // Buscamos la sesión activa
  37.   for i:= 0 to Count-1 do
  38.   begin
  39.     if pSiA[i].State = 0 then
  40.     begin
  41.       // Duplicamos el token del usuario de la sesión
  42.       if WTSQueryUserToken(pSiA[i].SessionId, @hToken) then
  43.         DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY or TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, Result);
  44.       break;
  45.     end;
  46.   end;
  47.   WTSFreeMemory(pSi);
  48. end;
  49.  
  50. function RunAsCurrentUser(Cmd: PCHAR): boolean;
  51. var
  52.   hToken: THandle;
  53.   pInfo:  PROCESS_INFORMATION;
  54.   sInfo:  STARTUPINFO;
  55.   sa:    SECURITY_ATTRIBUTES;
  56. begin
  57.   Result:= false;
  58.   hToken:= GetCurrentUserToken();
  59.  
  60.   if hToken <> 0 then
  61.   begin
  62.     ZeroMemory(@sInfo, sizeof(STARTUPINFO));
  63.     sInfo.cb:= sizeof(STARTUPINFO);
  64.     sInfo.wShowWindow:= SW_SHOW;
  65.  
  66.     ZeroMemory(@sa, sizeof(STARTUPINFO));
  67.     sa.nLength:= sizeof(sa);
  68.     Result:= CreateProcessAsUser(hToken, Cmd, nil, nil, nil, FALSE, NORMAL_PRIORITY_CLASS, nil, nil, sInfo, pInfo);
  69.     CloseHandle(hToken);
  70.     if Result then
  71.     begin
  72.       CloseHandle(pInfo.hProcess);
  73.       CloseHandle(pInfo.hThread);
  74.     end;
  75.   end;
  76. end;
  77.  
  78. begin
  79.   RunAsCurrentUser('C:\Windows\notepad.exe');
  80. end.

En este enlace podéis encontrar una aplicación que escribí (MiniSystem.exe) para conseguir lanzarla como SYSTEM. Recordar que para que MiniSystem funcione se debe ejecutar como administrador.

El sistema está probado en Win XP y Win 7


Espero que sea de utilidad y aclare dudas.



Saludos.
Edito para arreglar etiquetas de código cambiadas desde la última "mudanza"
  • 0

#2 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.839 mensajes
  • LocationMadrid - España

Escrito 17 septiembre 2012 - 11:50

Completo el truco con el código en C/C++.
 


cpp
  1. #include <windows.h>
  2. #pragma hdrstop
  3.  
  4. #include <Wtsapi32.h>
  5. #pragma comment(lib, "Wtsapi32.lib")
  6. #pragma comment(lib, "Userenv.lib")
  7.  
  8. extern "C"
  9. BOOL WINAPI WTSQueryUserToken(ULONG SessionId, PHANDLE phToken);
  10.  
  11.  
  12. HANDLE GetCurrentUserToken()
  13. {
  14.   HANDLE hToken = 0, hDupToken = 0;
  15.   PWTS_SESSION_INFO pSi = 0;
  16.   DWORD Count = 0;
  17.  
  18.   // Obtener la lista de las sesiones
  19.   WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSi, &Count);
  20.  
  21.   // Buscamos la sesión activa
  22.   for(DWORD i = 0; i < Count; ++i){
  23.     if(pSi.State == WTSActive){
  24.       // Duplicamos el token del usuario de la sesión
  25.       if(WTSQueryUserToken(pSi[i].SessionId, &hToken))
  26.         DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, 0, SecurityImpersonation, TokenPrimary, &hDupToken);
  27.       break;
  28.     }
  29.   }
  30.   WTSFreeMemory(pSi);
  31.  
  32.   return hDupToken;
  33. }
  34.  
  35. BOOL RunAsCurrentUser(char* Cmd)
  36. {
  37.   BOOL Result = false;
  38.   HANDLE hToken = GetCurrentUserToken();
  39.   if(hToken){
  40.     STARTUPINFO sInfo = {sizeof(STARTUPINFO)};
  41.     PROCESS_INFORMATION pInfo = {0};
  42.     Result = CreateProcessAsUser(hToken, 0, Cmd, 0, 0, FALSE, NORMAL_PRIORITY_CLASS, 0, 0, &sInfo, &pInfo);
  43.     if(Result){
  44.       CloseHandle(pInfo.hProcess);
  45.       CloseHandle(pInfo.hThread);
  46.     }
  47.     CloseHandle(hToken);
  48.   }
  49.   return Result;
  50. }
  51.  
  52.  
  53. //---------------------------------------------------------------------------
  54.  
  55. #pragma argsused
  56. WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  57. {
  58.   RunAsCurrentUser("notepad.exe");
  59.   return 0;
  60. }

Las explicaciones dadas para la versión delphi sirven para esta versión C/C++.

Añadir que en caso de necesidad se puede añadir un bloque con todas las variables de entorno del usuario con la API CreateEnvironmentBlock.

Recordar que este código está diseñado para ejecutarse desde un servicio o proceso que corre como SYSTEM.


Saludos.
Edito para arreglar etiquetas de código cambiadas desde la última "mudanza"


  • 0

#3 ELKurgan

ELKurgan

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 543 mensajes
  • LocationEspaña

Escrito 18 septiembre 2012 - 11:10

Gracias por el aporte  (y)

Saludos
  • 0

#4 seoane

seoane

    Advanced Member

  • Administrador
  • 1.243 mensajes
  • LocationEspaña

Escrito 19 septiembre 2012 - 12:05

Wow! La de tiempo que llevaba yo buscando algo como esto.  (y)
  • 0