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?
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;
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.
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.
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;
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:
Enable the SeIncreaseQuotaPrivilege in your current token
Get an HWND representing the desktop shell (GetShellWindow)
Get the Process ID (PID) of the process associated with that window (GetWindowThreadProcessId)
Open that process (OpenProcess)
Get the access token from that process (OpenProcessToken)
Make a primary token with that token (DuplicateTokenEx)
Start the new process with that primary token (CreateProcessWithTokenW)
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.
Related
How to ping an IP address (or by server name) in Delphi 10.1 without using Indy components? TIdICMPClient works with elevated privileges but I want to do it as a normal user.
The other answers had some things missing from them.
Here is a complete unit that does the trick:
unit Ping2;
interface
function PingHost(const HostName: AnsiString; TimeoutMS: cardinal = 500): boolean;
implementation
uses Windows, SysUtils, WinSock;
function IcmpCreateFile: THandle; stdcall; external 'iphlpapi.dll';
function IcmpCloseHandle(icmpHandle: THandle): boolean; stdcall;
external 'iphlpapi.dll';
function IcmpSendEcho(icmpHandle: THandle; DestinationAddress: In_Addr;
RequestData: Pointer; RequestSize: Smallint; RequestOptions: Pointer;
ReplyBuffer: Pointer; ReplySize: DWORD; Timeout: DWORD): DWORD; stdcall;
external 'iphlpapi.dll';
type
TEchoReply = packed record
Addr: In_Addr;
Status: DWORD;
RoundTripTime: DWORD;
end;
PEchoReply = ^TEchoReply;
var
WSAData: TWSAData;
procedure Startup;
begin
if WSAStartup($0101, WSAData) <> 0 then
raise Exception.Create('WSAStartup');
end;
procedure Cleanup;
begin
if WSACleanup <> 0 then
raise Exception.Create('WSACleanup');
end;
function PingHost(const HostName: AnsiString;
TimeoutMS: cardinal = 500): boolean;
const
rSize = $400;
var
e: PHostEnt;
a: PInAddr;
h: THandle;
d: string;
r: array [0 .. rSize - 1] of byte;
i: cardinal;
begin
Startup;
e := gethostbyname(PAnsiChar(HostName));
if e = nil then
RaiseLastOSError;
if e.h_addrtype = AF_INET then
Pointer(a) := e.h_addr^
else
raise Exception.Create('Name doesn''t resolve to an IPv4 address');
d := FormatDateTime('yyyymmddhhnnsszzz', Now);
h := IcmpCreateFile;
if h = INVALID_HANDLE_VALUE then
RaiseLastOSError;
try
i := IcmpSendEcho(h, a^, PChar(d), Length(d), nil, #r[0], rSize, TimeoutMS);
Result := (i <> 0) and (PEchoReply(#r[0]).Status = 0);
finally
IcmpCloseHandle(h);
end;
Cleanup;
end;
end.
You can call it with a click event like this:
procedure TForm1.button1Click(Sender: TObject);
begin
if PingHost('172.16.24.2') then
ShowMessage('WORKED')
else
ShowMessage('FAILED');
end;
Remember to add the "Ping2" unit in your uses list.
Use the Windows API.
Something like this crude translation from: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366050(v=vs.85).aspx
Should do the trick.
var
ICMPFile: THandle;
IpAddress: ULONG;
SendData: array[0..31] of AnsiChar;
ReplyBuffer: PICMP_ECHO_REPLY;
ReplySize: DWORD;
NumResponses: DWORD;
begin
IpAddress:= inet_addr('127.0.0.1');
SendData := 'Data Buffer';
IcmpFile := IcmpCreateFile;
if IcmpFile <> INVALID_HANDLE_VALUE then
try
ReplySize:= SizeOf(ICMP_ECHO_REPLY) + SizeOf(SendData);
GetMem(ReplyBuffer, ReplySize);
try
NumResponses := IcmpSendEcho(IcmpFile, IPAddress, #SendData, SizeOf(SendData),
nil, ReplyBuffer, ReplySize, 1000);
if (NumResponses <> 0) then begin
Writeln(Format('Received %d icmp message responses', [NumResponses]));
Writeln('Information from the first response:');
Writeln(Format('Received from %s', [inet_ntoa(in_addr(ReplyBuffer.Address))]));
Writeln(Format('Data: %s', [PAnsiChar(ReplyBuffer.Data)]));
Writeln(Format('Status = %d', [ReplyBuffer.Status]));
WriteLn(Format('Roundtrip time = %d milliseconds',[ReplyBuffer.RoundTripTime]));
end else begin
WriteLn('Call to IcmpSendEcho failed');
WriteLn(Format('IcmpSendEcho returned error: %d', [GetLastError]));
end;
finally
FreeMem(ReplyBuffer);
end;
finally
IcmpCloseHandle(IcmpFile);
end
else begin
Writeln('Unable to open handle');
Writeln(Format('IcmpCreateFile returned error: %d', [GetLastError]));
end;
Here is a Delphi unit which does the ping with a timeout:
unit Ping2;
interface
function PingHost(const HostName:string;TimeoutMS:cardinal=500):boolean;
implementation
uses Windows, SysUtils, WinSock, Sockets;
function IcmpCreateFile:THandle; stdcall; external 'iphlpapi.dll';
function IcmpCloseHandle(icmpHandle:THandle):boolean; stdcall; external 'iphlpapi.dll'
function IcmpSendEcho(IcmpHandle:THandle;DestinationAddress:In_Addr;RequestData:Pointer;
RequestSize:Smallint;RequestOptions:pointer;ReplyBuffer:Pointer;ReplySize:DWORD;
Timeout:DWORD):DWORD; stdcall; external 'iphlpapi.dll';
type
TEchoReply=packed record
Addr:in_addr;
Status:DWORD;
RoundTripTime:DWORD;
//DataSize:
//Reserved:
//Data:pointer;
//Options:
end;
PEchoReply=^TEchoReply;
function PingHost(const HostName:string;TimeoutMS:cardinal=500):boolean;
const
rSize=$400;
var
e:PHostEnt;
a:PInAddr;
h:THandle;
d:string;
r:array[0..rSize-1] of byte;
i:cardinal;
begin
//assert WSAStartup called
e:=gethostbyname(PChar(HostName));
if e=nil then RaiseLastOSError;
if e.h_addrtype=AF_INET then pointer(a):=e.h_addr^ else raise Exception.Create('Name doesn''t resolve to an IPv4 address');
d:=FormatDateTime('yyyymmddhhnnsszzz',Now);
h:=IcmpCreateFile;
if h=INVALID_HANDLE_VALUE then RaiseLastOSError;
try
i:=IcmpSendEcho(h,a^,PChar(d),Length(d),nil,#r[0],rSize,TimeoutMS);
Result:=(i<>0) and (PEchoReply(#r[0]).Status=0);
finally
IcmpCloseHandle(h);
end;
end;
end.
I write a service and want to run an application (with GUI) from it. So I write a procedure like below. my application start but still in service session! and so i can't see it's GUI.
Any help please.
Procedure RunAppFromService(Path, FileName: string);
var
zPath : array[0..512] of char;
zAppName : array[0..512] of char;
StartupInfo : TStartupInfo;
ProcessInfo : TProcessInformation;
begin { WinExecAndWait32V2 }
StrPCopy(zPath, Path);
StrPCopy(zAppName, FileName);
FillChar(StartupInfo, Sizeof(StartupInfo), #0);
StartupInfo.cb := Sizeof(StartupInfo);
StartupInfo.lpDesktop := PChar('winsta0\Default');
StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := Visibility;
FillChar(ProcessInfo, Sizeof(ProcessInfo), #0);
CreateProcessAsUser(0, nil,
zAppName, { pointer to command line string }
nil, { pointer to process security attributes }
nil, { pointer to thread security attributes }
false, { handle inheritance flag }
CREATE_NEW_CONSOLE or { creation flags }
NORMAL_PRIORITY_CLASS,
nil, { pointer to new environment block }
zPath, { pointer to current directory name }
StartupInfo, { pointer to STARTUPINFO }
ProcessInfo); { pointer to PROCESS_INF }
end;
You need to call WTSQueryUserToken with WtsGetActiveConsoleSessionID to get the current active user token then pass it to CreateEnvironmentBlock and CreateProcessAsUserW.
function WTSQueryUserToken(SessionId: ULONG; var phToken: THandle): BOOL; stdcall; external 'Wtsapi32.dll';
function CreateEnvironmentBlock(var lpEnvironment: Pointer;
hToken: THandle;
bInherit: BOOL): BOOL;
stdcall; external 'Userenv.dll';
function DestroyEnvironmentBlock(pEnvironment: Pointer): BOOL; stdcall; external 'Userenv.dll';
function RunAppFromService(const Path, FileName: string): Boolean;
var
zPath : array[0..512] of char;
zAppName : array[0..512] of char;
StartupInfo : TStartupInfo;
ProcessInfo : TProcessInformation;
hUserToken : THandle;
p : Pointer;
begin { WinExecAndWait32V2 }
Result := False;
StrPCopy(zPath, Path);
StrPCopy(zAppName, FileName);
if NOT WTSQueryUserToken(WtsGetActiveConsoleSessionID, hUserToken) then exit;
if CreateEnvironmentBlock(P, hUserToken, True) then
begin
ZeroMemory(#StartupInfo, sizeof(StartupInfo));
StartupInfo.lpDesktop := ('winsta0\default');
StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := Visibility;
if CreateProcessAsUserW(
hUserToken,
nil,
zAppName,
nil,
nil,
False,
CREATE_UNICODE_ENVIRONMENT,
P,
zPath,
StartupInfo,
ProcessInfo) then
begin
Result := True;
end;
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
DestroyEnvironmentBlock(P);
end;
if hUserToken <> INVALID_HANDLE_VALUE then
CloseHandle(hUserToken);
end;
Update as per Remy's comment and advice
Note : WTSQueryUserToken() only works if the service is running in the SYSTEM account.
type
WTS_INFO_CLASS = (
WTSInitialProgram,
WTSApplicationName,
WTSWorkingDirectory,
WTSOEMId,
WTSSessionId,
WTSUserName,
WTSWinStationName,
WTSDomainName,
WTSConnectState,
WTSClientBuildNumber,
WTSClientName,
WTSClientDirectory,
WTSClientProductId,
WTSClientHardwareId,
WTSClientAddress,
WTSClientDisplay,
WTSClientProtocolType,
WTSIdleTime,
WTSLogonTime,
WTSIncomingBytes,
WTSOutgoingBytes,
WTSIncomingFrames,
WTSOutgoingFrames,
WTSClientInfo,
WTSSessionInfo,
WTSSessionInfoEx,
WTSConfigInfo,
WTSValidationInfo,
WTSSessionAddressV4,
WTSIsRemoteSession
);
WTS_CONNECTSTATE_CLASS = (
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
);
PWTS_SESSION_INFO = ^WTS_SESSION_INFO;
WTS_SESSION_INFO = record
SessionId: DWORD;
pWinStationName: LPTSTR;
State: WTS_CONNECTSTATE_CLASS;
end;
........
function WTSEnumerateSessions(hServer: THandle; Reserved: DWORD; Version: DWORD; var ppSessionInfo: PWTS_SESSION_INFO; var pCount: DWORD): BOOL; stdcall; external 'Wtsapi32.dll' name {$IFDEF UNICODE}'WTSEnumerateSessionsW'{$ELSE}'WTSEnumerateSessionsA'{$ENDIF};
procedure WTSFreeMemory(pMemory: Pointer); stdcall; external 'Wtsapi32.dll';
function WTSQueryUserToken(SessionId: ULONG; var phToken: THandle): BOOL; stdcall; external 'Wtsapi32.dll';
function CreateEnvironmentBlock(var lpEnvironment: Pointer;
hToken: THandle;
bInherit: BOOL): BOOL;
stdcall; external 'Userenv.dll';
function DestroyEnvironmentBlock(pEnvironment: Pointer): BOOL; stdcall; external 'Userenv.dll';
function RunAppFromService(const Path, FileName: string): Boolean;
const
WTS_CURRENT_SERVER_HANDLE: THandle = 0;
var
zPath : array[0..512] of char;
zAppName : array[0..512] of char;
StartupInfo : TStartupInfo;
ProcessInfo : TProcessInformation;
hUserToken : THandle;
p : Pointer;
Sessions, Session : PWTS_SESSION_INFO;
NumSessions : DWORD;
I : Integer;
begin { WinExecAndWait32V2 }
Result := False;
StrPCopy(zPath, Path);
StrPCopy(zAppName, FileName);
if not WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, Sessions, NumSessions) then
exit;;
try
if NumSessions > 0 then
begin
Session := Sessions;
for I := 0 to NumSessions-1 do
begin
if Session.State = WTSActive then
begin
if WTSQueryUserToken(Session.SessionId, hUserToken) then begin
if CreateEnvironmentBlock(P, hUserToken, True) then
begin
ZeroMemory(#StartupInfo, sizeof(StartupInfo));
StartupInfo.lpDesktop := ('winsta0\default');
StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := Visibility;
if CreateProcessAsUserW(
hUserToken,
nil,
zAppName,
nil,
nil,
False,
CREATE_UNICODE_ENVIRONMENT,
P,
zPath,
StartupInfo,
ProcessInfo) then
begin
Result := True;
end;
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
DestroyEnvironmentBlock(P);
end;
if hUserToken <> INVALID_HANDLE_VALUE then
CloseHandle(hUserToken);
end;
end;
Inc(Session);
end;
end;
finally
WTSFreeMemory(Sessions);
end;
end;
After I found this useful question, I am able to create an application as Windows Service with sucess.
Then, I have created a separate executable to execute my application as a service, but my application isn't executed.
Flsh_Service.exe is the separate executable that executes Start_Dll.exe
This is my application that should work, but fails.
So i ask:
Why is my application not correctly executed as a service?
with relation to question refered above, cmd.exe here is target to injection and application to run as service is: Start_Dll.exe ( that in question refered above is cmd.exe ).
program Start_Dll;
{$APPTYPE CONSOLE}
uses
SysUtils,
Winapi.windows,
CreateProcessIntr,
vcl.forms,
Winapi.messages,
shellapi,
tlhelp32;
Const SE_DEBUG_NAME = 'SeDebugPrivilege';
procedure GetDebugPrivs;
var
hToken: THandle;
tkp: TTokenPrivileges;
retval: dword;
begin
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken)) then
begin
LookupPrivilegeValue(nil, SE_DEBUG_NAME, tkp.Privileges[0].Luid);
tkp.PrivilegeCount := 1;
tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, false, tkp, 0, nil, retval);
end;
end;
procedure InjectDLL(const ADLLName: AnsiString; targetproc: Cardinal);
var
dllname: AnsiString;
pDLLname, pStartAddr: Pointer;
bw: NativeUInt;
hProcess, hRemoteThread: THandle;
TID: Cardinal;
begin
hProcess := OpenProcess(PROCESS_ALL_ACCESS, false, targetproc);
pDLLname := VirtualAllocEx(hProcess, 0, length(dllname) + 1,
MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, pDLLname, Pointer(dllname),
length(dllname) + 1, bw);
pStartAddr := GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryW');
hRemoteThread := CreateRemoteThread(hProcess, nil, 0, pStartAddr,
pDLLname, 0, TID);
WaitForSingleObject(hRemoteThread, INFINITE);
CloseHandle(hProcess);
end;
function search(name:string): Cardinal;
var ExeFile : String;
PE : TProcessEntry32;
FSnap: THandle;
begin
result:= 0;
FSnap:= Tlhelp32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
PE.dwSize:= SizeOf(PE);
if (Tlhelp32.Process32First(FSnap,PE)) Then
Repeat
ExeFile:= PE.szExeFile;
if pos(pchar(lowercase(name)), lowercase(ExeFile))>0 then
Begin
result:= PE.th32ProcessID;
break
End;
Until Not Process32Next(FSnap,PE)
end;
var
PID: Dword;
dll_to_inject: AnsiString;
Process: String;
////////////////////////////////////////////////////////////////////////////////
begin
{Sleep(120000);}
//GetDebugPrivs;
dll_to_inject:=ExtractFilePath(application.ExeName) + 'myDLL.dll;
Process:= 'cmd.exe';
ExecuteProcessAsLoggedOnUser(Process);
PID:=search(PROCESS);
if PID=0 then begin
exit;
end;
InjectDll(dll_to_inject, PID);
end.
CreateProcessIntr.pas
unit CreateProcessIntr;
interface
uses
Windows, SysUtils, Registry, TlHelp32;
procedure ExecuteProcessAsLoggedOnUser(FileName: string);
implementation
function GetShellProcessName: string;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.OpenKeyReadOnly('Software\Microsoft\Windows NT\CurrentVersion\WinLogon');
Result := Reg.ReadString('Shell');
finally
Reg.Free;
end;
end;
function GetShellProcessPid(const Name: string): Longword;
var
Snapshot: THandle;
Process: TProcessEntry32;
B: Boolean;
begin
Result := 0;
Snapshot := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if Snapshot <> INVALID_HANDLE_VALUE then
try
FillChar(Process, SizeOf(Process), 0);
Process.dwSize := SizeOf(Process);
B := Process32First(Snapshot, Process);
while B do
begin
if CompareText(Process.szExeFile, Name) = 0 then
begin
Result := Process.th32ProcessID;
Break;
end;
B := Process32Next(Snapshot, Process);
end;
finally
CloseHandle(Snapshot);
end;
end;
function GetShellHandle: THandle;
var
Pid: Longword;
begin
Pid := GetShellProcessPid(GetShellProcessName);
Result := OpenProcess(PROCESS_ALL_ACCESS, False, Pid);
end;
procedure ExecuteProcessAsLoggedOnUser(FileName: string);
var
ph: THandle;
hToken, nToken: THandle;
ProcInfo: TProcessInformation;
StartInfo: TStartupInfo;
begin
ph := GetShellHandle;
if ph > 0 then
begin
if OpenProcessToken(ph, TOKEN_DUPLICATE or TOKEN_QUERY, hToken) then
begin
if DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY or TOKEN_DUPLICATE or TOKEN_QUERY,
nil, SecurityImpersonation, TokenPrimary, nToken) then
begin
if ImpersonateLoggedOnUser(nToken) then
begin
// Initialize then STARTUPINFO structure
FillChar(StartInfo, SizeOf(TStartupInfo), 0);
StartInfo.cb := SizeOf(TStartupInfo);
StartInfo.dwFlags := STARTF_USESHOWWINDOW;
StartInfo.wShowWindow := 0;
// Specify that the process runs in the interactive desktop
StartInfo.lpDesktop := PChar('WinSta0\Default');
// Launch the process in the client's logon session
CreateProcessAsUser(nToken, nil, PChar(FileName), nil, nil, False,
CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, StartInfo, ProcInfo);
// End impersonation of client
RevertToSelf();
end;
CloseHandle(nToken);
end;
CloseHandle(hToken);
end;
end;
end;
end.
How to create a process by the SYSTEM NT Authority account in Delphi ?
is there an API for it such as CreateProcessAsUser function.
You need to create service that installed & starts at run time by
itself.
On Service execute procedure Call CreateProcessAsUserW with the token of winlogon.exe process.
Notes
if you want the new proccess runs in the same caller session call
WTSQueryUserToken with WtsGetActiveConsoleSessionID to get the
current active user token then call CreateEnvironmentBlock with that
token, and assinge the received pointer on CreateProcessAsUserW.
Set a random Name & DisplayName (such created time) for that
service. if you want to run a multiple SYSTEM process with the same
serevice.
Here what i use
uSysAccount.pas
unit uSysAccount;
interface
uses
WinSvc,
SvcMgr,
Winapi.Windows,
System.SysUtils,
TlHelp32,
System.Classes;
type
TsSysAccount = class(TService)
procedure ServiceExecute(Sender: TService);
private
lpApplicationName,
lpCommandLine,
lpCurrentDirectory: PWideChar;
public
function GetServiceController: TServiceController; override;
end;
procedure CreateProcessAsSystem(const lpApplicationName: PWideChar;
const lpCommandLine:PWideChar = nil;
const lpCurrentDirectory: PWideChar = nil);
var
sSysAccount: TsSysAccount;
implementation
{$R *.dfm}
function WTSQueryUserToken(SessionId: ULONG; var phToken: THandle): BOOL; stdcall; external 'Wtsapi32.dll';
type
TServiceApplicationEx = class(TServiceApplication)
end;
TServiceApplicationHelper = class helper for TServiceApplication
public
procedure ServicesRegister(Install, Silent: Boolean);
end;
function IsUserAnAdmin: BOOL; stdcall; external 'shell32.dll' name 'IsUserAnAdmin';
function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle;
bInherit: BOOL): BOOL;
stdcall; external 'Userenv.dll';
function DestroyEnvironmentBlock(pEnvironment: Pointer): BOOL; stdcall; external 'Userenv.dll';
function _GetIntegrityLevel() : DWORD;
type
PTokenMandatoryLabel = ^TTokenMandatoryLabel;
TTokenMandatoryLabel = packed record
Label_ : TSidAndAttributes;
end;
var
hToken : THandle;
cbSize: DWORD;
pTIL : PTokenMandatoryLabel;
dwTokenUserLength: DWORD;
begin
Result := 0;
dwTokenUserLength := MAXCHAR;
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, hToken) then begin
pTIL := Pointer(LocalAlloc(0, dwTokenUserLength));
if pTIL = nil then Exit;
cbSize := SizeOf(TTokenMandatoryLabel);
if GetTokenInformation(hToken, TokenIntegrityLevel,
pTIL, dwTokenUserLength, cbSize) then
if IsValidSid( (pTIL.Label_).Sid ) then
Result := GetSidSubAuthority((pTIL.Label_).Sid, GetSidSubAuthorityCount((pTIL.Label_).Sid )^ - 1)^;
if hToken <> INVALID_HANDLE_VALUE then
CloseHandle(hToken);
LocalFree(Cardinal(pTIL));
end;
end;
function IsUserAnSystem(): Boolean;
const
SECURITY_MANDATORY_SYSTEM_RID = $00004000;
begin
Result := (_GetIntegrityLevel = SECURITY_MANDATORY_SYSTEM_RID);
end;
function StartTheService(Service:TService): Boolean;
var
SCM: SC_HANDLE;
ServiceHandle: SC_HANDLE;
begin
Result:= False;
SCM:= OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if (SCM <> 0) then
begin
try
ServiceHandle:= OpenService(SCM, PChar(Service.Name), SERVICE_ALL_ACCESS);
if (ServiceHandle <> 0) then
begin
Result := StartService(ServiceHandle, 0, pChar(nil^));
CloseServiceHandle(ServiceHandle);
end;
finally
CloseServiceHandle(SCM);
end;
end;
end;
procedure SetServiceName(Service: TService);
begin
if Assigned(Service) then begin
Service.DisplayName := 'Run as system service created ' + DateTimeToStr(Now);
Service.Name := 'RunAsSystem' + FormatDateTime('ddmmyyyyhhnnss', Now);
end;
end;
procedure CreateProcessAsSystem(const lpApplicationName: PWideChar;
const lpCommandLine:PWideChar = nil;
const lpCurrentDirectory: PWideChar = nil);
begin
if not ( IsUserAnAdmin ) then begin
SetLastError(ERROR_ACCESS_DENIED);
Exit();
end;
if not ( FileExists(lpApplicationName) ) then begin
SetLastError(ERROR_FILE_NOT_FOUND);
Exit();
end;
if ( IsUserAnSystem ) then
begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm(TsSysAccount, sSysAccount);
sSysAccount.lpApplicationName := lpApplicationName;
sSysAccount.lpCommandLine := lpCommandLine;
sSysAccount.lpCurrentDirectory := lpCurrentDirectory;
SetServiceName(sSysAccount);
SvcMgr.Application.Run;
end
else begin
SvcMgr.Application.Free;
SvcMgr.Application := TServiceApplicationEx.Create(nil);
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm(TsSysAccount, sSysAccount);
SetServiceName(sSysAccount);
SvcMgr.Application.ServicesRegister(True, True);
try
StartTheService(sSysAccount);
finally
SvcMgr.Application.ServicesRegister(False, True);
end;
end;
end;
procedure TServiceApplicationHelper.ServicesRegister(Install, Silent: Boolean);
begin
RegisterServices(Install, Silent);
end;
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
sSysAccount.Controller(CtrlCode);
end;
function TsSysAccount.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
Function ProcessIDFromAppname32( szExeFileName: String ): DWORD;
var
Snapshot: THandle;
ProcessEntry: TProcessEntry32;
Begin
Result := 0;
szExeFileName := UpperCase( szExeFileName );
Snapshot := CreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS,
0 );
If Snapshot <> 0 Then
try
ProcessEntry.dwSize := Sizeof( ProcessEntry );
If Process32First( Snapshot, ProcessEntry ) Then
Repeat
If Pos( szExeFileName,
UpperCase(ExtractFilename(
StrPas(ProcessEntry.szExeFile)))
) > 0
then Begin
Result:= ProcessEntry.th32ProcessID;
Break;
end;
until not Process32Next( Snapshot, ProcessEntry );
finally
CloseHandle( Snapshot );
end;
End;
function TerminateProcessByID(ProcessID: Cardinal): Boolean;
var
hProcess : THandle;
begin
Result := False;
hProcess := OpenProcess(PROCESS_TERMINATE,False,ProcessID);
if hProcess > 0 then
try
Result := Win32Check(TerminateProcess(hProcess,0));
finally
CloseHandle(hProcess);
end;
end;
procedure TsSysAccount.ServiceExecute(Sender: TService);
var
hToken, hUserToken: THandle;
StartupInfo : TStartupInfoW;
ProcessInfo : TProcessInformation;
P : Pointer;
begin
if NOT WTSQueryUserToken(WtsGetActiveConsoleSessionID, hUserToken) then exit;
if not OpenProcessToken(
OpenProcess(PROCESS_ALL_ACCESS, False,
ProcessIDFromAppname32('winlogon.exe'))
,
MAXIMUM_ALLOWED,
hToken) then exit;
if CreateEnvironmentBlock(P, hUserToken, True) then
begin
ZeroMemory(#StartupInfo, sizeof(StartupInfo));
StartupInfo.lpDesktop := ('winsta0\default');
StartupInfo.wShowWindow := SW_SHOWNORMAL;
if CreateProcessAsUserW(
hToken,
lpApplicationName,
lpCommandLine,
nil,
nil,
False,
CREATE_UNICODE_ENVIRONMENT,
P,
lpCurrentDirectory,
StartupInfo,
ProcessInfo) then
begin
end;
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
DestroyEnvironmentBlock(P);
end;
CloseHandle(hToken);
CloseHandle(hUserToken);
TerminateProcessByID(GetCurrentProcessId);
end;
end.
uSysAccount.dfm
object sSysAccount: TsSysAccount
OldCreateOrder = False
DisplayName = 'sSysAccount'
OnExecute = ServiceExecute
Height = 150
Width = 215
end
Usage as follow ( must run as an administrator )
program Project7;
uses
uSysAccount;
{$R *.res}
begin
CreateProcessAsSystem('c:\windows\system32\cmd.exe');
end.
I am trying to Create a Hook for CreateFile, so when a process tryies to create a file
the hookdll we created will notify the user that: "this process xx.exe trying to create xx.exe, Do you want to proceceed?"
So far i am here, So What i need to modify in this code:
library CreateFileHook;
uses
Windows, Dialogs, SysUtils;
type
OldCode = packed record
One: dword;
two: word;
end;
far_jmp = packed record
PuhsOp: byte;
PushArg: pointer;
RetOp: byte;
end;
var
JmpCfw, JmpCfa: far_jmp;
OldCfw, OldCfa: OldCode;
CfwAdr, CfaAdr: pointer;
function NewCreateFileA(lpFileName: PChar;
dwDesiredAccess: DWORD;
dwShareMode: DWORD;
lpSecurityAttributes: PSecurityAttributes;
dwCreationDisposition: DWORD;
dwFlagsAndAttributes: DWORD;
hTemplateFile: THandle): THandle; stdcall;
var
file_name: PWideChar;
name_len: dword;
begin
name_len := lstrlen(lpFileName) * SizeOf(WideChar) + 2;
GetMem(file_name, name_len);
StringToWideChar(lpFileName, file_name, name_len);
CreateFileW(file_name, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
FreeMem(file_name);
end;
function TrueCreateFileW(lpFileName: PWideChar;
dwDesiredAccess: DWORD;
dwShareMode: DWORD;
lpSecurityAttributes: PSecurityAttributes;
dwCreationDisposition: DWORD;
dwFlagsAndAttributes: DWORD;
hTemplateFile: THandle): THandle; stdcall;
var
Written: dword;
begin
WriteProcessMemory(INVALID_HANDLE_VALUE, CfwAdr,
#OldCfw, SizeOf(OldCode), Written);
CreateFileW(lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);
WriteProcessMemory(INVALID_HANDLE_VALUE, CfwAdr,
#JmpCfw, SizeOf(far_jmp), Written);
end;
function NewCreateFileW(lpFileName: PWideChar;
dwDesiredAccess: DWORD;
dwShareMode: DWORD;
lpSecurityAttributes: PSecurityAttributes;
dwCreationDisposition: DWORD;
dwFlagsAndAttributes: DWORD;
hTemplateFile: THandle): THandle; stdcall;
begin
TrueCreateFileW(lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);
end;
Procedure SetHook();
var
kernel32: dword;
Bytes: dword;
begin
kernel32 := GetModuleHandle('Kernel32.dll');
CfwAdr := GetProcAddress(kernel32, 'CreateFileW');
CfaAdr := GetProcAddress(kernel32, 'CreateFileA');
ReadProcessMemory(INVALID_HANDLE_VALUE, CfwAdr, #OldCfw, SizeOf(OldCode), Bytes);
ReadProcessMemory(INVALID_HANDLE_VALUE, CfaAdr, #OldCfa, SizeOf(OldCode), Bytes);
JmpCfw.PuhsOp := $68;
JmpCfw.PushArg := #NewCreateFileW;
JmpCfw.RetOp := $C3;
JmpCfa.PuhsOp := $68;
JmpCfa.PushArg := #NewCreateFileA;
JmpCfa.RetOp := $C3;
WriteProcessMemory(INVALID_HANDLE_VALUE, CfwAdr, #JmpCfw, SizeOf(far_jmp), Bytes);
WriteProcessMemory(INVALID_HANDLE_VALUE, CfaAdr, #JmpCfa, SizeOf(far_jmp), Bytes);
end;
Procedure Unhook();
var
Bytes: dword;
begin
WriteProcessMemory(INVALID_HANDLE_VALUE, CfaAdr, #OldCfa, SizeOf(OldCode), Bytes);
WriteProcessMemory(INVALID_HANDLE_VALUE, CfwAdr, #OldCfw, SizeOf(OldCode), Bytes);
end;
Function MessageProc(code : integer; wParam : word;
lParam : longint) : longint; stdcall;
begin
CallNextHookEx(0, Code, wParam, lparam);
Result := 0;
end;
Procedure SetGlobalHookProc();
begin
SetWindowsHookEx(WH_GETMESSAGE, #MessageProc, HInstance, 0);
Sleep(INFINITE);
end;
Procedure SetGlobalHook();
var
hMutex: dword;
TrId: dword;
begin
hMutex := CreateMutex(nil, false, 'CreateFileHook');
if GetLastError = 0 then
CreateThread(nil, 0, #SetGlobalHookProc, nil, 0, TrId) else
CloseHandle(hMutex);
end;
procedure DLLEntryPoint(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH: begin
SetGlobalHook();
Randomize();
SetHook()
end;
DLL_PROCESS_DETACH: UnHook();
end;
end;
begin
DllProc := #DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
At a quick glance, I see several problems with this code. Where did you get this from? I don't have a reference handy but I'm pretty sure you can find working examples of what you're trying to do on the web.
You shouldn't have to use Read/WriteProcessMemory since you're inside the process you're trying to modify - Windows will do copy-on-write for you.
If you do want/need to use Read/WriteProcessMemory the way to get a handle to use is OpenProcess.
This hook code is not reentrant - one thread may be exiting ReadFile, restoring the redirect code right before another thread attempt to call it but after that second thread thinks it's just 'repaired' it.
A cleaner way to do this is to save the pointer in the import address table that points to the function you wish to hook, then modify that to call your hook routine. Now you can use the saved pointer to call the original routine from within the hook.
Once (if) you get this working, be prepared to see a LOT of calls to CreateFile. CreateFile is used for creating/opening lots of stuff besides physical files, e.g. COM ports, pipes, console buffers, whatnot.