FIX: Elevation does not work under standard user account (fixes #217)

This commit is contained in:
Alexander Koblov 2021-10-22 20:43:31 +03:00
commit ff7d514f15
4 changed files with 230 additions and 112 deletions

View file

@ -985,10 +985,18 @@ begin
Result:= False;
end;
function SHGetValueW(hkey: HKEY; pszSubKey, pszValue: LPCWSTR; pdwType: PDWORD;
pvData: LPVOID; pcbData: PDWORD): DWORD; stdcall; external 'shlwapi.dll';
function IsUserAdmin: TDuplicates;
const
SYSTEM = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System';
var
Success: Boolean;
dwValue: DWORD = 0;
ReturnLength: DWORD = 0;
dwType: DWORD = REG_DWORD;
dwValueSize: DWORD = SizeOf(DWORD);
TokenHandle: HANDLE = INVALID_HANDLE_VALUE;
TokenInformation: array [0..1023] of Byte;
ElevationType: JwaVista.TTokenElevationType absolute TokenInformation;
@ -1008,9 +1016,32 @@ begin
if Success then
begin
case ElevationType of
TokenElevationTypeDefault: Result:= dupIgnore; // The token does not have a linked token. (UAC disabled)
TokenElevationTypeFull: Result:= dupAccept; // The token is an elevated token. (Administrator)
TokenElevationTypeLimited: Result:= dupError; // The token is a limited token. (User)
// The token is an elevated token. (Administrator)
TokenElevationTypeFull: Result:= dupAccept;
// The token is a limited token. (User)
TokenElevationTypeLimited: Result:= dupError;
// The token does not have a linked token. (UAC disabled or standard user)
TokenElevationTypeDefault:
begin
if (SHGetValueW( HKEY_LOCAL_MACHINE, SYSTEM, 'EnableLUA',
@dwType, @dwValue, @dwValueSize) <> ERROR_SUCCESS) then
begin
Exit(dupError);
end;
if (dwValue > 0) then
begin
if (SHGetValueW( HKEY_LOCAL_MACHINE, SYSTEM, 'ConsentPromptBehaviorUser',
@dwType, @dwValue, @dwValueSize) <> ERROR_SUCCESS) then
begin
Exit(dupError);
end;
end;
if (dwValue > 0) then
Result:= dupError
else begin
Result:= dupIgnore;
end;
end;
end;
end;
end;

View file

@ -3,7 +3,7 @@
-------------------------------------------------------------------------
Remote procedure call implementation (Windows)
Copyright (C) 2019 Alexander Koblov (alexx2000@mail.ru)
Copyright (C) 2019-2021 Alexander Koblov (alexx2000@mail.ru)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -72,7 +72,8 @@ type
implementation
uses
JwaWinNT, JwaAclApi, JwaAccCtrl, JwaWinBase, Windows, uNamedPipes, uDebug;
JwaWinNT, JwaAclApi, JwaAccCtrl, JwaWinBase, Windows, DCOSUtils,
uNamedPipes, uDebug, uProcessInfo;
{ TPipeTransport }
@ -80,7 +81,7 @@ procedure TPipeTransport.Connect;
begin
if (FPipe = 0) then
begin
DCDebug('Connect to ', FAddress,'_');
DCDebug('Connect to ', String(FAddress));
FPipe:= CreateFileW(PWideChar('\\.\pipe\' + FAddress),
GENERIC_WRITE or
@ -250,119 +251,183 @@ procedure TServerListnerThread.Execute;
var
SID: TBytes;
dwWait: DWORD;
hPipe: THandle;
ACL: PACL = nil;
hProcess: HANDLE;
bPending: Boolean;
cCount: ULONG = 1;
SecondSID: TBytes;
AName: UnicodeString;
ReturnLength: DWORD = 0;
Overlapped: TOverlapped;
SA: TSecurityAttributes;
SD: TSecurityDescriptor;
TokenHandle: HANDLE = 0;
Events: array[0..1] of THandle;
ExplicitAccess: TExplicitAccess;
hPipe: THandle = INVALID_HANDLE_VALUE;
TokenInformation: array [0..1023] of Byte;
ExplicitAccess: array [0..1] of TExplicitAccess;
ElevationType: TTokenElevationType absolute TokenInformation;
begin
AName:= UTF8Decode(FOwner.Name);
if not GetCurrentUserSID(SID) then Halt;
ZeroMemory(@ExplicitAccess, SizeOf(ExplicitAccess));
with ExplicitAccess do
begin
grfAccessPermissions:= GENERIC_ALL;
grfAccessMode:= DWORD(SET_ACCESS);
grfInheritance:= NO_INHERITANCE;
Trustee.TrusteeForm:= DWORD(TRUSTEE_IS_SID);
Trustee.TrusteeType:= DWORD(TRUSTEE_IS_USER);
Trustee.ptstrName:= PAnsiChar(@SID[0]);
if (FOwner.ProcessId > 0) then
dwWait:= FOwner.ProcessId
else begin
dwWait:= System.GetProcessId;
end;
if SetEntriesInAcl(1, @ExplicitAccess, nil, JwaWinNT.PACL(ACL)) <> ERROR_SUCCESS then
Halt;
if not InitializeSecurityDescriptor (@SD, SECURITY_DESCRIPTOR_REVISION) then
Halt;
if not SetSecurityDescriptorDacl(@SD, True, ACL, False) then
Halt;
try
hProcess:= OpenProcess(PROCESS_QUERY_INFORMATION, False, dwWait);
if hProcess = 0 then RaiseLastOSError;
ZeroMemory(@Overlapped, SizeOf(TOverlapped));
ZeroMemory(@Overlapped, SizeOf(TOverlapped));
try
if not OpenProcessToken(hProcess, TOKEN_QUERY, TokenHandle) then
RaiseLastOSError;
Overlapped.hEvent:= CreateEvent(nil, True, True, nil);
if not GetTokenUserSID(TokenHandle, SID) then
RaiseLastOSError;
Events[0]:= Overlapped.hEvent;
Events[1]:= FEvent;
while not Terminated do
begin
SA.nLength := SizeOf(SA);
SA.lpSecurityDescriptor := @SD;
SA.bInheritHandle := True;
// Create pipe server
hPipe := CreateNamedPipeW(PWideChar('\\.\pipe\' + AName),
PIPE_ACCESS_DUPLEX or
FILE_FLAG_OVERLAPPED,
PIPE_WAIT or
PIPE_READMODE_BYTE or
PIPE_TYPE_BYTE,
PIPE_UNLIMITED_INSTANCES,
maxSmallint,
maxSmallint,
0,
@SA);
if hPipe = INVALID_HANDLE_VALUE then
Halt;
DCDebug('Start server ', FOwner.Name);
FReadyEvent.SetEvent;
while not Terminated do
begin
bPending:= False;
if not ConnectNamedPipe(hPipe, @Overlapped) then
ZeroMemory(@ExplicitAccess, SizeOf(ExplicitAccess));
with ExplicitAccess[0] do
begin
case (GetLastError()) of
ERROR_IO_PENDING:
bPending:= True;
ERROR_PIPE_CONNECTED:
SetEvent(Overlapped.hEvent);
else
grfAccessPermissions:= GENERIC_ALL;
grfAccessMode:= DWORD(SET_ACCESS);
grfInheritance:= NO_INHERITANCE;
Trustee.TrusteeForm:= DWORD(TRUSTEE_IS_SID);
Trustee.TrusteeType:= DWORD(TRUSTEE_IS_USER);
Trustee.ptstrName:= PAnsiChar(@SID[0]);
end;
if not GetTokenInformation(TokenHandle, TokenElevationType, @TokenInformation,
SizeOf(TokenInformation), ReturnLength) then
begin
RaiseLastOSError;
end;
if ElevationType = TokenElevationTypeDefault then
begin
with ExplicitAccess[1] do
begin
grfAccessPermissions:= GENERIC_ALL;
grfAccessMode:= DWORD(SET_ACCESS);
grfInheritance:= NO_INHERITANCE;
Trustee.TrusteeForm:= DWORD(TRUSTEE_IS_SID);
end;
if (FOwner.ProcessId = 0) then
begin
if not GetAdministratorsSID(SecondSID) then
RaiseLastOSError;
ExplicitAccess[1].Trustee.TrusteeType:= DWORD(TRUSTEE_IS_GROUP);
end
else begin
if not GetProcessUserSID(GetCurrentProcess, SecondSID) then
RaiseLastOSError;
ExplicitAccess[1].Trustee.TrusteeType:= DWORD(TRUSTEE_IS_USER);
end;
ExplicitAccess[1].Trustee.ptstrName:= PAnsiChar(@SecondSID[0]);
cCount:= 2;
end;
if SetEntriesInAcl(cCount, @ExplicitAccess[0], nil, JwaWinNT.PACL(ACL)) <> ERROR_SUCCESS then
RaiseLastOSError;
if not InitializeSecurityDescriptor (@SD, SECURITY_DESCRIPTOR_REVISION) then
RaiseLastOSError;
if not SetSecurityDescriptorDacl(@SD, True, ACL, False) then
RaiseLastOSError;
Overlapped.hEvent:= CreateEvent(nil, True, True, nil);
Events[0]:= Overlapped.hEvent;
Events[1]:= FEvent;
while not Terminated do
begin
SA.nLength := SizeOf(SA);
SA.lpSecurityDescriptor := @SD;
SA.bInheritHandle := True;
// Create pipe server
hPipe := CreateNamedPipeW(PWideChar('\\.\pipe\' + AName),
PIPE_ACCESS_DUPLEX or
FILE_FLAG_OVERLAPPED,
PIPE_WAIT or
PIPE_READMODE_BYTE or
PIPE_TYPE_BYTE,
PIPE_UNLIMITED_INSTANCES,
maxSmallint,
maxSmallint,
0,
@SA);
if hPipe = INVALID_HANDLE_VALUE then
RaiseLastOSError;
DCDebug('Start server ', FOwner.Name);
FReadyEvent.SetEvent;
while not Terminated do
begin
bPending:= False;
if not ConnectNamedPipe(hPipe, @Overlapped) then
begin
case (GetLastError()) of
ERROR_IO_PENDING:
bPending:= True;
ERROR_PIPE_CONNECTED:
SetEvent(Overlapped.hEvent);
else
begin
DisconnectNamedPipe(hPipe);
Continue;
end;
end;
end;
// Wait client connection
dwWait := WaitForMultipleObjectsEx(Length(Events), Events, False, INFINITE, True);
if (dwWait = 1) or ((dwWait = 0) and bPending and (not GetOverlappedResult(hPipe, Overlapped, dwWait, False))) then
begin
DisconnectNamedPipe(hPipe);
Continue;
end;
if (FOwner.VerifyChild and not VerifyChild(hPipe)) or
(FOwner.VerifyParent and not VerifyParent(hPipe)) then
begin
DisconnectNamedPipe(hPipe);
Continue;
end;
Break;
end; // while
if not Terminated then
TClientHandlerThread.Create(hPipe, FOwner)
else begin
DisconnectNamedPipe(hPipe);
end;
end;
// Wait client connection
dwWait := WaitForMultipleObjectsEx(Length(Events), Events, False, INFINITE, True);
if (dwWait = 1) or ((dwWait = 0) and bPending and (not GetOverlappedResult(hPipe, Overlapped, dwWait, False))) then
begin
DisconnectNamedPipe(hPipe);
Continue;
end;
if (FOwner.VerifyChild and not VerifyChild(hPipe)) or
(FOwner.VerifyParent and not VerifyParent(hPipe)) then
begin
DisconnectNamedPipe(hPipe);
Continue;
end;
Break;
end; // while
if not Terminated then
TClientHandlerThread.Create(hPipe, FOwner)
else begin
DisconnectNamedPipe(hPipe);
end; // while
finally
CloseHandle(hProcess);
if Assigned(ACL) then LocalFree(HLOCAL(ACL));
if (TokenHandle > 0) then CloseHandle(TokenHandle);
if (hPipe <> INVALID_HANDLE_VALUE) then CloseHandle(hPipe);
if (Overlapped.hEvent > 0) then CloseHandle(Overlapped.hEvent);
end;
end; // while
CloseHandle(hPipe);
CloseHandle(Events[0]);
LocalFree(HLOCAL(ACL));
except
on E: Exception do
begin
DCDebug(E.Message);
if FOwner.ProcessId > 0 then
Halt
else
Exit;
end;
end;
end;
end.

View file

@ -26,12 +26,12 @@ type
function VerifyChild(hPipe: THandle): Boolean;
function VerifyParent(hPipe: THandle): Boolean;
function GetCurrentUserSID(var SID: TBytes): Boolean;
function GetAdministratorsSID(out SID: TBytes): Boolean;
implementation
uses
JwaWinNT, Windows, uProcessInfo, uDebug;
Windows, uProcessInfo, uDebug;
var
GetNamedPipeClientProcessId: function(Pipe: HANDLE; ClientProcessId: PULONG): BOOL; stdcall;
@ -64,7 +64,6 @@ begin
// Allow to connect from parent process and same executable only
if ClientProcessId = GetParentProcessId(GetCurrentProcessId) then
begin
DCDebug('My: ', GetProcessFileName(GetCurrentProcess));
DCDebug('Client: ', GetProcessFileNameEx(ClientProcessId));
@ -75,29 +74,23 @@ begin
Result:= False;
end;
function GetCurrentUserSID(var SID: TBytes): Boolean;
function GetAdministratorsSID(out SID: TBytes): Boolean;
const
SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5));
var
ReturnLength: DWORD = 0;
TokenHandle: HANDLE = INVALID_HANDLE_VALUE;
TokenInformation: array [0..SECURITY_MAX_SID_SIZE] of Byte;
UserToken: TTokenUser absolute TokenInformation;
AdministratorsGroup: PSID = nil;
begin
Result:= OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, TokenHandle);
if not Result then
begin
if GetLastError = ERROR_NO_TOKEN then
Result:= OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle);
end;
Result:= AllocateAndInitializeSid(SECURITY_NT_AUTHORITY,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
AdministratorsGroup);
if Result then
begin
Result:= GetTokenInformation(TokenHandle, TokenUser,
@TokenInformation, SizeOf(TokenInformation), ReturnLength);
CloseHandle(TokenHandle);
if Result then
begin
SetLength(SID, GetLengthSid(UserToken.User.Sid));
CopySid(Length(SID), PSID(@SID[0]), UserToken.User.Sid);
end;
SetLength(SID, GetLengthSid(AdministratorsGroup));
CopySid(Length(SID), PSID(@SID[0]), AdministratorsGroup);
FreeSid(AdministratorsGroup);
end;
end;

View file

@ -5,11 +5,13 @@ unit uProcessInfo;
interface
uses
Classes, SysUtils, Windows;
Classes, SysUtils, JwaWinNT, Windows;
function GetParentProcessId(ProcessId: DWORD): DWORD;
function GetProcessFileName(hProcess: HANDLE): UnicodeString;
function GetProcessFileNameEx(ProcessId: DWORD): UnicodeString;
function GetTokenUserSID(hToken: HANDLE; out SID: TBytes): Boolean;
function GetProcessUserSID(hProcess: HANDLE; out SID: TBytes): Boolean;
implementation
@ -66,6 +68,33 @@ begin
end;
end;
function GetTokenUserSID(hToken: HANDLE; out SID: TBytes): Boolean;
var
ReturnLength: DWORD = 0;
TokenInformation: array [0..SECURITY_MAX_SID_SIZE] of Byte;
UserToken: TTokenUser absolute TokenInformation;
begin
Result:= GetTokenInformation(hToken, TokenUser, @TokenInformation,
SizeOf(TokenInformation), ReturnLength);
if Result then
begin
SetLength(SID, GetLengthSid(UserToken.User.Sid));
CopySid(Length(SID), PSID(@SID[0]), UserToken.User.Sid);
end;
end;
function GetProcessUserSID(hProcess: HANDLE; out SID: TBytes): Boolean;
var
hToken: HANDLE = 0;
begin
Result:= OpenProcessToken(hProcess, TOKEN_QUERY, hToken);
if Result then
begin
Result:= GetTokenUserSID(hToken, SID);
CloseHandle(hToken);
end;
end;
initialization
Pointer(GetProcessImageFileNameW):= GetProcAddress(GetModuleHandle('psapi.dll'),
'GetProcessImageFileNameW');