assignments/help/sicherheitsluecken.md

5.0 KiB

Typische Sicherheitslücken

Im Folgenden finden Sie eine kleine Auflistung von Sicherheitslücken, die sich gut in Crackmes verwenden können. Die Liste ist natürlich nicht vollständig - lassen Sie Ihrer Kreativität freien Lauf. Gerne können Sie auch noch weitere Lücken vorschlagen.

Buffer-Overflow

Lokale Variablen überschreiben

Buffer auf dem Stack liegen dort zusammen mit allen anderen lokalen Variablen und können diese bei fehlender oder falscher Prüfung der Grenzen überschreiben (Buffer-Overflow).

int i = 0;
char x[3];

printf("%d\n", i); // -> 0
strcpy(x, "Hell");
printf("%d\n", i); // -> 108

Rücksprungadresse überschreiben

Buffer-Overflows können auch dazu benutzt werden, die Rücksprungadresse zur Aufrufenden Funktion zu überschreiben. Stack-Guards sollen dies verhindern, können aber manchmal übergangen oder explizit durch -fno-stack-protector beim Compilieren ausgeschaltet werden.

Stackbasierte Schwachstellen

Base-Pointer

Auf dem Stack liegt nicht nur die Rücksprungadresse, sondern auch der gespeicherte Base-Pointer (EBP) der vorhergehenden Funktion. Wenn man diesen durch einen Buffer-Overflow modifizieren kann, besteht die Möglichkeit lokale Variablen der aufrufenden Funktion zu verändern.

Reste auf dem Stack

Der Stack wird bei einem Funktionsaufruf nicht gesäubert. Wenn die Funktion ihre Variablen nicht initialisiert, kann man möglicherweise alte Daten auf dem Stack entdecken.

int a(void) {
  short a = 0xbeef;
  short b = 0xdead;

}

int b(void) {
  int z;
  printf("0x%x\n", z);
}

int main() {
  a();
  b(); // -> 0xbeefdead
}

String-Funktionen

Größe von String-Puffern

Wegen des Nullbytes muss der Buffer für einen String immer mindestens ein Zeichen länger sein, als der String selbst. Wenn man dies nicht berücksichtigt, kommt es Buffer-Overflows.

char *text = "Hallo";
char *copy = alloc(strlen(text)); /* zu klein */
strcpy(copy, text); /* Buffer overflow */

Länge von Strings

Die normalen String-Funktionen (strcpy. strlen ...) lesen bis zum ersten 0-Byte ('\0') im char*-Array. Dies kann man eventuell ausnutzen, um mehr Daten zu verändern als vom Entwickler gewollt, weil man z.B. einen Puffer bis zum letzten Byte füllt.

int k = 0x47554755;
char src[] = { 'H', 'a', 'l', 'l', 'o' };
char dest[30];

strcpy(dest, src);

printf("%s", dest); // -> HalloUGUGJ

Puffer überschreiben

Funktionen wie gets, strcpy und sprintf überprüfen die Größe des Zielpuffers nicht und lassen sich deswegen hervorragend für Buffer-Overflows nutzen.

int i = 0;
char b[5];
gets(b);
printf("%d\n", i);

Deswegen sollten die Funktionen nicht mehr genutzt werden. Findet man sie in den Imports eines Programms lohnt sich die Suche nach der Stelle, an der sie verwendet werden.

Integer-Overflow

Alle Integer-Datentypen ohne unsigned (char, short, int, long, long long) sind vorzeichenbehaftet und laufen von Plus nach Minus über.

int i = 2147483647;
i++;
printf("%d\n", i); // -> -2147483648

Alle Integer-Datentypen mit unsigned laufen zur 0 über und zum Max-Value unter.

unsigned int i = 0;
i--;
printf("%u\n", i); // -> 4294967295
i++;
printf("%u\n", i); // -> 0

Formatstring-Schwachstelle

Formatstringschwachstellen (printf) erlauben es, Variablen auf dem Stack zu lesen und zu schreiben.

int main() {
  long k = 0xcafebabe;
  long *p = &k;

  printf("0x%9$x"); // -> 0xcafebabe
}

Funktionspointer

Funktionspointer auf dem Stack können, wenn sie durch eine der anderen Schwachstellen modifiziert werden können, dazu genutzt werden beliebige Funktionen aufzurufen.

void f() {
  puts("f called");
}

int main() {
  void (*fp)(void);

  fp = f;
  fp(); // -> f called
}

"malloc can never fail"

malloc() gibt 0 im Fehlerfall zurück, dies sollte eigentlich vom Programm geprüft werden. Wird dies nicht geprüft, arbeitet das Programm mit einem Pointer weiter, der die Adresse 0x00 hat.

void *p = malloc(1717272222772);
printf("%p\n", p); // (nil)

Use-After-Free

Mit malloc allozierter Speicher wird mit free wieder freigegeben. Wenn man den Speicher aber nach dem free noch weiter benutzt, können beliebige Daten von anderen Teilen des Programms dort landen, weil malloc den Speicher natürlich "recycled". Hierdurch kann man Daten exfiltrieren oder Funktionen sogar eigene Daten unterschieben.

void printer() {
    static char *text = 0;
    if (!text) {
        text = (char*) malloc(sizeof(char) * 9);
        strcpy(text, "mutti123");
    }
    printf("%s\n", text); /* use after free */
    free(text);
}

int main(int argc, char** argv) {
    printer(); /* -> mutti123 */
    char *pwnd = (char*) malloc(sizeof(char) * 9);
    strcpy(pwnd, "pwned!");
    printer(); /* -> pwned! */
}

GOT.PLT

Wenn das Programm -z,norelro compiliert ist, kann man die Funktionen im global offset-table (GOT.PLT) ersetzen, so man durch eine andere Schwachstelle Zugriff darauf bekommt. Hierdurch kann man dann Standard-Funktionen der Library durch eigene ersetzen.