Formátum sztring példakódok: bevezető, egyszerű, picit kevésbé egyszerű
Elméleti bevezető
Az alábbi pár kép illetve a letölthető animáció segít felfrissíteni C programok memóriájáról illetve a függvényhívás menetéről tanultakat. A képek, leírások elsősorban x86-os, 32 bites architektúrákra vonatkoznak. Egy C program memóriájának átlagos felépítése az alábbi képen látható:
- A veremben tárolódik egy-egy függvényhívás adata, vagyis (egyebek mellett) a lokális változók, a függvényeknek átadott paraméterek. A felszabadítás automatikusan, a meghívott függvényből való visszatéréskor megtörténik (emiatt veszélyes egy függvény lokális változójának memóriacímét visszaadni, hiszen az a terület automatikusan felszabadul) A verem általában a magasabb memóriacímek felől növekszik az alacsonyabb memóriacímek felé.
- A heap-en/dinamikus memóriában tárolódik mindaz, amit dinamikusan (new, malloc, calloc) foglaltunk le. A heap általában az alacsonyabb címek felől növekszik a magasabb címek felé. Ennek felszabadítása nem automatikus, a programozónak kell nem elfelejtenie (delete, free). Ezt a memóriaterületet használják a dinamikusan betölött library-k is.
- BSS (Block Started by Symbol, Uninitalized Data Segment): olyan globális és statikus változók kerülnek ide, amelyeket a programozó nem inicializált saját maga, ezért a program indulásakor automatikusan 0 értéket kapnak. Pl. C-ből megszokott globális változók, függvények static kulcsszóval ellátott változói.
- DS (Initalized Data Segment): olyan globális és statikus változók, amiket a programozó ellátott kezdőértékkel.
- Text: a programból készült gépi utasítások helye
A verem tehát egy-egy függvényhívás adatát tartalmazza. Ezek az adatok frame-eket/kereteket alkotnak. Függvényhívás hatására egy új keret kerül a verem tetjére. A megértéshez fontos regiszterek listája:
- ESP: Stack Pointer. A verem tetejére mutat.
- EBP: Base Pointer. Az aktuális keret alját mutatja, ehhez képest lehet meghatározni, hogy hol vannak az átadott paraméterek és lokális változók.
- EAX: Függvények visszatérési értékét és bizonyos számítások eredményeit (szorzás, osztás stb.) tároló általános célú regiszter.


printf("%s %d %08x",a,b,&c)
. A négy paraméter fordított sorrendben kerül a veremre (utoljára kerül be a formátum sztring).
Általában ez az átadási sorrend, de nem szabvány. Amikor a printf végrehajtja a kiíratást jobbról balra kezd lépkedni a paramétereken.
Mi történik, hogy ha a programozó elfelejt átadni egy paramétert? A printf változó számú paramétert képes fogadni, tehát fordítási hibát nem kap (esetleg warningot, ha használja a -Wall kapcsolót).
A printf nem tudja, hogy ő kevesebb paramétert kapott. Egyszerűen kiolvas egy, a formázó karakternek megfelelő méretű adatot a veremről.
Itt jön majd be a formátum sztring sérülékenység, amit a következőkben tárgyalunk.

Formátum sztring sérülékenység C-ben
A sérülékenység alapja, hogy a nem vagy rosszul ellenőrzött user input a formátum sztring helyén lesz a printf (és a családjába tartozó) függvényeknek átadva. Emiatt ha az a sztring formázó karaktereket (%x, %s stb.) tartalmaz az nem szövegesen lesz kiírva, hanem a printf kiolvas a veremből egy memóriaterületet (ott kéne lennie a formázó karakterhez tartozó paraméternek). Így a veremből információ lopható ki, programelszállást lehet okozni illetve még a memória tartalma is módosítható.Bemutató videók
A formátum sztring sérülékenység bemutatására az alábbi videók készülték. Úgy gondoltam mindannyiunknak előnyére válik, hogy ha a videóknak nincs hangja.Az első videó szemlélteti mit tapasztalunk, ha eggyel kevesebb paramétert adunk át mint ahány formázó karakter van.
Ebben a videóban két egyszerű példát látunk, amikor a user input a formátum sztring helyén kerül átadásra (printf(user_input);
) illetve
hogy ennek a hibának a felismerése nem mindig evidens. A példakód ennek az anyagnak az elején tölthető le.
Átismételjük a %08x fomrázó karakter jelentését (8 karakter széles, vezető nullákkal feltöltött hexadecimális szám kiíratása).
A több futtatás során tapasztaljuk, hogy a memóriacímek folyamatosan változnak.
Ez az ASLR (Adress Space Layout Randomization) miatt van, a kernel védelmi funkciója. Randomizálva osztja ki a virtuális memória területeit a programoknak,
így lefutásról lefutásra változik, hogy éppen melyik memóriacímen lesz egy utasítás vagy egy változó.
A formátum sztring sérülékenység kihasználása
Attention: a videóknak a visszajelzéseknek megfelelően megint van hangja
A következő videóban a %n formázó karakter van ismertetve. Ez nem összetévesztendő a \n karakterrel, ami sortörést okoz a kiíratásban.
A %n paramétere egy pointer, ahová beírásra kerül, hogy a %n-ig hány karakter lett az adott printf hívásban kiírva. Ez az ártalmatlannak tűnő
formázó karakter tehát alkalmas a memória manipulációjára!
Ha a kódba belecsúszik egy formátum sztring sérülékenység, a %n rosszindulatú használata esetén programelszállást vagy a letutás módosulását lehet előidézni. Az utóbbit szemlélteti az alábbi feladatmegoldás:
A továbbiakban GDB használatra is támaszkodni fogunk. Ehhez lentebb található egy bőséges használati segédlet. Azon alaputasítások ismerete szükséges csak, amelyek a videókban is szerepelnek (de az érdeklődők kedvéért fent van ez a részletesebb dokumentum is):
- run: program elindítása
- continue: felfüggesztett futás újraindítása
- break: breakpoint berakása
- x: memóriaterület kiíratása
- set: változó, regiszter beállítása
Ez a videó az előző példafeladat megvizsgálása/megoldása GDB segítségével. Az x utasítás segítségével megnézzük a verem tartalmát és meggyőződünk róla, hogy a formátum sztring sérülékenység kihasználásakor valóban a verem tartalmát írattuk ki. A set utasítás segítségével átírjuk a lokális változót, lefuttatva így a titkos üzenet kiíratását.
A teljesség kedvéért pedig itt arról láttok egy rövid demót, hogy a bináris birtokában a formátum sztring sérülékenység kihasználása nélkül is meg lehet szerezni a titkos értékeket.
Ez a jellegű formátum sztring sérülékenység, amit a fentiekben tárgyaltunk tipikusan C/C++ jellegzetesség: a %n támogatása és a verem védelmének hiánya veszélyeztetetté teszi a programokat. Más nyelvek esetén is előfordulhat azonban különböző súlyossággal ez a sérülékenység, ha támogatják a formátum sztringes kiíratási módot (Java, PHP, Pearl, Ruby, Python). Verem védelmi funkciók, illetve a %n támogatásának hiánya vagy egyéb beépített ellenőrző funkciók azonban nehezebben kihasználhatóvá teszik. Itt lehet olvasni egy kis összefoglalót más nyelvek érintettségéről.