A MS08-014 és CVE 2008-0081 jelű sebezhetőségek egy incializálatlan stack változóból adódó Excel sebezhetőségről szólnak. Már valószínűleg láttál ehhez hasonló figyelmeztetéseket a fordítótól:
C:\temp>cl stack.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved.
stack.cpp
c:\temp\stack.cpp(49) : warning C4700: uninitialized local variable 'pNoInit' used ...
A kód lefordul és rendben linkelődik, lehet, hogy eddig figyelmen kívül is hagytad ezt a figyelmeztetést. Erről a tipusú kódhibáról sokkal kevesebbet hallani, mint a puffer túlfutásokról, de az MS08-014 jó példája annak, hogy miért potenciálisan veszélyes az inicializálatlan stack változók használata. Az alábbi PoC kódot fogjuk használni a hiba szemléltetésére, rámutatunk a sebezhetőségre, majd részletesebben tárgyaljuk az ilyen jellegű fordítói hibaüzeneteket:
// stack.cpp
unsigned char scode[] = "shell code";
void parse();
void p1();
void p2();
int main(int argc, char* argv[])
{
parse();
return 0;
}
void parse()
{
p1();
p2();
}
void p1()
{
// Assume p1 is reading data from the file to s
// Content of s could be controlled by the attacker.
// For simplicity, assign s to 0x0013fee8, location of return address to stack!main+0x8
int s[64];
for (int i=0; i<64; i++)
{
s[i] = 0x0013fee8; // spray the stack buffer
}
}
void p2()
{
// Assume p2 is the next parsing function to read data.
// For example, it tries to load an image from the file, and assign to its field.
// If the content image is the shellcode, by modifying the return address in the
// stack, the shell code runs.
struct {
char * pImage;
} *pNoInit;
// This is to align the stack layout to demonstrate the attack
char s[100];
pNoInit->pImage = (char *) scode;
}
A pNoInit változót nem inicializáljuk, mielőtt a p2() függvényben használjuk. Kihasználható-e ez a programozói hiba kódfuttatás céljából? A válasz attól függ, hogy a támadó elő tudja-e készíteni a stacket a p2() hívása előtt. Ha igen, akkor a pNoInit változó a támadó irányítása alá kerül.
A példánkban a p1() éppen a p2() előtt kerül hívásra. A p1() függvényben megpróbálunk a verem adott címeire értékeket beállítani. Ezek az értékek p1() lefutása után is a veremben maradnak. Emiatt, amikor p2() meghívódik, a pNoInit értékét a támadó állíthatja be. Ebben az esetben elég a main() visszatérési címére mutatnia vele, és készen is van.
Ez így néz ki egy debuggerben:
A /W4 kapcsoló segítségével még több segítséget kaphatsz a fordítótól. Nézd meg a kövekező test.cpp-t:
Az előbbihez hasonlóan láthatjuk a C4700-as figyelmeztetést, és megjelent egy C4701 jelű figyelmeztetés is, amely egy "potenciálisan" inicializálatlan változóra figyelmeztet. A /W4
Például a következő kód potenciálisan inicialiálatlan változót használóként lesz megjelölve:
A fordító nem tudja, hogy a MyInitializationFunction() sikerrel inicializálja-e *p-t. Azonban a p NULL-ra állítása deklarációkor megszünteti a félreérthetőséget - és ez arra az esetre is jó gyakorlat, ha a MyInitializationFunction() hiubás, és sikerrel tér vissza akkor is ha a paraméterét nem inicializálta megfelelően.
A 4701-es figyelmeztetések egy másik gyakori forrása:
Reméljük, hogy a fennti értekezés rámutat ezeknek a figyelmeztetéseknek a komolyságára. Valójában azt szeretnénk, hogy ezek a hibaüzenetek tiltott figyelmezetéstípusként jelennek meg az SDL következő változatában.
A példánkban a p1() éppen a p2() előtt kerül hívásra. A p1() függvényben megpróbálunk a verem adott címeire értékeket beállítani. Ezek az értékek p1() lefutása után is a veremben maradnak. Emiatt, amikor p2() meghívódik, a pNoInit értékét a támadó állíthatja be. Ebben az esetben elég a main() visszatérési címére mutatnia vele, és készen is van.
Ez így néz ki egy debuggerben:
stack!p2:Amikor a fordító az inicializálatlan változóra figyelmeztetett, megpróbált megóvni minket egy valódi sebezhetőségtől!
00401090 55 push ebp
0:000> k
ChildEBP RetAddr
0013fed4 0040101d stack!p2 [h:\work\stack\stack\stack.cpp @ 57] 0013fedc 00401008 stack!parse+0xd [h:\work\stack\stack\stack.cpp @ 33]
0013fee4 004012ae stack!main+0x8 [h:\work\stack\stack\stack.cpp @ 26] 0013ffc0 7c816fd7 stack!mainCRTStartup+0x173 [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 259]
WARNING: Stack unwind information not available. Following frames may be wrong.
0013fff0 00000000 kernel32!RegisterWaitForInputIdle+0x49
0:000> dv
pNoInit = 0x0013fee8
s = char [100] "???"
stack!p2+0x32:
004010c2 8b458c mov eax,dword ptr [ebp-74h] ss:0023:0013fe60=0013fee8
0:000> t
eax=0013fee8 ebx=7ffdf000 ecx=00000063 edx=7c90eb63 esi=00000a28 edi=00000000
eip=004010c5 esp=0013fe5c ebp=0013fed4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
stack!p2+0x35:
004010c5 c70030704000 mov dword ptr [eax],offset stack!scode (00407030) ds:0023:0013fee8=004012ae
0:000> k
ChildEBP RetAddr
0013fed4 0040101d stack!p2+0x3b [h:\work\stack\stack\stack.cpp @ 79] 0013fedc 00401008 stack!parse+0xd [h:\work\stack\stack\stack.cpp @ 33]
0013fee4 00407030 stack!main+0x8 [h:\work\stack\stack\stack.cpp @ 26] 0013ffc0 7c816fd7 stack!scode
WARNING: Stack unwind information not available. Following frames may be wrong.
A /W4 kapcsoló segítségével még több segítséget kaphatsz a fordítótól. Nézd meg a kövekező test.cpp-t:
int main(int argc, char *argv[])
{
int *pNoInit ;
int *pMaynotInit;
if (argc < 3)
{
pMaynotInit = 0;
}
return *pNoInit + *pMaynotInit;
}
C:\test>cl /W4 test.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.10.3077 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.
test.cpp
c:\test\test.cpp(11) : warning C4700: local variable 'pNoInit' used without having been initialized
c:\test\test.cpp(11) : warning C4701: local variable 'pMaynotInit' may be used without having been initialized
Az előbbihez hasonlóan láthatjuk a C4700-as figyelmeztetést, és megjelent egy C4701 jelű figyelmeztetés is, amely egy "potenciálisan" inicializálatlan változóra figyelmeztet. A /W4
kapcsoló hatalmas mennyiségű figyelmeztetést generál (emiatt nem is használják a legtöbben), azonban a legtöbb 4701-es hiba triviálisan orvosolható. A többi figyelmeztetés egy-egy körmönfontabb hibára utalhat.
Például a következő kód potenciálisan inicialiálatlan változót használóként lesz megjelölve:
MYOBJECT *p;
HRESULT hr;
hr = MyInitializationFunction(&p);
if(SUCCEEDED(hr))
{
p->DoSomething(); // flagged as potential uninitialized use
}
A fordító nem tudja, hogy a MyInitializationFunction() sikerrel inicializálja-e *p-t. Azonban a p NULL-ra állítása deklarációkor megszünteti a félreérthetőséget - és ez arra az esetre is jó gyakorlat, ha a MyInitializationFunction() hiubás, és sikerrel tér vissza akkor is ha a paraméterét nem inicializálta megfelelően.
A 4701-es figyelmeztetések egy másik gyakori forrása:
foo(BYTE packet_type)A fordító itt arra a lehetséges kódútra figyel fel, amikro a packet_type sem REQUEST sem RESPONSE. Az implicit "default" eset fut le, ami nem inicializálja p-t - innen a figyelmeztetés. Lehetséges, hogy tényleg csak kétféle csomagtípus érheti el ezt a pontot (pl. azért mert a megfelelő ellenőrzés már ezelőtt a kódrészlet előtt lefutott.) Ebben az esetben a switch szerkezet egyik ága biztosan lefut, és p inicializálódik. Ismét látjuk, hogy a p NULL-ra deklarálásával [definiálásával?] a hiba könnyedén megszüntethető - és arra az esetre, ha valahogy mégis sikerül a kódot [az ellenőrzések előtt] lefuttatni (pl. egy támadó illegitim típust állít be egy hálózati csomagon) a kódfuttatás lehetőségét sikerült szinte nullára redukálnunk. Persze a legjobb megoldás a hibák elkerülésére, ha explicite megadjuk a default ágat, amiben a nem megfelelő csomagtípusra utaló hibát jelzünk.
{
MYOBJECT *p;
switch(packet_type)
{
case REQUEST:
p= new MYOBJECT ...;
case RESPONSE:
p= new MYOBJECT ...;
}
p->DoSomething(); <- flagged as potential uninitialized use
...
Reméljük, hogy a fennti értekezés rámutat ezeknek a figyelmeztetéseknek a komolyságára. Valójában azt szeretnénk, hogy ezek a hibaüzenetek tiltott figyelmezetéstípusként jelennek meg az SDL következő változatában.
EQ · http://rycon.hu 2008.03.12. 21:11:44
Aikon 2008.03.12. 22:03:15
hello 2008.03.13. 14:02:16
Aikon 2008.03.14. 12:58:20
(A C/C++ függvényhívás úgy néz ki, hogy leteszed a stack-re a hívandó függvény paramétereit, majd azt a címet, ahonnan a kódnak a futtatást a return után folytatnia kell, és ezután ugrasz a hívott függvény címére, akiis eléri ezeket az adatokat, majd ha végzett a futással, az utasítás-pointert ráállítja a megadott címre, hogy onnan folytatódjon tovább a futás. Itt a 0x0013fee8 egy olyan cím, ahol egy ilyen visszatérési cím van tárolva, és ezzel lett inicializálva a stack, tehát pNoInit értéke is ez, szóval *pNoInit egy olyat struct, ami 32 bit hosszú, és a 0x0013fee8 címen található. A pNoInit->pImage = (char*)scode; az itt található mutatót írja felül a saját kódunk címére.)