Impersonating a User on Windows NT is a three step process (Views: 300)
Problem/Question/Abstract: How do I simulate the unix SU command under windows NT. In other words I want to run an app under a different user... Answer: This code was not written by me. The original copyright information is still intact. {* SU.DPR for Delphi32 Pascal by Fred - APIKing - de Jong, Heerlen, Netherlands 1997 home: frejon@worldonline.nl, office: fjng@cbs.nl su.cpp UNIX-like Substitute User for Windows NT Usage: su [NewDomain\][NewUser] [command-line] where: NewDomain\ is desired domain logon (\\ is ok also) NewUser is the name of the user to be impersonated. Default is Administrator. command-line is the command to be executed, with parameters. Default is CMD (Console) Authors: David Wihl (wihl@shore.net) Steffen Krause (skrause@informatik.hu-berlin.de) Revision History: xx-JUL-1995. - Removed restriction on command line (User can now specify anything) - Added NewDomain logon on command line - Added Unicode support but found bug in LogonUserW 03-JUL-1995. Initial public release Design: Impersonating a User on Windows NT is a three step process: 1- Logon the User to create a Security identifier 2- Enabling access to the Windows Station so the newly logged on NewUser can interact. This is necessary even if the Administrator is logging on. 3- Creating a process using the Security identifier Different privileges are required for steps (1) and (3). Logging on a User (LogonUser()) requires the SeTcbPrivilege. Creating a process as another User CreateProcessAsUser()) requires SeAssignPrimary and SeIncreaseQuota privileges. To grant these privileges, see the Installation Section. These two Security API calls were only stablized in NT 3.51, build 1057. SU will not work with earlier versions. In NT, there is no direct equivalent of UNIX's rwsr-xr-x file permission. Restrictions and Limitations: - There is no logging of failed or successful usage. A future may incorporate writing to the Event Log. Installation: The easiest way to selectively grant the three privileges required to use this program is: 1- Start the User Manager (MUSRMGR) 2- Create a new group (e.g. "SU Users") 3- Add the three privileges to the group (via Policies\User Rights): "Act as part of the operating system" - SeTcbPrivilege "Increase quotas" - SeIncreaseQuota "Replace a process level token" - SeAssignPrimaryToken NOTE: The three privileges will only be visible if you check "Show Advanced User Rights" in the dialog box. 4- Add the desired users to the new group (via User\Properties\Group) This program was compiled under Visual C++ 2.1 with the June '95 SDK For more information about Porting from UNIX to NT check the FAQ: http://www.shore.net/~wihl/unix2nt.html *} program su; {$APPTYPE CONSOLE} uses Windows, SysUtils { already has SysErrorMessage function }; //{$R VersInfo.RES} // // CUSTOMIZATION OPTIONS - put 'em here const DEFAULT_USER: string = 'Administrator'; // if we don't specify a username, who are we? DEFAULT_CMD: string = 'cmd'; // if we don't specify a command, what do we do? {$DEFINE VERBOSE} // quiet ?la UNIX, or chatty? // // END CUSTOMIZATION OPTIONS // const SECURITY_DESCRIPTOR_REVISION = 1; // from winnt.h, missing in windows.pas //////////////////////////////////////////////////////////////////////// // // // NT Defined Privileges // // // //////////////////////////////////////////////////////////////////////// SE_CREATE_TOKEN_NAME = 'SeCreateTokenPrivilege'; SE_ASSIGNPRIMARYTOKEN_NAME = 'SeAssignPrimaryTokenPrivilege'; SE_LOCK_MEMORY_NAME = 'SeLockMemoryPrivilege'; SE_INCREASE_QUOTA_NAME = 'SeIncreaseQuotaPrivilege'; SE_UNSOLICITED_INPUT_NAME = 'SeUnsolicitedInputPrivilege'; SE_MACHINE_ACCOUNT_NAME = 'SeMachineAccountPrivilege'; SE_TCB_NAME = 'SeTcbPrivilege'; SE_SECURITY_NAME = 'SeSecurityPrivilege'; SE_TAKE_OWNERSHIP_NAME = 'SeTakeOwnershipPrivilege'; SE_LOAD_DRIVER_NAME = 'SeLoadDriverPrivilege'; SE_system_PROFILE_NAME = 'SesystemProfilePrivilege'; SE_systemTIME_NAME = 'SesystemtimePrivilege'; SE_PROF_SINGLE_PROCESS_NAME = 'SeProfileSingleProcessPrivilege'; SE_INC_BASE_PRIORITY_NAME = 'SeIncreaseBasePriorityPrivilege'; SE_CREATE_PAGEFILE_NAME = 'SeCreatePagefilePrivilege'; SE_CREATE_PERMANENT_NAME = 'SeCreatePermanentPrivilege'; SE_BACKUP_NAME = 'SeBackupPrivilege'; SE_RESTORE_NAME = 'SeRestorePrivilege'; SE_SHUTDOWN_NAME = 'SeShutdownPrivilege'; SE_DEBUG_NAME = 'SeDebugPrivilege'; SE_AUDIT_NAME = 'SeAuditPrivilege'; SE_system_ENVIRONMENT_NAME = 'SesystemEnvironmentPrivilege'; SE_CHANGE_NOTIFY_NAME = 'SeChangeNotifyPrivilege'; SE_REMOTE_SHUTDOWN_NAME = 'SeRemoteShutdownPrivilege'; { ------------------------------------------------- } { support standard Error output, besides standard Output/Input } var Error: TextFile; procedure InitErrorOutput; begin AssignFile(Error, EmptyStr); Rewrite(Error); TTextRec(Error).Handle := GetStdHandle(STD_ERROR_HANDLE); end; var _TokenizeStr: PChar = nil; _TokenizeLast: PChar = nil; function Tokenize(const SourceText: string; const Delimiters: string): string; { this is my Delphi version of C's strtok(): 1st call: SourceText is not empty, next calls: SourceText is EmptyStr; set of delimiters can change while tokenizing; implicit string memory allocation is hidden for the outside: Tokenize only parses one SourceText at a time. } var R, S: PChar; begin if length(SourceText) = 0 then R := _TokenizeLast else begin { cleanup and (re)initialize } _TokenizeLast := nil; StrDispose(_TokenizeStr); _TokenizeStr := StrNew(PChar(SourceText)); R := _TokenizeStr; end; if R <> nil then begin S := R; { find next delim } while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) = nil) do inc(S); if S^ <> chr(0) then begin S^ := chr(0); { got delim, truncate R result } inc(S); { skip over delims to set _TokenizeLast } while (S^ <> chr(0)) and (StrScan(PChar(Delimiters), S^) <> nil) do inc(S); if S^ <> chr(0) then _TokenizeLast := S; end; Result := string(R); if S^ = chr(0) then begin { cleanup early } _TokenizeLast := nil; StrDispose(_TokenizeStr); _TokenizeStr := nil; end end else Result := EmptyStr end; { -------------------------------------------------------------- } const DEFWINSTATION: string = 'WinSta0'; DEFDESKTOP: string = 'Default'; WHITESPACE: string = ' ' {SPACE} + chr(9) {TAB} + chr(10) {LF}; DOMUSERSEP: string = '\'; procedure ErrorHandler(const errmsg: string); var err: dword; begin err := GetLastError; writeln(Error, 'Error: ', errmsg, '.'); write(Error, SysErrorMessage(err)); end; function SetUserObjectAllAccess(hUserObject: THANDLE): boolean; var pSD: PSecurity_Descriptor; si: Security_Information; { dword } begin (* Initialize a security descriptor. *) pSD := PSecurity_Descriptor( LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH)); if pSD = nil then begin ErrorHandler('Can''t Allocate Local Memory'); Result := FALSE; exit; end; if not InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) then begin ErrorHandler('Can''t Initialize Security Descriptor'); LocalFree(HLOCAL(pSD)); Result := FALSE; exit; end; {* Add a NULL disc. ACL to the security descriptor. *} if not SetSecurityDescriptorDacl(pSD, TRUE, // specifying a disc. ACL PACL(nil), FALSE) then // not a default disc. ACL begin ErrorHandler('Can''t Set Security Descriptor DACL'); LocalFree(HLOCAL(pSD)); Result := FALSE; exit; end; {* Add the security descriptor to the userobject (like a window or a DDE conversation), NOT to a kernelobject (like a process, thread or event). *} si := DACL_SECURITY_INformATION; Result := SetUserObjectSecurity(hUserObject, si, pSD); LocalFree(HLOCAL(pSD)); if not Result then ErrorHandler('Can''t Set NewUser Object Security') end; function GetUserObjectName(hUserObject: THandle; var Name: string): boolean; var dw: DWord; begin Name := EmptyStr; GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), 0, dw); SetLength(Name, dw + 1); Result := GetUserObjectInformation(hUserObject, UOI_NAME, PChar(Name), dw, dw); if Result then SetLength(Name, dw - 1) else Name := EmptyStr; end; function GetPrivilegeDisplayName(const PrivilegeName: string): string; { PrivilegeName is of string type 'SE_'* } var dw, li: DWord; begin Result := EmptyStr; dw := 0; li := 0; { li:= dword(MAKELANGID(LANG_DEFAULT, LANG_USER)); } if not LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw, li) then dw := 256; SetLength(Result, dw + 1); if LookupPrivilegeDisplayName(nil, PChar(PrivilegeName), PChar(Result), dw, li) then SetLength(Result, StrLen(PChar(Result))) else Result := EmptyStr; end; function GetAccountInfo(var CurUser, CurDomain: string): boolean; var dw, dw2: DWord; pSD: PSecurity_Descriptor; snu: Sid_Name_Use; begin Result := False; dw := 255; Setlength(CurUser, dw + 1); if GetUserName(PChar(CurUser), dw) then begin SetLength(CurUser, dw - 1); dw2 := 256; SetLength(CurDomain, dw2); snu := SidTypeUser; pSD := nil; dw := 0; { get needed length for SID } LookUpAccountName(nil {LocalMachine}, PChar(CurUser), pSD, dw, PChar(CurDomain), dw2, snu); if dw <> 0 then begin pSD := PSecurity_Descriptor(LocalAlloc(LPTR, dw)); if pSD <> nil then begin if LookUpAccountName(nil, PChar(CurUser), { get the real thing } pSD, dw, PChar(CurDomain), dw2, snu) then begin SetLength(CurDomain, dw2); Result := True; end else CurDomain := EmptyStr; LocalFree(HLOCAL(pSD)); end; end; end else CurUser := EmptyStr; end; function GetMachineName: string; var dw: DWord; begin dw := MAX_COMPUTERNAME_LENGTH + 1; SetLength(Result, MAX_COMPUTERNAME_LENGTH + 1); if GetComputerName(PChar(Result), dw) then SetLength(Result, dw) else Result := EmptyStr; end; { ---------------------------------------------------------- } var CurUser, // Current User CurDomain, // Current Domain pwstr, // password string consoleTitle, // Title if new console only NewDomUser, // NewDomain\NewUser combination CommandLine, // command line we pass to the new process NewDomain, // NewDomain to log onto NewUser: string; // NewUser to log onto startUpInfo: TStartupInfo; procInfo: TProcessInformation; // child process info, from CreateProcessAsUser hDesktop: HDESK; hWindowStation: HWINSTA; hUserToken, hConsIn: THANDLE; OldConsInMode, NewConsInMode: DWORD; NTversion: TOSVersionInfo; S, DeskTopName, WinStaName: string; RC: integer; begin { program } InitErrorOutput; // Attach outputfile Error to STDERR // Make sure we are using the minimum OS version. NTversion.dwOSVersionInfoSize := sizeof(TOSVersionInfo); if not GetVersionEx(NTversion) then begin ErrorHandler('Unable to get OS version'); halt(1); end; if NTversion.dwPlatformId <> VER_PLATform_WIN32_NT then begin writeln(Error, 'SU will run only on Windows NT.'); halt(1); end; if NTversion.dwBuildNumber < 1057 then // Commercial 3.51 release begin writeln(Error, 'SU requires at minimum NT version 3.51 build 1057.'); halt(1); end; //{$IFDEF DEBUG} writeln('SU: NT Version ', NTversion.dwMajorVersion, '.', NTversion.dwMinorVersion, ', build ', NTversion.dwBuildNumber); // {$ENDIF} GetAccountInfo(CurUser, CurDomain); writeln('You are ', CurDomain, '\', CurUser); // Process the command line parameters Tokenize(string(CmdLine), WHITESPACE); NewDomUser := Tokenize(EmptyStr, WHITESPACE); if length(NewDomUser) = 0 then begin NewDomUser := DEFAULT_USER; CommandLine := DEFAULT_CMD; end else begin CommandLine := Tokenize(EmptyStr, EmptyStr); if length(CommandLine) = 0 then CommandLine := DEFAULT_CMD; end; if Pos(DOMUSERSEP, NewDomUser) > 0 then begin NewDomain := Tokenize(NewDomUser, DOMUSERSEP); NewUser := Tokenize(EmptyStr, DOMUSERSEP); if length(NewUser) = 0 then NewUser := DEFAULT_USER; end else begin NewDomain := EmptyStr; NewUser := NewDomUser; end; if (length(NewDomain) = 0) and ((NewUser = '-?') or (NewUser = '/?') or (NewUser = '?')) then begin writeln; writeln('Runs Windows NT commands under another user''s account.'); writeln; writeln('SU [newdomain\][newuser] [command-line]'); writeln; writeln(' [newdomain\] Specifies desired domain logon (\\ is ok also).'); writeln(' [newuser] Specifies the name of the user to be impersonated.'); writeln(' The default is Administrator.'); writeln(' [command-line] Specifies the command to be executed, with parameters.'); writeln(' The default is CMD (a new NT Console).'); writeln; writeln('Requires three extended NT privileges:'); writeln; writeln(' ', GetPrivilegeDisplayName(SE_TCB_NAME), ','); writeln(' ', GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME), ' and'); writeln(' ', GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME), '.'); writeln; writeln('These can be granted as User Rights with NT User Manager.'); halt(0); end; // Turn off console mode echo, since we don't want clear-screen passwords system.Reset(Input); {GetStdHandle(STD_INPUT_HANDLE)} hConsIn := TTextRec(Input).Handle; //if hConsIn = INVALID_HANDLE_values then //begin // ErrorHandler ('Can''t get handle of STDIN'); halt(1); //end; if not GetConsoleMode(hConsIn, OldConsInMode) then begin ErrorHandler('Can''t get current Console Mode'); halt(1); end; NewConsInMode := OldConsInMode and (not ENABLE_ECHO_INPUT); if not SetConsoleMode(hConsIn, NewConsInMode) then begin ErrorHandler('Unable to turn off Echo'); halt(1); end; // Ask for the password {$IFDEF VERBOSE} if length(NewDomain) = 0 then S := CurDomain else S := NewDomain; writeln('Logging onto ', S, ' domain as ', NewUser, '.'); {$ENDIF} write('Enter password: '); readln(pwstr); // When echo is off and NewUser hits , CR-LF is not echoed, so do it for him writeln; if not SetConsoleMode(hConsIn, OldConsInMode) then begin ErrorHandler('Unable to reset previous console mode'); halt(1); end; CloseHandle(hConsIn); // Do the Logon if not LogonUser(PChar(NewUser), PChar(NewDomain), PChar(pwstr), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, hUserToken) then begin case GetLastError of ERROR_PRIVILEGE_NOT_HELD: begin writeln(Error, 'Error: you do not have the following extended User Right:'); writeln(Error, GetPrivilegeDisplayName(SE_TCB_NAME), '.'); end; ERROR_LOGON_FAILURE: ErrorHandler('LogonUser failed.'); ERROR_ACCESS_DENIED: ErrorHandler('Access is denied'); else ErrorHandler('Unable to logon'); end; halt(2); end; // give the NewUser access to the current WindowStation and Desktop hWindowStation := GetProcessWindowStation; if not GetUserObjectName(hWindowStation, WinStaName) then WinStaName := DEFWINSTATION; if not SetUserObjectAllAccess(hWindowStation) then begin write(Error, 'Can''t set WindowStation ', WinStaName, ' security.'); CloseHandle(hUserToken); halt(3); end; hDesktop := GetThreadDesktop(GetCurrentThreadId); if not GetUserObjectName(hDesktop, DeskTopName) then DeskTopName := DEFDESKTOP; if not SetUserObjectAllAccess(hDesktop) then begin write(Error, 'Can''t set Desktop ', DeskTopName, ' security.'); CloseHandle(hUserToken); halt(3); end; // Set the STARTUPINFO for the new process if length(NewDomain) <> 0 then NewDomain := NewDomain + '\'; consoleTitle := 'SU: ' + NewDomain + NewUser; FillChar(startUpInfo, sizeof(startUpInfo), 0); with startUpInfo do begin cb := sizeof(startUpInfo); lpTitle := PChar(consoleTitle); S := WinStaName + '\' + DeskTopName; lpDesktop := PChar(S); end; // Create the child process if not CreateProcessAsUser(hUserToken, nil, PChar(CommandLine), nil, nil, FALSE {no inherit handles}, CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP, nil, nil, startUpInfo, procInfo) then begin case GetLastError of ERROR_PRIVILEGE_NOT_HELD: begin writeln(Error, 'Error: missing (one of) following extended User Rights:'); writeln(Error, GetPrivilegeDisplayName(SE_ASSIGNPRIMARYTOKEN_NAME), ', or'); writeln(Error, GetPrivilegeDisplayName(SE_INCREASE_QUOTA_NAME), '.'); ErrorHandler(EmptyStr); end; ERROR_FILE_NOT_FOUND: ErrorHandler('Error: command in ''' + CommandLine + ''' not found.'); else ErrorHandler('Error: CreateProcessAsUser failed.'); end; RC := 4; end else RC := 0; CloseHandle(hWindowStation); CloseHandle(hDesktop); CloseHandle(hUserToken); if RC = 0 then begin CloseHandle(procInfo.hThread); CloseHandle(procInfo.hProcess); end; halt(RC); end. |