![]() |
Administratorrechte anfordern (Elevation/Impersonation ?)
Moin,
folgendes Szenario. Ich habe einen Dienst, nennen wir ihn Dienst.exe. Zum Installieren oder Deinstallieren benötigt man Administratorrechte (oder nicht?). Das ist bis hierhin unter Windows XP oder 2003 nicht weiter problematisch, unter Vista hingegen startet man bekanntermaßen ein Programm auch dann ohne Administratorrechte, wenn man als Administrator angemeldet ist. Wenn jetzt also ein Benutzer
Code:
aufruft, und nur dann, soll das Programm als Administrator ausgeführt werden. Vielleicht habe ich hier und in Google nur die falschen Suchworte benutzt, aber ich habe bislang nur die folgende Möglichkeit gefunden:
Dienst.exe -install
bzw. Dienst.exe -uninstall Über ein Vista-Manifest highestAvailablePrivileges anfordern. Das bedeutet, dass das Programm, von einem Benutzer gestartet, von diesem Administratorrechte anfordert, ist das richtig? Also als Administrator muss man einmal auf Bestätigen klicken, als normaler Benutzer muss man Benutzername/Passwort eingeben? Oder startet das Programm dann als normaler Benutzer gar nicht oder mit niedrigeren Rechten? Und was passiert nun, wenn der SCM das Programm als Dienst starten möchte? Hat es dann eventuell höhere Rechte als notwendig (SYSTEM-Account)? Mir war so, als hätte ich auch von der Möglichkeit gelesen, einem Dienst so wenig Rechte wie möglich (aber so viel wie nötig) zuzuweisen. Gibt es alternativ die Möglichkeit, die Rechte erst später anzufordern, nämlich dann, wenn sie benötigt werden, und das am besten so, dass es unter XP auch funktioniert (wenn das dort überhaupt möglich ist)? Das wäre mir nämlich eigentlich lieber. Ich habe von dem Stichwort Impersonation gehört, aber konnte nicht recht eine schöne Erklärung dazu finden, bzw. ob das der richtige Weg ist. Sachdienliche Hinweise werden mit Freude entgegengenommen, auch, ob eventuell eine komplett andere Vorgehensweise günstiger ist. |
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Hmm, ausnahmsweise pushe ich mal meinen Beitrag - da muss es doch jemanden geben, der sich damit auskennt :stupid:
|
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Zitat:
Zitat:
Ich sehe also keinen Grund, warum das 'Programm' von einem unterpriviligierten Benutzer verwendet werden sollte. Zitat:
Admin = kein UAC-Dialog, eingeschränktes Token Users = kein UAC-Dialog [highestAvailable] Admin = UAC-Dialog, komplettes Token Users = kein UAC-Dialog [requireAdministrator] Admin = UAC-Dialog, komplettes Token Users = UAC-Dialog mit Username/Passwort für Admin-Account requireAdministrator wäre wohl das gesuchte. Zitat:
Zitat:
|
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Danke, das hilft mir schon sehr weiter. Vermutlich hast du recht, eigentlich besteht keine Notwendigkeit, das Programm von einem normalen Benutzer ausführen zu lassen. Was mir noch nicht ganz klar ist: Ich bekomme also mit einem requireAdministrator im Manifest unter Vista das gewünschte Verhalten - gibt es eine direkt vergleichbare Möglichkeit unter XP, oder muss dort der Benutzer schon vorher als Administrator angemeldet sein bzw. das Programm explizit als Administrator ausführen? Vermutlich ist das nicht so wichtig, weil es keine Katastrophe wäre, zum Administrieren eines Rechners als Administrator eingeloggt zu sein, aber man weiß ja nie...
|
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Zitat:
ps: das hat aber eher mit Benutzerfreundlichkeit zu tun, die Installation als nicht-Admin würde ohnehin fehlschlagen. |
Re: Administratorrechte anfordern (Elevation/Impersonation ?
So, jetzt habe ich einfach mal das Manifest eingebunden und das funktioniert soweit. Also PowerShell geöffnet, Dienst.exe -install aufgerufen, UAC meldet sich mit einem nichtidentifizierten Programm (das wird erst richtig schön, wenn es signiert ist, oder?), ich bestätige... und es öffnet sich eine neue Konsole, gibt was aus und schließt die Konsole wieder. Plöd, man sieht ja gar nicht, was das Ding ausgibt, weil es zu schnell wieder zu ist und überhaupt lieber in die ursprüngliche Shell gehört. Ich schätze, da gibt es keine schöne Möglichkeit drumherum, außer indem es sich mit runas noch einmal selbst startet, oder? Aber ich dachte, um genau so eine Notwendigkeit zu vermeiden, gibt es jetzt diese Manifest-UAC-Geschichte...^^
Edit: Gerade gelesen unter IsUserAnAdmin(): Zitat:
|
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Zitat:
Ich schau später mal, ob ich meine Wrapper-Funktionen noch finde :) |
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Meinst du die:
Delphi-Quellcode:
////////////////////////////////////////////////////////////////////////////////
// Procedure : GetAdminSid // Author : NBe // Comment : function GetAdminSid: PSID; const // bekannte SIDs ... (WinNT.h) SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5)); // bekannte RIDs ... (WinNT.h) SECURITY_BUILTIN_DOMAIN_RID: DWORD = $00000020; DOMAIN_ALIAS_RID_ADMINS: DWORD = $00000220; begin Result := nil; AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, Result); end; //////////////////////////////////////////////////////////////////////////////// // Procedure : IsAdmin // Author : NBe // Comment : function IsAdmin: LongBool; var TokenHandle : THandle; ReturnLength : DWORD; TokenInformation : PTokenGroups; AdminSid : PSID; Loop : Integer; begin Result := False; TokenHandle := 0; if OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle) then try ReturnLength := 0; GetTokenInformation(TokenHandle, TokenGroups, nil, 0, ReturnLength); TokenInformation := GetMemory(ReturnLength); if Assigned(TokenInformation) then try if GetTokenInformation(TokenHandle, TokenGroups, TokenInformation, ReturnLength, ReturnLength) then begin AdminSid := GetAdminSid; for Loop := 0 to TokenInformation^.GroupCount - 1 do begin if EqualSid(TokenInformation^.Groups[Loop].Sid, AdminSid) then begin Result := True; Break; end; end; FreeSid(AdminSid); end; finally FreeMemory(TokenInformation); end; finally CloseHandle(TokenHandle); end; end; |
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Klingt toll ^^
Ich hab das mit ShellExecute und so jetzt mal getestet - das Programm mit Administratorrechten wird dann ja trotzdem nicht in derselben Konsole gestartet, wie unangenehm. Nun gibt es natürlich nicht viele Ausgaben im Normalfall, aber im Fehlerfall eben doch. Bei der ShellExecute-Geschichte seh ich gar keine Ausgabe, bei der requireAdministrator-Geschichte ist sie in einer eigenen Konsole, die sich sofort wieder schließt, da hast du nicht zufällig auch noch nen Tipp? In einer bereits als Administrator gestarteten PowerShell bekomme ich alle Ausgaben. Und dass ich beim Linken die Warnung
Code:
bekomme, klingt so, als ob da noch was nicht stimmt, aber Vista liest das Ding trotzdem richtig aus.
1>.\VistaAdmin.manifest : manifest authoring warning 81010002: Unrecognized Element "requestedPrivileges" in namespace "urn:schemas-microsoft-com:asm.v3".
Huch, roter Kasten, danke für den Code, Luckie. Damit kann ich schonmal gezielter Fehlermeldungen ausgeben als auf ERROR_ACCESS_DENIED zu warten. Aber irgendwie schmeckt mir die ganze Geschichte mit entweder fehlenden bzw. an der falschen Stelle stehenden Ausgaben, oder alternativ ohne UAC nicht so richtig. Naja, mal schauen. Edit: Ich habe den Code jetzt ausprobiert. Wenn ich das richtig sehe, gibt der Code ja nur an, ob man in der Gruppe Administratoren ist... Nicht, ob man Admin-Rechte hat. Da muss ich wohl noch etwas suchen, oder hoffen, dass NicoDE seine Wrapper-Funktionen findet ;) Und noch ein Edit: In Absprache mit meinem Kollegen ist es wohl besser, wenn wir auf UAC verzichten und stattdessen bei fehlenden Rechten nur eine Fehlermeldung ausgeben, weil man mit UAC ja eventuelle Fehlermeldungen nicht oder nur ungünstig lesen kann. Also wäre eine IsAdmin-Funktion inklusive Vista-Unterstützung das Beste (wobei ich zurzeit einfach schaue, ob das Öffnen des SCM mit ERROR_ACCESS_DENIED fehlschlägt, nicht schön, aber reicht im Endeffekt erstmal). |
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Zitat:
(sind genauso zu verwenden wie die API-Funktionen - die Fallbacks dürften auch das gleiche machen wie die jeweilige API *g*)
Delphi-Quellcode:
ps: Die von Luckie kopierte IsAdmin-Funktion enthält den klassischen Fehler (es wurde vergessen zu prüfen, ob die aktuelle Sid überhaupt im Token aktiviert ist).
unit TokenTools;
interface uses Windows; function CheckTokenMembership(TokenHandle: THandle; SidToCheck: PSID; out IsMember: BOOL): BOOL; stdcall; function SHTestTokenMembership(hToken: THandle; ulRID: ULONG): BOOL; stdcall; function IsUserAnAdmin(): BOOL; stdcall; implementation function GetAdvApi32Lib(): HMODULE; const ModuleName = 'ADVAPI32'; {$WRITEABLECONST ON} const ModuleHandle: HMODULE = HMODULE(nil); {$WRITEABLECONST OFF} begin Result := ModuleHandle; if Result = HMODULE(nil) then begin Result := LoadLibrary(ModuleName); if Result <> HMODULE(nil) then ModuleHandle := Result; end; end; function CheckTokenMembership(TokenHandle: THandle; SidToCheck: PSID; out IsMember: BOOL): BOOL; type TFNCheckTokenMembership = function(TokenHandle: THandle; SidToCheck: PSID; out IsMember: BOOL): BOOL; stdcall; {$WRITEABLECONST ON} const Initialized: Integer = 0; RealApiFunc: TFNCheckTokenMembership = nil; {$WRITEABLECONST OFF} type TAceHeader = packed record AceType : Byte; AceFlags: Byte; AceSize : Word; end; TAccessAllowedAce = packed record Header : TAceHeader; Mask : ACCESS_MASK; SidStart: DWORD; end; const ACL_REVISION = 2; DesiredAccess = 1; GenericMapping: TGenericMapping = ( GenericRead : STANDARD_RIGHTS_READ; GenericWrite : STANDARD_RIGHTS_WRITE; GenericExecute: STANDARD_RIGHTS_EXECUTE; GenericAll : STANDARD_RIGHTS_ALL ); var ClientToken: THandle; ProcessToken: THandle; SecurityDescriptorSize: Cardinal; SecurityDescriptor: PSecurityDescriptor; Dacl: PACL; PrivilegeSetBufferSize: ULONG; PrivilegeSetBuffer: packed record PrivilegeSet: TPrivilegeSet; Buffer: array [0..2] of TLUIDAndAttributes; end; GrantedAccess: ACCESS_MASK; AccessStatus: BOOL; begin if Initialized = 0 then begin RealApiFunc := TFNCheckTokenMembership( GetProcAddress(GetAdvApi32Lib(), 'CheckTokenMembership')); InterlockedIncrement(Initialized); end; if Assigned(RealApiFunc) then Result := RealApiFunc(TokenHandle, SidToCheck, IsMember) else begin Result := False; IsMember := False; ClientToken := THandle(nil); try if TokenHandle <> THandle(nil) then ClientToken := TokenHandle else if not OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, False, ClientToken) then begin ClientToken := THandle(nil); if GetLastError() = ERROR_NO_TOKEN then begin if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY or TOKEN_DUPLICATE, ProcessToken) then try if not DuplicateToken(ProcessToken, SecurityImpersonation, @ClientToken) then begin ClientToken := THandle(nil); end; finally CloseHandle(ProcessToken); end; end; end; if ClientToken <> THandle(nil) then begin SecurityDescriptorSize := SizeOf(TSecurityDescriptor) + SizeOf(TAccessAllowedAce) + SizeOf(TACL) + 3 * GetLengthSid(SidToCheck); SecurityDescriptor := PSecurityDescriptor( LocalAlloc(LMEM_ZEROINIT, SecurityDescriptorSize)); if SecurityDescriptor <> nil then try if InitializeSecurityDescriptor(SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION) then begin if SetSecurityDescriptorOwner(SecurityDescriptor, SidToCheck, False) then begin if SetSecurityDescriptorGroup(SecurityDescriptor, SidToCheck, False) then begin Dacl := PACL(SecurityDescriptor); Inc(PSecurityDescriptor(Dacl)); if InitializeAcl(Dacl^, SecurityDescriptorSize - SizeOf(TSecurityDescriptor), ACL_REVISION) then begin if AddAccessAllowedAce(Dacl^, ACL_REVISION, DesiredAccess, SidToCheck) then begin if SetSecurityDescriptorDacl(SecurityDescriptor, True, Dacl, False) then begin PrivilegeSetBufferSize := SizeOf(PrivilegeSetBuffer); Result := AccessCheck(SecurityDescriptor, ClientToken, DesiredAccess, GenericMapping, PrivilegeSetBuffer.PrivilegeSet, PrivilegeSetBufferSize, GrantedAccess, AccessStatus); if Result then IsMember := AccessStatus and (GrantedAccess = DesiredAccess); end; end; end; end; end; end; finally LocalFree(HLOCAL(SecurityDescriptor)); end; end; finally if (ClientToken <> THandle(nil)) and (ClientToken <> TokenHandle) then begin CloseHandle(ClientToken); end; end; end; end; function GetShell32Lib(): HMODULE; const ModuleName = 'SHELL32'; {$WRITEABLECONST ON} const ModuleHandle: HMODULE = HMODULE(nil); {$WRITEABLECONST OFF} begin Result := ModuleHandle; if Result = HMODULE(nil) then begin Result := LoadLibrary(ModuleName); if Result <> HMODULE(nil) then ModuleHandle := Result; end; end; function SHTestTokenMembership(hToken: THandle; ulRID: ULONG): BOOL; stdcall; type TFNSHTestTokenMembership = function(hToken: THandle; ulRID: ULONG): BOOL; stdcall; {$WRITEABLECONST ON} const Initialized: Integer = 0; RealApiFunc: TFNSHTestTokenMembership = nil; {$WRITEABLECONST OFF} const SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5)); SECURITY_BUILTIN_DOMAIN_RID = $00000020; var SidToCheck: PSID; begin if Initialized = 0 then begin RealApiFunc := TFNSHTestTokenMembership( GetProcAddress(GetShell32Lib(), 'SHTestTokenMembership')); InterlockedIncrement(Initialized); end; if Assigned(RealApiFunc) then Result := RealApiFunc(hToken, ulRID) else begin Result := AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, ulRID, 0, 0, 0, 0, 0, 0, SidToCheck); if Result then try if not CheckTokenMembership(THandle(nil), SidToCheck, Result) then Result := False; finally FreeSid(SidToCheck); end; end; end; function IsUserAnAdmin(): BOOL; const DOMAIN_ALIAS_RID_ADMINS = $00000220; type TFNIsUserAnAdmin = function(): BOOL; stdcall; {$WRITEABLECONST ON} const Initialized: Integer = 0; RealApiFunc: TFNIsUserAnAdmin = nil; {$WRITEABLECONST OFF} begin if Initialized = 0 then begin RealApiFunc := TFNIsUserAnAdmin( GetProcAddress(GetShell32Lib(), 'IsUserAnAdmin')); InterlockedIncrement(Initialized); end; if Assigned(RealApiFunc) then Result := RealApiFunc() else Result := SHTestTokenMembership(THandle(nil), DOMAIN_ALIAS_RID_ADMINS); end; end. pps: upz, da war aber noch nen blöder Fehler drin *g* |
Re: Administratorrechte anfordern (Elevation/Impersonation ?
So, ich danke dir für die Funktion. Ich habe sie übersetzt und soweit scheint sie zu funktionieren. Falls es jemanden interessiert (oder jemand Lust hat, meinen Code auf Fehler zu überprüfen :mrgreen:), so sieht sie jetzt bei mir aus:
Code:
Ich habe mir den letzten (und längsten) Fallback gespart, weil der laut Doku nur unterhalb von Windows 2000 nötig sein sollte, was Mindestanforderung für die Software ist. Wenn da jemand mehr weiß, immer her mit Informationen.
typedef BOOL(__stdcall *PIsUserAnAdminFunc)();
typedef BOOL(__stdcall *PSHTestTokenMembershipFunc)(HANDLE, ULONG); typedef BOOL(__stdcall *PCheckTokenMembershipFunc)(HANDLE, PSID, PBOOL); bool TService::IsUserAnAdmin() { static HMODULE hShellLib = LoadLibrary(L"SHELL32"); static PIsUserAnAdminFunc ApiIsUserAnAdmin = (PIsUserAnAdminFunc)GetProcAddress(hShellLib, "IsUserAnAdmin"); if (ApiIsUserAnAdmin) { return ApiIsUserAnAdmin(); } static PSHTestTokenMembershipFunc ApiTestTokenMembership = (PSHTestTokenMembershipFunc)GetProcAddress(hShellLib, "SHTestTokenMembership"); if (ApiTestTokenMembership) { return ApiTestTokenMembership(NULL, DOMAIN_ALIAS_RID_ADMINS); } static HMODULE hAdvApiLib = LoadLibrary(L"ADVAPI32"); static PCheckTokenMembershipFunc ApiCheckTokenMembership = (PCheckTokenMembershipFunc)GetProcAddress(hAdvApiLib, "CheckTokenMembership"); if (ApiCheckTokenMembership) { PSID sidToCheck; static SID_IDENTIFIER_AUTHORITY sidia = SECURITY_NT_AUTHORITY; if (AllocateAndInitializeSid(&sidia, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sidToCheck)) { BOOL bIsMember; bool bSuccess = ApiCheckTokenMembership(NULL, sidToCheck, &bIsMember); FreeSid(sidToCheck); if (bSuccess) { return bIsMember; } } } return false; } |
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Zitat:
Sicherheitshalber könte man es gleich so schreiben:
Code:
static PIsUserAnAdminFunc ApiIsUserAnAdmin = (PIsUserAnAdminFunc)
GetProcAddress(LoadLibrary(L"SHELL32"), "IsUserAnAdmin"); Zitat:
|
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Mir ist auch aufgefallen, dass man sich die HMODULE-Dinger sparen könnte, weil damit ja nicht viel gemacht wird, aber statische Variablen innerhalb einer Funktion sollten eigentlich auch in der Reihenfolge ihrer Definition initialisiert werden. Unser ISO-C++-Auswendigkenner ist gerade nicht anwesend, ich frag ihn bei Gelegenheit mal :mrgreen:
|
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Zitat:
Zitat:
|
Re: Administratorrechte anfordern (Elevation/Impersonation ?
Moin !
@NicoDE: Vielen Dank für die TokenTools! Funktioniert auch unter Windows 7 ganz vorzüglich. :feuerchen: |
AW: Re: Administratorrechte anfordern (Elevation/Impersonation ?
Liebe Leute, bitte verzeiht mir, daß ich diese "Asbach-Diskussion" reanimiere.
Zitat:
Auch wenn sie genaugenommen fehlerhaft sein mag, so hat sie (bei mir jedenfalls) bisher immer das gewünschte, also korrekte Ergebnis geliefert. Tut sie das aber immer? Falls, ja, dann ist sie "phänomenologisch" eben doch richtig. Oder sind Konstellationen möglich (nichtaktivierte SID im Token?), bei der ihre Fehlerhaftigkeit eben doch durchschlägt? |
AW: Re: Administratorrechte anfordern (Elevation/Impersonation ?
Zitat:
Es macht einen erheblichen Unterschied, ob ich im aktuellen Kontext administrative Rechte habe oder haben könnte. Im letzteren Fall würde man eher etwas wie "IsUserInAdminGroup" im ![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:11 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz