Windows XP SP2: Auswirkungen auf die Sicherheit

04.06.2004 von THOMAS WOELFER 
Das Service Pack 2 für Windows XP läutet den ersten größeren Schub an Sicherheitsänderungen bei Microsoft-Betriebssystemen ein. Es hat auch konkrete Auswirkungen auf die Softwareentwicklung, die zu beachten sind.

Um die Auswirkungen des Service Pack 2 auf die selbst entwickelten Anwendungen besser abschätzen zu können, sollten Sie sich zunächst einen allgemeinen Überblick über die darin durchgeführten Änderungen an Windows und seinem Sicherheitsmodell verschaffen. Diesen Überblick erhalten Sie zum Beispiel durch ein Studium der von Microsoft veröffentlichten Dokumente zum Thema.

Dort gibt es auch eine Vorabversion des Service Packs zum Download, sodass man den Code auch tatsächlich auf einer Testmaschine installieren und ausprobieren kann.

Unabhängig von der neuen Firewall oder anderen Eigenheiten des Service Pack 2 sind zwei Punkt wichtig, die allgemein wenig Beachtung finden, aber eigentlich extrem wichtige Bestandteile des Service Pack sind. Und darum wird hier auch kurz darauf eingegangen, obwohl die Auswirkungen auf Entwickler eher gering sind:

Zum einen enthält es nicht nur ein neues Release von XP (Home und Professional), sondern auch eine komplett neues Windows XP Tablet PC Edition, das die Nutzung von Tablet PCs erheblich verbessert.

Aus einem Sicherheitsstandpunkt heraus betrachtet ist die zweite Änderung aber noch wesentlich dramatischer: Für das Service Pack 2 wurden alle ausführbaren Programme von XP mit dem neuen VC++ Compiler unter Nutzung der dort seit einiger Zeit verfügbaren Compileroption zum Schutz von Speicherbereichen übersetzt (/GS). Dadurch erhalten diese Programme einen erheblich besseren Schutz gegen überschriebene Puffer.

Ist die neue Compileroption eingeschaltet, fügt der Compiler beim Übersetzen so genannte Cookie-Bytes in bestimmte Speicherblöcke ein. Beim Rücksprung einer Funktion werden diese Bytes überprüft. Sind sie verändert, wird ein Buffer-Overwrite erkannt und das betroffene Programm angehalten. Windows zeigt eine entsprechende Fehlermeldung an. Das schafft natürlich keinen absoluten Schutz vor Angriffen die Buffer-Overruns ausnutzen, macht das Ausnutzen solcher Buffer Overruns aber erheblich schwerer.

Buffer-Overruns als Angriffs-Vektor

Bevor wir diese Sachlage und deren Auswirkungen demonstrieren, zunächst eine kurze Erklärung, weshalb die neue Compiler-Option das Problem nicht völlig aus der Welt schafft. Wie ein Buffer-Overrun Angriff funktioniert, können Sie im Artikel Basiswissen Buffer Overflow nachlesen.

Prinzipiell ist es so, dass ein Angreifer mögliche Buffer-Overflows ausnutzt, um eine von ihm gewünschte Rücksprungadresse im Stack (oder in selteneren Fällen auch im Heap) zu platzieren. Der Angreifer muss also schon erhebliche Mengen an Energie und Wissen investieren, um überhaupt die richtigen Code-Bytes per Aufruf an die angreifbare Methode zu übergeben. Dazu wird er fast immer einen String mit den von ihm gewünschten Bytes an die Funktion übergeben und die eigentlich richtige Rücksprungadresse überschreiben.

Nun sollte man annehmen, dass ein Test, der überschriebene Puffer-Bytes entdeckt, auch diese Angriffsmethode aufdeckt. Das ist aber nicht der Fall, denn wenn der Angreifer von diesem Test weiß, hält ihn natürlich nichts davon ab, auch die Guard-Bytes zu überschreiben - und zwar mit genau den passenden Werten, die die Testroutinen an der Stelle dieser Bytes erwarten.

Allerdings: Der Angriff wird deutlich schwieriger, da es nun nicht mehr ausreicht, "passend" erzeugte Code-Bytes in den Speicherbereich zu schreiben. Zusätzlich ist noch zu berücksichtigen, dass diese Bytes die richtigen Testwerte an den passenden Stellen enthalten. Das erschwert die Konstruktion des Speicherblocks, macht sie aber nicht unmöglich.

Puffer-Überlauf: Ein Beispiel

Soviel zur Vorgeschichte: Stellt sich die Frage, welche Auswirkungen diese Änderung in Windows auf Entwickler hat. Um diese Auswirkung zu verdeutlichen, zeigen wir zunächst einmal ein kleines Beispielprogramm, das einen Buffer-Overflow ermöglicht. Hier der C++ Quellcode zum Beispiel:

void bar() {
char buffer[100];

for (int i=0 ; i < 200; i++) {
buffer[i] = "a";
}

buffer[sizeof( buffer)-1] = 0;
printf( buffer);
}

int _tmain(int argc, _TCHAR* argv[]) {
bar();
return 0;
}

Die Funktion bar() hat einen offensichtlichen Fehler: Sie schreibt 200 Zeichen in einen Puffer, der nur Platz für 100 Zeichen hat.

Dieses Programm sollten Sie nun mit der folgenden Kommandozeile übersetzen:

cl /O2 /ML /Ehsc unchecked.cpp

Wie man am Parameter /O2 unschwer erkennen kann, handelt es sich hier um ein optimiertes Build - es geht also nicht um eine Debug-Version.

Wenn Sie das Programm starten, stützt es irgendwann ab, und Windows zeigt die von solchen Fällen bekannte Fehlermeldung an. Die ist aber nur eine Reaktion auf den Absturz!

Wäre die Rücksprung-Adresse nicht überschrieben worden, so könnte das Programm in ähnlichen Fällen den Eindruck erwecken, völlig korrekt zu laufen - und das, obwohl es im Daten-Bereich Amok läuft.

Pufferschutz in Aktion

Übersetzen Sie das Programm nun einmal mit dem Parameter /GS, also mit der folgenden Kommandozeilen-Variante:

cl /O2 /GS /ML /Ehsc unchecked.cpp

Das resultierende Programm enthält dann die Tests auf einen Puffer-Überlauf, die sich beim Start des Programms auch sofort bemerkbar machen: Das Programm stürzt nämlich nicht unkontrolliert ab - sondern kontrolliert. Dementsprechend erscheint auch eine Fehlermeldung der VC++-Laufzeitumgebung, die auf den Pufferüberlauf hinweist.

Genau dieses Verhalten ist nun also in Windows XP hineinkompiliert worden. Das hat im Wesentlichen eine Auswirkung auf Entwickler: Es gibt nun einen besseren Selbstschutz-Mechanismus, der verhindert, dass Windows-APIs mit falschen Parametern aufgerufen werden.

Konkreter Handlungsbedarf: Zunächst keiner

Bei Windows gibt es eine ganze Menge Programmierschnittstellen, bei denen man einen von Windows übergebenen Puffer benutzen muss. Nutzt man die in Zukunft nicht richtig, wird das mit einer sofortigen Fehlermeldung und einer Beendigung des Programms quittiert - und zwar auch in der Release-Version der Software.

Konkreter Handlungsbedarf entsteht dadurch aber nicht: Man sollte die eigene Software natürlich unter der neuen Umgebung testen und Fehler gegebenenfalls ausbügeln - aber eine Veränderung bei der normalen Entwicklungsarbeit oder gar zusätzlicher Code ist nicht notwendig. Eines sollte man aber auf jeden Fall tun: Man sollte alle eigenen Projekte überprüfen und sicherstellen, das die Compiler-Option /GS eingeschaltet ist. Bei der Gelegenheit kann man auch noch direkt die anderen Runtime-Checks (/RTC) einschalten.

Data Execution Prevention

Wenn Sie von DEP betroffen sind, sieht die Sache dramatischer aus: Sie müssen dann tatsächlich massiv tätig werden, allerdings ist die Chance, dass Sie betroffen sind zumindest momentan noch eher gering. Mit DEP (Data Execution Prevention) wird im Wesentlichen verhindert, dass Code aus dem Datensegment heraus ausgeführt wird.

Um genau zu sein: DEP soll sicherstellen, dass nur ausgeführt wird, was von Haus aus explizit als Code markiert ist. Das passiert mit Hilfe eines Prozessor-Features, dem NX (No Execute) Seitenschutzmechanismus. Und das ist auch der erste von zwei Gründen, weshalb Sie von dieser Änderung vermutlich nicht betroffen sein werden: Diesen Mechanismus gibt es nur auf den aktuellen 64-Bit-CPUs von AMD und Intel.

Microsoft erwartet allerdings, dass sich NX in Zukunft auch in 32-Bit-CPUs finden wird, es ist also mittelfristig notwendig, sich mit dieser Problematik auseinander zu setzen.

Der zweite Grund, weshalb Sie vermutlich nicht von dieser Änderung betroffen sein werden, ist die Tatsache, dass Sie mit recht großer Sicherheit keinerlei Code aus dem Datensegment heraus ausführen. Bei Programmen, die ganz normal übersetzt und gelinkt werden, landet der Code im Code-Segment - und das macht auch auf einer Hardware mit NX keinerlei Probleme.

Nur sich selbst verändernde Programme, Programme mit einem Jitter (Just in Time Compiler), bei denen Code erst zur Laufzeit erzeugt wird, oder Programme, die mit voller Absicht Code im Datensegment oder im Default-Heap (oder Stack) unterbringen, sind von dieser Änderung betroffen. Der bekannteste Kandidat dafür dürfte die .NET Laufzeitumgebung sein, .NET Programme selbst sind hingegen nicht betroffen.

Detaillierte Informationen zu DEP/NX finden Sie demnächst in einem separaten Artikel.

AES: Attachment Execution Service

Um die nächste Änderung zu verstehen, müssen Sie zunächst ein Feature des NTFS -Dateisystems kennen lernen. Unter NTFS kann jede Datei aus mehreren Strömen zusammengesetzt sein. Die einzelnen Ströme werden dabei über eine Änderung des Dateinamens angegeben, dieser wird einfach um einen per Doppelpunkt abgeteilten zusätzlichen String erweitert. Wenn Sie also die Datei

C:\\test.txt

öffnen, dann erhalten Sie genau den Inhalt den Sie erwarten. Mit

C:\\test.txt:strom2

hingegen erhalten Sie den Inhalt, der im "Strom2" benannten Teil der Datei liegt. Auf diese Weise kann man also beliebige zusätzliche Datenströme mit einer Datei verbinden. Das geht aber eben nur mit NTFS - was wiederum zur Folge hat, dass das folgende Feature auch nur mit NTFS zur Verfügung steht: Der Attachment Execution Service, kurz AES.

Dabei geht es um Programme, die Daten aus dem Internet herunterladen oder ausführen. Wenn Sie ohne SP2 eine Datei aus einem Email-Attachment von Outlook auf der Festplatte speichern, wird allenfalls beim Speichern des Attachments eine Sicherheitsabfrage durchgeführt. Befindet sich die Datei erst einmal auf der Platte, so kommen keine weiteren Sicherheitsabfragen.

Das Service Pack 2 ändert die Sachlage, denn die Informationen über den Ursprung der Datei werden in einem separaten Datenstrom abgelegt. Mit Hilfe der AES-API kann nun jede Anwendung überprüfen, ob eine Datei ausgeführt werden darf oder nicht.

Anders ausgedrückt: Wenn Ihre Anwendung Programme starten kann, sollten Sie in Zukunft die AES-API verwenden, um vor der Ausführung eines anderen Programms zu überprüfen, ob dieser Vorgang überhaupt zulässig ist.

Auch dies wird nicht sonderlich viele Programmierer betreffen - im Wesentlichen sind wohl die Programmierer von Outlook selbst betroffen. Wenn Sie hingegen selbst Messaging-Anwendungen programmieren, dann ist die AES-API natürlich auch für Sie von Belang.

Allerdings: Die API selbst ist bisher nur sehr notdürftig dokumentiert. Es gibt weder Header-Files noch Libaries und abgesehen von einem eher allgemeinen Text auch noch keinerlei offizielle Dokumentation.

Das ist momentan allerdings auch noch nicht notwendig: Die Verfügbarkeit der API macht zunächst keinerlei Änderungen an Messaging-Anwendungen notwendig, sondern diese nur wünschenswert.

Die Windows Firewall

Wenn Sie Anwendungen mit DCOM oder Sockets programmieren, oder Ihre Anwendungen anderweitig über das Netzwerk kommunizieren müssen, müssen Sie allerdings tätig werden.

Beim SP2 hat Microsoft in der Windows Firewall einige zum Teil recht massive Änderungen vorgenommen. So ist die Firewall per Default eingeschaltet und außerdem an alle Netzwerkverbindungen gebunden, und nicht nur an Dial-Up Verbindunden. Mit anderen Worten: Auch die ganz normale LAN Verbindung ist von Haus aus mit der Windows-Firewall geschützt.

Dabei hat Microsoft spezielle Maßnahmen für normale LAN-Operationen unter Windows getroffen. So sind File-Shares im LAN von den neuen, strengeren Firewall-Regeln beispielsweise nicht betroffen.

Die Firewall blockiert dabei nur den eingehenden Verbindungsaufbau. Wenn Ihre Anwendung also eine Verbindung vom lokalen Rechner zu einem entfernten Server aufbaut, dann müssen Sie nicht weiter tätig werden: Diese Art der Verbindungsaufnahme ist weiterhin ohne Probleme möglich.

Wenn Ihre Software auf eine Verbindungsaufnahme reagiert und Antwortpakete sendet, so ist auch das kein Problem: Antwortpakete auf Anfragen die lokal ausgelöst wurden, werden von der Windows Firewall ebenfalls ohne Probleme durchgelassen.

Damit bleibt der eine kritische Fall übrig: Die erste Verbindungsaufnahme erfolgt von außen. Mit anderen Worten: Wenn es sich bei Ihrer Software um einen Server handelt, müssen Sie unter Umständen tätig werden. Inwieweit Sie tätig werden müssen, hängt von dem Komfort ab, den Sie Ihren Anwendern bieten möchten.

Beispiel-Server

Dazu ebenfalls ein kurzes Beispiel-Programm (ConsoleServer). Das Programm ist ein asynchroner Socket-Server, der auf Port 11000 auf eine Verbindung wartet.

IPHostEntry ipHostInfo = Dns.Resolve( Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);

Socket listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );

listener.Bind(localEndPoint);
listener.Listen(100);

while (true) {
allDone.Reset();

Console.WriteLine("Waiting for a connection...");
listener.BeginAccept( new AsyncCallback(AcceptCallback), listener );

allDone.WaitOne();
}

Sobald das Programm nun signalisiert, dass es auf eine Socket-Verbindung wartet, greift die Firewall ein: Es erscheint sofort eine Dialogbox der Firewall - die hätte nämlich gern gewusst, ob es in Ordnung ist, die Server-Anwendung zu starten.

Dabei zeigt die Firewall den Namen der Anwendung und den Namen des Herausgebers an. (Beim Beispielprogramm fehlen diese Angaben, darum wird von der Firewall auch nichts angezeigt.) Bei einem normalen Win32 Programm entnimmt die Firewall diese Angabe aus der einkompilierten Version-Ressource.

Wenn der Benutzer Ihr Programm nun mit dieser Dialogbox freischaltet, ist die Welt in Ordnung: Ihr Server kann laufen und wartet auf Verbindungsanfragen von außen. Beim nächsten Start Ihres Servers kennt die Firewall Ihr Programm, sodass eine weitere Nachfrage nicht stattfindet.

Automatische Freischaltung in Firewall

Wenn Sie mit diesem Zustand zufrieden sind, müssen Sie nichts am Programm ändern. Wollen Sie Ihren Benutzern jedoch diese Dialogbox ersparen, ist eine Anpassung der Installationsroutine erforderlich.

Vermutlich wird Ihr Setup-Programm ohnehin mit administrativen Rechten ausgeführt werden müssen: Genau diese Rechte brauchen Sie auch, wenn Sie Ihr eigenes Programm programmatisch in der Liste der von der Firewall erlaubten Programme eintragen möchten.

Die Windows Firewall führt eine solche Liste an Programmen mit, und diese Liste der "erlaubten" Programme können Sie mit Hilfe der Firewall-GUI auch einsehen. Folgendes ist nun zu tun, wenn Sie Ihr Programm zu dieser Liste hinzufügen wollen. (Leider gibt es auch für diesen Teil der SP2 Funktionalität noch kein wirklich fertigen Header-Dateien oder Libraries - die hier vorliegende Information beruht auf einem Microsoft-Webcast von Darius Parys.)

NetFwTypeLib.INetFwMgr mgr = ( NetFwTypeLib.INetFwMgr ) Activator.CreateInstance( Type.GetTypeFromProgID("hnetcfg.fwmgr" ));

NetFwTypeLib.INetFwAuthorizedApplication app = ( NetFwTypeLib.INetFwAuthorizedApplication )
Activator.CreateInstance( Type.GetTypeFromProgID("hnetcfg.fwauthorizedapplication" ));

app.Name = "Name der Anwendung";
app.ProcessImageFileName = ImageFileName;
app.Enabled = enable;

mgr.LocalPolicy.CurrentProfile.AuthorizedApplications.Add( app );

Im Wesentlichen brauchen Sie also zwei Instanzen: Eine vom Typ Firewall-Manager, und eine vom Typ "AuthorizedApp". Letztere verwenden Sie, um die Angaben über Ihre Anwendung zu speichern und dann dem Firewall-Manager zu übergeben. Danach ist Ihre Anwendung Teil der zulässigen Programme, und die Windows Firewall wird Ihre Anwender nicht mehr mit Nachfragen belästigen.

In diesem Beitrag haben Sie erfahren worauf Sie als Entwickler in Zukunft achten müssen, wenn Ihre Software auch unter dem Windows XP SP2 laufen soll. Da das endgültige Service-Pack 2 erst für September erwartet wird, beziehen sich die Informationen in diesem Beitrag auf die "Technical Preview" des Service Pack 2 und die mit dieser Preview veröffentlichte Dokumentation. (mha)