Stack Overflow

{{#include ../../banners/hacktricks-training.md}}

Was ist ein Stack Overflow

Ein Stack Overflow ist eine Sicherheitsanfälligkeit, die auftritt, wenn ein Programm mehr Daten auf den Stack schreibt, als ihm zugewiesen sind. Diese überschüssigen Daten werden benachbarte Speicherbereiche überschreiben, was zur Korruption gültiger Daten, zur Störung des Kontrollflusses und möglicherweise zur Ausführung von schädlichem Code führt. Dieses Problem tritt häufig aufgrund der Verwendung unsicherer Funktionen auf, die keine Grenzkontrollen für Eingaben durchführen.

Das Hauptproblem bei diesem Überschreiben ist, dass der gespeicherte Befehlszeiger (EIP/RIP) und der gespeicherte Basiszeiger (EBP/RBP), um zur vorherigen Funktion zurückzukehren, auf dem Stack gespeichert sind. Daher wird ein Angreifer in der Lage sein, diese zu überschreiben und den Ausführungsfluss des Programms zu steuern.

Die Sicherheitsanfälligkeit tritt normalerweise auf, weil eine Funktion mehr Bytes auf den Stack kopiert, als dafür zugewiesen sind, und somit in der Lage ist, andere Teile des Stacks zu überschreiben.

Einige gängige Funktionen, die anfällig dafür sind, sind: strcpy, strcat, sprintf, gets... Auch Funktionen wie fgets, read & memcpy, die ein Längenargument annehmen, könnten auf eine anfällige Weise verwendet werden, wenn die angegebene Länge größer ist als die zugewiesene.

Zum Beispiel könnten die folgenden Funktionen anfällig sein:

void vulnerable() {
char buffer[128];
printf("Enter some text: ");
gets(buffer); // This is where the vulnerability lies
printf("You entered: %s\n", buffer);
}

Finden von Stack Overflow Offsets

Die häufigste Methode, um Stack Overflows zu finden, besteht darin, eine sehr große Eingabe von As zu geben (z. B. python3 -c 'print("A"*1000)') und einen Segmentation Fault zu erwarten, der anzeigt, dass die Adresse 0x41414141 versucht wurde zuzugreifen.

Darüber hinaus, sobald Sie festgestellt haben, dass es eine Stack Overflow-Sicherheitsanfälligkeit gibt, müssen Sie den Offset finden, bis es möglich ist, die Rücksprungadresse zu überschreiben. Dafür wird normalerweise eine De Bruijn-Sequenz verwendet. Diese ist für ein gegebenes Alphabet der Größe k und Teilsequenzen der Länge n eine zyklische Sequenz, in der jede mögliche Teilsequenz der Länge n genau einmal als zusammenhängende Teilsequenz erscheint.

Auf diese Weise ist es anstelle von Hand erforderlich, herauszufinden, welcher Offset benötigt wird, um das EIP zu steuern, möglich, als Padding eine dieser Sequenzen zu verwenden und dann den Offset der Bytes zu finden, die es überschrieben haben.

Es ist möglich, pwntools dafür zu verwenden:

from pwn import *

# Generate a De Bruijn sequence of length 1000 with an alphabet size of 256 (byte values)
pattern = cyclic(1000)

# This is an example value that you'd have found in the EIP/IP register upon crash
eip_value = p32(0x6161616c)
offset = cyclic_find(eip_value)  # Finds the offset of the sequence in the De Bruijn pattern
print(f"The offset is: {offset}")

oder GEF:

#Patterns
pattern create 200 #Generate length 200 pattern
pattern search "avaaawaa" #Search for the offset of that substring
pattern search $rsp #Search the offset given the content of $rsp

Ausnutzen von Stack-Überläufen

Während eines Überlaufs (vorausgesetzt, die Überlaufgröße ist groß genug) werden Sie in der Lage sein, Werte von lokalen Variablen im Stack zu überschreiben, bis Sie das gespeicherte EBP/RBP und EIP/RIP (oder sogar mehr) erreichen.
Die häufigste Methode, diese Art von Schwachstelle auszunutzen, besteht darin, die Rücksprungadresse zu modifizieren, sodass beim Ende der Funktion der Kontrollfluss dorthin umgeleitet wird, wo der Benutzer in diesem Zeiger angegeben hat.

In anderen Szenarien könnte es jedoch ausreichen, einige Variablenwerte im Stack zu überschreiben, um die Ausnutzung zu erreichen (wie bei einfachen CTF-Herausforderungen).

Ret2win

In dieser Art von CTF-Herausforderungen gibt es eine Funktion, die im Binärprogramm nie aufgerufen wird und die Sie aufrufen müssen, um zu gewinnen. Für diese Herausforderungen müssen Sie nur den Offset finden, um die Rücksprungadresse zu überschreiben, und die Adresse der Funktion finden, die aufgerufen werden soll (in der Regel wäre ASLR deaktiviert), sodass, wenn die verwundbare Funktion zurückkehrt, die versteckte Funktion aufgerufen wird:

{{#ref}} ret2win/ {{#endref}}

Stack Shellcode

In diesem Szenario könnte der Angreifer einen Shellcode im Stack platzieren und den kontrollierten EIP/RIP ausnutzen, um zum Shellcode zu springen und beliebigen Code auszuführen:

{{#ref}} stack-shellcode/ {{#endref}}

ROP & Ret2... Techniken

Diese Technik ist das grundlegende Framework, um den Hauptschutz der vorherigen Technik zu umgehen: Kein ausführbarer Stack (NX). Und sie ermöglicht die Durchführung mehrerer anderer Techniken (ret2lib, ret2syscall...), die letztendlich beliebige Befehle ausführen, indem sie vorhandene Anweisungen im Binärprogramm ausnutzen:

{{#ref}} ../rop-return-oriented-programing/ {{#endref}}

Heap-Überläufe

Ein Überlauf muss nicht immer im Stack sein, er könnte auch im Heap sein, zum Beispiel:

{{#ref}} ../libc-heap/heap-overflow.md {{#endref}}

Arten von Schutzmaßnahmen

Es gibt mehrere Schutzmaßnahmen, die versuchen, die Ausnutzung von Schwachstellen zu verhindern. Überprüfen Sie diese in:

{{#ref}} ../common-binary-protections-and-bypasses/ {{#endref}}

Beispiel aus der Praxis: CVE-2025-40596 (SonicWall SMA100)

Eine gute Demonstration, warum sscanf niemals für das Parsen von nicht vertrauenswürdigen Eingaben vertraut werden sollte, erschien 2025 im SSL-VPN-Gerät SMA100 von SonicWall. Die verwundbare Routine innerhalb von /usr/src/EasyAccess/bin/httpd versucht, die Version und den Endpunkt aus jeder URI zu extrahieren, die mit /__api__/ beginnt:

char version[3];
char endpoint[0x800] = {0};
/* simplified proto-type */
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
  1. Die erste Umwandlung (%2s) speichert sicher zwei Bytes in version (z.B. "v1").
  2. Die zweite Umwandlung (%s) hat keinen Längenbezeichner, daher wird sscanf bis zum ersten NUL-Byte weiter kopieren.
  3. Da endpoint sich im Stack befindet und 0x800 Bytes lang ist, führt das Bereitstellen eines Pfades, der länger als 0x800 Bytes ist, zur Beschädigung von allem, was sich nach dem Puffer befindet einschließlich des Stack-Canyons und der gespeicherten Rücksprungadresse.

Ein einzeiliger Proof-of-Concept reicht aus, um den Absturz vor der Authentifizierung auszulösen:

import requests, warnings
warnings.filterwarnings('ignore')
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)

Auch wenn Stack-Canaries den Prozess abbrechen, erhält ein Angreifer dennoch ein Denial-of-Service-Primitive (und möglicherweise mit zusätzlichen Informationslecks eine Codeausführung). Die Lektion ist einfach:

  • Geben Sie immer eine maximale Feldbreite an (z.B. %511s).
  • Bevorzugen Sie sicherere Alternativen wie snprintf/strncpy_s.

References

{{#include ../../banners/hacktricks-training.md}}