| Infos Home | Impressum | Original Artikel & Autoren Liste |
Buffer-Overflow-Attacken sind ein wichtiges Thema in der Computersicherheit und Netzwerksicherheit. Sie können nicht nur über jegliche Art von Netzwerken, sondern auch lokal auf dem System versucht werden. Behoben werden sie in der Regel nur durch kurzfristig gelieferte Fehlerkorrekturen (Patcheses) der Hersteller.
Neben Nachlässigkeiten bei der Programmierung werden Buffer Overflows vor allem durch auf der Von-Neumann-Architektur basierende Computersysteme ermöglicht, gemäß welcher Daten und Programm im gleichen Speicher liegen. Durch diese Hardwarenähe sind sie auch nur unter assemblierten oder compilierten Programmiersprachen ein Problem. Interpretierte Sprachen sind, abgesehen von dem Interpreter selber, nicht anfällig, da die Speicherbereiche für Daten unter vollständiger Kontrolle des Interpreters sind.
| Inhalt |
|
1 Programmiersprachen 2 Prozessoren und Programmierstil 3 Gegenmaßnahmen 5 Compiler und Compilererweiterungen |
Programmiersprachen
Die wesentliche Ursache für Buffer Overflows sind aber Programmiersprachen, die nicht die Möglichkeit bieten, Grenzen von Speicherbereichen automatisch zu überwachen, um eine Überschreitung von Speicherbereichen zu verhindern. Hierzu gehört besonders die Sprache C, die das Hauptgewicht auf Performance (und ursprünglich Einfachheit des Compilers) legt und auf eine Überwachung verzichtet, sowie die C-Weiterentwicklung C++. Hier ist ein Programmierer gezwungen, von Hand den entsprechenden Code zu generieren, wobei oft entweder absichtlich oder aus Nachlässigkeit darauf verzichtet wird. Die Überprüfung ist häufig auch fehlerhaft codiert, da während der Programmtests diese Programmteile meist nicht oder ungenügend getestet werden. Daneben bieten Sprachumfang und Standardbibliothek sehr viele fehleranfällige Konstrukte an, zu denen es in vielen Fällen kaum eine Alternative gibt.
Die häufig im professionellen Bereich verwendete Programmiersprache C++ bietet zwar theoretisch (eingeschränkte) Möglichkeiten zur automatischen Überprüfung von Feldgrenzen. Sie ist aber ursprünglich als C-Aufsatz entwickelt worden. Aus Gewohnheit, Kompatibilitätsgründen zu vorhandenem C-Code und Systemaufrufen in C-Konvention sowie aus Bequemlichkeits- und Performancegründen (die Laufzeitüberprüfungen sind im Gegensatz zu strukturell moderneren Sprachen wie z. B. Pascal oder Ada nicht Bestandteil der Sprache, sondern meist in der Sprache selbst geschriebener Bibliotheken oder müssen gar vom Programmierer selbst erstellt werden, sodass sie ihrerseits aufwendig, fehleranfällig und laufzeitmäßig sehr "teuer" sind) wird von diesen Möglichkeiten aber kaum Gebrauch gemacht.
Bei modernen Prozessoren ist es üblich, die Rücksprungadresse eines Unterprogramms und die lokalen Variablen auf einen als Stack bezeichneten Bereich zu legen. Dabei werden beim Unterprogrammaufruf zunächst die Rückkehradresse und danach die lokalen Variablen auf den Stack gelegt. Bei modernen Prozessoren wie dem Intel Pentium wird der Stack durch eingebaute Prozessorbefehle verwaltet und wächst zwingend nach unten. Werden Felder oder Zeichenketten in den lokalen Variablen verwendet, werden diese meist nach oben beschrieben. Wird die Feldgrenze nicht geprüft, kann man damit durch Überschreiten des Feldes die Rückkehradresse auf dem Stack erreichen und gegegenfalls absichtlich modifizieren.
Das folgende Programmstück in C, das in ähnlicher Form oft verwendet wird, zeigt einen solchen Buffer Overflow:
Prozessoren und Programmierstil
Weitere Eigentümlichkeiten sowohl der Sprachen C und C++ sowie die Eigentümlichkeiten der am häufigsten eingesetzten Prozessoren machen das Auftreten von Buffer Overflows wahrscheinlich. Die Programme in diesen Sprachen bestehen ausschließlich aus Unterprogrammen. Diese Programme besitzen lokale Variablen. input_line()
{ char line[1000]; /* Feld ist eigentlich Zeiger */
if (gets(line)) /* gets erhält Zeiger, keine Überprüfung */
parse_line(line);
}Bei Prozessoren, die den Stack nach unten beschreiben, sieht der Stack bei Aufruf von gets (Bibliotheksfunktion unter Unix) so aus:
| Rücksprungadresse | |
| 1000. Zeichen | |
| ... | |
| 3. Zeichen | |
| 2. Zeichen | |
| 1. Zeichen | Stackpointer |
| modifizierte Rücksprungadresse | |
| line, 1000. Zeichen | |
| ... | ... |
| line, 5. Zeichen | drittes Byte im Code |
| line, 4. Zeichen | zweites Byte im Code |
| line, 3. Zeichen | Ziel der Rücksprungadresse, Programmcodestart |
| line, 2. Zeichen | |
| line, 1. Zeichen | Stackpointer |
Gegenmaßnahmen
Programmerstellung
Bei der Erstellung von neuen Programmen sollte unbedingt auf die Überprüfung aller Feldgrenzen geachtet werden. Dadurch werden Buffer Overflows verhindert. Die Verwendung von Programmiersprachen, die automatisch Feldgrenzen überwachen, sollte in Erwägung gezogen werden. Dies ist jedoch nicht immer möglich. Bei Verwendung von C++ sollte die Verwendung von Feldern im C-Stil soweit wie möglich vermieden werden. input_line()
{ char line[1000]; /* Feld ist eigentlich Zeiger */
if (fgets(line,1000,stdin)) /* fgets überprüft die Länge */
parse_line(line);
}Überprüfung des Programmcodes
Spezielle Überprüfungswerkzeuge erlauben die Analyse des Codes und entdecken gegebenenfalls mögliche Schwachstellen. Allerdings kann der Code zur Feldgrenzenüberprüfung fehlerhaft sein, was oftmals nicht getestet wird.
| Rücksprungadresse | |
| Zufallszahlbarriere | |
| line, 1000. Zeichen | |
| ... | |
| line, 3. Zeichen | |
| line, 2. Zeichen | |
| line, 1. Zeichen | Stackpointer |
| Rücksprungadresse | |
| line, 1000. Zeichen | |
| ... | |
| line, 3. Zeichen | |
| line, 2. Zeichen | |
| line, 1. Zeichen | |
| Kopie der Rücksprungadresse | Stackpointer |
|
Der Ursprungsartikel stammt von der deutschsprachigen Wiki pedia (siehe oben: "Original Artikel & Autoren Liste"). Der Text steht unter der GNU Freie Dokumentation Lizenz. |