Aus Linux-Magazin 06/2012

Insecurity Bulletin: Formatstring-Schutz der Glibc ausgehebelt

© Julian Fletcher, 123RF

Die GNU-C-Bibliothek besitzt mit FORTIFY_SOURCE eine eingebaute Armierung, die Formatstring-Exploits verhindert. Doch erst kürzlich haben die Glibc-Entwickler eine mehr als ein Jahr alte Sicherheitslücke geschlossen, über die sich die Schutzeinrichtung umgehen ließ.

Formatstring-Fehler in C-Programmen waren vor mehr als zehn Jahren weit verbreitet. Heute sind sie weitgehend aus den Schwachstellen-Statistiken verschwunden. Das liegt zum einen an der Sorgfalt der Entwickler, zum anderen an Compiler-Optionen, die versuchen mögliche Formatstring-Attacken trotz falscher Programmierung abzuwehren. Eine dieser Sicherheitsmaßnahmen beruht auf der Option »FORTIFY_SOURCE« der Bibliothek Glibc.

Erst im März 2012 hat jedoch der Google-Entwickler Kees Cook eine bereits länger bekannte Schwachstelle [1] in diesem Schutzmechanismus korrigiert. Die Sicherheitslücke erlaubte es, Formatstring-Fehler trotz »FORTIFY_SOURCE« auszunutzen. Ein Fehler beim Einsatz von Formatstrings [2] in C-Code sieht in seiner einfachsten Form wie folgt aus:

printf(userinput);

Dabei ist »userinput« ein vom Benutzer übergebener String. Für diese Zeichenkette darf ein Angreifer einen beliebigen Formatstring angeben, zum Beispiel:

userinput="%08x.%08x.%08x.%08x.%08x"

Mit diesem String würde das Programm Speicher des Stack auslesen und anzeigen. Eine sichere Verwendung sähe dagegen so aus:

printf("%s", userinput);

Zwei Hauptformen von Formatstring-Attacken haben sich etabliert: das Auslesen und das Beschreiben von beliebigem Speicher. Die erste wird häufig mit »%s« realisiert. Das Ziel besteht darin, mit »%s« den Speicher an einer vorgegebenen Adresse auszulesen. Dazu bringt der Angreifer im Formatstring sowohl die Zieladresse als auch »%s« unter. Da der Formatstring selbst auf dem Stack liegt, kann »%s« den Speicher an der angegebenen Adresse lesen.

Das Beschreiben von Speicher haben Hacker oft über den Formatstring-Identifier »%n« realisiert, denn dieser verhält sich anders als alle anderen Identifier: Er gibt nichts aus, sondern sorgt dafür, dass der Rechner die Zahl der geschrieben Bytes an eine bestimmte Adresse schreibt. Ein Angreifer kann dies ausnutzen, um Speicherbereiche zu überschreiben und so Programmcode auszuführen.

Offensichtlich ist die zweite Angriffsform deutlich kritischer, da sie im schlimmsten Fall das Ausführen von Befehlen mit Rootrechten ermöglicht. In der Tat waren um das Jahr 2000 herum zahlreiche Serverapplikation anfällig für vergleichbare Schwachstellen. Ein positiver Aspekt von Formatstring-Fehlern ist, dass sie im Programmcode recht leicht zu finden sind. Das erklärt auch, warum diese Angriffsform heute fast ausgestorben ist.

Zugriff auf Parameter

Nach den ersten veröffentlichten Exploits verfeinerten die Angreifer im Laufe der Zeit ihre Attacken. Sie machten sich weitere Features von Formatstrings zu Nutze. Eine dieser Techniken heißt Direct Parameter Access. Diese Eigenschaft der Formatstrings ermöglicht eine flexiblere Handhabung von Formatstrings, wie Listing 1 zeigt. Der Programmierer hat hierbei mehr Kontrolle darüber, welche Daten sein Code an welcher Stelle im String ausgibt, da er durch die Notation »%Zahl$s« die gewünschte Variable spezifizieren kann.

Listing 1

Direct Parameter Access

01 printf("%1$s  %2$s", "String1", "String2");
02 // gibt "String 1  String 2" aus
03 printf("%2$s  %1$s", "String1", "String2");
04 // gibt "String 2  String 1" aus

Eingebauter Schutz

Unzählige Formatstring-Attacken machten sich Programmierfehler im Zusammenhang mit der Funktion »printf()« und deren Verwandten zu Nutze. Da all diese Funktionen in der Glibc implementiert sind, lag es nahe, einen allgemeinen Schutz in die Bibliothek einzubauen, der auch bei falscher Programmierung Attacken verhindert. Das haben die Entwickler mit dem »FORTIFY_SOURCE« -Patch realisiert, das sowohl Speicherschreib-Attacken via »%n« als auch Angriffe via Direct Parameter Access verhindert. Der Schutz wird aktiv, sobald man ein Programm mit dem GCC-Flag »-D_FORTIFY_SOURCE=2« übersetzt.

Wer sich den Assembler-Code der erzeugten Binärdatei ansieht, stellt fest, dass der Compiler Funktionen wie »printf()« ersetzt hat, beispielsweise durch »printf_chk()« . Diese Funktion ist in »libc/debug/printf_chk.c« implementiert (Listing 2). Beim Aufruf von »printf()« im Benutzerprogramm ist »flag=1« gesetzt und damit wird das Flag »_IO_FLAGS2_FORTIFY« für den »FILE« -Stream-Zeiger (beispielsweise Stdout) aktiviert. Dieses Flag signalisiert für alle weiteren Funktionen, die auf der »FILE« -Struktur arbeiten, dass sie Extrakontrollen durchführen sollen, die Angriffe verhindern.

Listing 2

libc/debug/printf_chk.c

01 /* Write formatted output to stdout from the format string FORMAT.  */
02 int ___printf_chk (int flag, const char *format, ...)
03 {
04   va_list ap;
05   int done;
06
07   _IO_acquire_lock_clear_flags2 (stdout);
08   if (flag > 0)
09     stdout->_flags2 |= _IO_FLAGS2_FORTIFY;
10
11   va_start (ap, format);
12   done = vfprintf (stdout, format, ap);
13   va_end (ap);
14
15   if (flag > 0)
16     stdout->_flags2 &= ~_IO_FLAGS2_FORTIFY;
17   _IO_release_lock (stdout);
18
19   return done;
20 }

Um Schreib-Attacken mit »%n« zu verhindern, sind in »libc/stdio-common/vfprintf.c« mehrere Kontrollmechanismen eingebaut, die das gesetzte Flag »_IO_FLAGS2_FORTIFY« einschaltet. Sie brechen die Ausführung des Programms ab, sobald sie feststellen, dass die »%n« -Adresse in einem beschreibbaren Speicherbereich wie Stack, BSS, Data oder im Heap liegt.

Auch wenn hiermit immer noch Denial-of-Service-Attacken möglich sind (denn das Programm wird ja beendet), so vereitelt es doch gefährliche Speichermanipulationen, die zum Ausführen von Code führen können. Das stellt einen zentralen Schutz gegen schreibende Formatstring-Attacken dar.

Falsche Typen bleiben draußen

Eine zweite Schutzfunktion befindet sich in »libc/stdio-common/vfprintf.c« , um Attacken auf Basis von Direct Parameter Access zu verhindern (Listing 3). In diesem Code-Ausschnitt speichert »args_type[]« den Typ eines Formatstring-Arguments, beispielsweise »PA_INT« (dies ist in einem C-Enum als 0 definiert) für eine Integer-Variable.

Listing 3

libc/stdio-common/ vfprintf.c

01 /* Determine the number of arguments the format string consumes.  */
02 nargs = MAX (nargs, max_ref_arg);
03
04 /* Allocate memory for the argument descriptions.  */
05 args_type = alloca (nargs * sizeof (int));
06 memset (args_type, s->_flags2 & _IO_FLAGS2_FORTIFY ? '\xff' : '\0', nargs * sizeof (int));
07 args_value = alloca (nargs * sizeof (union printf_arg));
08 args_size = alloca (nargs * sizeof (int));
09 [...]
10 for (cnt = 0; cnt < nargs; ++cnt)
11 [...]
12 switch (args_type[cnt])
13 [...]
14 case -1:
15  /* Error case.  Not all parameters appear in N$ format strings.  We have no way to determine their type.  */
16  assert (s->_flags2 & _IO_FLAGS2_FORTIFY);
17   __libc_fatal ("*** invalid %N$ use detected ***\n");
18 }

Dieser zweite Kontrollcode hat zur Folge, dass alle »args_type[]« -Einträge unter der Option »_IO_FLAGS2_FORTIFY« zunächst auf -1 gesetzt werden. Dadurch sind einzelne Direct-Parameter-Access-Formatstrings der Art »%4$x« nicht mehr möglich, sondern nur solche, bei denen keine Lücken auftreten, »%4$x %2$x %1$x %3$x« wäre beispielsweise in Ordnung. Damit sind viele Exploits, die auf Direct Parameter Access beruhen, nicht mehr anwendbar.

Beide Schutzmechanismen kombiniert machen es praktisch unmöglich, Formatstring-Attacken zum Ausführen von Programmcode oder Beschreiben von Speicher auszunutzen.

Aus der Hacker-Trickkiste

Wie ein gewisser Captain Planet in einem Phrack-Artikel vom November 2010 darlegt (Abbildung 1), gibt es aber einen Trick, um beide Kontrollen zu umgehen [3]. Das größte Hindernis beim Ausführen von Formatstring-Attacken ist die Tatsache, dass »FORTIFY_SOURCE« das Schreiben auf beliebigen Speicher via »%n« verhindert.

Abbildung 1: Ein Artikel im Hacker-Magazin "Phrack" beschrieb bereits 2010 die Schwachstelle, die die Glibc-Entwickler nun behoben haben.

Abbildung 1: Ein Artikel im Hacker-Magazin “Phrack” beschrieb bereits 2010 die Schwachstelle, die die Glibc-Entwickler nun behoben haben.

Bitmask zerstört

Captain Planets Ansatz, dies zu umgehen, besteht darin, das Flag »_IO_FLAGS2_FORTIFY« des »FILE« -Stream-Zeigers zu deaktivieren. Das Flag selbst ist ein 4-Byte-Wert im Speicher. Der Angreifer müsste also irgendwie in der Lage sein, diese 4 Bytes gezielt mit Nullen zu überschreiben und damit alle Bitflags auf 0 zu setzen. Damit ist zwar die gesamte Bitmask des Flag zerstört, das macht aber nichts, da in den meisten Fällen tatsächlich nur das Flag »_IO_FLAGS2_FORTIFY« gesetzt ist und keine weiteren.

Die entsprechende Attacke ist ein wenig trickreich, aber der Schlüssel zum Erfolg liegt in einigen Zeilen in der »vfprintf()« -Implementierung: Der Code in Listing 4 setzt die verschiedenen Typen für die Argumente. Ein Sonderfall sind hierbei Formatstrings mit variabler Breite, die der If-Fall behandelt. An dieser Stelle ist

Listing 4

vfprintf()

01 /* Fill in the types of all the arguments.  */
02 for (cnt = 0; cnt < nspecs; ++cnt)
03 {
04  /* If the width is determined by an argument this is an int.  */
05  if (specs[cnt].width_arg != -1)
06    args_type[specs[cnt].width_arg] = PA_INT;
07 [...]
08 enum
09 {                               /* C type: */
10   PA_INT,                       /* int */
args_type[specs[cnt].width_arg] = PA_INT

gesetzt, was wegen »PA_INT=0« bedeutet, dass hier 4 Bytes auf 0 gesetzt werden. Jetzt muss der Angreifer es irgendwie anstellen, »specs[cnt].width_arg]« auf den Flag-Eintrag in der »FILE« -Struktur zeigen zu lassen. Dies gelingt ihm durch einen Integer Overflow für die »nargs« -Variable.

Damit ist ein zentraler Formatstring-Schutz von »FORTIFY_SOURCE« umgangen, denn ohne das gesetzte Flag kann ein Angreifer via »%n« auf beschreibbaren Speicher zugreifen. Im Phrack-Artikel finden sich auch einige Beispiel-Exploits, die dies demonstrieren. Obwohl der Artikel bereits im November 2010 erschien, gab es erst im März 2012 das erwähnte Security-Fix.

Infos

  1. “Glibc FORTIFY_SOURCE Protection Mechanism Can Be Bypassed”, Security Tracker: http://www.securitytracker.com/id/1026810
  2. Formatstrings im Glibc-Manual: http://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html
  3. Captain Planet, “A Eulogy for Format Strings”, Phrack 67: http://www.phrack.org/issues.html?issue=67&id=9

Der Autor

Mark Vogelsberger ist derzeit wissenschaftlicher Mitarbeiter am Institute for Theory and Computation der Harvard University, wo er sich mit Simulationen zur Strukturbildung im Universum beschäftigt. Er war von 1999 bis 2010 Autor der “Insecurity News” des Linux-Magazins und schreibt nun auf https://www.linux-magazin.de die Online-Ausgabe des “Insecurity Bulletin”.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 3 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben