delphiwindows-10-desktopdelphi-11-alexandria

Running application as non-administrative user while elevated


How (if possible) do you - when you're running as elevated user - start a program with non-administrative rights, when you don't have logon credentials.

Scenario: A program running non-elevated executes a child process with elevated status (via ShellExecute with "runas" verb). This application needs to do something that requires administrative rights (replace the .EXE file that ShellExecute'd while it's in the ProgramFiles hierarchy), and once that's done, it should execute the original program, but without elevated status.

How do I run the original application non-elevated from an elevated program? It should simply run under the same user context as originally run, but I don't have the credentials to supply. I "just" want to strip the administrative token from the user when running the original application.


Solution

  • There were quite a few missing imports/definitions in Delphi, so I had to define them myself. But here is the finished implementation in Delphi of the C++ code from the link provided by Remy:

    FUNCTION GetShellWindow : HWND; EXTERNAL 'USER32.DLL';
    FUNCTION InitializeProcThreadAttributeList(lpAttributeList : PProcThreadAttributeList ; dwAttributeCount,dwFlags : DWORD ; VAR lpSize : NativeUInt) : ByteBool; stdcall; EXTERNAL 'KERNEL32.DLL';
    FUNCTION UpdateProcThreadAttribute(lpAttributeList : PProcThreadAttributeList ; dwFlags : DWORD ; Attribute : NativeUInt ; lpValue : Pointer ; cbSize : NativeUInt ; lpPreviousValue : POINTER = NIL ; lpReturnSize : PSIZE_T = NIL) : ByteBool; OVERLOAD; stdcall; EXTERNAL 'KERNEL32.DLL';
    FUNCTION UpdateProcThreadAttribute(lpAttributeList : PProcThreadAttributeList ; dwFlags : DWORD ; Attribute : NativeUInt ; VAR Process : THandle ; lpPreviousValue : Pointer = NIL ; lpReturnSize : PSIZE_T = NIL) : ByteBool; OVERLOAD;
      BEGIN
        Result:=UpdateProcThreadAttribute(lpAttributeList,dwFlags,Attribute,@Process,SizeOf(THandle),lpPreviousValue,lpReturnSize)
      END;
    PROCEDURE DeleteProcThreadAttributeList(lpAttributeList : PProcThreadAttributeList); stdcall; EXTERNAL 'KERNEL32.DLL';
    CONST PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = $0002000
    CONST EXTENDED_STARTUPINFO_PRESENT = $00080000;
    TYPE
      STARTUPINFOEXW        = PACKED RECORD
                                StartupInfo         : STARTUPINFOW;
                                lpAttributeList     : PProcThreadAttributeList
                              END;
      STARTUPINFOEX         = STARTUPINFOEXW;
    
    FUNCTION TryRunUnelevated(CONST Prog : TFileName ; CONST Tail : STRING ; CONST StartupDir : STRING = '') : BOOLEAN;
      VAR
        H           : HWND;
        PID         : DWORD;
        Process     : THandle;
        Size        : SIZE_T;
        P           : PProcThreadAttributeList;
        SIEX        : STARTUPINFOEX;
        PI          : PROCESS_INFORMATION;
    
      BEGIN
        Result:=FALSE; H:=GetShellWindow;
        IF H=0 THEN EXIT;
        IF GetWindowThreadProcessID(H,PID)=0 THEN EXIT;
        Process:=OpenProcess(PROCESS_CREATE_PROCESS,FALSE,PID);
        IF Process=0 THEN EXIT;
        TRY
          InitializeProcThreadAttributeList(NIL,1,0,Size);
          GetMem(P,Size);
          TRY
            IF NOT InitializeProcThreadAttributeList(P,1,0,Size) THEN EXIT;
            TRY
              IF NOT UpdateProcThreadAttribute(P,0,PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,Process) THEN EXIT;
              FillChar(SIEX,SizeOf(STARTUPINFOEX),0);
              SIEX.lpAttributeList:=P;
              SIEX.StartupInfo.cb:=SizeOf(STARTUPINFOEX);
              IF NOT CreateProcess(PChar(Prog),PChar(Tail),NIL,NIL,FALSE,CREATE_NEW_CONSOLE OR EXTENDED_STARTUPINFO_PRESENT,NIL,POINTER(StartupDir),SIEX.StartupInfo,PI) THEN EXIT
            FINALLY
              DeleteProcThreadAttributeList(P)
            END;
            CloseHandle(PI.hProcess);
            CloseHandle(PI.hThread)
          FINALLY
            FreeMem(P)
          END
        FINALLY
          CloseHandle(Process)
        END;
        Result:=TRUE
      END;
    
    PROCEDURE RunUnelevated(CONST Prog : TFileName ; CONST Tail : STRING ; CONST StartupDir : STRING = '');
      BEGIN
        IF NOT TryRunUnelevated(Prog,Tail,StartupDir) THEN RaiseLastOSError
      END;
    

    There are two routines - one that simply returns TRUE/FALSE to signify success or failure, and one that raises an exception if it can't do it.

    Edit: The re-declaration of DeleteProcThreadAttributeList is because the declaration of this routine in the standard Delphi sources is wrong - it uses TProcThreadAttributeList instead of PProcThreadAttributeList.