System Delphi

Title: Impersonating a User on Windows NT is a three step process
Question: 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.