Das Nachrichtensystem von Windows

Die meisten Hacker beschäftigen sich kaum mit Microsoft Betriebssystemen. Gründe dafür gibt es genug, die meisten davon sind mehr als verständlich. Doch es gibt auf diesen Systemen viel zum Spielen, so dass es sich lohnen könnte, einen genaueren Blick darauf zu werfen. Ein Beispiel dafür ist das Design der grafischen Benutzeroberfläche. Mit den einfachsten Mitteln kann man hier Programme manipulieren, wobei erfahrungsgemäß die wenigsten Entwickler mit diesem Angriffsvektor rechnen.

Windows ist ein ereignisgesteuertes System. Ereignisse erzeugen Nachrichten, die vom System an das betreffende Fenster weitergeleitet werden. Unter Windows kann jeder Thread ein oder mehrere Fenster öffnen, wobei jedes Fenster einen systemweit eindeutigen "WindowHandle" besitzt. Ein Fenster kann der Vater eines zweiten Fensters sein kann, so dass die Menge aller Fenster einen Baum mit dem Desktop als Wurzel bilden. Jedes Fenster gehört zu einer Fensterklasse. Bestandteil einer Fensterklasse ist u.A. die Routine, die eingehende Nachrichten behandelt. Diese Routine wird üblicherweise "WindowProc()" genannt.

Wenn der Benutzer z.B. mit seiner Maus klickt erzeugt der Maustreiber eine Fensternachricht und sendet diese an die so genannte "System Message Queue". Diese FIFO wird zyklisch vom "System Dispatcher" ausgelesen. Er erkennt z.B. am Fokus, für welches Fenster die Nachricht bestimmt ist und sendet sie an die "Thread Message Queue" des für das Empfangsfenster zuständigen Threads. Ein in der Benutzerapplikation laufender "Thread Dispatcher" nimmt die Nachricht aus dieser FIFO entgegen und übergibt sie der passenden WindowProc() des Fensters. Zuvor wird eine Kopie der Nachricht an den "Translator" gegeben. Diese Zustandsmaschine erzeugt z.B. aus den Nachrichten "KeyDown" und "KeyUp" eine neue Nachricht "KeyPressed" und hängt sie hinten an die Thread Message Queue an. So kann eine oder mehrere Nachrichten ein Ereignis für eine neue Nachricht sein.

Es gibt eine ganze Reihe von Nachrichtenklassen, wobei zu jeder Nachrichtenklasse viele verschiedene Nachrichten gehören. So werden z.B Nachrichten erzeugt, wenn ein Fenster verschoben, vergrößert, minimiert oder geschlossen wird. Eine andere Nachricht signalisiert einem Fenster, dass es sichtbar geworden ist und sich deshalb neu zeichnen soll. Wieder andere Nachrichten müssen beim Drücken von Buttons und Menüs oder beim Scrollen und Auswählen aus einer Liste behandelt werden. Damit sich kein Entwickler mit all diesen Details beschäftigen muss, gibt es eine sog. "DefaultWindowProc()" aus der Windows Bibliothek, die alle Nachrichten (hoffentlich) richtig behandelt. Ein Entwickler behandelt in seiner WindowProc() nur noch die Nachrichten, die ihn gezielt interessieren, und gibt den Rest weiter an die DefaultWindowProc(). Deshalb reagieren viele Programme auf die meisten Nachrichten gleich.

Nachrichten können nicht nur vom System erzeugt werden, denn Fensternachrichten sind ein Teil des IPC-Konzepts von Windows. Z.B. kann ein Entwickler benutzerdefinierte Nachrichten erzeugen und versenden. Eine andere Anwendung sind komplexere Protokolle wie z.B. DDE und OLE, die zur Fernsteuerung von Programmen und Automation von Prozessen verwendet werden können. Der Fehler an diesem Design ist, dass hier IPC im Push-Verfahren ohne Sicherheitssystem gemacht wird. Der Kommunikationspartner kann sich nicht gegen die ihm zugesandten Nachrichten wehren, vor allem, weil er nicht zuverlässig feststellen kann, wer der Absender ist. So kann jedes Programm Benutzerinteraktion simulieren, ohne dass das angegriffene Programm dies feststellen kann. Das heißt im Klartext: wenn ein Angreifer es schafft, lokal seinen Code auszuführen, kann er damit alles tun, was der Benutzer auch tun kann, z.B. die Personal Firewall umkonfigurieren oder abschalten. Die wwwsh nützt das z.B. um einen Browser fernzusteuern und damit Informationen an einer Personal Firewall vorbei aus und in das System zu schleusen.

Diese Möglichkeiten genügen für die meisten Fälle bereits. So gibt es z.B. Programme, die einen OK Button erst "enabled" machen, wenn man den Registrierungsschlüssel eingegeben hat. Enabled heißt, dass ein Klick auf den Button eine Nachricht erzeugt auf die die WindowProc() mit dem Aufruf des "OnClick()" Handlers reagiert. Generiert man diese Nachricht jedoch selbst und schickt sie dem Programm, so wird ebenfalls der OnClick()-Handler aufgerufen. Da dieser in den seltensten Fällen überprüft, ob der OK-Button überhaupt enabled ist, kann man so die Eingabe des Registrierungsschlüssels umgehen.

Für diejenigen, denen diese Möglichkeiten noch nicht genügen, bietet die Windows Bibliothek noch ein weiteres Feature: "MessageHooks". Das sind Nachrichtenfilter, die man an beliebigen Stellen des Zustellsystems installieren kann. Damit kann man beliebige Nachrichten systemweit abfangen oder verfälschen. Lagert man die Implementierung des Filters in eine DLL aus, kann man sogar die Thread Message Queue eines anderen Threads filtern ohne dafür besondere Rechte haben zu müssen. Damit existiert für jeden, der lokal Programme ausführen kann, ein mächtiger Angriffsvektor. Man ist der "Man in the Middle" in der Mensch-Maschine-Schnittstelle.

Ausnutzen kann man das z.B. beim Onlinebanking. Wenn der Benutzer die Kontonummer des Begünstigten eingibt fängt der Nachrichtenfilter die entstehenden Nachrichten ab und ersetzt sie durch neue, so dass das Programm eine andere Kontonummer empfängt. Jetzt muss man nur noch verhindern, dass die eingeschleuste Kontonummer in der Eingabemaske der Applikation sichtbar wird. Doch glücklicherweise darf jedes Programm auf beliebige Stellen des Desktops malen ...

Normalerweise würde es einige Schwierigkeiten machen herauszufinden, welche Fenster eine Applikation öffnet und welche Nachrichten diese Fenster empfangen. Doch auch hier hilft Microsoft weiter und liefert u.a. mit seiner Entwicklungsumgebung Visual C++ den Spy++ mit. Mit diesem Programm kann man sich alle Prozesse, Threads und Fenster sowie die Beziehungen zwischen ihnen anzeigen lassen. Zusätzlich kann man alle Nachrichten für ein Fenster und dessen Kindfenster mitprotokollieren lassen.

In der (MSDN-Dokumentation)[http://msdn.microsoft.com] findet man alle Details zum Windows-Nachrichtensystem. Es gibt verschiedene Nachrichtenklassen, wie z.B. die "General Window"-Klasse, deren Nachrichten mit dem Präfix "WM" gekennzeichnet sind. So gibt es z.B. die Nachrichten "WMSETTEXT" und "WM_GETTEXT" mit denen man den Inhalt eines Fensters schreiben und lesen kann. Zum Versenden einer Nachricht existieren die Funktionen "PostMessage()" und "SendMessage()". PostMessage() hängt eine Nachricht an die Thread Message Queue an und kehrt zurück. SendMessage() ruft die WindowProc() direkt und blockiert bis, diese zurückkehrt. Mit beiden Funktionen kann man beliebige Fenster adressieren.

Es gibt verschiedene Filtertypen. Die Unterschiede liegen in der Stelle der Installation und dem zu filternden Nachrichtentyp. Zum Installieren von Nachrichtenfiltern gibt es die Funktion "SetWindowsHookEx()". Falls mehrere Nachrichtenfilter des gleichen Typs an die gleiche Stelle des Zustellsystems installiert werden, bilden sie eine Filterkette. Jeder Filter kann die weitere Abarbeitung diese Kette abbrechen lassen.

Fensternachrichten können sogar dazu dienen, einen Privilege Elevation zu erreichen, wenn es einen privilegierten Prozess gibt, der ein Fenster auf macht. Dazu kann man z.B. die Fensternachricht "WMTIMER" verwenden. Der Zweck dieser Nachricht ist es eigentlich, dass sich ein Programm diese regelmäßig von System schicken lässt, um synchronisiert etwas zu tun. Deshalb besitzt die WMTIMER als Parameter einen Zeiger auf die "Callback Funktion", die das Programm beim Installieren des Timers angegeben hat. Wenn man jetzt eine WMTIMER selber erstellt und sie einem Programm schickt, dann stehen die Chancen gut, dass die DefaultWindowProc() darauf richtig reagiert und die Callback Funktion anspringt. Wenn man jetzt zuvor mit Hilfe einer WMSETTEXT in ein beliebiges Fenster der Applikation seinen Code reinschreibt, kann man den Zeiger der WM_TIMER so setzten, dass dieser Code vom Programm ausgeführt wird. Das heißt, so bald irgendein privilegierter Prozess ein Fenster aufmacht, kann jeder Angreifer beliebigen Code mit privilegierten Rechten ausführen. Der einzige Hacken an diesem Angriff ist, dass man die Adresse des eingeschleusten Codes herausfinden muss um den Zeiger richtig setzen zu können. Dazu muss man das Programm debuggen und ein wenig experimentieren. Im Prinzip handelt es sich dabei und das selbe Problem, das man bei der Ausnutzung eines Buffer Overflows hat.