Article

From:
To:
Christian Wimmer
Subject:
Solved: Impersonate user from service
Newsgroup:
embarcadero.public.delphi.nativeapi

Solved: Impersonate user from service

Thanks, it works now.
Here is the full code fromm a test service:

This is what I wanted to do: procedure TWorkerThread.SetControlPanelAccess(Access: Boolean);   const
cExplorerPolicies = 'Software\Microsoft\Windows\CurrentVersion\Policies\Explorer'; // No leading backslash
  begin
    if not xbwImpersonateLoggedOnUser then begin
      { log error }
      exit;
      end;
    if Access then xbwWriteCurrUserRegKey(cExplorerPolicies,'NoControlPanel',0)
              else xbwWriteCurrUserRegKey(cExplorerPolicies,'NoControlPanel',1);
    xbwRevertToSelf;
  end; { SetControlPanelAccess }

With the xbw.... procs in: (the Write2Log calls are just some log calls for testing }
unit xbwLogin; {$WARN SYMBOL_PLATFORM OFF}
(* This is a unit picked from  * http://www.delphifaq.com/faq/delphi_windows_API/f663.shtml  * We only need xbwImpersonateLoggedOnUser and xbwRevertToSelf.  * Other procedures read from an INI file which will not be applicable,  * so before using them, update and test this code (also the defined Consts)  *)
interface
uses   Windows,   TLHelp32,   SysUtils,   Classes;
const   wtsapi = 'wtsapi32.dll';   advapi32 = 'advapi32.dll';   userenvlib = 'userenv.dll';   TOKEN_ADJUST_SESSIONID = $0100;   {$EXTERNALSYM TOKEN_ADJUST_SESSIONID}   SE_DEBUG_NAME = 'SeDebugPrivilege';   {$EXTERNALSYM SE_DEBUG_NAME}
type   _TOKEN_INFORMATION_CLASS = (TokenInfoClassPad0, TokenUser, TokenGroups,   TokenPrivileges, TokenOwner, TokenPrimaryGroup, TokenDefaultDacl, TokenSource,   TokenType, TokenImpersonationLevel, TokenStatistics, TokenRestrictedSids,   TokenSessionId, TokenGroupsAndPrivileges, TokenSessionReference,   TokenSandBoxInert, TokenAuditPolicy, TokenOrigin);
  { kernel32 }   TWTSGetActiveConsoleSessionId = function : DWORD; stdcall;
TProcessIdToSessionId = function (dwProcessId: DWORD; var pSessionId: DWORD): BOOL; stdcall;
  { wtsapi }
TWTSQueryUserToken = function (SessionId: ULONG; var phToken: THANDLE): BOOL; stdcall;
  { advapi32 }
TSetTokenInformation = function (TokenHandle: THANDLE; TokenInformationClass: _TOKEN_INFORMATION_CLASS; TokenInformation: Pointer; TokenInformationLength: DWORD): BOOL; stdcall; TAdjustTokenPrivileges = function (TokenHandle: THANDLE; DisableAllPrivileges: BOOL;
                                     NewState: Pointer; BufferLength: DWORD;
PreviousState: Pointer; ReturnLength: LPDWORD): BOOL; stdcall; TRegOpenCurrentUser = function(samDesired: REGSAM; var phkResult: HKEY): DWORD; stdcall;

  { userenvlib }
TCreateEnvironmentBlock = function (lpEnvironment: Pointer; hToken: THANDLE; bInherit: BOOL): BOOL; stdcall;

var
  WTSGetActiveConsoleSessionId : TWTSGetActiveConsoleSessionId=nil;
  ProcessIdToSessionId         : TProcessIdToSessionId=nil;
  WTSQueryUserToken            : TWTSQueryUserToken=nil;
  SetTokenInformation          : TSetTokenInformation=nil;
  AdjustTokenPrivileges        : TAdjustTokenPrivileges=nil;
  CreateEnvironmentBlock       : TCreateEnvironmentBlock=nil;
  RegOpenCurrentUser           : TRegOpenCurrentUser=nil;

function xbwSessionNumber: Integer; function xbwActiveConsole: Integer; function xbwGetProcessID(strProcess: String; iSessionID: Integer = -1): DWORD;
procedure xbwExecProcess(strParameters: String; strConfig: String = ''); { <<< starts command line under user process } function xbwStartProcess(strProcess: String; bLocalSystem: Boolean = True; iSessionID: Integer = -1): Boolean;
function xbwImpersonateLoggedOnUser: Boolean;
procedure xbwRevertToSelf;
procedure xbwWriteCurrUserRegKey(NameOfKey,StringName: PChar; IntValue: DWORD);

implementation
uses IniFiles, SHFolder, Forms,
uviselog;
const   STR_PRODUCT = 'Vise';   INI_SESSION = 'Sessie';   INI_EXEC = 'Execute';   INI_RESULT = 'Result';
var   LibsLoaded : integer=0;   FhUserTokenDup: THandle; { used for user impersonation }
function GetProcedureAddress(var P: Pointer; const ModuleName, ProcName: string): boolean;
var ModuleHandle: HMODULE;
begin
  if not Assigned(P) then begin
    ModuleHandle := GetModuleHandle(PChar(ModuleName));
    if ModuleHandle = 0 then
      ModuleHandle := LoadLibrary(PChar(ModuleName));
    if ModuleHandle <> 0 then
      P := Pointer(GetProcAddress(ModuleHandle, PChar(ProcName)));
    Result := Assigned(P);
    end
  else
    Result := True;
end; { GetProcedureAddress }

function InitProcLibs: boolean; begin   if LibsLoaded>0 then     Result := True   else if LibsLoaded<0 then     Result := False   else begin     LibsLoaded := -1;
if GetProcedureAddress(@WTSGetActiveConsoleSessionId, kernel32, 'WTSGetActiveConsoleSessionId') and GetProcedureAddress(@ProcessIdToSessionId, kernel32, 'ProcessIdToSessionId') and
       GetProcedureAddress(@WTSQueryUserToken, wtsapi, 'WTSQueryUserToken') and
GetProcedureAddress(@SetTokenInformation, advapi32, 'SetTokenInformation') and GetProcedureAddress(@AdjustTokenPrivileges, advapi32, 'AdjustTokenPrivileges') and
    GetProcedureAddress(@RegOpenCurrentUser, advapi32, 'RegOpenCurrentUser') and
GetProcedureAddress(@CreateEnvironmentBlock, userenvlib, 'CreateEnvironmentBlock') then
       LibsLoaded := 1;
    Result := ( LibsLoaded=1);
  end;
end;  { InitProcLibs }

function xbwSessionNumber: Integer; var dwSessionID: DWord; begin   Result := 0;   if not InitProcLibs then Exit;   ProcessIdToSessionId(GetCurrentProcessId(), dwSessionID);   Result := dwSessionID; end; { xbwSessionNumber }
function xbwActiveConsole: Integer; begin   Result := 0;   if not InitProcLibs then Exit;   Result := WTSGetActiveConsoleSessionId; end;
function xbwGetProcessID(strProcess: String; iSessionID: Integer = -1): DWORD; (* Returns the process ID of process StrProcess in session iSessionID.  * If iSessionID is omitted, the active console session. *) var   dwSessionId,   winlogonSessId: DWord;   hsnap : THandle;   procEntry : TProcessEntry32;   myPID : Cardinal; begin   Result := 0;   if not InitProcLibs then Exit;   if iSessionID = -1 then
// Get Remote Desktop Services session that is currently attached to the physical console. The physical console is the monitor, keyboard, and mouse.
    dwSessionId := WTSGetActiveConsoleSessionId
  else
    dwSessionId := iSessionID;
  // Check running processes and return ID of process in current session
  hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnap = INVALID_HANDLE_VALUE) then Exit;
  strProcess := UpperCase(ExtractFileName(strProcess));
  myPID := GetCurrentProcessId;
  procEntry.dwSize := sizeof(TProcessEntry32);
  if (not Process32First(hSnap, procEntry)) then Exit;
  repeat
if (procEntry.th32ProcessID<>myPID) and ((UpperCase(procEntry.szExeFile) = strProcess) or (UpperCase(ExtractFileName(procEntry.szExeFile)) = strProcess)) then begin
      winlogonSessId := 0;
if (ProcessIdToSessionId(procEntry.th32ProcessID, winlogonSessId) and (winlogonSessId = dwSessionId)) then begin
        Result := procEntry.th32ProcessID;
        break;
        end;
      end;
  until (not Process32Next(hSnap, procEntry));
end; { xbwGetProcessID }

procedure xbwExecProcess(strParameters: String; strConfig: String = '');
  function GetSpecialPath(csidl: Integer): String;   var i: Integer;   begin     SetLength(Result, MAX_PATH);     SHGetFolderPath(0, csidl or CSIDL_FLAG_CREATE, 0, 0, PChar(Result));     i := Pos(#0, Result);     if i > 0 then SetLength(Result, Pred(i));   end; { GetSpecialPath }
var   strCommonAppData,   strResult : String;   iLoop : Integer;   TS : TStringList; begin { Main xbwExecProcess }   { Execute command using lower process executable... }
strCommonAppData := IncludeTrailingBackslash( GetSpecialPath(CSIDL_COMMON_APPDATA)) + STR_PRODUCT + '\';
  with TIniFile.Create(strCommonAppData + INI_SESSION) do
    try
      WriteString(STR_PRODUCT, INI_EXEC, strParameters);
      { Collect config into INI transfer... }
      if strConfig <> '' then begin
      strConfig := StringReplace(strConfig, #13, '&RC&', [rfReplaceAll]);
      strConfig := StringReplace(strConfig, ',', '&CM&', [rfReplaceAll]);
      TS := TStringList.Create;
      with TS do
        try
          Delimiter := '|';
          DelimitedText := strConfig;
          for iLoop := 0 to Count -1 do
          WriteString(STR_PRODUCT, Names[iLoop], Strings[iLoop]);
        finally
          Free;
        end;
      end;
      { Start process as standard user... }
xbwStartProcess(ExtractFilePath(Application.ExeName) + 'CMCProcess.exe', False, xbwSessionNumber);
      Sleep(1000);
      { Check result... }
      strResult := ReadString(STR_PRODUCT, INI_RESULT, '');
      if strResult <> '' then
        raise Exception.Create(strResult);
    finally
      Free;
    end;
end; { xbwExecProcess }

function xbwStartProcess(strProcess: String; bLocalSystem: Boolean = True; iSessionID: Integer = -1): Boolean;
var
  pi             : PROCESS_INFORMATION;
  si             : STARTUPINFO;
  winlogonPid,
  dwSessionId    : DWord;
  hUserToken,
  hUserTokenDup,
  hPToken,
  hProcess       : THANDLE;
  dwCreationFlags: DWORD;
  tp             : TOKEN_PRIVILEGES;
  lpenv          : pointer;
  bError         : Boolean;
  strClone       : String;
begin
{ start process as elevated by cloning existing process, as we're running as admin... }
  Result := True;
  bError := False;
  if not InitProcLibs then Exit;
if bLocalSystem then strClone := 'winlogon.exe' else strClone := 'explorer.exe';
  winlogonPid := xbwGetProcessID(strClone, iSessionID);
  try
{ Get user token for winlogon and duplicate it... (this gives us admin rights) }
    dwSessionId := WTSGetActiveConsoleSessionId;
    WTSQueryUserToken(dwSessionId, hUserToken);
    dwCreationFlags := NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE;
    ZeroMemory(@si, sizeof(STARTUPINFO));
    si.cb := sizeof(STARTUPINFO);
    si.lpDesktop := PChar('Winsta0\Default');
    ZeroMemory(@pi, sizeof(pi));
    hProcess := OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid);
if (not OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY or TOKEN_DUPLICATE or TOKEN_ASSIGN_PRIMARY or TOKEN_ADJUST_SESSIONID or TOKEN_READ or TOKEN_WRITE, hPToken)) then
      bError := True;
if (not LookupPrivilegeValue(nil, SE_DEBUG_NAME, tp.Privileges[0].Luid)) then
      bError := True;
    tp.PrivilegeCount := 1;
    tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, Nil, SecurityIdentification, TokenPrimary, hUserTokenDup);
    { adjust token privilege }
SetTokenInformation(hUserTokenDup, TokenSessionId, pointer(dwSessionId), sizeof(DWORD)); if (not AdjustTokenPrivileges(hUserTokenDup, FALSE, @tp, sizeof(TOKEN_PRIVILEGES), nil, nil)) then bError := True;
    if (GetLastError() = ERROR_NOT_ALL_ASSIGNED) then bError := True;
    lpEnv := nil;
    if (CreateEnvironmentBlock(lpEnv, hUserTokenDup,TRUE)) then
      dwCreationFlags := dwCreationFlags or CREATE_UNICODE_ENVIRONMENT
    else
      lpEnv := nil;
    { Launch the process in the client's logon session... }
if not CreateProcessAsUser(hUserTokenDup, nil, PChar(strProcess), nil, nil, FALSE, dwCreationFlags, lpEnv, PChar(ExtractFilePath(strProcess)), si, pi) then bError := True;
    { perform all the close handles tasks... }
    try
      CloseHandle(hProcess);
      CloseHandle(hUserToken);
      CloseHandle(hUserTokenDup);
      CloseHandle(hPToken);
    except
      {}
    end;
  except
    bError := True;
  end;
  Result := not bError;
end; { xbwStartProcess }

function GLE: String;   var LE: Integer;   begin     LE := GetlastError;     Result := '(' + InttoStr(LE) + ') ' + SysErrorMessage(LE);   end;
function xbwImpersonateLoggedOnUser: Boolean; var   winlogonPid,   dwSessionId: DWord;   hUserToken,   hPToken,   hProcess : THANDLE;   tp : TOKEN_PRIVILEGES;   bError : Boolean;   strClone : String; begin
{ Start process as elevated by cloning existing process, as we're running as admin... }
  Result := True;
  bError := False;
  if not InitProcLibs then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser InitProcLibs failed');
    Exit;
    end;
  // Get explorer.exe process ID:
  strClone := 'explorer.exe';
  winlogonPid := xbwGetProcessID(strClone);
Write2Log(AppDirFromReg + 'DoTheWork.log','winLogonPiD: ' + inttostr(winlogonpid));
  try
    // Get user token for winlogon:
    dwSessionId := WTSGetActiveConsoleSessionId;
Write2Log(AppDirFromReg + 'DoTheWork.log','dwSessionId: ' + inttostr(dwSessionId)); // Get the primary access token of the logged-on user specified by the session ID
    if not WTSQueryUserToken(dwSessionId, hUserToken) then
      begin
Write2Log(AppDirFromReg + 'DoTheWork.log','WTSQueryUserToken error: ' + GLE);
      bError := True;
      end;
Write2Log(AppDirFromReg + 'DoTheWork.log','hUserToken: ' + inttostr(hUserToken));
    // Open the existing local process object identified by winlogonPid     hProcess := OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid);
Write2Log(AppDirFromReg + 'DoTheWork.log','hProcess: ' + inttostr(hProcess));
    // Open the access token associated with a process, result is put in hPToken
if (not OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY or TOKEN_DUPLICATE or TOKEN_ASSIGN_PRIMARY or TOKEN_ADJUST_SESSIONID or TOKEN_READ or TOKEN_WRITE, hPToken)) then
      begin
Write2Log(AppDirFromReg + 'DoTheWork.log','OpenProcessToken error: ' + GLE);
      bError := True;
      end;
// Retrieve the locally unique identifier (LUID) used on a specified system to locally represent the specified privilege name.
    // Put it in the TOKEN_PRIVILEGES structure tp
if (not LookupPrivilegeValue(nil, SE_DEBUG_NAME, tp.Privileges[0].Luid)) then
      begin
Write2Log(AppDirFromReg + 'DoTheWork.log','LookupPrivilegeValue error: ' + GLE);
      bError := True;
      end;
    // Set other tp params:
    tp.PrivilegeCount := 1;
    tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
// Create a new access token FhUserTokenDup that duplicates the existing token hPToken
    if not DuplicateTokenEx(hPToken,
MAXIMUM_ALLOWED, // Request all access rights that are valid for the caller Nil, // Token gets a default security descriptor SecurityIdentification, // Value from the SECURITY_IMPERSONATION_LEVEL enumeration that indicates the impersonation level of the new token // http://msdn.microsoft.com/en-us/library/aa379572(VS.85).aspx TokenPrimary, // Value from the TOKEN_TYPE enumeration
                            FhUserTokenDup) then
      begin
Write2Log(AppDirFromReg + 'DoTheWork.log','DuplicateTokenEx error: ' + GLE);
      bError := True;
      end;
// Overwrite information for the FhUserTokenDup token (Adjust token privilege):
    if not SetTokenInformation(FhUserTokenDup,
TokenSessionId, // TOKEN_INFORMATION_CLASS enumeration type contains values that specify the type of information being assigned // TokenSessionId = You will receive a DWORD value that indicates the Terminal Services session identifier that is associated with the token
                               @dwSessionId,                                sizeof(DWORD)) then
      begin
      { ERROR 998 Invalid access to memory location. }
Write2Log(AppDirFromReg + 'DoTheWork.log','SetTokenInformation error: ' + GLE);
      bError := True;
      end;
// Now adjust the priviliges of the duplicated token with the settings in tp: if (not AdjustTokenPrivileges(FhUserTokenDup, FALSE, @tp, sizeof(TOKEN_PRIVILEGES), nil, nil)) then
      begin
Write2Log(AppDirFromReg + 'DoTheWork.log','AdjustTokenPrivileges error: ' + GLE);
      bError := True;
      end;
    if (GetLastError() = ERROR_NOT_ALL_ASSIGNED) then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser error ERROR_NOT_ALL_ASSIGNED');
      bError := True;
      end;
    // Finally, do the impersonation:
    if not ImpersonateLoggedOnUser(FhUserTokenDup) then
      begin
Write2Log(AppDirFromReg + 'DoTheWork.log','ImpersonateLoggedOnUser error: ' + GLE);
      bError := True;
      end;

    { perform all the close handles tasks... }     try       CloseHandle(hProcess);       CloseHandle(hUserToken);
// CloseHandle(FhUserTokenDup); { This is closed later in xbwRevertToSelf }
      CloseHandle(hPToken);
    except
      on E:Exception do
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser error (1): ' + E.Message);
    end;
  except
    on E:Exception do begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwImpersonateLoggedOnUser error (2): ' + E.Message);
      bError := True;
    end;
  end;
  Result := not bError;
end; { xbwImpersonateLoggedOnUser }

procedure xbwRevertToSelf; begin   RevertToSelf;   CloseHandle(FhUserTokenDup); end; { xbwRevertToSelf }
procedure xbwWriteCurrUserRegKey(NameOfKey,StringName: PChar; IntValue: DWORD); (* We must use RegOpenCurrentUser instead of RegOpenKey(Ex) (TRegistry).  * These functions don't work with impersonation:  * http://msdn.microsoft.com/en-us/library/ms685145(VS.85).aspx *)   var     phkRoot,     phkKey : HKEY;     Res : Integer;   begin     Res := RegOpenCurrentUser(KEY_ALL_ACCESS,phkRoot);     If Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegOpenCurrentUser error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
      Exit;
      end;
    Res := RegOpenKeyEx(phkRoot,NameOfKey,0,KEY_ALL_ACCESS,phkKey);
    if Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegOpenKeyEx error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
      Exit;
      end;
    Res := RegSetValueEx(phkKey,StringName,0,REG_DWORD,@IntValue,SizeOf(DWORD));
    if Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegSetValueEx error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
      Exit;
      end;
    Res := RegCloseKey(phkKey);
    if Res <> ERROR_SUCCESS then begin
Write2Log(AppDirFromReg + 'DoTheWork.log','xbwWriteCurrUserRegKey/RegCloseKey error: (' + IntToStr(Res) + '): ' + SysErrorMessage(Res));
      Exit;
      end;
  end; { xbwWriteCurrUserRegKey }

end.
FYI: Phrase searches are enclosed in either single or double quotes
 
 
Originally created by
Tamarack Associates
Mon, 20 May 2024 01:24:07 UTC
Copyright © 2009-2024
HREF Tools Corp.