Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Neuen Beitrag zur Code-Library hinzufügen (https://www.delphipraxis.net/33-neuen-beitrag-zur-code-library-hinzufuegen/)
-   -   Delphi Service: UAC Elevated Child Prozess im Benutzerkontext starten (https://www.delphipraxis.net/160355-service-uac-elevated-child-prozess-im-benutzerkontext-starten.html)

Zacherl 9. Mai 2011 17:37

Service: UAC Elevated Child Prozess im Benutzerkontext starten
 
Hallo zusammen,

nachdem ich nun fast 4 Stunden herumprobiert habe, poste ich hier mal eine Lösung, mit der man aus einem Dienst heraus einen Prozess mit UAC Elevation starten kann. Ist der momentan eingeloggte Benutzer kein Administrator, wird der Prozess mit eingeschränkten Rechten gestartet.

Das Besondere an dieser Lösung ist, dass der neu erzeugte Prozess auch wirklich im Kontext des Benutzers und nicht als SYSTEM läuft.

Delphi-Quellcode:
function OpenShellProcessToken(ProcessName: String;
  var hToken: THandle): Boolean;
var
  hSnapshot,
  hProcess: THandle;
  Process: TProcessEntry32;
begin
  Result := false;
  hSnapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnapshot <> 0) and (hSnapshot <> INVALID_HANDLE_VALUE) then
  try
    FillChar(Process, SizeOf(Process), #0);
    Process.dwSize := SizeOf(Process);
    if Process32First(hSnapshot, Process) then
    repeat
      if (AnsiLowerCase(Process.szExeFile) =
        AnsiLowerCase(ProcessName)) then
      begin
        hProcess :=
          OpenProcess(PROCESS_ALL_ACCESS, false, Process.th32ProcessID);
        if (hProcess <> 0) and (hProcess <> INVALID_HANDLE_VALUE) then
        try
          Result := OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, hToken);
        finally
          CloseHandle(hProcess);
        end;
        Break;
      end;
    until (not Process32Next(hSnapshot, Process));
  finally
    CloseHandle(hSnapshot);
  end;
end;

function CreateProcessElevated(lpApplicationName: PChar; lpCommandLine: PChar;
  lpCurrentDirectory: PChar; var ProcessInfo: TProcessInformation): Boolean;
var
  WTSGetActiveConsoleSessionId: function: DWord; stdcall;
  WTSQueryUserToken: function(SessionId: ULONG;
    var phToken: THandle): BOOL; stdcall;
  CreateEnvironmentBlock: function(lpEnvironment: PPointer; hToken: THandle;
    bInherit: BOOL): BOOL; stdcall;
  DestroyEnvironmentBlock: function(lpEnvironment: LPVOID): BOOL; stdcall;
var
  hUserToken,
  hLinkedToken,
  hElevatedToken: THandle;
  ReturnLength,
  ElevationType: DWord;
  Environment: Pointer;
  StartupInfo: TStartupInfo;
begin
  Result := false;
  @CreateEnvironmentBlock :=
    GetProcAddress(LoadLibrary('userenv.dll'), 'CreateEnvironmentBlock');
  @DestroyEnvironmentBlock :=
    GetProcAddress(LoadLibrary('userenv.dll'), 'DestroyEnvironmentBlock');
  if (not Assigned(CreateEnvironmentBlock)) or
    (not Assigned(DestroyEnvironmentBlock)) then Exit;
  @WTSGetActiveConsoleSessionId :=
    GetProcAddress(LoadLibrary('kernel32.dll'), 'WTSGetActiveConsoleSessionId');
  @WTSQueryUserToken :=
    GetProcAddress(LoadLibrary('wtsapi32.dll'), 'WTSQueryUserToken');
  if (Assigned(WTSGetActiveConsoleSessionId) and
    Assigned(WTSQueryUserToken)) then
  begin
    Result := WTSQueryUserToken(WTSGetActiveConsoleSessionId, hUserToken);
  end else
  begin
    Result := OpenShellProcessToken('explorer.exe', hUserToken);
  end;
  if Result then
  try
    if GetTokenInformation(hUserToken, TokenElevationType, @ElevationType,
      SizeOf(ElevationType), ReturnLength) then
    begin
      if (ElevationType = 3) then
      begin
        if GetTokenInformation(hUserToken, TokenLinkedToken,
          @hLinkedToken, SizeOf(hLinkedToken), ReturnLength) then
        try
          Result := DuplicateTokenEx(hLinkedToken, MAXIMUM_ALLOWED, nil,
            SecurityImpersonation, TokenPrimary, hElevatedToken);
        finally
          CloseHandle(hLinkedToken);
        end;
      end else
      begin
        hElevatedToken := hUserToken;
      end;
      try
        if CreateEnvironmentBlock(@Environment, hElevatedToken, false) then
        try
          FillChar(StartupInfo, SizeOf(StartupInfo), #0);
          StartupInfo.cb := SizeOf(StartupInfo);
          Result := CreateProcessAsUser(hElevatedToken, lpApplicationName,
            lpCommandLine, nil, nil, false, CREATE_NEW_CONSOLE or
            CREATE_DEFAULT_ERROR_MODE or CREATE_UNICODE_ENVIRONMENT,
            Environment, lpCurrentDirectory, StartupInfo, ProcessInfo);
        finally
          DestroyEnvironmentBlock(Environment);
        end;
      finally
        CloseHandle(hElevatedToken);
      end;
    end;
  finally
    CloseHandle(hUserToken);
  end;
end;
Lauffähig sollte das ganze ab Windows 2000 sein.

Viele Grüße
Zacherl

rollstuhlfahrer 9. Mai 2011 21:03

AW: Service: UAC Elevated Child Prozess im Benutzerkontext starten
 
Zitat:

Zitat von Zacherl (Beitrag 1099795)
Delphi-Quellcode:
    if GetTokenInformation(hUserToken, TokenElevationType, @ElevationType,
      SizeOf(ElevationType), ReturnLength) then
    begin
      if (ElevationType = 3) then
      begin
        if GetTokenInformation(hUserToken, TokenLinkedToken,
          @hLinkedToken, SizeOf(hLinkedToken), ReturnLength) then
        try
          Result := DuplicateTokenEx(hLinkedToken, MAXIMUM_ALLOWED, nil,
            SecurityImpersonation, TokenPrimary, hElevatedToken);
        finally
          CloseHandle(hLinkedToken);
        end;

Das ist also der Code, um dem neuen Prozess Admin-Rechte zu verschaffen? - Das hab ich jetzt nicht wirklich verstanden: Du erstellst ein User-Token. Dazu erstellst du ein 2. Token, welches mit dem ersten verknüpft ist. Dann erstellst du ein 3. Token, welches aus dem 2. Token dupliziert wurde. Soweit komme ich schon nicht mehr mit. ABER: Jetzt schließt du vorzeitig das 2. Token. Das will mir nicht in den Kopf.

Aber egal, Hauptsache der Code funktioniert. Mich würde natürlich trotzdem gerne interessieren, bei welcher Zeile jetzt dem System gesagt wurde, dass der Prozess Admin-Rechte bekommt. Und was mich auch brennend interessiert ist, was passiert, wenn eine Anwendung mit Admin-Manifest ohne Admin-Rechte gestartet wurde.

Übrigens: Da ist ein größerer Fehler drin: Laut MSDN kommt in ElevationType eine Zahl ungleich Null rein, wenn es Admin-Rechte zu verteilen gibt, ansonsten kommt da Null rein. Die Prüfung auf 3 ist somit nicht 100%ig sicher!

Zacherl 9. Mai 2011 22:42

AW: Service: UAC Elevated Child Prozess im Benutzerkontext starten
 
Zitat:

Zitat von rollstuhlfahrer (Beitrag 1099839)
Das ist also der Code, um dem neuen Prozess Admin-Rechte zu verschaffen? - Das hab ich jetzt nicht wirklich verstanden: Du erstellst ein User-Token. Dazu erstellst du ein 2. Token, welches mit dem ersten verknüpft ist. Dann erstellst du ein 3. Token, welches aus dem 2. Token dupliziert wurde. Soweit komme ich schon nicht mehr mit. ABER: Jetzt schließt du vorzeitig das 2. Token. Das will mir nicht in den Kopf.

UserToken ist das Standardtoken vom aktuell eingeloggten Benutzer. Jedes Benutzertoken hab ab Vista laut diversen Seiten ein LinkedToken. Dieses muss man verwenden, um den Prozess UAC elevated zu starten.
Ich konvertiere das Token dann in ein Primary Token namens ElevatedToken. Danach kann ich das "alte" Token ja ohne weiteres schließen :) Das Neue bleibt ja erhalten.

Zitat:

Zitat von rollstuhlfahrer (Beitrag 1099839)
Und was mich auch brennend interessiert ist, was passiert, wenn eine Anwendung mit Admin-Manifest ohne Admin-Rechte gestartet wurde.

Das habe ich noch gar nicht ausprobiert.

Zitat:

Zitat von rollstuhlfahrer (Beitrag 1099839)
Übrigens: Da ist ein größerer Fehler drin: Laut MSDN kommt in ElevationType eine Zahl ungleich Null rein, wenn es Admin-Rechte zu verteilen gibt, ansonsten kommt da Null rein. Die Prüfung auf 3 ist somit nicht 100%ig sicher!

Da hast du dich verguckt. Ich frage nicht die TokenElevation, sondern die TokenElevationType Klasse ab. Der MSDN Eintrag dazu ist hier zu finden:
http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx

Wobei ich sehe grade, dass ich den Typen als DWord deklariert habe. Ist das korrekt für ein C++ Enum oder muss da besser Byte hin?

rollstuhlfahrer 9. Mai 2011 22:53

AW: Service: UAC Elevated Child Prozess im Benutzerkontext starten
 
Zitat:

Zitat von Zacherl (Beitrag 1099873)
Da hast du dich verguckt. Ich frage nicht die TokenElevation, sondern die TokenElevationType Klasse ab. Der MSDN Eintrag dazu ist hier zu finden:
http://msdn.microsoft.com/en-us/libr...(v=vs.85).aspx

Nehm ich zurück.

Zitat:

Zitat von Zacherl (Beitrag 1099873)
Wobei ich sehe grade, dass ich den Typen als DWord deklariert habe. Ist das korrekt für ein C++ Enum oder muss da besser Byte hin?

Da scheinen sich die Meinungen zu spalten. Laut diesem Thread ist ein Enum 4 Bytes groß, also sollte DWord passen.

ADD: Es scheint dabei aber dem Compiler überlassen zu sein, wie groß der Typ dann wirklich ist (siehe Stackoverflow).

Bernhard

Zacherl 9. Mai 2011 22:56

AW: Service: UAC Elevated Child Prozess im Benutzerkontext starten
 
Danke dir fürs nachschlagen, dann werd ichs einfach mal so lassen. Habe zwar bisher nur unter Win7 64 bit getestet, aber da funktionierts schonmal wunderbar :)

Dezipaitor 9. Mai 2011 23:14

AW: Service: UAC Elevated Child Prozess im Benutzerkontext starten
 
Glückwunsch für diesen Akt. Ich will deine Freude nicht schmälern, aber den Code hättest du sicher auch mit etwas Suche und Geduld hier in irgendeiner Form gefunden. Trotzdem ist die daraus gewonnene Erkenntnis und Erfahrung unersetzlich und wird dir bei ähnlichen Problemen helfen.



Wenn du eine Anwendung mit Manifest, aber ohne Admintoken in CreateProcessAsUser starten willst, wird CPAU fehlschlagen und die Fehlermeldung ERROR_ELEVATION_REQUIRED (740) über GetLastError zurückmelden. Dann muss man ShellExecute (nicht im Dienst, sondern über extra Prozess!) verwenden.

Jedes Tokenn besitzt ein Feld LinkedToken, das ein weiteres Token enthält. Wenn UAC aktiv ist, dann bekommt man darüber das Admintoken. Das Admintoken hat auch so ein Feld und man bekommt darüber das eingeschränkte Token. Es ist also ein Ringschluss. Nun ist es aber dennoch möglich auch als normaler Benutzer das Admintoken über dieses Feld zu laden, jedoch verweigert CPAU und ImpersonateLoggedOnUser die Nutzung. Mehr Info siehe dazu in der Hilfe zur letztgenannten Funktion.

Enums haben tatsächlich keine exakte Größe, wenn man allgemein spricht. Man kann aber Compilern normalerweise irgendwie mitteilen, wie groß mindestens ein Enum sein soll, z.B. in Delphi mit $MINENUMSIZE (oder ähnlich). MS C++ Compiler kann es auch und das Windows SDK nutzt es auch. Am sichersten kannst du es sehen, indem du den Datentyp in C mit sizeof auf dessen Größe prüfst.

kuba 26. Jul 2013 00:07

AW: Service: UAC Elevated Child Prozess im Benutzerkontext starten
 
Zitat:

Zitat von rollstuhlfahrer (Beitrag 1099839)
Das ist also der Code, um dem neuen Prozess Admin-Rechte zu verschaffen?

Nee, steht doch im Einleitungstext. Der Prozess wird lediglich UAC-Elevated gestartet...

Zitat:

Zitat von Zacherl (Beitrag 1099795)
Ist der momentan eingeloggte Benutzer kein Administrator, wird der Prozess mit eingeschränkten Rechten gestartet.

Habe gerade ein paar Tests gemacht ...

KUBA


Alle Zeitangaben in WEZ +2. Es ist jetzt 03:54 Uhr.

Powered by vBulletin® Copyright ©2000 - 2021, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2021 by Daniel R. Wolf