Tak na marginesie to trzeba mieć talent, aby takie coś napisać. Musiałem przerobić mój przykład tak, aby atak się udał ponieważ pierwotnie nie było to możliwe.
Program działa prosto, oczekuje jako parametru hasła. Poprawnym hasłem jest "root". Autoryzacja powiedzie się tylko wtedy, gdy wpiszemy "$ ./bflow2 root".
Cały błąd projekcyjny tego programu leży w tym, że do autoryzacji używamy w ogóle nie potrzebnej zmiennej auth. Jak by tego było małe, to jest jeszcze ona zadeklarowana przed buforem, który można przepełnić. Nie ma sprawdzania długość hasła wprowadzonego przez użytkownika.
Zanim przejdę do rzeczy wspomnę jeszcze, że najpierw skompiluję i przetestuje program na swoim systemie Debian Squeeze amd64, a później na CentOS 5. Wyniki będą różne. W systemie Debian od wersji Lenny wszystkie możliwe paczki są kompilowane z opcją ochrony stosu. Mechanizm ten stworzył IBM, to tak zwany stack-smashing protector. Dokumentacje GCC zawiera te opcje.
Do rzeczy. Najpierw uruchomimy program z parametrem, który zapełni cały bufor, następnie w debuggerze gdb sprawdzimy ile jeszcze bajtów zostało do nadpisania zmiennej auth.
Cyfrę "1" w kodzie hexadecymalnym reprezentuje liczba "31". Można to sobie sprawdzić wydając polecenie 'echo "1111111111"|hd'.
Jedynek jest 10, zapełniamy cały bufor. Podkreśliłem je na czerwono. Trzeba pamiętać, że w pamięci bajty są umieszczone w odwrotnej kolejności. Dlatego wartości "31" nie są ciągłe.
Na zielono podkreśliłem adres zmiennej auth na stosie oraz wynik działania programu.
Jeżeli słowa zawierające koniec tablicy buff i zmienną auth odwrócimy to zobaczymy ile jest między nimi bajtów:
0x00313131 0x00000000 0xffe32000
0x31313100 0x00000000 0x0020e3ff odwrócone
Bajty stanowiące odstęp między końcem tablicy buff, a zmienną auth oznaczyłem kursywą. Natomiast samą zmienną auth pogrubiłem. Teraz nie ma już wątpliwości, należy dopisać jeszcze 6 jedynek ('1') aby nadpisać zmienną auth. (Znak '1' ani '0' nie reprezentują w pamięci liczb 1 i 0, zapisywane są odpowiednimi kodami ASCII)
Jak widać atak powiódł się. Program dotarł do końca, wypisując tekst 'end' i uzyskał dostęp. Mimo wszystko system wykrył ingerencję w stosie programu. Świadczy o tym komunikat "Segmentation fault.". Dzieje się tak ponieważ większość najważniejszych programów w systemie Debian są kompilowane, z ochroną stosu.
Po opuszczeniu debuggera wywołałem jeszcze trzy razy program. Pierwszy raz miał o jedną jedynkę za mało (15), druki 16, a trzeci raz o jedną za dużo (17). Wpisując szesnaście jedynek program sam się nie zakończył, ale kod wykonał do końca, o czym świadczy wypisanie testu 'end'.
Teraz skompilujemy program z opcją -fstack-protector.
Tym razem zamiast samych jedynek użyłem kolejnych liczb, teraz można zobaczyć jak powinno się czytać zrzut pamięci od tyłu. Jak widać kompilator odwrócił kolejność zmiennych na stosie i nie można teraz poprzez tablicę buff nadpisać zmiennej auth, ponieważ jest ona przed nią.
Nawet z opcją -fstack-protector da się nadpisac w taki sposób zmienne z funkcji main(). Aby temu zapobiec należy użyć opcji -fstack-protector-all.
Teraz przetestujemy ten sam kod na systemie CentOS 5.
Najpierw sprawdzamy jaki jest odstęp między zmiennymi. Łoł - nie ma żadnego. Wystarczy dodać tylko jeden znak, czyli zamiast jako parametr podać 10 znaków, należy podać 11 i gotowe.
Dodałem 'x', jak widać działa pięknie. Potem kompilujemy program z ochroną stosu i również działa jak powinno - przerwał program i nie pozwolił mu dalej pracować. Różnice w zachowaniach kompilatora na obu tych systemach wynikają z kompilacji z różnymi opcjami gcc i nie tylko.
W systemie Debian opcja ochrony stosu zapobiegła atakowi, a system w całości wykonała kod programu, w CentOS program został natychmiast przerwany.
No comments:
Post a Comment