Übungen Betriebssysteme (BS)

U6 - Sicherheit

Alwin Berger

🚀 by Decker

Agenda

  • UNIX, C und Sicherheit
    • Gefährliche Funktionen in C
    • Buffer Overflows
    • Schutzmaßnahmen

“Gefährliche” Funktionen in C

  • scanf()
  • gets() (verhält sich ähnlich wie scanf("%s", buf))
  • strcpy()
  • strcat()
  • sprintf()

“Gefährliche” Funktionen in C

  • scanf()
  • gets() (verhält sich ähnlich wie scanf("%s", buf))
  • strcpy()
  • strcat()
  • sprintf()

Was haben diese Funktionen gemeinsam?

  • Alle schreiben ab einer bestimmten Adresse in den Speicher

“Gefährliche” Funktionen in C

  • scanf()
  • gets() (verhält sich ähnlich wie scanf("%s", buf))
  • strcpy()
  • strcat()
  • sprintf()

Was haben diese Funktionen gemeinsam?

  • Alle schreiben ab einer bestimmten Adresse in den Speicher
  • Zur Laufzeit kann nicht entschieden werden, ob die Abbruchbedingung irgendwann erfüllt wird.

“Gefährliche” Funktionen in C - gets()

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    gets(buf);      // <--
    if (check_passwd(buf)) {
        return 1;
    } else {
        printf("Falsches Passwort!\n");
        return 0;
    }
}
studi@bsvm:~$ gcc -o login login.c
/tmp/ccoMGotc.o: In function 'ask_passwd': login.c:(.text+0x19): warning:
    the 'gets' function is dangerous and should not be used.

“Gefähliche” Funktionen in C - scanf()

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    scanf(buf);     // <--
    if (check_passwd(buf)) {
        return 1;
    } else {
        printf("Falsches Passwort!\n");
        return 0;
    }
}

“Gefähliche” Funktionen in C - scanf()

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    scanf(buf);     // <--
    if (check_passwd(buf)) {
        return 1;
    } else {
        printf("Falsches Passwort!\n");
        return 0;
    }
}
  • Übersetzt ohne eine Warnung!

Pufferüberlauf

void start_shell(void) {
    gid_t gid = getegid();
    uid_t uid = geteuid();
    if (setresgid(gid, gid, gid)) 
        perror("setresgid");
    if (setresuid(uid, uid, uid)) 
        perror("setresuid");
    printf("Starte Shell als Nutzer %d (uid=%d,gid=%d)\n", uid, uid, gid);
    execlp("/bin/bash", "/bin/bash", NULL);
}

int main(void) {
    if (ask_passwd())
        start_shell();
    return 0;
}

Pufferüberlauf

void start_shell(void) {
    gid_t gid = getegid();
    uid_t uid = geteuid();
    if (setresgid(gid, gid, gid)) 
        perror("setresgid");
    if (setresuid(uid, uid, uid)) 
        perror("setresuid");
    printf("Starte Shell als Nutzer %d (uid=%d,gid=%d)\n", uid, uid, gid);
    execlp("/bin/bash", "/bin/bash", NULL);
}

int main(void) {
    if (ask_passwd())       // <--
        start_shell();
    return 0;
}

Pufferüberlauf

void start_shell(void) {
    gid_t gid = getegid();
    uid_t uid = geteuid();
    if (setresgid(gid, gid, gid)) 
        perror("setresgid");
    if (setresuid(uid, uid, uid)) 
        perror("setresuid");
    printf("Starte Shell als Nutzer %d (uid=%d,gid=%d)\n", uid, uid, gid);
    execlp("/bin/bash", "/bin/bash", NULL);
}

int main(void) {
    if (ask_passwd())       
        start_shell();      // <--
    return 0;
}

Pufferüberlauf

void start_shell(void) {
    gid_t gid = getegid();      // <--
    uid_t uid = geteuid();      // <--
    if (setresgid(gid, gid, gid)) 
        perror("setresgid");
    if (setresuid(uid, uid, uid)) 
        perror("setresuid");
    printf("Starte Shell als Nutzer %d (uid=%d,gid=%d)\n", uid, uid, gid);
    execlp("/bin/bash", "/bin/bash", NULL);
}

int main(void) {
    if (ask_passwd())
        start_shell();
    return 0;
}
  • Abfragen der effektiven Nutzer- und Gruppen-ID. Entspricht Nutzer und Gruppe der Datei.

Pufferüberlauf

void start_shell(void) {
    gid_t gid = getegid();
    uid_t uid = geteuid();
    if (setresgid(gid, gid, gid))       // <--
        perror("setresgid");
    if (setresuid(uid, uid, uid))       // <--
        perror("setresuid");
    printf("Starte Shell als Nutzer %d (uid=%d,gid=%d)\n", uid, uid, gid);
    execlp("/bin/bash", "/bin/bash", NULL);
}

int main(void) {
    if (ask_passwd())
        start_shell();
    return 0;
}
  • Setzt die Nutzer und Gruppen-ID auf die Eigentümer der Datei

Pufferüberlauf

void start_shell(void) {
    gid_t gid = getegid();
    uid_t uid = geteuid();
    if (setresgid(gid, gid, gid)) 
        perror("setresgid");
    if (setresuid(uid, uid, uid))
        perror("setresuid");
    printf("Starte Shell als Nutzer %d (uid=%d,gid=%d)\n", uid, uid, gid);
    execlp("/bin/bash", "/bin/bash", NULL);     // <--
}

int main(void) {
    if (ask_passwd())
        start_shell();
    return 0;
}
studi@bsvm:~$ ./login
Passwort:12345678abcdefgh12345678
Falsches Passwort
Segmentation Fault

Was ist passiert?

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    scanf("%s", buf);       // <--
    // ...
  • Eingabe: “12345678abcdef12345678”
images/U6/image1.png

Was ist passiert?

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    scanf("%s", buf);       // <--
    // ...
  • Eingabe: “12345678abcdef12345678”
images/U6/image2.png

Was ist passiert?

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    scanf("%s", buf);       // <--
    // ...
  • Eingabe: “12345678abcdef12345678”
  • Die Eingabe hat den Framepointer
images/U6/image3.png

Was ist passiert?

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    scanf("%s", buf);       // <--
    // ...
  • Eingabe: “12345678abcdef12345678”
  • Die Eingabe hat den Framepointer und die Rücksprungadresse überschrieben
images/U6/image4.png

Was ist passiert?

int ask_passwd(void) {
    char buf[8];
    printf("Passwort:");
    scanf("%s", buf);       // <--
    // ...
  • Eingabe: “12345678abcdef12345678”
  • Die Eingabe hat den Framepointer und die Rücksprungadresse überschrieben
  • Sprung zu einer nicht-ausführbaren Adresse
  • Segmentation Fault
  • Das ist noch der harmloseste Fall!
images/U6/image1.png

Pufferüberlauf hinter den Kulissen

  • Prozess kann auf die Weise zum Sprung zu beliebigen Adressen gezwungen werden!

Pufferüberlauf hinter den Kulissen

  • Prozess kann auf die Weise zum Sprung zu beliebigen Adressen gezwungen werden!
  • Das kann auch ausgenutzt werden
studi@bsvm:~$ nm login | grep -F start_shell
00000000004008a1 T start_shell

Pufferüberlauf hinter den Kulissen

  • Prozess kann auf die Weise zum Sprung zu beliebigen Adressen gezwungen werden!
  • Das kann auch ausgenutzt werden
studi@bsvm:~$ nm login | grep -F start_shell
00000000004008a1 T start_shell
  • Da Adressen aus nicht-darstellbaren Zeichen bestehen \(\rightarrow\) Hilfsprogramm/-skript nötig
    • Umleiten der Standardeingabe
    • Ausgabe der Bytesequenz mit der Adresse am Ende (Byteorder!)
    • “Weiterleitung” der Standardeingabe
studi@bsvm:~$ ( printf "1234567812345678\xa1\x08\x40\x00\x00\x00\x00\x00\n" ; cat /dev/stdin ) | ./login

Codeinjektion

  • Ein solcher Einsprungspunkt (start_shell()) ist nett, aber nicht unbedingt nötig
  • Auch Programme mit “ungefährlichen” Funktionen können dazu gebracht werden, Beliebiges zu tun!
  • Anstatt nur einer Rücksprungadresse wird gleich Maschinencode injiziert \(\rightarrow\) Rücksprungadresse zeigt auf den Stack selbst, wo der Code liegt

Weitere Techniken zur Codeinjektion

Ist das praxisrelevant?

  • Ja, unter den “CWE Top 25 Most Dangerous Software Weaknesses” finden sich weiterhin häufige Fehler im Umgang mit Speicher.
    • #2 Out-of-bounds Write (-> z.B. arbitrary code execution)
    • #6 Out-of-bounds Read (-> z.B. credential leakage)
    • #8 Use After Free (-> credential leakage)
    • #21 NULL Pointer Dereference (-> crashes)
  • Beispiel: Heartbleed (2012-2014)
    • Fehlender Vergleich zwischen angegebener Puffergröße und tatsächlich übertragenen Daten
    • Rückgabe von nicht-überschriebenen Speicherinhalten aus OpenSSL (z.B. Schlüsselmaterial)

Schutzmaßnahmen (1)

  • Hardware
    • NX-Bit / XD-Bit (SPARC, IA32 seit 2005, IA64-Prozessoren)
    • Stack-/Heap- und Daten-Speicherseiten Not eXecutable
  • Betriebssystem
    • stack/address space randomization (ASLR)
    • Benötigt relozierbare Programme (gcc -pie)
  • Compiler
    • z.B. GCC Stack Smashing Protector
    • Standardmäßig im Einsatz bei z.B. OpenBSD, FreeBSD, Linux
    • Teils Standard, ansonsten via Parameter -fstack-protector
    • Funktionsweise: Bekannte Zahlen, sogenannte Canaries, vor und hinter Puffer schreiben und überwachen

Schutzmaßnahmen (1)

  • Hardware
    • NX-Bit / XD-Bit (SPARC, IA32 seit 2005, IA64-Prozessoren)
    • Stack-/Heap- und Daten-Speicherseiten Not eXecutable
  • Betriebssystem
    • stack/address space randomization (ASLR)
    • Benötigt relozierbare Programme (gcc -pie)
  • Compiler
    • z.B. GCC Stack Smashing Protector
    • Standardmäßig im Einsatz bei z.B. OpenBSD, FreeBSD, Linux
    • Teils Standard, ansonsten via Parameter -fstack-protector
    • Funktionsweise: Bekannte Zahlen, sogenannte Canaries, vor und hinter Puffer schreiben und überwachen
      • “Canaries” sind eine Anspielung auf die Kanarienvögel in Kohleminen, die bei giftigen Gasen als erste umkippten.

Schutzmaßnahmen (2)

  • Programmierung
    • strcpy(),strcat() \(\rightarrow\) strncpy(),strncat()
    • sprintf() \(\rightarrow\) snprintf()
    • gets() \(\rightarrow\) fgets()
    • scanf() \(\rightarrow\) Feldbreite beschränken: scanf("%10s", buf)

Was haben wir heute gelernt?

One careless strcat…

images/U6/image32.jpg

Informationen

Bei Fragen und Problemen

Viel Erfolg bei der Klausur!