CreateProcessAsUser from service and user security issues

0

I have a windows service that launches my application. This service is a Scheduler, to perform some custom actions defined by end-users.

It runs as LocalSystem account, and will launch my application (that has windows).

For this I use the functions LogongUser, LoadUserProfile, CreateProcessAsUser. This works perfectly but only if the user, to log on, is member of Administrators group.

I cannot leave the user as an Administrator, he needs to be a default user (member of Users group).

As administrator it runs fine from the service. But when the process starts as a default user, it terminates abnormally with:

ExitCode: 0xC0000142 STATUS_DLL_INIT_FAILED
Message: {DLL Initialization Failed} Initialization of the dynamic link library %hs failed. The process is terminating abnormally.

If I logon with that user (member of Users group) and launch the application manually, everything goes fine.

I have not figured out what is the library that is causing the problem. I checked the system event log, but there is no logs. I already tried to ImporsonateLoggedOnUser but without success.

This is the code:

type 
    TProcessRec = record
        Token: Cardinal;
        ProfileInfo: TProfileInfo;
        Job: Cardinal;
        Environment: Pointer;
        ProcessInfo: TProcessInformation;
        WinStat: HWINSTA;
      end;

      TProcess = class
      private
        fProcess: TProcessRec;
      public
        constructor Create(aUser, aDomain, aPassword, aCommand, aWorkingDir: string; aUtil: TJJWServiceUtil);
        destructor Destroy; override;

        procedure Terminate(aExitCode: Cardinal);

        function getExitCode: Cardinal;
      end;

    constructor TProcess.Create(aUser, aDomain, aPassword, aCommand, aWorkingDir: string; aUtil: TJJWServiceUtil);
    type
      TCreateProcessFuncType = (cpAsUser, cpWithLogon);
    const
      DES_CREATION_TYPE: array [TCreateProcessFuncType] of string =
        ('CreateProcessAsUser', 'CreateProcessWithLogon');
    var
      sa: TSecurityAttributes;
      si: TStartupInfo;
      limits : TJobObjectExtendedLimitInformation;
      wUser, wDomain, wPassword, wCommand, wWorkingDir: WideString;
      creationType: TCreateProcessFuncType;
    begin

        // se estiver rodando com a conta LocalSystem (em serviço de windows)
        if
          IsPrivilegeEnabled(SE_TCB_NAME) or IsPrivilegeEnabled(SE_INCREASE_QUOTA_NAME)
        then
          creationType := cpAsUser
        else
          creationType := cpWithLogon;

      aUtil.debug('ENV: ' + AnsiReplaceStr(GetEnvironmentVariable('PATH'), '%', '%%'));
      aUtil.debug('Criando novo processo em modo: ' + DES_CREATION_TYPE[creationType]);

      FillChar(fProcess, SizeOf(fProcess), 0);

      FillChar(sa, SizeOf(sa), 0);
      sa.nLength := SizeOf(sa);

      FillChar(si, SizeOf(si), 0);
      si.cb := SizeOf(si);

      case creationType of

        cpAsUser:
          begin
            aUtil.debug('Efetuando o login do usuário %s', [aUser]);
            // login
            if not LogonUser(PChar(aUser), PChar(aDomain), PChar(aPassword), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, fProcess.Token) then
              RaiseLastOSError;

            try
              aUtil.debug('Carrengado o profile do usuário %s', [aUser]);

              // carrega o profile do usuário
              fProcess.ProfileInfo.dwSize := SizeOf(fProcess.ProfileInfo);
              fProcess.ProfileInfo.dwFlags := PI_NOUI;
              fProcess.ProfileInfo.lpUserName := PChar(aUser);
              if not LoadUserProfile(fProcess.Token, fProcess.ProfileInfo) then
                RaiseLastOSError;

              try
                aUtil.debug('Criando o bloco de variáveis de ambiente do usuário %s', [aUser]);
                // variaveis de ambiente
                if not CreateEnvironmentBlock(fProcess.Environment, fProcess.Token, false) then
                  RaiseLastOSError;

                aUtil.debug('Criando o JOB para associar o processo filho ao processo pai');
                // job para associar
                fProcess.Job := CreateJobObject(@sa, nil);
                if fProcess.Job = 0 then
                  RaiseLastOSError;

                try
                  // limita o job para matar o seu processo caso o processo pai termine antes
                  FillChar(Limits,SizeOf(Limits),0);
                  with Limits,BasicLimitInformation do
                    LimitFlags := JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE or JOB_OBJECT_LIMIT_BREAKAWAY_OK;

                  if not SetInformationJobObject(fProcess.Job, JobObjectExtendedLimitInformation, @limits, SizeOf(limits)) then
                    RaiseLastOSError;

                  try
                    fProcess.WinStat := CreateWindowStation(SCHEDULER_WINSTAT, 0, 0, nil);
                    if fProcess.WinStat = 0 then
                      RaiseLastOSError;
                    si.lpDesktop := PChar(SCHEDULER_WINSTAT);

                    aUtil.debug('Criando o processo: %s', [aCommand]);

                    // processo!
                    if not CreateProcessAsUser(
                      fProcess.Token,
                      nil,
                      PChar(aCommand),
                      @sa,
                      nil,
                      false,
                      CREATE_SUSPENDED or CREATE_BREAKAWAY_FROM_JOB or NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP or CREATE_UNICODE_ENVIRONMENT,
                      fProcess.Environment,
                      PChar(aWorkingDir),
                      si,
                      fProcess.ProcessInfo)
                    then
                      RaiseLastOSError;

                    try
                      aUtil.debug('Associando o JOB ao processo filho');
                      // associa ao processo do plugin o JOB (gruda nele!)
                      if not AssignProcessToJobObject(fProcess.Job, fProcess.ProcessInfo.hProcess) then
                        RaiseLastOSError;

                      // VOA PROCESSO, VOA!
                      if ResumeThread(fProcess.ProcessInfo.hThread) = $FFFFFFFF then
                        RaiseLastOSError;

                      aUtil.debug('Processo disparado');
                    except
                      TerminateProcess(fProcess.ProcessInfo.hProcess, Cardinal(-1));

                      CloseHandle(fProcess.ProcessInfo.hThread);
                      CloseHandle(fProcess.ProcessInfo.hProcess);

                      FillChar(fProcess.ProcessInfo, SizeOf(fProcess.ProcessInfo), 0);

                      raise;
                    end;

                  except
                    DestroyEnvironmentBlock(fProcess.Environment);
                    fProcess.Environment := nil;
                    raise;
                  end;

                except
                  CloseHandle(fProcess.Job);
                  fProcess.Job := 0;
                  raise;
                end;

              except
                UnloadUserProfile(fProcess.Token, fProcess.ProfileInfo.hProfile);
                FillChar(fProcess.ProfileInfo, SizeOf(fProcess.ProfileInfo), 0);
                raise;
              end;

            except
              CloseHandle(fProcess.Token);
              fProcess.Token := 0;
              raise;
            end;
          end;

        //////////////////////////////////////////////////////////////////////////////

        cpWithLogon:
          begin
            wUser := aUser;
            wDomain := aDomain;
            wPassword := aPassword;
            wCommand := aCommand;
            wWorkingDir := aWorkingDir;

            aUtil.debug('Disparando o processo: %s', [aCommand]);

            // o CreateProcessWithLogon já cria um JOB para evitar
            // que o processo fique órfão no limbo
            if not CreateProcessWithLogon(
               PWideChar(wUser),
               PWideChar(wDomain),
               PWideChar(wPassword),
               LOGON_WITH_PROFILE,
               nil,
               PWideChar(wCommand),
               NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP,
               nil,
               PWideChar(wWorkingDir),
               si,
               fProcess.ProcessInfo
             )
            then
              RaiseLastOSError;
          end;

          end;
    end;

    destructor TProcess.Destroy;
    begin
      if fProcess.ProcessInfo.hProcess > 0 then
      begin
        if getExitCode = STILL_ACTIVE then
          TerminateProcess(fProcess.ProcessInfo.hProcess, Cardinal(-1));

        CloseHandle(fProcess.ProcessInfo.hThread);
        CloseHandle(fProcess.ProcessInfo.hProcess);
      end;

      if fProcess.Environment <> nil then
        DestroyEnvironmentBlock(fProcess.Environment);

      if fProcess.Job > 0 then
        CloseHandle(fProcess.Job);

      if fProcess.ProfileInfo.hProfile > 0 then
        UnloadUserProfile(fProcess.Token, fProcess.ProfileInfo.hProfile);

      if fProcess.Token > 0 then
        CloseHandle(fProcess.Token);

      if fProcess.WinStat > 0 then
        CloseWindowStation(fProcess.WinStat);

      FillChar(fProcess, SizeOf(fProcess), 0);

      inherited;
    end;

    function TProcess.getExitCode: Cardinal;
    begin
      if not GetExitCodeProcess(fProcess.ProcessInfo.hProcess, Result) then
        RaiseLastOSError;
    end;

This can be usefull

Starting an Interactive Client Process in C++

windows
security
delphi
service
asked on Stack Overflow Mar 1, 2013 by Beto Neto • edited Mar 1, 2013 by Beto Neto

1 Answer

1

According to this: Starting an Interactive Client Process in C++

And this: CreateProcessAsUser() windowstations and desktops

I solved the problem creating a new WindowStation and Deskyop to launch the process:

myWinStat := CreateWindowStation('myWinStat', 0, GENERIC_READ or GENERIC_WRITE, nil);
myDesk := CreateDesktop('myDesktop', nil, nil, 0, GENERIC_READ or GENERIC_WRITE, nil);
startupInfo.lpDesktop := 'myDesktop';
try
   // createProcessAsUser...
   // Wait for process termination...
finally
   CloseDesktop(myDesk);
   CloseWindowStation(myWinStat);
end;
answered on Stack Overflow Mar 1, 2013 by Beto Neto • edited Mar 5, 2013 by Beto Neto

User contributions licensed under CC BY-SA 3.0