(c) teso. all rights reversed.
exploiting format string vulnerabilities - Format String im Heap

Format String im Heap

Bisher sind wir immer davon ausgegangen, dass der Format String selbst auf dem Stack liegt, oder das zumindestens ein Buffer auf dem Stack liegt, den wir beeinflussen können. Das Problem bei allen bisher genannten Exploit Techniken ist, dass wir immer nur auf Daten im Stack zugreifen können. Wenn der Zielbuffer auf dem Stack liegt, können wir erst die von uns benötigten Daten dorthin printf'en, und dann diese Daten benutzen:
void
func (char *user_at_heap)
{
        char    outbuf[512];

        snprintf (outbuf, sizeof (outbuf), user_at_heap);
        outbuf[sizeof (outbuf) - 1] = '\0';

        return;
}
Hier benutzen wir wie sonst einen Format String, der die Adressen an die wir schreiben wollen enthält. Doch im Unterschied zu bisherigen Verfahren greift der Stackpop oder der Direct-Parameter Access nicht auf die Daten im Format String zu, sondern auf die Daten im outbuf. Dabei ist zu beachten, dass die Adressen im Format String vor dem write-Code liegen müssen, weil sie sonst nicht auf dem Stack sind, wenn die "%n" Parameter geparsed werden.

Wenn jedoch beide Buffer (Format String und Zielbuffer) im Heap liegen, sieht es schlecht aus:
void
func (char *user_at_heap)
{
        char *  outbuf = calloc (1, 512);

        snprintf (outbuf, 512, user_at_heap);
        outbuf[511] = '\0';

        return;
}
Es hängt jetzt sehr davon ab, ob wir sonst irgendeine Möglichkeit haben, beliebige Daten im Stack zu speichern. Bei vielen Exploits zu wuftpd 2.6.0 behalfen sich ihre Programmierer bei einem ähnlichen Problem dadurch, die Daten im password unterzubringen, was bei anonymous-accounts nicht geprüft wird (sie können so aber keine non-anonymous Server exploiten). Bei wuftpd war das Problem, dass der Format String, obwohl er 510 Bytes lang ist und auf dem Stack liegt nicht lang genug ist, um neben dem Stackpop und den Adressen noch den Shellcode unterzubringen. (Ausser der Shellcode ist ziemlich klein, siehe 7350wu ;-)

Manchmal ist es jedoch gar nicht nötig eigene Daten im Stack zu haben, und zwar dann, wenn ein Pointer auf dem Stack liegt, der auf wichtige Daten zeigt. Dazu muss ein Pointer auf die zu überschreibenden Daten auf dem Stack liegen.
Auf der x86 Architektur sieht der Stack üblicherweise so aus:

...
0xbfffed90      return address
0xbfffed8c      old frame pointer
0xbfffed88      local stack variable (zum Beispiel filedescriptor)
0xbfffed84      return address
0xbfffed80      old frame pointer (inhalt: 0xbfffed88)
...
Generell zeigt der auf dem Stack liegende frame pointer immer auf die erste lokale Variable der aufrufenden Funktion. Beispielcode dazu (ptr-in-frame-reachable.c):
int
main () {
        int     fd;

        fd = auth_open ();
        foo ();
        if (auth_check_ok (fd)) {
                /* ... */
        }
}

void foo (void) {
        char *  in = calloc (1, 512);

        fgets (stdin, in, sizeof (in));
        in[511] = '\x00';
        printf (in);
}
Dieses ziemlich inszenierte Beispiel sieht natürlich in realem Code wesentlich komplexer aus. Hier passiert folgendes: Eine suid Binary soll checken, ob ein Nutzer eine bestimmte Aktion ausführen darf. Dazu öffnet es eine Verbindung zu einem priveligierten Authorisationsdaemon (fd). Dann wird foo() aufgerufen. foo liesst von stdin eine Zeile auf den Heap ein, und gibt sie über printf aus. Klar ist dies eine Format String Vulnerability, nur sieht sie ziemlich schwer zu exploiten aus, da wir keine Möglichkeit haben, eigene Daten für "%n" Operationen zu benutzen.

Wir können aber die Pointer, die bereits auf dem Stack liegen ausnutzen. Der alte Framepointer bei 0xbfffed80 zum Beispiel zeigt auf das Stack Frame der main() Funktion, das als erste Variable zufällig einen Filedescriptor hat.
Diesen können wir jetzt wie gewohnt mit "%n" überschreiben, jedoch nur einmal. Mit "%...$n" könnten wir zwar mehrere Male schreiben, aber immer wieder an die selbe Adresse, was keinen Sinn macht.

Der Format String könnte jetzt so aussehen: "%08x%08x%08x%n". Er schreibt an 0xbfffed88 "\x18\x00\x00\x00". fd ist somit 24. Wenn wir jetzt vorher auf den Filedescriptor 24 etwas sinnvolles legen und dann das suid Programm mit unserem Format String starten, so könnte man zum Beispiel immer bei der auth_check_ok Funktion ein positiven Rückgabewert liefern ;-)

Jeder Exploit und jede Vulnerability ist unterschiedlich, und man sollte schon Stunden investiert haben, um sicher zu sagen, dass etwas nicht exploitbar ist, und selbst dann irrt man allzu oft, wie die Vergangenheit nicht nur bei Format String Vulnerabilities zeigt (ein dreifaches "Hallo OpenBSD Team !" :-).

<< - < - > - >>