Kolejnosc zmiennych na stosie

Bash, C, C++, Java, PHP, Ruby, GTK, Qt i wiele innych - wszystko tutaj.
Awatar użytkownika
joekid
Piegowaty Guziec
Piegowaty Guziec
Posty: 6
Rejestracja: 05 mar 2009, 16:21
Płeć: Mężczyzna
Wersja Ubuntu: 8.04
Środowisko graficzne: GNOME

Kolejnosc zmiennych na stosie

Post autor: joekid »

Witam

Mam następujący problem: debugując prostą aplikacje w C na procesorze Intel Core 2 Duo, skompilowana w trybie 32 bit, zaobserwowalem pewna ciekawostke. Otoz mam w programie nastepujaca kolejnosc instrukcji przypisania:

int flag = 0;
char buffer[24];

W czasie debugowanie gdb kolejnosc zmiennych na stosie jest wlasnie taka, jak w deklaracji powyzej (wydaje mi sie, ze najpierw powinien byc bufor, a potem flaga). Nawet przy zmianie kolejnosci deklaracji i przekompilwoniu kolejnosc na stosie nie zmienia sie. Polecenie do kompilacji to gcc -m32 -g -o program plik.c.

Prosze o krotkie wyjasnienie czemu tak sie dzieje?
Awatar użytkownika
doles2
Sędziwy Jeż
Sędziwy Jeż
Posty: 46
Rejestracja: 24 lip 2006, 19:58
Płeć: Mężczyzna
Wersja Ubuntu: 11.10
Środowisko graficzne: KDE Plasma
Architektura: x86_64

Odp: Kolejnosc zmiennych na stosie

Post autor: doles2 »

Najlepiej będzie jak wkleisz kod Asemblera (gcc -S plik.c), wtedy można popatrzeć co robi procesor :)
Awatar użytkownika
joekid
Piegowaty Guziec
Piegowaty Guziec
Posty: 6
Rejestracja: 05 mar 2009, 16:21
Płeć: Mężczyzna
Wersja Ubuntu: 8.04
Środowisko graficzne: GNOME

Odp: Kolejnosc zmiennych na stosie

Post autor: joekid »

Dla funkcji w C (oczywiscie nie jest to cala funkcja:P).

int check_authentication(char * pass){
int flag = 0;
char buffer[16];
}

mamy nastepujacy kod asemblera:

(gdb) disass check_authentication
Dump of assembler code for function check_authentication:
0x08048434 <check_authentication+0>: push ebp
0x08048435 <check_authentication+1>: mov ebp,esp
0x08048437 <check_authentication+3>: sub esp,0x28
0x0804843a <check_authentication+6>: mov eax,DWORD PTR [ebp+0x8]
0x0804843d <check_authentication+9>: mov DWORD PTR [ebp-0x24],eax
0x08048440 <check_authentication+12>: mov eax,gs:0x14
0x08048446 <check_authentication+18>: mov DWORD PTR [ebp-0x4],eax
0x08048449 <check_authentication+21>: xor eax,eax
0x0804844b <check_authentication+23>: mov DWORD PTR [ebp-0x18],0x0
0x08048452 <check_authentication+30>: mov edx,DWORD PTR [ebp-0x4]
0x08048455 <check_authentication+33>: xor edx,DWORD PTR gs:0x14
0x0804845c <check_authentication+40>: je 0x8048463 <check_authentication+47>
0x0804845e <check_authentication+42>: call 0x8048378 <__stack_chk_fail@plt>
0x08048463 <check_authentication+47>: leave
0x08048464 <check_authentication+48>: ret
End of assembler dump.

Oraz stan stosu:

(gdb) x/32xw $esp
0xffc97950: 0xff0a0000 0xffc9971b 0x00000000 0x00000000
0xffc97960: 0x00000000 0x00000000 0xffc996db 0xf7e9fd1e
0xffc97970: 0xf7f46f59 0xff0a0000 0xffc97998 0x080484b8
0xffc97980: 0xffc9971b 0x0804971c 0xffc979a8 0x08048529
0xffc97990: 0xffc979b0 0xffc979b0 0xffc97a08 0xf7e4c450
0xffc979a0: 0xf7fb7cc0 0x08048510 0xffc97a08 0xf7e4c450
0xffc979b0: 0x00000002 0xffc97a34 0xffc97a40 0xf7f99cb8
0xffc979c0: 0x00000000 0x00000001 0x00000000 0x08048267
(gdb)

Dla pewnosci

(gdb) x/x &flag
0xffc97960: 0x00000000
(gdb) x/xs buffer
0xffc97964: ""

Zamiana kolejnosci deklaracji w kodzie powoduje dokladnie taki sam kod asm, oraz kolejnosc na stosie.
Awatar użytkownika
doles2
Sędziwy Jeż
Sędziwy Jeż
Posty: 46
Rejestracja: 24 lip 2006, 19:58
Płeć: Mężczyzna
Wersja Ubuntu: 11.10
Środowisko graficzne: KDE Plasma
Architektura: x86_64

Odp: Kolejnosc zmiennych na stosie

Post autor: doles2 »

Nie wiem czy pomoże Tobie coś co napisze marny student I roku informatyki, ale niech spróbuję:
Zaczynamy tutaj:

Kod: Zaznacz cały

0x08048437 <check_authentication+3>: sub esp,0x28
Oto coś dziwnego - program rezerwuje na stosie 0x28 bajtów (40 decymalnie) zamiast dwudziestu (16 znaków plus czterobajtowy int). To już daje do myślenia czemu tak wiele. Jedźmy dalej...

Kod: Zaznacz cały

0x0804843a <check_authentication+6>: mov eax,DWORD PTR [ebp+0x8]
0x0804843d <check_authentication+9>: mov DWORD PTR [ebp-0x24],eax
EBP+0x8 wskazuje na adres gdzie zaczyna się Twój ciąg znaków o nazwie *pass, czyli argument tej funkcji. Już jest dziwnie, że ten argument jest kopiowany na stos oddalony od EBP o 36 bajtów. Na tym nie poprzestajemy:

Kod: Zaznacz cały

0x08048440 <check_authentication+12>: mov eax,gs:0x14
0x08048446 <check_authentication+18>: mov DWORD PTR [ebp-0x4],eax
Tej linii w ogóle nie rozumiem przez to: gs:0x14. Do zmiennej lokalnej wpisywana jest pewnie jakaś stała związana z Twoją funkcją (tak myślę). Lokacja EBP-0x4 to de facto pierwsza zmienna na stosie. Następnie:

Kod: Zaznacz cały

0x0804844b <check_authentication+23>: mov DWORD PTR [ebp-0x18],0x0
Dopiero tutaj w lokacji EBP-0x18 wpisywane jest 0 czyli to musi być zmienna "flag". Zauważ, że jest oddalona aż o 24 bajty od EBP...

Kod: Zaznacz cały

0x08048452 <check_authentication+30>: mov edx,DWORD PTR [ebp-0x4]
0x08048455 <check_authentication+33>: xor edx,DWORD PTR gs:0x14
To jest dopiero dziwadło ! Do rejestru EDX ładujemy zmienną spod EBP-0x4 (ta w której było coś z gs:0x14) i co robimy ? Xorujemy EDX z gs:0x14. Jeśli wychodzi 0 (czyli zmienna z EBP-0x4 równa się GS:0x14) to wyjdź z funkcji, jeśli nie to wywołaj __stack_chk_fail@plt. Co to jest ta ostatnia funkcja zaraz powiem. Pomyślmy, na pierwszy rzut oka kod jest bez sensu, pobieramy zmienną z pamięci ładujemy na stos a potem xorujemy jej kopie z stosu z oryginałem i jeśli są takie same to zakończ funkcje a jak nie to wywołaj coś co ma "fail" w nazwie. To daje do myślenia, mianowicie myliłem się. GS:0x14 nie jest Twoim tworem od szyfrowania czy czegokolwiek :) To musi być jakaś zmienna w stylu "stack protector", nie chce mi się domyślać jak ona działa ale pewnie tak że jeśli ją jakiś "cwaniaczek" zamarze pewną wartością to trzeba wywołać funkcję od tzw stack-smashingu. Pewnie samo __stack_chk_fail@plt ma coś wspólnego z fajnym napisem "*** stack smashing detected ***: ./blablabla terminated".
Rzućmy okiem na nasz stos jak są poukładane dane :
EBP - 0x4 - czterobajtowy protector
EBP - 0x18 - czterobajtowa zmienna flag, wyzerowana
EBP - 0x24 - wskaźnik na char *pass
Widać że w tym 40-bajtowym bloku są puste luki. Narysowałem sobie ładnie jak to jest rozłożone i widzę że tylko od EBP-0x14 jest miejsce na 16 bajtowy bufor. Twój kod nie inicjalizuje bufora zatem nie ma nigdzie w Asemblerze rozkazów wypełniających go czymś. Stąd brak odwołań do EBP-0x14 :)
Nie wiem po co jest ta reszta pustych luk, albo po co jest EBP-0x28, co kryje się w dwunastobajtowym bloku od EBP-0x23 (lepiej, co dopiero będzie tam się kryć) ?
Szperałem w sieci na temat ten dziwnej funkcji __stack_chk_fail@plt i znalazłem takie coś:
http://forum.smashthestack.org/viewtopic.php?pid=1000
Szczegołnie ciekawy jest fragment:

Kod: Zaznacz cały

1. Reorders local variables so that buffers are placed after pointers
2. Copies pointers in function arguments to an area before local buffers
3. omits instrumentation code from functions to reduce overhead
4. adds a call to __stack_chk_fail() in the epilog
1. Wskaźnik powinien być przed buforem (wględem adresu bufora i wskaźnika, mam na myśli adres wskaźnika mniejszy niż adres początku bufora) gdyż jeśli damy do bufora więcej danych niż on jest w stanie pomieścić to nadpiszemy zmienne powyżej niego, czyli jeśli nasz bufor zaczyna się od EBP-0x14 to możemy nadpisać tego właśnie strażnika stosu. Wskaźnik na argument jest bezpieczny, gdyż nie da się go nadpisać (nie zapisujemy do pamięci wstecz). Jeśli coś naruszymy to naruszymy właśnie protectora a ten zaś potem jest sprawdzany czy jest taki sam jak oryginał. Zatem wszystko jest ok :)
2. Tak, właśnie znaleźliśmy kopiowanie wskaźnika do argumentu funkcji.
3. Nie wiem, nie zauważyłem tego w Twoim kodzie :P
4. Tak, jest to wywołanie.

Wychodzi na to, że jeśli dasz flage -fno-stack-protector do kompilatora to stos będzie wyglądał klasycznie. To tyle jeśli chodzi o mnie, nie czytałem nic wcześniej w necie. Nie rozgryzłem Twojego programu a potem pisałem posta. Nie, to wyglądało inaczej - na bierząco co wymyśliłem od razu tutaj pisałem zatem jest to robota wykonana w locie i gdzieś mogłem się machnąć. Przeczytaj sobie tę stronkę co podałem, ja czytałem tylko pierwsze górne linijki :P
Awatar użytkownika
joekid
Piegowaty Guziec
Piegowaty Guziec
Posty: 6
Rejestracja: 05 mar 2009, 16:21
Płeć: Mężczyzna
Wersja Ubuntu: 8.04
Środowisko graficzne: GNOME

Odp: Kolejnosc zmiennych na stosie

Post autor: joekid »

Kod asm w pelni rozumiem i jedyna niejasnosc jaka sie pojawia to dlaczego int w obu przypadkach jest przed charem. Bufor w tym wypadku owszem jest pusty bo w tym przykladzie oczywiscie nic do niego nie wstawiam. Rozmiary tez sie zgadzaja, bo tak zwyczajowo dziala alokacja miejsca na nowa ramke;) Ale jedyne co mnie zastanawia to to ze w pamieci int znajduje sie przed buforem char, a wg. kolejnosci kodu C powinno byc inaczej. Przynajmniej zwyczajowo tak bylo. I stad wlasnie moje pytanie.

Dzieki wielkie za checi:)
Ale moje pytanie jest nadal nie rostrzygniete i czekam na jakies pomysly.
Awatar użytkownika
doles2
Sędziwy Jeż
Sędziwy Jeż
Posty: 46
Rejestracja: 24 lip 2006, 19:58
Płeć: Mężczyzna
Wersja Ubuntu: 11.10
Środowisko graficzne: KDE Plasma
Architektura: x86_64

Odp: Kolejnosc zmiennych na stosie

Post autor: doles2 »

Nie wiem juz jak to jest w C. Najpierw tworzona jest ramka stosu, potem od ESP odejmuje się jakieś wartości aby zaalokować miejsce na zmienne tymczasowe, ale jak to miejsce się traktuje to już nie pamiętam :) Spróbuj skompilować kod z flagą -fno-stack-protector i zobacz jak wówczas wyglądają zmienne. Czy są jakoś przestawione czy zgodnie z Twoimi oczekiwaniami.
Awatar użytkownika
joekid
Piegowaty Guziec
Piegowaty Guziec
Posty: 6
Rejestracja: 05 mar 2009, 16:21
Płeć: Mężczyzna
Wersja Ubuntu: 8.04
Środowisko graficzne: GNOME

Odp: Kolejnosc zmiennych na stosie

Post autor: joekid »

W sumie masz racje:) Idac za Twoja rada udalo mi sie doczytac, ze gcc automatycznie wykrywa potencjalna mozliwosc ataku poprzez przepelnienie bufora, ktore w tym przypadku moglo by zaistniec jesli tablica charow znajdowala by sie przed zmienna int:)

Dzieki wielkie za wskazowke:)
ODPOWIEDZ

Wróć do „Programowanie”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 2 gości