Wie einen Prozess unter Vista mit gehobenen Rechten starten?

Was macht man eigentlich wenn man einen Prozess unter Vista hat, der mit normalen Rechten arbeitet, nun aber einen Prozess starten möchte, der angehobene Rechte (elevated) benutzen soll? Also quasi eine Abkürzung für „rechter Mausklick -> Als Administrator ausführen“.

Das ist ausgesprochen einfach! Man verwendet ShellExecute oder ShellExecuteEx mit dem neuen Verb „runas“.

Der nachfolgende Code startet die Systemsteuerung mit angehobenen (administrativen/elevated) Rechten. Vorher erfolgt natürlich die allseits bekannte UAC Nachricht. Ist der User kein Administrator bekommt er eine Meldung, dass er sich entsprechend anmelden soll. Ansonsten muss er dem Zugriff zustimmen. Bricht der User den Dialog ab wird nichts ausgeführt.

int _tmain(int argc, _TCHAR* argv[])
{
  SHELLEXECUTEINFO shExecInfo;
  
  shExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
  shExecInfo.fMask = NULL;
  shExecInfo.hwnd = NULL;
  shExecInfo.lpVerb = _T(„runas“);
  shExecInfo.lpFile = _T(„control.exe“);
  shExecInfo.lpParameters = NULL;
  shExecInfo.lpDirectory = NULL;
  shExecInfo.nShow = SW_SHOW;
  shExecInfo.hInstApp = NULL;
  
  ShellExecuteEx(&shExecInfo);
  return 0;
}

❗ PS: Übrigens versteht auch Windows 2003 Server und Windows XP das „runas“ Verb. In diesem Fall wird der entsprechende Anmeldedialog gezeigt, der es erlaubt den aktuellen Benutzer zu verwenden (mit evtl. eingeschränkten Rechten), oder eben einen anderen Account. Nett! 🙂
Nur scheinbar auch ein Feature, dass wieder mal nicht dokumentiert ist.

PRJ0041 in einem simplen Win32 Projekt in VC-2005

Ich habe ein ganz billiges Win32 API Projekt, dass auf den Bugslayer Utilities von John Robbins basiert. Das Projekt wurde jetzt auf VC-2005 umgestellt.

Keine Probleme aber eigentümliche Meldungen PRJ0041 im Buildlog:

Project : warning PRJ0041 : Cannot find missing dependency ‚winwlm.h‘ for file ‚MyProject.rc‘.  Your project may still build, but may continue to appear out of date until this file is found.
Project : warning PRJ0041 : Cannot find missing dependency ‚macwin32.h‘ for file ‚MyProject.rc‘.  Your project may still build, but may continue to appear out of date until this file is found.
Project : warning PRJ0041 : Cannot find missing dependency ‚macwin32.h‘ for file ‚MyProject.rc‘.  Your project may still build, but may continue to appear out of date until this file is found.
Project : warning PRJ0041 : Cannot find missing dependency ‚macwin32.h‘ for file ‚MyProject.rc‘.  Your project may still build, but may continue to appear out of date until this file is found.
und die Liste geht noch weiter

Die meisten dieser Dateien sind irgendwie für den MAC bestimmt. und im normalen SDK gar nicht vorhanden. Scheinbar kommt der Parser des VS-2005 mit diesen Dateien irgendwienicht klar.

Ausgelöst werden sie offensichtlich durch dem #include der windows.h in den Read-Only Symbols directives:

#define APSTUDIO_HIDDEN_SYMBOLS
#include <windows.h>
#undef APSTUDIO_HIDDEN_SYMBOLS
#include <winver.h>

Ich habe alles mögliche versucht diese Fehler weg zu bekommen, aber irgendwie hat alles nichts gebracht. Und langsam wurde ich ungeduldig länger als 30 Minuten an so einem Seiteneffekt zu verbringen.
Schließlich habe ich zu einem kleinen Trick gegriffen und den Parser überlistet:

#define APSTUDIO_HIDDEN_SYMBOLS
/**/ #include <windows.h>
#undef APSTUDIO_HIDDEN_SYMBOLS
/**/ #include <winver.h>

Jetzt ignoriert der Parser die entsprechenden Include-Dateien und das Projekt erzeugt keine Warnungen mehr und wen interessieren schon die Dependencies der windows.h 🙂 ?

Vista beendet Programme auch wenn WM_QUERYENDSESSION FALSE returniert

Jeder kennt die nette Nachricht WM_QUERYENDSESSION die einem mitteilt, dass das System herunter gefahren wird, oder der Benutzer sich abmeldet.

Und wir alle sind auch einfach gewöhnt FALSE zu returnieren wenn wir das nicht wollen und damit waren wir sicher das WM_ENDSESSION mit FALSE gesendet wird und das System nicht herunterfährt. Ja! Richtig gelesen waren!

Das hat sich mit Vista nun auch geändert. Wird im lParam Bit ENDSESSION_CLOSEAPP (0x1) gesetzt, dann spielt es keine Rolle wie das Programm es gerne hätte. Die Applikation soll und muss beendet werden. D.h. die Applikation muss auch mit dem Zustand zurecht kommen, dass die Daten nicht gesichert sind, bzw. eben mit dem Zustand, der uns veranlasst hat FALSE zu returnieren.
Dies war bisher ausgeschlossen. Deshalb sollte man sich über das Verhalten seines Programmes an dieser Stelle mal Gedanken machen.

Für MFC Anwender noch eine kleine Herausforderung denn OnQueryEndSession wird von der MFC ohne Parameter aufgerufen und ohne das Vista SDK ist natürlich ENDSESSION_CLOSEAPP nicht definiert

#ifndef ENDSESSION_CLOSEAPP
#define ENDSESSION_CLOSEAPP 0x1
#endif

BOOL CMainFrame::OnQueryEndSession()
{
// Need to get the lParam value for this message to
// determine the reason of the shutdown
LPARAM lParam = AfxGetCurrentMessage()->lParam;
if (lParam & ENDSESSION_CLOSEAPP)
{
  // We are forced to exit here, the user want to close the
  // application, even if we loose data
  OnForcedShutdown():
  return TRUE;
}
else
  // do the default
  return __super::OnQueryEndSession();
}

BTW: Eine normale MFC Applikation berücksichtigt dies nicht korrekt. Es wird der Status von allen Dokumenten geprüft und evtl. einen entsprechender Dialog angezeigt, wenn eines der Dokumente nicht gespeichert ist. Wird aber die Anfrage mit ENDSESSION_CLOSEAPP gesendet, dann muss die Antwort in 5 Sekunden erfolgen. Einen Dialog anzuzeigen bzw. Frage zu stellen wie es die Standardimplementierung macht, passt hier nicht!

Man sollte sich unbedingt hier die Vista Änderungen zu Gemüte führen und gegebenenfalls auch ShutdownBlockReasonCreate implementieren.

Weitere Infos zu den Änderungen findet sich in der aktuellen MSDN:

Anmerkung: Jeder der sich mit der Vista-Zertifizierung auseinandergesetzt hat wird erkannt haben, dass es sich bei dem hier beschriebenen Verhalten um den Test Case 30 handelt, bzw. die Requirements, Reliability 3.1!

Ermitteln ob ein Prozess „als Administrator“ läuft oder nicht

Es gibt einigen Code im Netz, der ermittelt ob man administrative Rechte hat oder eben nicht.
Dazu wurden zwei Methoden meistens verwendet. Die meisten prüften ob im aktuellen Token der vordefinierte SID für den Administrator vorhanden war. Oder man verwendete NetUserGetInfo.
Besagte klassische Methoden sind auf Felix Kasza’s Win32-MPV Seiten (noch)* nachzulesen.

Leider berücksichtigt dieser Code nicht, dass es unter Vista ein sogenanntes Restricted Token geben kann. Theoretisch ist dies schon seit Windows 2000 und auch unter XP möglich. Nur richtig problematisch wird dies unter Vista, denn diese Restricted Token werden hier auch entsprechend verwendet.
Meldet man sich als Admin unter Vista an, dann meldet der Beispielcode, den man unter dem obigen Link findet, immer, dass der Benutzer Administrative Rechte hat! Egal ob nun der Prozess mit angehobenen oder ohne angehobene Rechte gestartet wurde.

Will also ein Programm das ein Trustinfo Manifest mit asInvoker hat wissen, ob es nun evtl. doch Als Administrator ausgeführt wird oder nicht, muss man das Restricted Token berücksichtigen. Dies macht der Code der in dem überarbeiteten KB-Artikel 118626 veröffentlicht wurde. Dieser Artikel hat in der Vergangenheit, die gleiche Methode verwendet die auch Felix Kasza’s (noch)* veröffentlicht.
Der neue Code den wir nun in dem KB-Artikel finden baut eine DACL auf mit der ACE des lokalen Admins, diese wird dann mit der Funktion AccessCheck gegen das aktuelle Token geprüft. Dieser Code ist nicht trivial und ist gut dokumentiert. Man sollte ihn sich wirklich mal komplett durchlesen. 

Das sehr interessante an diesem Code ist auch, dass er mit und ohne Trustinfo Manifest funktioniert. Er liefert TRUE wenn der Prozess mit administrativen Rechten läuft und FALSE wenn eben nicht.

Für was ist so etwas gut?
Nun man kann in seinem Programm auf diese Weise direkt ermitteln ob bestimmte Funktionen die z.B. schreibend auf die HKLM zugreifen erlaubt sind oder nicht. Sicher man könnte auch ein „Probe-Schreiben“, aber der in dem KB-Artikel beschriebene Weg ist mit Sicherheit eleganter.
Wobei das „Probe-Schreiben“ auch ein Trustinfo Manifest voraussetzt.

BTW: Der Code in dem KB-Artikel hat (noch)* einen lästigen Fehler. Er definiert ACCESS_READ und ACCESS_WRITE also const DWORD. Das ist unnötig mit den aktuellen Headern. Die Fehlermeldungen sind nicht ganz einfach zu verstehen. Am Besten einfach die beiden Zeilen auskommentieren.

*(noch) beschreibt den Zustand der entsprechenden Artikel, als ich diesen Beitrag schrieb. Das mag sich ändern, weil ich bereits entsprechende Emails über die Probleme mit den Beispielen geschrieben habe.

CreateStreamOnHGlobal und GlobalAlloc

Man könnte diesen Artikel auch den folgenden Titel „Was passieren kann, wenn man die Dokumentation nicht richtig liest!“ geben. 😈

Dieser Code

HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE,iMaximumSize);
if (!hMem)
    AfxThrowMemoryException();
LPVOID pImage = ::GlobalLock(hMem);
int iSize = FillBufferWithData(pImage);
::GlobalUnlock(hMem);
CComPtr<IStream> spStream;
HRESULT hr = ::CreateStreamOnHGlobal(hMem,FALSE,&spStream);
if (SUCCEEDED(hr))
{
    // Limit the stream to its real size
    ULARGE_INTEGER ulSize;
    ulSize.QuadPart = iSize;
    VERIFY(SUCCEEDED(spStream->SetSize(ulSize)));
    // Do whatever has to be done
   DoSomethingWithStream(spStream);
}
// Release Stream
spStream = NULL;
// Free memory WITH A RANDOM CRASH
::GobalFree(hMem);

sieht ganz normal aus. Eigentümlicher Weise passiert es manchmal, genauer gesagt recht selten, dass der GlobalFree fehlschlägt. Der Application Verifier meldet einen inkorrekten Heap Block. Aber wie das. Der Block wird mit GlobalAlloc allokiert an CreateStreamOnHGlobal übergeben und freigegeben nachdem der Stream entsorgt wurde.

Was ist die Ursache für das Problem? Nach einigem Prüfen und Tests kam ich dahinter, dass offensichtlich nach einer Reallocation des Speichers mein Heap-Block ungültig wird.
Erstaunlich ist, dass SetSize hier nicht den Speicherbedarf vergrößert sondern nur den Stream auf die korrekte Größe verkleinert! Dennoch… unter bestimmten Umständen wird hier eine Reallocation vorgenommen.
Alles Rumexperimentieren nützt nichts. Der Fehler oder das Problem in diesem Stück Code wird auch nicht vom stundenlangen Ansehen und Debuggen nicht klarer.

Also nochmal richtig RTFM (Read the fine MSDN) und dort finden wir diesen netten Absatz:
hGlobal [in] Memory handle allocated by the GlobalAlloc function. The handle must be allocated as ➡ movable and nondiscardable.

Und nun wird klar wo der Fehler liegt. Der Speicherblock der mit GMEM_FIXED alloziert wurde kann nicht realloziert und vergrößert werden. CreateStreamOnHGlobal reagiert nicht mit einem Fehler, wenn man den Block mit GMEM_FIXED allokiert. Aber das hat später evtl. einen sehr schwierig zu lokalisierenden Fehler zur Folge.

Also flink die Allokation in GMEM_MOVEABLE geändert und siehe da. Das Handle bleibt erhalten, der GlobalFree schlägt nicht mehr fehl.

Die Dokumentation ist an dieser Stelle aber auch wirklich nicht sehr auffällig. 😐
Deshalb habe ich einen Verbesserungsvorschlag dazu gemacht:
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=260866

Anmerkung: Der Ursprüngliche Code verwendete CHeapPtr<BYTE,CGlobalAllocator>, weil ich es liebe wenn Destruktoren aufräumen. Der CGlobalAllocator verwendet auch immer GMEM_FIXED, was das ganze noch etwas unübersichtlicher gemacht hat, denn wer denkt schon dran wie eine solche Wrapper-Klasse Speicher allokiert.

BTW: Auch hier führte der Application Verifier auf die korrekte Spur.

Notwendigkeit von Manifesten für DLL’s mit VC-2005

Durch eine Anfrage in meiner Lieblingsgruppe microsoft.public.de.vc bin ich auf folgende interessante Frage gestoßen: Benötigt eine DLL die mit VC-2005 erzeugt wurde ein Manifest?

Nun wie viele Antworten im Leben lässt sich dies nicht eindeutig mit Ja oder Nein beantworten.

Nein! Es wird kein Manifest benötigt, wenn die CRT statisch gelinkt wird. In diesem Fall wird auch kein Manifest benötigt zumindest nicht für die CRT, was nicht heißt, dass nicht auch andere Assemblies per Manifest angebunden werden müssen.

Nein! Wenn die EXE-Datei bereits über ein Manifest verfügt und eine CRT-DLL mit gleichem Namen lädt, hier also z.B. die MSVCR80.DLL. In diesem Fall kann man sich ein Manifest sparen. Es kann aber hier spannend werden, weil die entsprechende DLL natürlich so nicht beeinflussen kann dir 8.0 CRT als SP1 oder RTM zu nutzen. Aber vermutlich ist das sowieso egal.

Ja! Die DLL benötigt ein Manifest, wenn ein beliebiges Programm die DLL nutzt. Zum Beispiel eines, das die CRT statisch linkt, oder das mit einer älteren VC Version erzeugt wurde. In diesem Fall muss die DLL zwingend ein Manifest haben. Und jetzt wird es noch strenger. Wird die DLL per LoadLibrary geladen, dann muss dieses Manifest sogar embedded sein! 🙄
Externe Manifeste bei DLLs werden nur beim impliziten Laden berücksichtigt. Wird eine DLL mit LoadLibrary geladen, dann werden nur eingebette Manifeste berücksichtigt.

❗ Falle: Hat die DLL kein Manifest wird immer garantiert, dass die CRT Version von EXE und DLL passen. Man kann also gefahrlos Speicher in der DLL allozieren und in der EXE freigeben und umgekehrt. Oder eben auch CRT Objekte austauschen. Nutzt die DLL ein eigenes Manifest besteht die Möglichekit, dass EXE und DLL eine unterschiedliche CRT verwenden! Und noch spannender wird es wenn eine EXE mit CRTx eine DLL1 benutzt die per Manifest CRTy verwendet und diese nun eine DLL ohne Manifest lädt… Ja es wird die CRT der EXE verwendet…

Ja ja. Wir haben dank der Manifeste nun keine DLL Hölle mehr. Wir haben eine Manifest Hölle 😀

Meine Empfehlung daher:

  1. Wenn es eigenständige kleine Module sind, dann statisch gegen die CRT linken.
  2. Ansonsten immer ein Manifest einbetten, wenn die DLL eigenständig genutzt wird!
  3. Wird die DLL im Kontext einer bestimmten EXE(s) Datei verwendet, sollte nur die EXE(s) ein  Manifest haben.
  4. In jedem Fall darauf achten, dass gleiche CRT Versionen verwendet werden. RTM, SP1 und irgendwann wahrscheinlich SP2 werden hier Konflikte möglich machen.
  5. MFC Extension DLLs müssen zwingend mit der selben MFC Version (RTM oder SP1) verwendet werden!

Stoff zum weiterlesen:
Isolated Applications and Side-by-side Assemblies

Besonders lesenswert hier der Beitrag von Nicola Dudar:
How to Debug ‚The System cannot Execute the specified program‘ message

Der VirtualStore und seine Schattenseiten

Es ist ja bekannt, dass Programme, die nicht mit einem entsprechenden Trustinfo Manifest ausgestattet sind, unter Vista besonders behandelt werden. Werden diese Programme nicht im administrativen Rechten ausgeführt werden alle Zugriffe auf das Verzeichne C:\Program Files und auf HKEY_LOCAL_MACHINE\Software in der Registry umgeleitet.

Ein Registry Zugriff landet dann in HKEY_CURRENT_USER auf HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\SOFTWARE. Ein Dateizugriff auf C:\Program Files wird umgeleitet nach C:\Users\Username\AppData\Local\VirtualStore\Program Files.

Was passiert aber nun, wenn der Administrator nun tatsächlich die „richtigen“ Daten in HKLM und C:\Program Files ändert UND es existieren bereits entsprechende Daten im VirtualStore?

Die Antwort ist logisch! Eine Änderung der Daten bleibt ohne Wirkung! Denn die Daten aus dem VirtualStore werden hier mit Vorrang behandelt. Sonst macht das ganze ja keinen Sinn, denn der VirtualStore soll ja scheinbar Schreibzugriffe erlauben.

Im Klartext: Existieren Daten sowohl im VirtualStore als auch in den eigentlichen originalen Positionen, dann werden immer die Daten des VirtualStore verwendet. Nur wenn der entsprechende Registry Key nicht vorhanden/gelöscht wird oder die entsprechende Datei nicht vorhanden/gelöscht ist, dann werden die Daten aus den original Verzeichnissen bzw. Registryästen verwendet.

Nur daraus folgt ein Problem:
Macht der Admin Korrekturen an den realen Daten UND es existieren Daten im VirtualStore, dann bleiben die Änderungen des Admins wirkungslos, bis er die Daten aus dem VirtualStore entfernt. Das kann eine spannende Aufgabe werden.
Vielleicht ist dies eine profane Erkenntnis, aber die Implikationen sind meines Erachtens immens.

PS: Gemein ist einfach, dass Software die bisher mit einer Fehlermeldung gerechnet hat, wenn ein Schreibversuch nach HKLM oder C:\Program Files erfolgte, nun auf einmal erfolgreich schreiben kann. Da kann man sich noch so sehr an alle Empfehlungen halten, hier wird man von Vista reingelegt und man muss schleunigst das entsprechende Trustinfo Manifest nachrüsten.

Button + Accelerator + ShowWindow(SW_HIDE) – EnableWindow(FALSE) = Falle

Da hat man einen Multifunktionalen Dialog. Einer der Schalter in dem Dialog heißt Delete. Und das D ist als Accelerator mit einem & versehen. Gemäß einer internen Rechteverwaltung haben manche Nutzer nicht das Recht diesen Button zu benutzen. Der Programmierer (nicht ich ;-)) hat in diesem Fall einfach den Schalter mit ShowWindow(SW_HIDE) verborgen. Ein weiterer Test ob die Rechte wirklich gegeben sind entfiel im OnBtDelete Handler.

Nun stellte sich aber heraus, dass es manche Nutzer geschafft haben, dennoch Einträge zu löschen.

Nun der Grund ist einfach. Solange der Button nicht mit EnableWindow(FALSE) auch disabled wird, kann man mit ALT+D, also Drücken der ALT-Taste und des Accelerators diesen Schalter auslösen.

Jo. So einfach hat man ein Userinterface gebastelt, mit dem man sich hereinlegen kann.

Vista und die Notwendigkeit eines Manifestes für die UAC

Bei den vorbereiten von Software für Vista (Certified for Vista) ist mir die UAC (User Access Control) untergekommen. Unter Vista verschärft sich das ganze noch einmal gegenüber Windows 2000.

Einen der wichtigsten Punkte möchte einfach mal hier darlegen.
Unsere Software ist so geschrieben, dass ein Admin entsprechende Einstellungen in bestimmten Dateien im Programmverzeichnis ablegen kann und auch Einstellungen vornehmen kann die in HKLM abgelegt werden. Unter anderem dienen diese Dateien/Registry Einstellungen eben zur Kontrolle von Services und IPC. Sie müssen eben in einem allgemein zugänglichem Bereich liegen. Das ist ja nichts ungewöhnliches.

Brav wie wir sind haben wir entsprechende Prüfungen eingebaut und sagen dem Anwender, wenn er keine Rechte hat in HKLM zu schreiben, oder die entsprechenden Dateien im Programmverzeichnis zu ändern, z.B. INI Dateien die mit WritePrivateProfileString geschrieben werden.

Vista ist es nun egal, dass wir brave Entwickler sind. Vista behandelt uns per UAC Richtlinie wie einen Bösewicht und leitet alle Änderungen in den lokalen Bereich des Anwenders. Meine Software hat keine Chance zu erkennen, dass das schreiben in HKLM und den Programm-Datei Ordner eigentlich nicht erlaubt ist.

Man muss also zwingend ein Manifest für die UAC hinzufügen:

<?xml version=“1.0″ encoding=“utf-8″?>
<assembly xmlns=“urn:schemas-microsoft-com:asm.v1″ manifestVersion=“1.0″>
  <trustInfo xmlns=“urn:schemas-microsoft-com:asm.v3″>
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level=“asInvoker“ />
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

Erst mit diesem Manifest verhält sich meinem Programm wieder so, wie ich es auch erwarte!

Eine gute Anleitung hierfür:
– How To: Tell Vista’s UAC What Privelege Level Your App Requires
http://channel9.msdn.com/Showpost.aspx?postid=211271

Weitere Infos, Dokus und Hilfsprogramme:
– Understanding and Configuring User Account Control in Windows Vista
http://technet.microsoft.com/en-us/windowsvista/aa905117.aspx
– Windows Vista Application Development Requirements for User Account Control Compatibility
http://www.microsoft.com/downloads/details.aspx?FamilyID=BA73B169-A648-49AF-BC5E-A2EEBB74C16B&displaylang=en
– Microsoft Application Verifier
http://www.microsoft.com/downloads/details.aspx?familyid=BD02C19C-1250-433C-8C1B-2619BD93B3A2&displaylang=en
– Microsoft Standard User Analyze
http://www.microsoft.com/downloads/details.aspx?familyid=DF59B474-C0B7-4422-8C70-B0D9D3D2F575&displaylang=en