5

Once you've started an application with Administrator permission, programs executed using ShellExecute in that application will inherit the Administrator permission. But that isn't what I want: it just has to start regularly without the extra permissions. ShellExecute accepts the parameter OPEN (regular) and RUNAS (Administrator). But if you use OPEN after you've started the application as Administrator, it still acts like RUNAS.

The following example demonstrates this: If you start it with regular permissions it says 'Started regular'. Once you press 1 for 'admin' it will start as administrator. If you press 2 in the newly created prompt it will not starts a 'regular' prompt, but again a 'Administrator' prompt.

I've found something about some parameters in RUNAS (https://superuser.com/a/374866), but they cannot be passed in ShellExecute. Any ideas?

program WindowsPrivilegeTest;
{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  Winapi.Windows,
  Winapi.ShellAPI;

function CheckTokenMembership(TokenHandle: THANDLE; SidToCheck: Pointer; var 
  IsMember: BOOL): BOOL; stdcall; external advapi32 name 'CheckTokenMembership';

// Source: http://stackoverflow.com/a/28572886/1870208
function IsAdministrator: Boolean;
var
  psidAdmin: Pointer;
  B: BOOL;
const
  SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5));
  SECURITY_BUILTIN_DOMAIN_RID  = $00000020;
  DOMAIN_ALIAS_RID_ADMINS      = $00000220;
  SE_GROUP_USE_FOR_DENY_ONLY  = $00000010;
begin
  psidAdmin := nil;
  try
    Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
      SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
      psidAdmin));

    if CheckTokenMembership(0, psidAdmin, B) then
      Result := B
    else
      Result := False;
  finally
    if psidAdmin <> nil then
      FreeSid(psidAdmin);
  end;
end;

var
  lLine : String;
  lOperation : PChar;
begin
  try
    if IsAdministrator then
    begin
      Writeln('Started as administrator');
    end
    else
    begin
      Writeln('Started regular');
    end;

    while True do
    begin
      Writeln('');
      Writeln('How to start? 1 = admin, 2 = regular user. Type number and press enter');
      ReadLn(lLine);
      lOperation := '';

      if lLine = '1' then
      begin
        lOperation := 'RUNAS';
      end
      else
      if lLine = '2' then
      begin
        lOperation := 'OPEN';
      end;

      if lOperation <> '' then
      begin
        ShellExecute(0, lOperation, PChar(ParamStr(0)), nil, nil, SW_SHOWNORMAL);
        Break;
      end;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Community
  • 1
  • 1
renevondecafe
  • 129
  • 1
  • 7
  • Voted to re-open as the answer in the question that is being referred to is not the right way, the right way would be to get the process token, the call `GetTokenInformation` with the `TokenLinkedToken` parameter to get the non elavated token. Use that token to launch the new process. Obviously also check if UAC is actually enabled (http://www.remkoweijnen.nl/blog/2011/08/11/gettokeninformation-with-tokenlinkedtoken-returns-error-1312/). – Remko Nov 05 '16 at 14:54

1 Answers1

3

I used to solve this puzzle when creating an installer, here's one of the possible solutions.

The idea is to create a background process that runs with user privileges, your main application communicates with it and lets it know what program to start - the started process will have user privileges.

It should work like this: Background process is launched as usual, with User privileges. When launched, it starts your main program as Admin. The background process thn stays hidden and awaits the command from the main process.

djsoft
  • 1,051
  • 8
  • 19
  • This is certainly the easiest method if the only requirement is to launch processes *as the currently logged-in session's user*. Otherwise, the only way to get the token for the logged-in user, without knowing or storing their password, is [via API calls as the local system user](https://msdn.microsoft.com/en-us/library/aa383840(v=vs.85).aspx) (ie: you need to write a system service to call this, then follow up with `ShellExecuteEx`, passing in the user's token). – J... Nov 04 '16 at 14:43
  • @J... Not quite, see the dupe – David Heffernan Nov 04 '16 at 15:00
  • @DavidHeffernan Surprising that this is possible given the cautions for using `WTSQueryUserToken` (and the risks of leaking the token), even from the system context it is restricted to. +1 for that, in any case. – J... Nov 04 '16 at 15:05