Kolejnosc zmiennych na stosie
- joekid
- 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
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?
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?
- doles2
- 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
Najlepiej będzie jak wkleisz kod Asemblera (gcc -S plik.c), wtedy można popatrzeć co robi procesor
- joekid
- 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
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.
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.
- doles2
- 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
Nie wiem czy pomoże Tobie coś co napisze marny student I roku informatyki, ale niech spróbuję:
Zaczynamy tutaj:
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...
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:
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:
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...
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:
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
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
Zaczynamy tutaj:
Kod: Zaznacz cały
0x08048437 <check_authentication+3>: sub esp,0x28
Kod: Zaznacz cały
0x0804843a <check_authentication+6>: mov eax,DWORD PTR [ebp+0x8]
0x0804843d <check_authentication+9>: mov DWORD PTR [ebp-0x24],eax
Kod: Zaznacz cały
0x08048440 <check_authentication+12>: mov eax,gs:0x14
0x08048446 <check_authentication+18>: mov DWORD PTR [ebp-0x4],eax
Kod: Zaznacz cały
0x0804844b <check_authentication+23>: mov DWORD PTR [ebp-0x18],0x0
Kod: Zaznacz cały
0x08048452 <check_authentication+30>: mov edx,DWORD PTR [ebp-0x4]
0x08048455 <check_authentication+33>: xor edx,DWORD PTR gs:0x14
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
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
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
- joekid
- 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
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.
Dzieki wielkie za checi:)
Ale moje pytanie jest nadal nie rostrzygniete i czekam na jakies pomysly.
- doles2
- 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
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.
- joekid
- 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
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:)
Dzieki wielkie za wskazowke:)
Kto jest online
Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 0 gości