Sicherheitsüberprüfungen zur Laufzeit

Veröffentlicht: 13. Jul 2004 | Aktualisiert: 14. Nov 2004

Dieser Artikel veranschaulicht Visual C++-Compiler- und -Bibliothekfeatures, die Entwickler einsetzen können, um die Stabilität und Sicherheit ihrer Anwendungen zu erhöhen.

Dieser Artikel ist Teil eines Codebeispiels aus dem Visual C++ Toolkit 2003, das als Download unter http://msdn2.microsoft.com/visualc/aa336490 (in Englisch) verfügbar ist.

* * *

Auf dieser Seite

"/GS" - Puffer-Sicherheitsüberprüfung "/GS" - Puffer-Sicherheitsüberprüfung
"/RTC" "/RTC"
Schlussfolgerung Schlussfolgerung

Die Beispielanwendung umfasst sechs Tests. Anhand dieser Tests sollen von Programmierern verursachte Fehler oder die Ergebnisse einer böswilligen Eingabe in ein Programm, das die Eingabe nicht ausreichend überprüft, simuliert werden:

  1. Überschreiben eines Puffers, indem eine Schleife zu oft durchlaufen wird. Da nur ein Puffer definiert ist, betrifft das Überschreiben den Rest des Stapels einschließlich der Rückgabeadresse.

  2. Überschreiben eines Puffers, wenn sich im Stapel auch ein anderer Puffer befindet.

  3. Unterschreiben eines Puffers.

  4. Verwenden einer nicht initialisierten Variablen.

  5. Umwandlung, durch die möglicherweise Informationen verloren gehen.

  6. Verwenden einer nicht initialisierten Variablen auf komplexere Art und Weise.

Einige dieser Tests sind für die beiden Optionen /GS und /RTC relevant.

"/GS" - Puffer-Sicherheitsüberprüfung

Wenn Sie mit der Option /GS kompilieren, wird Code zum Erkennen von Pufferüberläufen eingefügt, durch die möglicherweise die Rückgabeadresse der Funktion überschrieben wurde. Der Benutzer wird durch ein Dialogfeld gewarnt, und das Programm wird dann beendet. Auf diese Weise wird verhindert, dass ein Angreifer die Kontrolle über die Anwendung übernimmt. Optional kann eine benutzerdefinierte Fehlerroutine geschrieben werden, um den Fehler zu behandeln, anstatt das standardmäßige Dialogfeld anzuzeigen.

Ein besonderes "Cookie" wird genau vor der Rückgabeadresse eingefügt, so dass dieses bei einem Pufferüberlauf geändert wird. Kurz vor der Rückgabe der Funktion wird der Wert des Cookies geprüft. Wenn sich der Wert geändert hat, wird der Handler aufgerufen. Für einen Server oder Dienst kann eine andere Behandlung als das Anzeigen eines Dialogfelds erforderlich sein; weitere Informationen zum Schreiben eines Handlers finden Sie auf der MSDN-Website.

Das Cookie wird von der C-Laufzeit beim Start generiert, so dass Angreifer den Wert des Cookies nicht kennen; der Wert ist bei jeder Programmausführung unterschiedlich. Da die C-Laufzeitinitialisierung die CRT (C Run-Time; C-Laufzeit) verwendet, ist es wichtig, dass sie nicht zweimal durchgeführt und der Cookie neu generiert wird, da in diesem Fall so genannte "False-Positives" auftreten, bei denen irrtümlicherweise ein Angriff angenommen wird.

Diese Compileroption ist für freigegebenen Code vorgesehen. Der Eingabeaufforderungscode zum Kompilieren des Beispielcodes zum Testen lautet:

cl /O2 /ML /GS /EHsc GS-RTC.cpp

(Die Option /O2 aktiviert die Optimierung. Dies ist kein Debugbuild.) Durch das Ausführen dieses Befehls wird gs-rtc.exe erstellt.
Test 1 veranschaulicht, was die Option /GS bewirkt:

void Test1() 
{ 
   char buffer1[100]; 
   for (int i=0 ; i < 200; i++) 
   { 
   buffer1[i] = 'a'; 
   } 
   buffer1[sizeof(buffer1)-1] = 0; 
   cout << buffer1 << endl; 
}

Die for-Schleife wird zu oft ausgeführt, verursacht einen Überlauf in der Rückgabeadresse und hat zudem noch weitere Auswirkungen. Zum Ausführen von Test 1 kompilieren Sie mit der Option /GS und geben dann folgenden Befehl aus:

gs-rtc 1

Das folgende Dialogfeld wird angezeigt:

SicherheitsueberpruefungenZurLaufzeit_01.gif

Abbildung 1. Dieser Pufferüberlauffehler wird durch die Option "GS/" generiert

Kompilieren Sie zum Vergleich denselben Code ohne die Option /GS:
c

l /Od /MLd /EHsc /ZI GS-RTC.cpp /link

Führen Sie den Test erneut aus. Daraufhin wird das folgende Dialogfeld angezeigt:
SicherheitsueberpruefungenZurLaufzeit_02.gif
Abbildung 2. Dieser Fehler wird ohne die Option "GS/" generiert

Ein Angreifer, der einen Pufferüberlaufangriff startet, überschreibt die Rückgabeadresse mit einer sorgfältig durchdachten Adresse, die ihm Kontrolle über die Anwendung verleiht. In diesem Beispiel wird die Rückgabeadresse einfach mit 'a's (hex 61) überschrieben.

/GS erkennt keine Überläufe, die die Rückgabeadresse nicht überschreiben, aber anderen Speicher beschädigen und ungenaue Ergebnisse erzeugen. Versuchen Sie, Test 2 mit einem der bisher gezeigten Kompilierbefehle auszuführen.

gs-rtc 2

Obwohl Test 2 den Speicher überschreibt, wird dies nicht von der Option /GS erkannt. In dem optimierten Releasebuild dieses Codes ist die Zuordnung der Null-Terminatoren hinter die Schleife verschoben worden, so dass das Überschreiben keine Folgen hat. Dies ist im Allgemeinen nicht zutreffend.

 

"/RTC"

RTC steht für RunTime Checks (Laufzeitüberprüfungen). Für RTC ist eine Reihe von Unteroptionen verfügbar. Im Gegensatz zu /GS wurde /RTC für Debugbuilds konzipiert. RTC funktioniert nicht bei optimiertem Code. Wie bei /GS können Sie einen eigenen Handler schreiben, wenn Sie das standardmäßige Dialogfeld nicht verwenden möchten.

Wenn Sie eine Anwendung mit Microsoft Visual Studio debuggen, geben Ihnen die RTC-Dialogfelder die Möglichkeit, die Anwendung an der Fehlerstelle zu debuggen. Zudem können Sie in Visual Studio mehrere Konfigurationen erstellen, die über verschiedene Kombinationen von Optionen ein Build generieren; verwenden Sie z.B. /GS immer für Releasebuilds und eine oder mehrere /RTC-Optionen für Debugbuilds.
"/RTCs" - Stapelrahmen-Fehlerüberprüfung zur Laufzeit
Diese Option führt verschiedene Aufgaben aus, die eine Beschädigung des Stapels verhindern können.

  • Initialisieren aller lokalen Variablen auf Nicht-Null-Werte bei jedem Funktionsaufruf. Dadurch wird verhindert, dass versehentlich Werte im Stapel aus vorherigen Aufrufen verwendet werden.

  • Prüfen des Stapelzeigers, um eine Beschädigung festzustellen, z.B. wenn eine Beschädigung verursacht wird, weil eine Funktion an einer Stelle als __stdcall und an anderer Stelle als __cdecl definiert wurde.

  • Erkennen von Überläufen und Unterläufen von lokalen Variablen. Diese Option unterscheidet sich von der Option /GS, da sie nur in Debugbuilds verfügbar ist, und sie erkennt Beschädigungen an beiden Enden eines Puffers und in allen Puffern.

cl /Od /MLd /ZI /EHsc /RTCs GS-RTC.cpp

Dieser Befehl deaktiviert die Optimierung (/Od) und legt die _DEBUG-Präprozessordefinition fest.

Führen Sie Test 1 erneut aus, nachdem Sie das Beispiel mit diesem Befehl kompiliert haben. Dadurch wird das Überschreiben abgefangen.

Test 2 veranschaulicht einen Stapelüberlauf, bei dem die Rückgabeadresse nicht einbezogen ist:

void Test2() 
{ 
   char buffer1[100]; 
   char buffer2[100]; 
   buffer1[0] = 0; 
   for (int i=0 ; i <= sizeof(buffer2); i++) 
   { 
     buffer2[i] = 'a'; 
   } 
   buffer2[sizeof(buffer2)-1] = 0; 
   cout << buffer2 << '-' << buffer1 << endl; 
}

Diese Schleife führt zu einem Überlauf von buffer2 um ein Zeichen, da <= anstelle von < verwendet wird. Das erste Zeichen von buffer1 wird überschrieben. Wenn Compilerschalter /RTCs zum Kompilieren verwendet wird und die Ausführung über gs-rtc 2 erfolgt, wird dieses Dialogfeld angezeigt:
SicherheitsueberpruefungenZurLaufzeit_03.gif
Abbildung 3. Dieser Fehler wird bei Verwendung der Option "gs-rtc 2" generiert

Test 3 veranschaulicht einen Unterlauf:

void Test3() 
{ 
   char buffer1[100]; 
   char buffer2[100]; 
   memset(buffer1,'a',sizeof(buffer1)-1); 
   buffer1[sizeof(buffer1)-1]=0; 
   memset(buffer2,'b',sizeof(buffer2)-1); 
   buffer2[sizeof(buffer2)-1]=0; 
   *(buffer1-1) = 'c'; 
   cout << buffer1 << endl; 
   cout << buffer2 << endl; 
 }

(Verwenden Sie den Befehl gs-rtc 3, um Test 3 auszuführen.) In diesem Fall wird das letzte Byte von buffer2 überschrieben, und das Dialogfeld gibt an, dass das Problem in buffer1 aufgetreten ist. Ohne diese Laufzeitüberprüfungen wird bei diesem Test Folgendes ausgegeben:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
aaaaaaaaaaaaaaaaaaaaaaaa 
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Da der Terminator von buffer2 durch das Unterschreiben zerstört wurde, wird bei jeder Verwendung von buffer2 als Zeichenfolge mit Null-Terminator eine Zeichenfolge generiert, die doppelt so lang ist wie erwartet.

Test 4 veranschaulicht, wie die Option /RTCs nicht initialisierte Variablen auf einen Flag-Wert setzt:

void Test4() 
{ 
   unsigned int var; 
   cout << hex << var; 
}

Führen Sie Test 4 aus, nachdem Sie mit Hilfe der Option /RTCs kompiliert haben. Sie sehen dann den hexadezimalen Wert cccccccc. Wenn Sie ohne die Option /RTCs kompilieren, sehen Sie den Zufallswert, der im Stapel gelassen wurde.
"/RTCc" - Erkennt Zuordnungen, die einen Datenverlust verursacht haben
Diese Option fügt Code ein, der eine Warnung ausgibt, wenn eine Zuordnung einen Datenverlust verursacht. Auf diese Weise können Sie sicherstellen, dass beim Umwandeln in kleinere Datentypen keine Daten verloren gehen. Dies wird durch Test 5 veranschaulicht.

void Test5(int value) 
{ 
   unsigned char ch; 
   ch = (unsigned char)value; 
}

Kompilieren Sie diesen Code mit dem folgenden Befehl:

cl /Od /MLd /ZI /EHsc /RTCc GS-RTC.cpp

Führen Sie diesen Befehl mit einer anderen Zahl in der Eingabeaufforderung aus. Dieser Befehl löst z.B. folgenden Fehler aus:

gs-rtc 5 300

SicherheitsueberpruefungenZurLaufzeit_04.gif
Abbildung 4. Dieser Fehler wird generiert, wenn eine Zahl größer als 255 mit einem "/RTCc"-Schalter verwendet wird

Ein nicht signiertes Zeichen kann maximal den Wert 255 annehmen, so dass es zu einem Datenverlust kommt, wenn der Wert 300 zugewiesen wird. Wenn Sie den Befehl erneut mit gs-rtc 5 200 ausführen, tritt kein Fehler auf.

Wenn Sie eine Umwandlung in einen kleineren Typ durchführen möchten und absichtlich die oberen Bits verlieren, verwenden Sie eine Maske wie die folgende:

ch = (unsigned char)(value & 0xFF);

"/RTCu" - Verwendung von Berichtsvariablen ohne Initialisierung
Diese Option gibt eine Warnung aus, wenn auf eine nicht initialisierte Variable zugegriffen wird. Dies wird durch Test 6 veranschaulicht, der drei Untertests umfasst:

void Test6(int value)
{
   int uninitialized;
   int var;
   switch (value) {
      case 3:
         uninitialized = 4;
      case 2:
         var = 5 * uninitialized;
         break;
      case 1:
         int *var2;
         var2= &uninitialized;
         var = 5 * uninitialized;
         break;
   }
}

(Beachten Sie, dass nach case 3 mit case 2 fortgefahren wird.) Kompilieren Sie diesen Code mit dem folgenden Befehl:

cl /Od /MLd /ZI /EHsc /RTCu GS-RTC.cpp

Wenn Sie gs-rtc 6 2 ausführen, wird folgendes Dialogfeld angezeigt:
SicherheitsueberpruefungenZurLaufzeit_05.gif
Abbildung 5. Dieser Fehler wird generiert, wenn die Option "gs-rtc 2" verwendet wird

Wenn Sie gs-rtc 6 3 ausführen, wird kein Dialogfeld angezeigt, aber die Variable wird initialisiert. "gs-rtc 6 1 verursacht trotz der initialisierten Variablen dennoch keinen Fehler, da der Compiler keine Variablen verfolgt, die durch einen Zeiger initialisiert werden können.

 

Schlussfolgerung

In dieser Tabelle werden die verschiedenen Tests und die Ergebnisse der einzelnen Compileroptionen zusammengefasst.

Einfaches Kompilieren

/GS

/RTCs

/RTCu

/RTCc

gs-rtc 1

Rückgabeadresse ist beschädigt. Programm wird beendet (aber ein Pufferüberlaufangriff ist ggf. erfolgreich).

Pufferüber-lauf wird erkannt. Programm wird beendet.

Beschädigung wird erkannt.

Nicht zutreffend

Nicht zutreffend

gs-rtc 2

Buffer1 wird erweitert mit zufälligen Zeichen, da die abschließende Null überschrieben wird.

Buffer1 wird erweitert mit zufälligen Zeichen, da die abschließende Null überschrieben wird.

Beschädigung wird erkannt.

Nicht zutreffend

Nicht zutreffend

gs-rtc 3

Buffer2 wird mit dem gesamten Inhalt von Buffer1 erweitert.

Buffer2 wird mit dem gesamten Inhalt von Buffer1 erweitert.

Beschädigung wird erkannt.

Nicht zutreffend

Nicht zutreffend

gs-rtc 4

Gibt zufällige Ganzzahl aus.

Nicht zutreffend

Gibt cccccccc aus.

Nicht zutreffend

Nicht zutreffend

gs-rtc 5.200

Kein Ergebnis

Nicht zutreffend

Nicht zutreffend

Nicht zutreffend

Kein Ergebnis - keine Kürzung

gs-rtc 5.300

Kein Ergebnis

Nicht zutreffend

Nicht zutreffend

Nicht zutreffend

Datenverlust wird erkannt.

gs-rtc 6 1

Kein Ergebnis

Nicht zutreffend

Nicht zutreffend

Nicht zutreffend

Verwendung von nicht initialisierter Variable wird nicht erkannt - maskiert durch Zeiger.

gs-rtc 6 2

Kein Ergebnis

Nicht zutreffend

Nicht zutreffend

Nicht zutreffend

Verwendung von nicht initialisierter Variable wird erkannt.

gs-rtc 6 3

Kein Ergebnis

Nicht zutreffend

Nicht zutreffend

Nicht zutreffend

Kein Ergebnis, Variable wurde initialisiert.

Weitere Informationen zur Option /GS finden Sie online (in Englisch) unter:
http://msdn2.microsoft.com/library/8dbf701c(vs.71).aspx
http://msdn.microsoft.com/chats/vstudio/vstudio_030802.asp
http://www.codeproject.com/tips/seccheck.asp