MSDN Magazin > Home > Ausgaben > 2007 > November >  Absturzanalyse: Untersuchen von Abstürzen ...
Absturzanalyse
Untersuchen von Abstürzen zur Ermittlung von Sicherheitsrisiken in Ihren Anwendungen
Adel Abouchaev and Damian Hasse and Scott Lambert and Greg Wroblewski

Themen in diesem Artikel:
  • Ursachen von Programmabstürzen
  • Ausnutzbare und nicht ausnutzbare Fehler
  • Bewährte Methoden der Analyse
  • Strategien der Fehlerselektierung
In diesem Artikel werden folgende Technologien verwendet:
Windows-Debugging, C/C++
Wie können Sie sicherstellen, dass ein Absturz Ihres Programms nicht ausgenutzt werden kann? Die Antwort ist einfach: Gehen Sie grundsätzlich davon aus, dass jeder Absturz ausnutzbar ist, und beheben Sie die Absturzursache! Es ist mindestens eine Frage der Qualität und häufig auch billiger (und praktischer), das Problem zu beheben, bevor das Produkt an Kunden geliefert wird. Die Analyse zur Ermittlung der Ausnutzbarkeit kann teuer ausfallen.
Die Analyse von mit einer Speicherbeschädigung in Verbindung stehenden Programmfehlern auf deren Konsequenzen für die Sicherheit kann ein komplizierter und fehleranfälliger Arbeitsprozess sein. Dabei müssen mehrere Faktoren berücksichtigt werden. Dazu gehören die Position des Puffers im Speicher, die möglichen Überschreibungsziele, die Größe der Überschreibung, die geltenden Beschränkungen für die Daten, die während des Überschreibens verwendet werden können, der Zustand der Umgebung der Laufzeitausführung und die Möglichkeiten der Umgehung bestehender Funktionen zur Schadensbegrenzung. Kurz und gut: Sie müssen die Grundursache des Fehlers kennen, um diese Fragen richtig beantworten zu können.
Denken Sie daran, dass nicht jeder Fehler sichtbare Konsequenzen hat. Ein Beispiel dafür ist das Problem der Remoteausführung von GDI-Code, das im Microsoft-Sicherheitsbulletin MS07-017 behandelt wird. Die Software, die den anfälligen Analysecode aufgerufen hat, nutzt dabei einen Ausnahmehandler, um praktisch jede evtl. ausgelöste Ausnahme zu umgehen und den Betrieb fortzusetzen, als sei nichts geschehen. (Weitere Informationen dazu finden Sie unter blogs.msdn.com/sdl/archive/2007/04/26/lessons-learned-from-the-animated-cursor-security-bug.aspx.) Ein weiteres, aber weniger offensichtliches Beispiel lässt sich bei bestimmten Arten von Stapel- und Speicherbeschädigungen finden, bei denen es vorkommen kann, dass Fehler aufgetreten sind, aber der aktuelle Zustand des Programms und seiner Ausführungsumgebung keine auffälligen Symptome erkennen lässt.
Der vorliegende Artikel bietet Leitfäden zur Analyse von Programmabstürzen mit potenziellen Folgen für die Sicherheit, wie z. B. einer Speicherbeschädigung, die eine Ausführung eines beliebigen Codes oder zumindest Denial-of-Service-Angriffe ermöglicht. Es werden die häufigsten Hardware- und Softwareausnahmen aufgezeigt, auf die Sie stoßen können, wenn Sie diese Arten von Problemen untersuchen. Darüber hinaus werden einige allgemeine Richtlinien behandelt, an denen Sie sich bei solchen Untersuchungen orientieren können. Abbildung 1 zeigt beispielsweise einen grafischen Verlauf des Untersuchungsprozesses, anhand dessen Sie entscheiden können, ob ein bestimmter Absturz ausnutzbar ist. Dabei handelt es sich lediglich um Richtlinien. Nur durch eine komplette Ursachenanalyse kann festgestellt werden, ob ein Absturz als nicht ausnutzbar diagnostiziert werden kann. Ständig werden neue Angriffsmethoden oder Variationen bestehender Angriffsstrategien entdeckt.
Abbildung 1 Verlauf einer Zugriffsverletzungsanalyse (Klicken Sie zum Vergrößern auf das Bild)
Die häufigste Ursache für Abstürze sind Hardware- oder Softwareausnahmen. Ein typischer moderner Prozessor kann viele verschiedene Arten von Hardwareausnahmen generieren, aber in der Windows®-Umgebung stehen nur einige dieser Probleme mit der Softwaresicherheit in Zusammenhang. Die häufigste Hardwareausnahme ist eine Zugriffsverletzung. Im Folgenden wird zunächst die Analyse von Hardwareausnahmen und dann die der Softwareausnahmen behandelt.

Zugriffsverletzungen
Zugriffsverletzungsausnahmen (0xc0000005=STATUS_ACCESS_VIOLATION) werden von modernen Prozessoren generiert, wenn ein durch eine Anweisung oder eine Programmausführung verursachter Speicherzugriff bestimmte Bedingungen nicht erfüllt, die von der Prozessorarchitektur oder den Strukturen der Speicherverwaltungseinheiten vorgegeben sind.
Ein reiner Absturz kann zwar lediglich einen Denial-of-Service-Zustand zur Folge haben, es wäre jedoch riskant, anzunehmen, dass ein Absturz nicht dazu missbraucht werden könnte, gefährlichere Auswirkungen einschließlich einer Codeausführung zu erzielen. Beim Analysieren von Abstürzen müssen Sie davon ausgehen, dass der gesamte Speicherinhalt mit einigen kleinen Ausnahmen unter der Kontrolle eines Angreifers steht und daher eine Zugriffsverletzung in den meisten Fällen dazu führen könnte, dass Daten von diesem gesteuert werden. Dies trifft auf eine Ausnahme zu, die ausgelöst wird, während eine Anweisung Daten liest oder schreibt.
Kann die Zugriffsverletzung dazu führen, dass Ihre Daten durch den Angreifer gesteuert werden, dann kann jede Zugriffsverletzung, die durch ein Lesen des Speichers verursacht wurde, zur Folge haben, dass die von einem Angreifer gesteuerten Daten geladen werden. Die Auswirkungen eines derartigen Vorgangs auf die Sicherheit sind nicht immer leicht zu erkennen. Sie könnten eine komplette Datenflussanalyse des binären Codes oder des Quellcodes durchführen, um den Wirkungsbereich der Quelladresssteuerung und die Folgen der Einschleusung zufälliger Daten in das Programm an bestimmten Ausführungspunkten zu ermitteln. Dies ist eine zeitaufwendige und schwierige Aufgabe. Aus diesem Grund haben wir eine einfache Heuristik entwickelt, um Abstürze durch eine Schreibzugriffsverletzung schnell auf ihr Potenzial zur Codeausführung zu analysieren.
Das folgende Beispiel zeigt einen Absturz, der durch einen ungültigen Speicherzeiger im Register „eax“ ausgelöst wurde. In diesem Fall erhält der Angreifer durch die Kontrolle über die Speicherinhalte volle Kontrolle über den Programmablauf:
Application!Function+0x133:
3036a384 eb32            call     [eax]           ds:0023:6c7d890d=??
0:000> ub
mov    eax, [ebx]  ->  eax = invalid memory pointer
...                         (instructions not affecting register eax)
call        [eax]  ->  crash
Situationen, in denen die Adresse, die gelesen wird, vom Angreifer nicht ausreichend gesteuert werden kann, können als Denial-of-Service-Zustände behandelt werden. Beispielsweise kann in der typischen Benutzermodusumgebung von Windows ein an einem NULL-Zeiger stattfindender Absturz, der von einem Angreifer initialisiert wurde, aber nicht von ihm beeinflusst werden kann, nicht allein eine Codeausführung zur Folge haben.
In dem folgenden Beispiel sehen Sie einen Absturz, der dadurch verursacht wurde, dass durch einen Wert im Register „eax“ auf die Adresse null verwiesen wurde:
Application!Function+0x133:
3036a384 8b14            mov     ecx, [eax]           ds:0023:00000000=??
0:000> ub
xor    eax, eax
...                         (instructions not affecting register eax)
cmp    ebx, 2
jne        label123
mov    ecx, [eax]  ->  crash, eax = 0 (NULL)
Durch Disassemblierung (mit dem Befehl „ub“ im Befehlszeilendebugger von Visual Studio®) können wir den Datenfluss in diesem Register nachverfolgen, bis wir bestätigen können, dass der Wert im Register nicht durch schädliche Eingaben beeinflusst werden kann. In diesem Beispiel wurde das Register von XORing durch sich selbst auf null gesetzt und erst verwendet, als die Anweisung erreicht wurde, die den Absturz ausgelöst hat.
Manchmal ist die Ausnutzbarkeit eines Fehlers aus der Anweisung, die zum Absturz führt, nicht sofort ersichtlich. Beispielsweise können Sie nach der Disassemblierung der folgenden Anweisungen sehen, dass es eine Folge der Ablaufsteuerung ist, wenn auf die (im vorherigen Absatz beschriebene) fehlschlagende Anweisung eine kritische Anweisung folgt:
(1258.1638): Access violation - code c0000005 (second chance)

Application!Function+0x123:
3036a384 8b12            mov    eax, [ebx]           ds:0023:6c7d890d=??
0:000> u
mov    eax, [ebx]  -> crash, ebx points to invalid memory
...             (instructions not affecting regist an example er eax)
call        [eax]  ->  possibility of code execution
Dieses Beispiel ähnelt dem ersten, doch diesmal hat der Absturz bei der Anweisung stattgefunden, die Daten ins Register „eax“ lädt. Obwohl dieser Vorgang allein keinen Hinweis auf Sicherheitsprobleme liefert, zeigt die Disassemblierung deutlich, dass die Kontrolle über den Wert von Register „ebx“ die Kontrolle über den Wert von Register „eax“ impliziert, was eine Codeausführung ermöglichen kann.
Die übrigen Fälle können analysiert werden, indem man entweder manuell mit einem Debugger oder automatisch durch ein Debuggingtool die Einschleusung schädlicher Daten während der Programmlaufzeit simuliert. Sobald wir die Anweisung erreichen, von der die Zugriffsverletzung verursacht wird, können wir entweder die Quelladresse ändern, damit auf eine gültige Speicheradresse verwiesen wird, und die Anweisung erneut ausführen oder zufällige Daten ins Zielregister setzen und die fehlerhafte Anweisung überspringen, um die Ausführung fortzusetzen. Dieser Prozess wird für jede gefundene Zugriffsverletzung wiederholt, bis ein ausnutzbarer Zustand vorgefunden wird.
Es folgt die Analyse zweier Beispiele. Verfolgen wir zunächst den Datenfluss bei einer Änderung der Quelladresse:
Application!Function+0xa70:
3036a37e 8b4708          mov     eax,dword ptr [edi+8] ds:0023:040fd004=????????
Hier wird der Absturz durch einen falschen Wert im Register „edi“ verursacht. Wir werden dies so ändern, dass auf einen gültigen Speicherbereich verwiesen wird. Dabei stehen viele Möglichkeiten zur Wahl, in der Praxis wird jedoch häufig der aktuelle Wert von Register „eip“ verwendet. Dies gewährleistet, dass ein verhältnismäßig großer Anteil des Speichers in der Umgebung des neuen Werts von „edi“ gültig ist, und gibt uns die Möglichkeit, jeden evtl. nachfolgenden Schreibvorgang in den Speicherblock, in dem Code enthalten ist, abzufangen, da der Speicherblock immer als schreibgeschützt gekennzeichnet ist:
0:000> r edi=eip
0:000> g
Nach Einstellung von Register „edi“ auf den aktuellen Wert von Register „eip“ setzen wir die Ausführung fort und stoßen bei Register „esi“ auf eine weitere Ausnahme. Dort wiederholen wir den Vorgang:
(1258.1638): Access violation - code c0000005 (second chance)

Application!Function+0xa76:
3036a384 f60601          test    byte ptr [esi],1           ds:0023:6c7d890d=??
0:000> r esi=eip
0:000> g
Wenn wir die Ausführung fortsetzen, stoßen wir bei einer Anweisung, die versucht, Daten in das Codesegment zu schreiben, auf eine Schreibzugriffsverletzung. Dies bedeutet, dass auch ein Angreifer in jede beliebige Speicheradresse Daten schreiben kann:
 (1258.1638): Access violation - code c0000005 (second chance)

Application!Function+0xbef:
3036a4fd 894710          mov     dword ptr [edi+10h],eax ds:0023:3036a38e=0c46f60d
Wie Sie sehen, hat sich die ursprüngliche Lesezugriffsverletzung als ernsthaftes Sicherheitsproblem entpuppt, das Möglichkeiten zur Codeausführung bietet.
Verfolgen wir jetzt den Datenfluss, indem wir Zieldaten festlegen und Anweisungen überspringen. Während wir denselben Absturz wie zuvor analysieren, werden wir die Zieldaten ständig ändern. Nach dem Auslösen der ursprünglichen Ausnahme stellen wir das Register „eax“ auf einen beliebigen, leicht nachzuverfolgenden Wert ein und erhöhen Register „eip“ um die Größe der aktuellen Anweisung, um diese zu überspringen:
Application!Function+0xa70:
3036a37e 8b4708          mov     eax,dword ptr [edi+8] ds:0023:03f93004=????????
0:000> r eax=deadbeef
0:000> r eip=eip+3
0:000> g
Bei Fortsetzung der Ausführung stoßen wir auf die nächste Ausnahme ohne Ziel, weshalb wir die betreffende Anweisung einfach überspringen können:
(1258.1638): Access violation - code c0000005 (second chance)

Application!Function+0xa76:
3036a384 f60601          test    byte ptr [esi],1           ds:0023:deadbefb=??
0:000> r eip=eip+3
0:000> g
Wir setzen die Ausführung fort und kommen zur selben Schreibzugriffsverletzungsausnahme, die die Möglichkeit der Ausnutzbarkeit birgt:
(1258.1638): Access violation - code c0000005 (second chance)

Application!Function+0xbef:
3036a4fd 894710          mov     dword ptr [edi+10h],eax ds:0023:03f9300c=0c46f60d
Obwohl beide Verfahren zur Simulierung einer Einschleusung schädlicher Daten oft die gleichen Ergebnisse liefern, können wir aufgrund praktischer Erfahrung sagen, dass sie in vielen Fällen unterschiedliche Codepfade abdecken und dabei nur eines der Verfahren die Ausnutzbarkeit eines Problems offen legt. Alle bisher behandelten Verfahren ermöglichen die schnelle Ermittlung potenziell ausnutzbarer Abstürze durch Lesezugriffsverletzungen. Allerdings bieten sie keine endgültige Bestätigung, ob ein Absturz das Risiko der Ausnutzbarkeit birgt oder nicht.

Zugriffsverletzung beim Schreiben von Daten
Eine Zugriffsverletzung während des Schreibens von Daten weist auf eine mögliche Speicherbeschädigung hin, die fast immer zu einem ausnutzbaren Zustand mit Potenzial zur Codeausführung führt. Solche Schreibvorgänge sind sehr oft Indikatoren für Pufferüberlaufbedingungen im abstürzenden Programm. In der Praxis erweisen sich einige Abstürze durch Schreibzugriffsverletzungen häufig als nicht ausnutzbar. Dennoch erfordern Situationen wie diese, dass Sie eine vollständige Datenflussanalyse durchführen, um die Grundursache des Problems zu ermitteln. Sie müssen sicherstellen, dass die Beschädigung einem potenziellen Angreifer nicht die Möglichkeit bietet, Daten zu überschreiben, um den Ausführungsablauf willkürlich zu beeinflussen.
Die meisten Zugriffsverletzungen im Zusammenhang mit Datenschreibvorgängen können ausnutzbare Situationen herbeiführen, die die Ausführung schädlichen Codes zur Folge haben. Eine Codeausführung wird in der Regel erzielt, indem entweder im Stapel oder im Heap ein (zufälliger) Speicherbereich überschrieben wird.
Im folgenden Beispiel wurde der Absturz durch eine Speicherkopieranweisung ausgelöst, als das Zielregister die Obergrenze des Stapels erreichte und auf einen nicht zugewiesenen Speicherbereich zugriff. Die Größe des Kopiervorgangs wurde der Variablen an der Adresse [ebp-8] entnommen, und der Absturz weist darauf hin, dass sie unter der Kontrolle des Angreifers steht:
Application!Function+0x143:
3036a384 f3a4            rep     movsb           es:0023:00140000=??   ds:0023:0125432c=41414141
0:000> ub
mov    esi, [ebp-4]
mov    edi, [ebp+4]
mov    ecx, [ebp-8]
rep    movsb  ->  write access violation if value in ecx is big enough
Wir können mit großer Sicherheit sagen, dass eine Schreibzugriffsverletzung nur dann keine Codeausführung nach sich ziehen kann, wenn die Zieladressen, die unter der Kontrolle des Angreifers stehen, auf ungültige Speicherbereiche oder auf Daten verweisen, die keinen Einfluss auf die Programmausführung haben. In der Praxis wird diese Bedingung nur von einer kleinen Gruppe von Schreibzugriffsverletzungen erfüllt. In der Benutzermodusumgebung von Windows kann ein Schreibvorgang an einem NULL-Zeiger- oder Systemadressbereich (i. d. R. Adressen oberhalb von 2 GB) ein Beispiel für ein nicht ausnutzbares Problem sein (vorausgesetzt, dass die Seite mit der Adresse 0x00000000 nicht zugewiesen werden kann). Ferner würde in einem Serverszenario die Anweisung zu einem Denial-of-Service-Zustand führen. Dies ist als Sicherheitsschwachstelle zu betrachten, wenn sie von einem Benutzer, der kein Administrator ist, ausgelöst wird.
Dieses Beispiel ähnelt dem Fall, der weiter oben im Zusammenhang mit Lesezugriffsverletzungen behandelt wurde. Eine schnelle Disassemblierung zeigt, dass das Register „eax“ mit einem null-Wert initialisiert wurde und bis zum Erreichen der abstürzenden Anweisung nicht geändert wurde:
Application!Function+0x133:
3036a384 8d14            mov     [eax], ecx           ds:0023:00000000=??
0:000> ub
xor    eax, eax
...                       (instructions not affecting register eax)
cmp    ebx, 2
jne    label123
mov    [eax], ecx  ->  crash, eax = 0 (NULL)

Nicht ausnutzbare Ausnahmen
Im Benutzermodus (nicht im Kernelmodus) ist es unwahrscheinlich, dass diese Klasse von Ausnahmen Opfer eines Einzelphasenexploits wird. Normalerweise führen diese Ausnahmen zu einem Denial-of-Service-Zustand. Diese Ausnahmen können nur in einigen Fällen, nämlich dann, wenn der Ausnahmehandler infolge einer anderen Sicherheitsschwachstelle geändert wird, die Ausführung schädlichen Codes nach sich ziehen. Der Denial-of-Service-Zustand ist bei Serverplattformen ein Fehler von hoher Priorität und bei Arbeitsstationen ein Fehler von mittlerer Priorität.
Ein Beispiel solcher Ausnahmen ist das statische oder globale Dereferenzieren (beim Lesen und Schreiben). In diesem Beispiel hat der Prozess weder Lesezugriff auf die Seite 0x310000, um eine Lesezugriffsverletzung auslösen, noch Schreibzugriff, um eine Schreibzugriffsverletzung auszulösen.
mov ebp, 310046h
mov eax, [ebp+4h]

inc eax
mov [ebp+8h], eax
Die ersten zwei Ausnahmen müssen sorgfältig analysiert werden. Wenn der statische oder globale Wert auf die Adresse im Speicher verweist, an der ein schädlicher Code ohne Längenlimit schreiben und andere Strukturen überschreiben kann, um einen komplexen Exploit auszuführen, muss diese Situation als ausnutzbar gekennzeichnet werden. Wenn lediglich erlaubt wird, ein einzelnes DWORD an der Adresse zu speichern, die nicht Teil von Steuerstrukturen ist, dann ist diese Situation höchstwahrscheinlich nicht ausnutzbar. Trotzdem muss diese Anweisung überprüft werden, indem das Programm ausgeführt und die Werte beobachtet werden (dies wird auch als Laufzeitanalyse bezeichnet). Wenn der gespeicherte Wert im weiteren Verlauf des Codes nicht verwendet wird, kann er keinen weiteren ausnutzbaren Zustand herbeiführen und daher ignoriert werden. Wenn der Wert als Speicheradresse oder in einem Speicherkopiervorgang verwendet werden könnte, ist das Risiko einer Ausnutzung höher.
Auch bei der Codeausführung an einer statischen oder globalen Adresse in nicht steuerbaren Adressbereichen (Seite 0 o. Ä.) hängt die Ausnutzbarkeit davon ab, ob die Speicherseite an der Adresse vom Operanden für Schreibvorgänge gesteuert werden kann:
0040137F B8 DE C0 AD DE    mov     eax, 0DEADC0DEh
00401384 FF D0             call    eax
Eine Analyse der Codeausführung an einer statischen oder globalen Adresse hat weitere Implikationen. Wenn die Adresse zu den Seiten gehört, die nie mit normaler Prozessausführung zu tun haben (Seite 0 oder Seiten mit Adressen oberhalb von 0x80000000), dann ist diese Situation nicht ausnutzbar.
Eine Teilung durch null löst ebenfalls eine Ausnahme aus, ist aber an dieser Stelle nicht direkt ausnutzbar. Diese Situation erfordert eine zusätzliche Analyse, um zu entscheiden, ob das Ergebnis dieser Ausnahme eine Codeausführung durch die CPU, und damit einen erfolgreichen Exploit zur Folge haben kann:
004013D6 33 C9    xor    ecx, ecx
004013D8 8B C1    mov    eax, ecx
004013DA 40       inc    eax
004013DB F7 F1    div    ecx
C++-Ausnahmefehler können die Ausführung des Prozesses stören. Während der Laufzeit führen sie zum Abbruch der Anwendung. Im Debugger ist es möglich, die Ausführung nach dem Ausnahmefehler fortzusetzen. C++-Ausnahmefehler treten dann auf, wenn die Ausnahmeauslösungsfunktionen der Laufzeitbibliothek aufgerufen werden:
00401902    mov     [ebp+var_4], 1
00401909    push    offset __TI1H
0040190E    lea     eax, [ebp+var_4]
00401911    push    eax
00401912    call    __CxxThrowException@8 ; _CxxThrowException(x,x)
Dieser Zustand kann nur dann ausgenutzt werden, wenn der Ausnahmehandler bereits überschrieben wurde. Ansonsten sind C++-Ausnahmefehler nicht ausnutzbar. Die Stapelverfolgung dieser Ausnahme ist in Abbildung 2 zu sehen.
CommandLine: test.exe
Symbol search path is: srv*c:\Symbols*\\symbols\symbols
Executable search path is:
ModLoad: 00400000 00405000   test.exe
ModLoad: 7c900000 7c9b0000   ntdll.dll
ModLoad: 7c800000 7c8f5000   C:\WINDOWS\system32\kernel32.dll
ModLoad: 78130000 781cb000   C:\WINDOWS\WinSxS\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.762_x-ww_6b128700\MSVCR
80.dll
ModLoad: 77c10000 77c68000   C:\WINDOWS\system32\msvcrt.dll
(1494.14c4): Break instruction exception - code 80000003 (first chance)
eax=00251eb4 ebx=7ffda000 ecx=00000004 edx=00000010 esi=00251f48 edi=00251eb4
eip=7c901230 esp=0012fb20 ebp=0012fc94 iopl=0    nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
7c901230 cc              int     3
0:000> g
1
(1494.14c4): C++ EH exception - code e06d7363 (first chance)
(1494.14c4): C++ EH exception - code e06d7363 (!!! second chance !!!)
eax=0012fee0 ebx=00000000 ecx=00000000 edx=781c3c58 esi=0012ff68 edi=004033a4
eip=7c812a5b esp=0012fedc ebp=0012ff30 iopl=0    nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
kernel32!RaiseException+0x53:
7c812a5b 5e              pop     esi
0:000> kb
ChildEBP RetAddr  Args to Child
0012ff30 78158e69 e06d7363 00000001 00000003 kernel32!RaiseException+0x53
0012ff68 00401917 0012ff78 004022b8 00000001 MSVCR80!_CxxThrowException+0x46
0012ff7c 004011b2 00000001 00353098 003530d0 test!wmain+0x27
0012ffc0 7c816fd7 00011970 7c9118f1 7ffda000 test!__tmainCRTStartup+0x10f
0012fff0 00000000 004012fb 00000000 78746341 kernel32!BaseProcessStart+0x23

Beachten Sie, dass die Stapelüberlaufausnahme zur Laufzeit andere Probleme kaschieren, den Codefluss in eine andere Bahn lenken, vom Compiler generierte Schutzmechanismen wie z. B. /GS deaktivieren oder Inkonsistenzen in die Anwendung einbringen kann, indem Speicher freigesetzt wird, der noch verwendet wird. Wenn der Stapelspeicher fast restlos aufgebraucht ist, ist es möglich, dass eine Anwendung innerhalb von C++-Methoden durch eine Stapelüberlaufausnahme fehlschlägt und wichtige Teile des Codes nicht in der korrekten Reihenfolge ausgeführt werden. In solchen Fällen ist eine zusätzliche Selektierung erforderlich.

/GS-Ausnahmen
/GS (0xc0000409=STATUS_STACK_BUFFER_OVERRUN)-Ausnahmen sind Ausnahmen, die von Windows ausgelöst werden, wenn eine Manipulation des Sicherheitscookies, das die Rückgabeadresse schützt, erkannt wird. Da das Ziel von /GS darin besteht, Pufferüberläufe, die zur Codeausführung führen, in Denial-of-Service-Angriffe umzufunktionieren, können Sie bei jedem derartigen Absturz von einem Sicherheitsfehler ausgehen. (Leider wird der Code, der das Cookie prüft, aufgrund fehlerhaften Speichers, einer übertakteten Hauptplatine, fehlerhafter Hardware und anderer Probleme manchmal ausgelöst, ohne dass tatsächlich ein Pufferüberlauf vorliegt.)
In Windows Vista® löst das Betriebssystem eine „int 3“-Ausnahme aus, wenn ein STATUS_STACK_BUFFER_OVERRUN erkannt wird (vorausgesetzt, es ist ein Debugger vorhanden). In älteren Windows-Versionen muss in „kernel32!UnhandledExceptionFilter“ ein Haltepunkt gesetzt werden, damit erkennbar ist, ob ein Sicherheitscookie manipuliert wurde (andernfalls wird der Prozess abgebrochen und der Benutzer nicht verständigt).
In Abbildung 3 löst die Funktion „foo“ einen Pufferüberlauf aus, indem sie zu viele Daten in einen Stapelpuffer kopiert, was zur Folge hat, dass das /GS-Cookie überschrieben wird.
<the /GS cookie is being setup in the function prolog>
0:000:x86> u gs!foo
gs!foo:
010011b9 8bff            mov     edi,edi
010011bb 55              push    ebp
010011bc 8bec            mov     ebp,esp
010011be 83ec18          sub     esp,18h
<global cookie will be moved to eax register>
010011c1 a100200001      mov     eax,dword ptr [gs!__security_cookie (01002000)] 
010011c6 53              push    ebx
010011c7 56              push    esi
010011c8 57              push    edi
010011c9 8b7d08          mov     edi,dword ptr [ebp+8]
<cookie will be placed on the stack (ebp-4)>
010011cc 8945fc          mov     dword ptr [ebp-4],eax 
<content of the source (src) and destination buffers (dst) – before the overrun>
0:000:x86> dv /V
000bfefc @ebp+0x08             src = 0x009d16cb "123456789012345678901234567890"
000bfedc @ebp-0x18             dst = char [20] ""
<value of the security cookie on the stack; note that it is located right after the buffer, before saved ebp (0x000bff24) and the return address (0x0100124a)> 
0:000:x86> dd 000bfedc+0n20 l3
000bfef0  0000b96f 000bff24 0100124a
<value of global cookie>
0:000:x86> dd gs!__security_cookie l1 
01002000  0000b96f
... code runs ...
<after the overrun has happened, the cookie got overwritten (with 0x34333231) as well as the last two bytes of the return address (with (0x3039)>
0:000:x86> dd 000bfedc+0n20 l3
000bfef0  34333231 38373635 01003039
<in the function epilog before it returns the /GS cookie gets checked (i.e. the return address has not been used yet)> 
0:000:x86> u gs!foo+0x54
gs!foo+0x54:
0100120d 59              pop     ecx
<cookie is placed in the ecx register>
0100120e 8b4dfc          mov     ecx,dword ptr [ebp-4]
01001211 5f              pop     edi
01001212 5e              pop     esi
01001213 5b              pop     ebx
01001214 e882020000      call    gs!__security_check_cookie (01002000)
01001219 c9              leave
0100121a c20400          ret     4
<the below functions compares the global cookie with the one that was stored in the stack (currently in ecx register)>
0:000:x86> r
eax=0000000c ebx=7efde000 ecx=34333231 edx=00000000 esi=00000000 edi=00000000
eip=0100149b esp=000bfed8 ebp=000bfef4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
gs!__security_check_cookie:
0100149b 3b0d00200001    cmp     ecx,dword ptr [gs!__security_cookie (01002000)] ds:002b:01002000=0000b96f
<if a debugger was attached to the process, then an (int 3) will be issued (in Windows Vista) before the process gets terminated>
0:000:x86>
STATUS_STACK_BUFFER_OVERRUN encountered
 (15d0.1708): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
773e0004 cc              int     3


NX-Ausnahmen
NX-Ausnahmen (0xc0000005=STATUS_ACCESS_VIOLATION) werden von Windows ausgelöst, sobald eine Codeausführung auf einer Seite erkannt wird, die nicht als ausführbar gekennzeichnet ist (d. h. die Seite besitzt weder die Kennzeichen „PAGE_EXECUTE“ oder „PAGE_EXECUTE_READ“ noch andere relevante Kennzeichen). NX wird in 64-Bit-Versionen des Betriebssystems durchgesetzt. Da einige Anwendungen wie z. B. Entpacker und Anwendungen zur Verwaltung digitaler Rechte (Digital Rights Management, DRM) von der Ausführung von Code im Heap abhängen, darf nicht jede NX-Ausnahme sofort als Sicherheitsrisiko betrachtet werden. Trotzdem ist es sinnvoll, die Grundursache des Fehlers zu ermitteln, um sicherzustellen, dass er keine Auswirkungen auf die Sicherheit hat.
Es muss darauf hingewiesen werden, dass diese Art von Ausnahme den Fehlercode 0xc0000005 ausgibt, der nicht NX-spezifisch ist, denn jede Anwendung, die sich nicht normal verhält (indem sie beispielsweise aus einem nicht zugewiesenen Speicherbereich liest), kann diesen Fehler auslösen. Um zu ermitteln, ob die Ausnahme bzw. der Fehlercode tatsächlich mit NX zu tun hat, muss der auf der Seite eingestellte Schutz überprüft werden. Wenn die Seite nicht als ausführbar gekennzeichnet ist, liegt eine NX-Ausnahme vor. Andernfalls haben wir es mit einem Problem einer anderen Art zu tun. In Abbildung 4 wurde beispielsweise eine erste zufällige Ausnahme gefunden, aber die Anweisung scheint gültig zu sein und verweist auf gültige Daten.
(1424.a78): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
00000000`003b0020 2801            sub     byte ptr [rcx],al ds:00000000`7702e6aa=c3
0:000> r
rax=0000000000000001 rbx=000000000021fd10 rcx=000000007702e6aa
rdx=0000000000000000 rsi=000000000000000a rdi=0000000000000000
rip=00000000003b0020 rsp=000000000021fca0 rbp=00000000ff130000
 r8=000000000021fc98  r9=00000000ff130000 r10=0000000000000000
r11=0000000000000244 r12=00000000ff131728 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
00000000`003b0020 2801            sub     byte ptr [rcx],al ds:00000000`7702e6aa=c3
<when we execute the actual instruction again, we hit a second chance access violation>
0:000> t
(1424.a78): Access violation - code c0000005 (!!! second chance !!!)
00000000`003b0020 2801            sub     byte ptr [rcx],al ds:00000000`7702e6aa=c3
<a closer look at the protection of the page, shows that it does not have PAGE_EXECUTE set, this explains why the code cannot be executed>
0:000> !address 00000000`003b0020
 ProcessParametrs 00000000003b25c0 in range 00000000003b0000 00000000003e0000
 Environment 00000000003b1370 in range 00000000003b0000 00000000003e0000
    00000000003b0000 : 00000000003b0000 - 0000000000030000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageHeap
                    Handle   00000000003b0000


Umsetzung in die Praxis
Wie Sie sehen, ist das Analysieren von Programmfehlern auf Konsequenzen für die Sicherheit eine komplizierte und potenziell fehlerbehaftete Angelegenheit. Es wurde auf Ausnahmen eingegangen, auf die man bei solchen Analysen am häufigsten stößt, wie z. B. Schreib- und Lesezugriffsverletzungen, Teilung durch null, C++-Ausnahmen, /GS-Ausnahmen und Probleme im Zusammenhang mit NX. Am wichtigsten ist es, nie zu vergessen, dass nur durch eine komplette Grundursachenanalyse sichergestellt werden kann, ob eine Diagnose der Ausnutzbarkeit eines Absturzes richtig ist.
Wir haben eine Kurzübersicht über alle wichtigen Informationen zusammengestellt, die Sie als nützliche Richtlinien für die Analyse Ihrer eigenen Anwendungen auf oder in der Nähe Ihrer Arbeitsstation aufbewahren können. Abbildung 5 enthält eine Tabelle der in diesem Artikel behandelten Richtlinien. Tritt beispielsweise eine Schreibzugriffsverletzung auf, weil die CPU versucht, Daten in eine Speicherseite zu schreiben, für diese jedoch keinen Schreibzugriff besitzt, müssen Sie dieses Problem beheben.

   
Ausnahme Kommentare
Muss behoben werden  
Schreibzugriffsverletzung Die Zugriffsverletzung tritt ein, wenn die CPU versucht, Daten auf die Speicherseite zu schreiben, ohne Schreibzugriff darauf zu haben.
Lesezugriffsverletzung beim Anweisungszeiger (Zugriffsverletzung auf EIP) Die Zugriffsverletzung tritt ein, wenn die CPU versucht, eine Anweisung auf der Speicherseite auszuführen, ohne Lesezugriff zu auf diese haben.
Lesezugriffsverletzung Eine der folgenden Situationen kann eintreten: •Die Zugriffsverletzung tritt bei einer „rep assembly“-Anweisung ein (in einem Intel-Prozessor), deren Zählregister („ecx“) groß ist. •Die Zugriffsverletzung tritt bei einer „mov“-Anweisung ein, deren Ergebnis in den unmittelbar auf die „mov“-Anweisung folgenden Anweisungen als Ziel eines Aufrufs verwendet wird. •Die Zugriffsverletzung tritt bei einer „mov“-Anweisung ein, deren Ergebnis später in einer „rep“-Anweisung als Quelle („esi“), Ziel („edi“) oder Zählerwert („ecx“) verwendet wird.
Selektierung erforderlich  
Lesezugriffsverletzung Wenn die Zugriffsverletzung beim Lesen aus NULL (Adresse 0x00000000) oder beim Lesen aus der Speicheradresse eintritt, die nicht durch die Eingabe gesteuert wird, und der Wert vom Angreifer nicht manipuliert werden kann.
Normalerweise nicht ausnutzbar  
Teilung durch null Wenn die Zugriffsverletzung als eigenständiges Problem eintritt und vor dieser Zugriffsverletzung keine anderen Strukturen (z. B. Ausnahmehandler) beschädigt werden.
C++-Ausnahme Hier gilt dasselbe wie oben.
In Abbildung 1 oben wird der gleiche Prozess in Form einer Diagrammstruktur zur Entscheidungsfindung bei der Ausnutzbarkeitsdiagnose dargestellt. Sie beginnen beim obersten Knoten und fahren dann mit den jeweils zutreffenden Folgeknoten fort. Diesen Prozess setzen Sie so lange fort, bis Sie entweder den Knoten „Not Exploitable“ (nicht ausnutzbar) oder den Knoten „Exploitable“ (ausnutzbar) erreichen.
Wir hoffen, dass Ihnen diese Richtlinien bei der Ermittlung von Programmfehlern helfen. Weitere Informationen erhalten Sie über die Links in der Randleiste „Ressourcen zur Absturzanalyse“. Wenn Sie bei einem Microsoft-Produkt einen Absturz als ausnutzbar diagnostizieren, melden Sie dies bitte bei secure@microsoft.com.

Adel Abouchaev (CCIE#12037, MCSE, CISSP) ist Security Software Engineer im Secure Windows Initiative-Team (SWI) bei Microsoft. Er ist für Tests mit zufälligen Daten und Laufzeitanalysetools zuständig und stellt für Produktteams bei Microsoft automatisierte Verfahren zur Sicherheitsanalyse und Sicherheitsbeurteilung bereit.

Damian Hasse ist Lead Security Software Engineer bei Microsoft und leitet ein Team von Sicherheitsexperten, die als Angehörige des Microsoft Security Response Center (MSRC) Sicherheitsbedrohungen und Sicherheitsrisiken untersuchen.

Scott Lambert ist Security Program Manager im SWI-Team bei Microsoft. Er ist für die Verbesserung interner Sicherheitstools einschließlich verschiedener Tools für Tests mit zufälligen Daten zuständig. Lambert nutzt seine Branchenerfahrung, um sicherzustellen, dass SWI-Tools die überwiegende Mehrheit der Sicherheitsrisikoklassen identifizieren können.

Greg Wroblewski ist Security Software Engineer bei Microsoft und arbeitet in einem Security Response-Team.

Page view tracker