How to run a process non-elevated with Delphi2007

流过昼夜 提交于 2019-12-04 12:14:05

问题


I have an installer-like application that I have to run as elevated on Vista. But from there I have to start a new process as non-elevated. Any hints how to do this with Delphi2007?


回答1:


I found an excellent example for C++ and adapted it for Delphi:

unit MediumIL;

interface

uses
  Winapi.Windows;

function CreateProcessMediumIL(lpApplicationName: PWChar; lpCommandLine: PWChar; lpProcessAttributes: PSecurityAttributes; lpThreadAttributes: PSecurityAttributes; bInheritHandle: BOOL; dwCreationFlags: DWORD; lpEnvironment: LPVOID; lpCurrentDirectory: PWChar; const lpStartupInfo: TStartupInfoW; var lpProcessInformation: TProcessInformation): DWORD;

implementation

type
  TOKEN_MANDATORY_LABEL = record
    Label_: SID_AND_ATTRIBUTES;
  end;

  PTOKEN_MANDATORY_LABEL = ^TOKEN_MANDATORY_LABEL;

  TTokenMandatoryLabel = TOKEN_MANDATORY_LABEL;
  PTokenMandatoryLabel = ^TTokenMandatoryLabel;

  TCreateProcessWithTokenW = function (hToken: THandle; dwLogonFlags: DWORD; lpApplicationName: LPCWSTR; lpCommandLine: LPWSTR; dwCreationFlags: DWORD; lpEnvironment: LPVOID; lpCurrentDirectory: LPCWSTR; const lpStartupInfo: TStartupInfoW; out lpProcessInfo: TProcessInformation): BOOL; stdcall;

const
  SECURITY_MANDATORY_UNTRUSTED_RID = $00000000;
  SECURITY_MANDATORY_LOW_RID = $00001000;
  SECURITY_MANDATORY_MEDIUM_RID = $00002000;
  SECURITY_MANDATORY_HIGH_RID = $00003000;
  SECURITY_MANDATORY_SYSTEM_RID = $00004000;
  SECURITY_MANDATORY_PROTECTED_PROCESS_RID = $00005000;

function GetShellWindow: HWND; stdcall; external 'user32.dll' name 'GetShellWindow';

// writes Integration Level of the process with the given ID into dwProcessIL
// returns Win32 API error or 0 if succeeded
function GetProcessIL(dwProcessID: DWORD; var dwProcessIL: DWORD): DWORD;
label
  _CleanUp;
var
  hProcess: THandle;
  hToken: THandle;
  dwSize: DWORD;
  pbCount: PByte;
  pdwProcIL: PDWORD;
  pTIL: PTokenMandatoryLabel;
  dwError: DWORD;
begin
  dwProcessIL := 0;

  pTIL := nil;

  hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, dwProcessID);
  if (hProcess = 0) then
    goto _CleanUp;

  if (not OpenProcessToken(hProcess, TOKEN_QUERY, hToken)) then
    goto _CleanUp;

  if (not GetTokenInformation(hToken, TokenIntegrityLevel, nil, 0, dwSize) and (GetLastError() <> ERROR_INSUFFICIENT_BUFFER)) then
    goto _CleanUp;

  pTIL := HeapAlloc(GetProcessHeap(), 0, dwSize);
  if (pTIL = nil) then
    goto _CleanUp;

  if (not GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwSize, dwSize)) then
    goto _CleanUp;

  pbCount := PByte(GetSidSubAuthorityCount(pTIL^.Label_.Sid));
  if (pbCount = nil) then
    goto _CleanUp;

  pdwProcIL := GetSidSubAuthority(pTIL^.Label_.Sid, pbCount^ - 1);
  if (pdwProcIL = nil) then
    goto _CleanUp;

  dwProcessIL := pdwProcIL^;
  SetLastError(ERROR_SUCCESS);

  _CleanUp:
  dwError := GetLastError();
  if (pTIL <> nil) then
    HeapFree(GetProcessHeap(), 0, pTIL);
  if (hToken <> 0) then
    CloseHandle(hToken);
  if (hProcess <> 0) then
    CloseHandle(hProcess);
  Result := dwError;
end;

// Creates a new process lpApplicationName with the integration level of the Explorer process (MEDIUM IL)
// If you need this function in a service you must replace FindWindow() with another API to find Explorer process
// The parent process of the new process will be svchost.exe if this EXE was run "As Administrator"
// returns Win32 API error or 0 if succeeded
function CreateProcessMediumIL(lpApplicationName: PWChar; lpCommandLine: PWChar; lpProcessAttributes: PSecurityAttributes; lpThreadAttributes: PSecurityAttributes; bInheritHandle: BOOL; dwCreationFlags: DWORD; lpEnvironment: LPVOID; lpCurrentDirectory: PWChar; const lpStartupInfo: TStartupInfoW; var lpProcessInformation: TProcessInformation): DWORD;
label
  _CleanUp;
var
  hProcess: THandle;
  hToken: THandle;
  hToken2: THandle;
  bUseToken: BOOL;
  dwCurIL: DWORD;
  dwErr: DWORD;
  f_CreateProcessWithTokenW: TCreateProcessWithTokenW;
  hProgman: HWND;
  dwExplorerPID: DWORD;
  dwError: DWORD;
begin
  bUseToken := False;

  // Detect Windows Vista, 2008, Windows 7 and higher
  if (GetProcAddress(GetModuleHandleA('Kernel32'), 'GetProductInfo') <> nil) then
  begin
    dwErr := GetProcessIL(GetCurrentProcessId(), dwCurIL);
    if (dwErr <> 0) then
    begin
      Result := dwErr;
      Exit;
    end;
      if (dwCurIL > SECURITY_MANDATORY_MEDIUM_RID) then
        bUseToken := True;
  end;

  // Create the process normally (before Windows Vista or if current process runs with a medium IL)
  if (not bUseToken) then
  begin
    if (not CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandle, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation)) then
    begin
      Result := GetLastError();
      Exit;
    end;

    CloseHandle(lpProcessInformation.hThread);
    CloseHandle(lpProcessInformation.hProcess);
    Result := ERROR_SUCCESS;
    Exit;
  end;

  f_CreateProcessWithTokenW := GetProcAddress(GetModuleHandleA('Advapi32'), 'CreateProcessWithTokenW');

  if (not Assigned(f_CreateProcessWithTokenW)) then // This will never happen on Vista!
  begin
    Result := ERROR_INVALID_FUNCTION;
    Exit;
  end;

  hProgman := GetShellWindow();

  dwExplorerPID := 0;
  GetWindowThreadProcessId(hProgman, dwExplorerPID);

  // ATTENTION:
  // If UAC is turned OFF all processes run with SECURITY_MANDATORY_HIGH_RID, also Explorer!
  // But this does not matter because to start the new process without UAC no elevation is required.
  hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, dwExplorerPID);
  if (hProcess = 0) then
    goto _CleanUp;

  if (not OpenProcessToken(hProcess, TOKEN_DUPLICATE, hToken)) then
    goto _CleanUp;

  if (not DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, hToken2)) then
    goto _CleanUp;

  if (not f_CreateProcessWithTokenW(hToken2, 0, lpApplicationName, lpCommandLine, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation)) then
    goto _CleanUp;

  SetLastError(ERROR_SUCCESS);

  _CleanUp:
  dwError := GetLastError();
  if (hToken <> 0) then
    CloseHandle(hToken);
  if (hToken2 <> 0) then
    CloseHandle(hToken2);
  if (hProcess <> 0) then
    CloseHandle(hProcess);
  CloseHandle(lpProcessInformation.hThread);
  CloseHandle(lpProcessInformation.hProcess);
  Result := dwError;
end;

end.

To use this in your project, simply use unit MediumIL:

uses MediumIL;

…

procedure TForm1.FormCreate(Sender: TObject);
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
  ZeroMemory(@ProcessInfo, SizeOf(ProcessInfo));
  CreateProcessMediumIL('C:\Windows\notepad.exe', nil, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
end;



回答2:


This blog post is detailed and useful

http://developersoven.blogspot.com/2007/02/leveraging-vistas-uac-with-delphi-part.html

The idea is to use your app with low privilege and COM Dll with elevated privilege. Then when you need elevation, you just fire up COM. Full MPLed source link is included in the post.




回答3:


Note sure if this will help but there is a similar questions here its in c#.net but it may give you some clues where to look, or you could try a port to Delphi.

and just a tip try not to have update/install/setup in the application file name as vista will automatically add the security icon to there exe's.




回答4:


You could use the CreateProcessWithLogonW() API call:

function CreateProcessWithLogonW(lpUsername: PWideChar; lpDomain: PWideChar;
  lpPassword: PWideChar; dwLogonFlags: DWORD; lpApplicationName: PWideChar;
  lpCommandLine: PWideChar; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall;
  external 'advapi32.dll' name 'CreateProcessWithLogonW';



procedure RunAs(AUsername, APassword, ADomain, AApplication: string);
const
  LOGON_WITH_PROFILE = $00000001;
var
  si: TStartupInfo;
  pi: TProcessInformation;
begin
  ZeroMemory(@si, SizeOf(si));
  si.cb := SizeOf(si);
  si.dwFlags := STARTF_USESHOWWINDOW;
  si.wShowWindow := SW_NORMAL;
  ZeroMemory(@pi, SizeOf(pi));

  if not CreateProcessWithLogonW(PWideChar(WideString(AUsername)),
    PWideChar(WideString(ADomain)), PWideChar(WideString(APassword)),
    LOGON_WITH_PROFILE, nil, PWideChar(WideString(AApplication)),
    0, nil, nil, si, pi)
  then
    RaiseLastOSError;

  CloseHandle(pi.hThread);
  CloseHandle(pi.hProcess);
end;



回答5:


The following article by Aaron Margosis covers exactly this topic: FAQ: How do I start a program as the desktop user from an elevated app?

The basic idea is to obtain the user token of the shell process, i.e. explorer.exe, make a primary token from that and finally launch the new process with that token.

The article includes some C++ code which should be easy enough to translate to Delphi. It also includes the following itemised list outlining the approach:

  1. Enable the SeIncreaseQuotaPrivilege in your current token
  2. Get an HWND representing the desktop shell (GetShellWindow)
  3. Get the Process ID (PID) of the process associated with that window (GetWindowThreadProcessId)
  4. Open that process (OpenProcess)
  5. Get the access token from that process (OpenProcessToken)
  6. Make a primary token with that token (DuplicateTokenEx)
  7. Start the new process with that primary token (CreateProcessWithTokenW)



回答6:


I'd like to add to Elçins answer above:

If the code line:

  if (not DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, hToken2)) then
goto _CleanUp;

returns with error 5 (Access Denied), then the TOKEN_ALL_ACCESS needs to be ORed with TOKEN_ADJUST_SESSIONID (0x100).

On Delphi 2010, change LPVOID to POINTER.



来源:https://stackoverflow.com/questions/514968/how-to-run-a-process-non-elevated-with-delphi2007

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!