1

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.

HeartWare
  • 7,464
  • 2
  • 26
  • 30
  • 1
    https://devblogs.microsoft.com/oldnewthing/?p=2643 – Dalija Prasnikar Jun 17 '23 at 09:02
  • @DalijaPrasnikar: Thank you for the pointer, but that is a bit beyond my C++ knowledge (there are many types and constructs that I don't know the Delphi equivalent of and the FindDesktopView link on that page leads to a "Forbidden" page, and the other functions with that name that I can find using Google doesn't seem to be compatible with the call in the linked-to-sample - they have multiple arguments, whereas the linked-to-sample seems to call it with only one parameter)). If you (or someone else) could translate the linked-to-sample into Delphi, it'll be much appreciated. – HeartWare Jun 17 '23 at 12:52
  • @HeartWare the `FindDesktopFolderView()` function is defined in this blog post: [Manipulating the positions of desktop icons](https://devblogs.microsoft.com/oldnewthing/20130318-00/?p=4933) – Remy Lebeau Jun 17 '23 at 17:15
  • 2
    @DalijaPrasnikar a much simpler approach is outlined in a later blog post: [How can I launch an unelevated process from my elevated process, redux](https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443) – Remy Lebeau Jun 17 '23 at 17:15
  • @RemyLebeau: The blog post you refer to for FindDesktopFolderView has two parameters, whereas the one linked to by Dalilja has one. – HeartWare Jun 17 '23 at 18:34
  • @RemyLebeau: The second blog post you refer to does indeed seem much simpler. I'll try to use that as the basis for my Delphi implementation. – HeartWare Jun 17 '23 at 18:35
  • @HeartWare [`IID_PPV_ARGS()`](https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-iid_ppv_args) is a preprocessor macro that expands into 2 parameters, the IID of the interface, and a pointer to the interface variable. – Remy Lebeau Jun 17 '23 at 23:29
  • @RemyLebeau: Didn't know that, about IID_PPV_ARGS() - Not _that_ well-versed in C++ :-). But the simpler routine you found was a great help, thank you... – HeartWare Jun 18 '23 at 04:36

1 Answers1

2

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.

HeartWare
  • 7,464
  • 2
  • 26
  • 30
  • Bug-report for the wrong signature of DeleteProcThreadAttributeList: https://quality.embarcadero.com/browse/RSP-41866 – HeartWare Jun 18 '23 at 09:57