Gefahrenquellen für Pufferüberläufe gibt es in den Programmiersprachen C und C++ häufig. Auch der Stil, wie der Programmierer ein Programm gestaltet, hat maßgeblich Einfluß auf die Wahrscheinlichkeit von Pufferüberläufen durch einen Angreifer.
Die Eigentümlichkeiten, der am häufigsten eingesetzten Prozessoren, wie auch die Besonderheiten der Sprachen C und C++, machen das Auftreten von Pufferüberläufen für wahrscheinlich. Programme geschrieben in diesen Sprachen bestehen zum Teil aus Unterprogrammen. Diese besitzen lokale Variablen.
Bei den heutigen modernen Prozessoren ist es üblich, die Rücksprungadresse eines Unterprogramms und die lokalen Variablen auf den Stack zu legen. Der Stack ist ein Stapel, der nach dem LIFO Prinzip arbeitet. Dabei werden bei dem Aufruf des Unterprogramms zunächst die Rückkehradresse und anschließend die lokalen Variablen auf den Stack gespeichert. Bei Prozessoren der modernen Generation, wie beispielsweise dem Intel Pentium, wird der Stack durch eingebaute Befehle in der CPU verwaltet. Der Stack wächst zwingend nach unten. Werden Datenfelder oder Zeichenketten (Strings) in den lokalen Variablen verwendet, werden sie meist nach oben geschrieben. Wird nun die Feldgrenze nicht überprüft, so kann der Angreifer damit durch das Überschreiten des Feldes die Rückkehradresse auf dem Stack erreichen und diese gegebenenfalls absichtlich verändern. Wenn dies gelingt, springt das Unterprogramm nicht zur vorgesehenen Adresse zurück.
Dazu ein beliebtes Beispiel. Das folgendes Programmstück in C zeigt die Gefahr eines Pufferüberlaufes:
void eingabe_zeichenkette()
{ char zeichenkette[2342]; // Das Feld ist eigentlich ein Zeiger
if ( gets (zeichenkette) ) // gets bekommt den Zeiger
// an dieser Stelle gibt es keine
// Überprüfung!!
schau_kette_an (zeichenkette);
}
Bei Mikroprozessoren, die den Stack weiter nach unten wachsen lassen, sieht der Stack vor dem Aufruf von der Funktion gets folgendermaßen aus:
+++++++++++++++++++++++ + Rücksprungadresse + +++++++++++++++++++++++ + 2342. Zeichen + +++++++++++++++++++++++ + . . . + +++++++++++++++++++++++ + . . . + +++++++++++++++++++++++ + 3. Zeichen + +++++++++++++++++++++++ + 2. Zeichen + +++++++++++++++++++++++ + 1. Zeichen + <---- Stackpointer +++++++++++++++++++++++
Der Stack wächst hier nach unten, die Variable wird nach oben überschrieben. Würde der Angreifer eine Kopie des Programms besitzen, das er genauer analysieren kann, so könnte er weitaus mehr Code überschreiben und dem Programm völlig neue Fähigkeiten übergeben. Auf diese Weise lassen sich auch größere Menge Code in ein System einschleusen.
Betrachte man nochmals unser Beispiel. Die Funktion gets, aus der C Standardbibliothek, liest eine Zeile von der Eingabe ein und schreibt diese Zeichen ab zeichenkette[0] in den Stack hinein. Jedoch prüft die Funktion gets die Länge der Zeichen nicht. Sollte die Eingabe länger sein, als der dafür vorgesehene Speicher, so bemerkt das gets nicht.
Die Funktion gets erhält, ganz nach der Semantik von C, nur die Speicheradresse als Pointer. Dabei fehlt völlig die verfügbare Länge der Zeichenkette. Wenn man nun jetzt 2346 Zeichen eingibt, so überschreiten die letzten 4 Bytes die Rücksprungadresse. Es wird hier angenommen, das eine Adresse 32 Bit lang ist, also 4 Bytes. Der Angreifer, kann in den ersten 2342 Bytes ein von ihm gestaltetes Programm eingeben und nur mithilfe der Manipulation der Rücksprungadresse dieses zur Ausführung bringen.
Schaue man sich nun den Stackaufbau in der modifizierten Art an.
++++++++++++++++++++++++++++++++++++ + modifizierte Rücksprungadresse + ++++++++++++++++++++++++++++++++++++ + line, 2342. Zeichen + ++++++++++++++++++++++++++++++++++++ + . . . . . . + ++++++++++++++++++++++++++++++++++++ + line, 5. Zeichen 3. Byte im Code + ++++++++++++++++++++++++++++++++++++ + line, 4. Zeichen 2. Byte im Code + ++++++++++++++++++++++++++++++++++++ + line, 3. Zeichen + <-- Ziel der Rücksprungadresse ++++++++++++++++++++++++++++++++++++ Hier Programmcodestart. + line, 2. Zeichen + ++++++++++++++++++++++++++++++++++++ + line, 1. Zeichen + <-- Stackpointer ++++++++++++++++++++++++++++++++++++
Der Angreifer kann durch diese Fähigkeit versuchen weitere Informationen über das System zu erlangen. Würde Beispielsweise es sich hierbei um ein UNIX System handeln und der betroffene Prozess besitzt bereits root-Rechte so kann der Angreifer die Dateien /etc/passwd und /etc/shadow auslesen und daraus versuchen die Kennwörter wiederherzustellen. Sehr alte UNIX Derivate legen sogar diese Kennwörter unverschlüsselt ab. In diesem Fall steht anschließend dem Systemzugriff nichts mehr im Wege.