Jump to content
Mustafa E. Korkmaz

Run as admin on unauthorized Windows username

Recommended Posts

I log in to Windows 11 with a username that does not have admin privileges.
My program knows the admin username and password.
When a button is pressed in the program, I want it to work again with admin authority.
The person using the program should not be asked for the admin username and password.
How can I do it?

 

function RunAsAdmin(const Path, Params: string): Boolean;
var
  sei: TShellExecuteInfo;
begin
  try
    FillChar(sei, SizeOf(sei), 0);
    sei.cbSize := SizeOf(sei);
    sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
    sei.lpVerb := PChar('runas');
    sei.lpFile := PChar(ExtractFileName(Path));
    sei.lpDirectory := PChar(ExtractFilePath(Path));
    sei.lpParameters := PChar(Params);
    sei.nShow := SW_SHOWNORMAL;
    Result := ShellExecuteEx(@sei);
  except
    result := false;
  end;
end;

 

procedure TForm1.Button1Click(Sender: TObject);
begin
  RunAsAdmin( application.ExeName, '' );
end;

 

When I use the ShellExecuteEx function Windows asks for the admin username and password. When these are entered, the program runs with admin authority.

But the program already knows the admin username and password. I want it to run with admin permission without having to enter it again.

 

function RunAs2( User, Password, commandline: String ) : integer;
var
   StrInf : TStartupInfo;
   PrcInf : TProcessInformation;
begin
   result := 1;

   try
     FillChar(StrInf,SizeOf(TStartupInfo),0);
     FillChar(PrcInf,SizeOf(TProcessInformation),0);
     StrInf.cb := SizeOf(TStartupInfo);
     StrInf.wShowWindow := SW_SHOWNORMAL;

     if CreateProcessWithLogon( PWideChar( WideString( User ) ),
                                 nil,
                                 PWideChar( WideString( Password ) ),
                                 1,
                                 nil,
                                 PWideChar( WideString( commandline ) ),
                                 CREATE_UNICODE_ENVIRONMENT,//0,
                                 nil,
                                 nil,
                                 StrInf,
                                 PrcInf ) then result := 0;

     if Result = 0 then
     begin
        CloseHandle(PrcInf.hProcess);
        CloseHandle(PrcInf.hThread);
     end
     else
     result := GetLastError;
   except
   end;
end;

 

procedure TForm1.Button1Click(Sender: TObject);
begin
 Showmessage( inttostr( RunAs2( 'User', 'test123', application.ExeName ) ) )
end;


When I use the CreateProcessWithLogon function the following error is received:   5 ---> access denied


For example, after connecting to the unauthorized computer remotely in one of the remote desktop applications, you send the remote admin username and password from the actions menu.
The UAC dialog appears on the remote computer. If the user selects Yes, the application runs again with admin authority without being asked for the admin username and password.

 

 

 

Edited by Mustafa E. Korkmaz

Share this post


Link to post

I have this code which could help you :

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Author:       François PIETTE @ OverByte
Creation:     July 22, 2021
Description:  Classes to act (File access or other) using another user account.
License:      This program is published under MOZILLA PUBLIC LICENSE V2.0;
              you may not use this file except in compliance with the License.
              You may obtain a copy of the License at
              https://www.mozilla.org/en-US/MPL/2.0/
Version:      1.0
History:
Jul 22, 2021  1.00 F. Piette Initial release


 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
unit ImpersonateUser;

interface

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

const
    LOGON32_LOGON_NEW_CREDENTIALS  = 9;    // Missing in Delphi 10.4.2

type
    TImpersonateUser = class(TComponent)
    protected
        FUserToken : THandle;
        FErrorCode : DWORD;
    public
        destructor Destroy; override;
        function  Logon(const UserName : String;
                        const Domain   : String;
                        const Password : String) : Boolean;
        procedure Logoff();
        property ErrorCode : DWORD read FErrorCode;
    end;

implementation

{ TImpersonateUser }

destructor TImpersonateUser.Destroy;
begin
    if FUserToken <> 0 then begin
        CloseHandle(FUserToken);
        FUserToken := 0;
    end;
    inherited Destroy;
end;

procedure TImpersonateUser.Logoff;
begin
    if FUserToken <> 0 then begin
        RevertToSelf();   // Revert to our user
        CloseHandle(FUserToken);
        FUserToken := 0;
    end;
end;

function TImpersonateUser.Logon(
    const UserName : String;
    const Domain   : String;
    const Password : String): Boolean;
var
    LoggedOn : Boolean;
begin
    Result := FALSE;
    if FUserToken <> 0 then
        Logoff();

    if UserName = '' then begin // Must at least provide a user name
        FErrorCode := ERROR_BAD_ARGUMENTS;
        Exit;
    end;

    if Domain <> '' then
        LoggedOn := LogonUser(PChar(UserName),
                              PChar(Domain),
                              PChar(Password),
                              LOGON32_LOGON_INTERACTIVE,
                              LOGON32_PROVIDER_DEFAULT,
                              FUserToken)
    else
        LoggedOn := LogonUser(PChar(UserName),
                              PChar(Domain),
                              PChar(Password),
                              LOGON32_LOGON_NEW_CREDENTIALS,
                              LOGON32_PROVIDER_WINNT50,
                              FUserToken);
    if not LoggedOn then begin
        FErrorCode := GetLastError();
        Exit;
    end;

    if not ImpersonateLoggedOnUser(FUserToken) then begin
        FErrorCode := GetLastError();
        Exit;
    end;

    FErrorCode := ERROR_SUCCESS;
    Result     := TRUE;
end;

end.

Once your code has called Logon(), it act a the user specified by the credentials. When finished, call Logoff().

Share this post


Link to post

I tested the component you sent as follows. It still gave error number 5. The logon function is successful but I get an error in the next step.

 

function RunAs(User, Password, commandline: String; hToken:THandle): Integer;
var  dwSize:        DWORD;
     lpvEnv:        Pointer;
     pi:            TProcessInformation;
     si:            TStartupInfo;
     szPath:        Array [0..MAX_PATH] of WideChar;
begin

  try
    ZeroMemory(@szPath, SizeOf(szPath));
    ZeroMemory(@pi, SizeOf(pi));
    ZeroMemory(@si, SizeOf(si));
    si.cb:=SizeOf(TStartupInfo);

       try
         if CreateEnvironmentBlock(lpvEnv, hToken, True) then
         begin
            try
              dwSize:=SizeOf(szPath) div SizeOf(WCHAR);
              if (GetCurrentDirectoryW(dwSize, @szPath) > 0) then
              begin
                 if (CreateProcessWithLogon(PWideChar(WideString(User)), nil, PWideChar(WideString(Password)),
                     LOGON_WITH_PROFILE, nil, PWideChar(WideString(commandline)), CREATE_UNICODE_ENVIRONMENT,
                     lpvEnv, szPath, si, pi)) then
                 begin
                    result:=ERROR_SUCCESS;
                    CloseHandle(pi.hProcess);
                    CloseHandle(pi.hThread);
                 end
                 else
                 result:=GetLastError;
              end
              else
              result:=GetLastError;
            finally
              DestroyEnvironmentBlock(lpvEnv);
            end;
         end
         else
          result:=GetLastError;
       finally
         CloseHandle(hToken);
       end;

  except
   result := 1;
  end;
end;

 

procedure TForm1.FormCreate(Sender: TObject);
begin
 ImpersonateUser := TImpersonateUser.Create( self );
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
 if ImpersonateUser.Logon( 'User', '.', 'test123' ) then
 begin
   Showmessage( inttostr( RunAs( 'User', 'test123', application.ExeName, ImpersonateUser.FUserToken ) ) );  //------> 5
 end;
end;
 

 

Edited by Mustafa E. Korkmaz

Share this post


Link to post

Why you use '.' for domain in the LogOn function?

 

In the RunAs function you should use CreateProcessAsUser() API, not the CreateProcessWithLogon() .... you don't need to pass the credentials to RunAs, only the token.

Edited by DelphiUdIT
  • Like 1

Share this post


Link to post

CreateProcessAsUser gives this error: 1314  "A required privilige is not held by the client"

 

function RunAsXX(commandline: String; hToken:THandle): Integer;
var  dwSize:        DWORD;
     lpvEnv:        Pointer;
     pi:            TProcessInformation;
     si:            TStartupInfo;
     szPath:        Array [0..MAX_PATH] of WideChar;
begin

  try
    ZeroMemory(@szPath, SizeOf(szPath));
    ZeroMemory(@pi, SizeOf(pi));
    ZeroMemory(@si, SizeOf(si));
    si.cb:=SizeOf(TStartupInfo);

       try
         if CreateEnvironmentBlock(lpvEnv, hToken, True) then
         begin
            try
              dwSize:=SizeOf(szPath) div SizeOf(WCHAR);
              if (GetCurrentDirectoryW(dwSize, @szPath) > 0) then
              begin
                 if CreateProcessAsUser(hToken, nil, PWideChar(WideString(commandline)), nil, nil, FALSE, CREATE_UNICODE_ENVIRONMENT, lpvEnv, nil, si, pi) then
                 begin
                    result:=ERROR_SUCCESS;
                    CloseHandle(pi.hProcess);
                    CloseHandle(pi.hThread);
                 end
                 else
                 result:=GetLastError;
              end
              else
              result:=GetLastError;
            finally
              DestroyEnvironmentBlock(lpvEnv);
            end;
         end
         else
          result:=GetLastError;
       finally
         CloseHandle(hToken);
       end;

  except
   result := 1;
  end;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
 if ImpersonateUser.Logon( 'User', '', 'test123' ) then
 begin
   Showmessage( inttostr( RunAsXX( application.ExeName, ImpersonateUser.FUserToken ) ) );
 end;
end;

 

Edited by Mustafa E. Korkmaz

Share this post


Link to post
4 hours ago, Mustafa E. Korkmaz said:

I tested the component you sent as follows. It still gave error number 5. The logon function is successful but I get an error in the next step.

I the next step your app *has* the administrator privileges. Just run the external process as any user using only CreateProcess().

  • Like 1

Share this post


Link to post

Running a process as an (impersonated) admin user, and running a process in an elevated state, are two different things.  Being an admin user does not imply automatic elevation, but an elevation prompt does require an admin user.

 

In any case, perhaps have a look at the CreateProcessWithLogonElevatedW() and CreateProcessWithTokenElevatedW() functions provided in the Elevate DLL of this old CodeProject article: Vista UAC: The Definitive Guide (I think the site is down undergoing a redesign at the moment, though. Maybe you can find another copy of the DLL elsewhere).

 

Edited by Remy Lebeau

Share this post


Link to post
17 hours ago, Mustafa E. Korkmaz said:

CreateProcessAsUser gives this error: 1314  "A required privilige is not held by the client"

As Francois pointed, your user itself doesn't privilege to execute such operation, which is in this case CreateProcess(xx) with different token on different station, not %100 sure but i think your user should be have SeAssignPrimaryTokenPrivilege

 

https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/user-rights-assignment

https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/replace-a-process-level-token

 

Share this post


Link to post

Thanks for all the advice.
I couldn't find anything about The Elevate package (Elevate_BinariesAndDocs.zip)
I couldn't find it but even if I found these, users do not want Elevate.dll and Elevate.exe to run separately outside the normal application.
The interesting thing is that the Anydesk application can do this with a single exe, without requiring installation or making any settings on the computer.

At first glance it doesn't seem like a difficult subject.

Share this post


Link to post
2 hours ago, Mustafa E. Korkmaz said:

Thanks for all the advice.
I couldn't find anything about The Elevate package (Elevate_BinariesAndDocs.zip)
I couldn't find it but even if I found these, users do not want Elevate.dll and Elevate.exe to run separately outside the normal application.
The interesting thing is that the Anydesk application can do this with a single exe, without requiring installation or making any settings on the computer.

At first glance it doesn't seem like a difficult subject.

Not sure what are you talking about ?! Elevate package and zip file !

 

You don't need to rerun your process with higher (elevated) privileges, you can elevate or lets say enable a specific privilege for the current process (or token in general, which is most the time is a handle) by using AdjustTokenPrivileges 

https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges

 

You can use something like this 

function EnablePrivilege(const PrivilegeName: string): Boolean;
var
  TokenHandle: THandle;
  NewState: TOKEN_PRIVILEGES;
  ReturnLength: DWORD;
begin
  Result := False;
  if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then
  try
    NewState.PrivilegeCount := 1;
    NewState.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
    if LookupPrivilegeValue(nil, PChar(PrivilegeName), NewState.Privileges[0].Luid) then
    begin
      if AdjustTokenPrivileges(TokenHandle, False, NewState, SizeOf(NewState), nil, ReturnLength) then
        Result := GetLastError <> ERROR_NOT_ALL_ASSIGNED;
    end;
  finally
    CloseHandle(TokenHandle);
  end;
end;

..
const
  SE_ASSIGN_PRIMARY_TOKEN_NAME = 'SeAssignPrimaryTokenPrivilege';
..
// call it with something like this
    if EnablePrivilege(SE_ASSIGN_PRIMARY_TOKEN_NAME) then
    begin
    ..

Just remember to check for elevated process before calling as it will fail, in other words if you are not administrator will not be able to enable SeAssignPrimaryTokenPrivilege hence your process will most likely will be able to run remove process with login etc..

If your account doesn't have TOKEN_ADJUST_PRIVILEGES enabled which is enabled for Administrator ... users then you can't adjust for the current process.

 

Also i said i am not sure if this will solve your problem in full or it will be enough, as there is different causes might prevent such execution, also there is policies should be enabled allowing the such run for a remote process.

 

in all cases run your application or test one, then use Process Explorer, you can see in the Security tab for that application like this screenshot 

image.thumb.png.1dcfbc6a35ae41825e07fb0104e3948b.png

I put a break point using the debugger and confirmed that the SeAssignPrimaryTokenPrivilege privilege is enabled or elevated for this particular privilege, one thing though Process explorer tab need to be closed and reopened to refresh these information.

Share this post


Link to post

The Elevate package is mentioned in the link Remy sent.

 

The program is run normally under a non-admin user name.
As far as I know If the program is not run with admin rights, we cannot use the AdjustTokenPrivileges function for SE_ASSIGN_PRIMARY_TOKEN_NAME.

Share this post


Link to post
28 minutes ago, Mustafa E. Korkmaz said:

The Elevate package is mentioned in the link Remy sent.

 

The program is run normally under a non-admin user name.
As far as I know If the program is not run with admin rights, we cannot use the AdjustTokenPrivileges function for SE_ASSIGN_PRIMARY_TOKEN_NAME.

 

The User token must exist else you cannot enable it.

 

Also Impersonations work on a thread basis, it may not be possible to use the Main Thread for this!

Quote

The ImpersonateLoggedOnUser function lets the calling thread impersonate the security context of a logged-on user. The user is represented by a token handle.

It may be worth your while to supply us with Process Explorer details as Kas has.

Share this post


Link to post

I found a not bad solution.


If the exe does not have admin permissions, CreateProcessAsUser does not work at all.

 

If CreateProcessWithLogonW is used directly, the exe does not need to have admin privileges.

 

When using CreateProcessWithLogonW, the exe to be run must be in a common directory. Otherwise, it gives an access denied error.
In other words, it must be in a directory that both the unauthorized user and the admin user can access.
It can be the C:\Users\Public\ directory.

 

When the button is pressed while the Delphi application is on the desktop of the unauthorized user, it can copy the exe file to the C:\Users\Public\ directory and run it directly as admin with CreateProcessWithLogonW.

 

function RunAsAdminWithPassword( UserName, Password, Domain, AppPath: WideString)  :integer;
var
  SI: TStartupInfoW;
  PI: TProcessInformation;
begin
  result := 0;

  ZeroMemory(@SI, SizeOf(SI));
  SI.cb := SizeOf(SI);

  if CreateProcessWithLogon(
        PWideChar(UserName),
        PWideChar(Domain),
        PWideChar(Password),
        LOGON_WITH_PROFILE,
        nil,
        PWideChar(AppPath),
        CREATE_NEW_CONSOLE,
        nil,
        nil,
        SI,
        PI) then
  begin
    CloseHandle(PI.hProcess);
    CloseHandle(PI.hThread);
  end
  else
    result := GetLastError;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  copyExetoPublic();
  RunAsAdminWithPassword( 'User', 'test123', '.', 'C:\Users\Public\' + ExtractFileName( application.ExeName ) );
end;
 

 

Edited by Mustafa E. Korkmaz

Share this post


Link to post
6 hours ago, Mustafa E. Korkmaz said:

RunAsAdminWithPassword

To test this out I used an app which requires elevation.

The error below seems like an awkward way to handle this..

 

This Errors with "The requested operation requires elevation" when executed from a non elevated process.

More info here: https://stackoverflow.com/questions/4713196/createprocesswithlogon-error-requires-elevation

 

 

 

 

Edited by FredS

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×