Rejestry

https://chacker.pl/

Rejestry służą do tymczasowego przechowywania danych. Pomyśl o nich jak o szybkich fragmentach pamięci o długości od 8 do 64 bitów, które będą wykorzystywane wewnętrznie przez procesor. Rejestry można podzielić na cztery kategorie (rejestry 32-bitowe są poprzedzone literą E, a rejestry 64-bitowe są poprzedzone literą R, tak jak w EAX i RAX). Są one wymienione i opisane w Tabeli .

Procesory Intela

https://chacker.pl/

Istnieje kilka powszechnie używanych architektur komputerów. W tym rozdziale skupimy się na rodzinie procesorów lub architekturze Intel. Termin architektura odnosi się po prostu do sposobu, w jaki konkretny producent zaimplementował swój procesor. Architektury x86 (32-bitowe) i x86-64 (64-bitowe) są nadal najczęściej używane, a inne architektury, takie jak ARM, rozwijają się każdego roku. Każda architektura wykorzystuje unikalny zestaw instrukcji. Instrukcje z jednej architektury procesora nie są rozumiane przez inny procesor.

Składanie fragmentów pamięci w całość

https://chacker.pl/

Teraz, gdy znasz już podstawy, przyjrzymy się prostemu przykładowi ilustrującemu użycie pamięci w programie.

memory.c

Najpierw wylistujemy zawartość programu za pomocą cat:

Ten program niewiele robi. Po pierwsze, kilka fragmentów pamięci jest alokowanych w różnych sekcjach pamięci procesu. Kiedy wykonywana jest funkcja main (1), wywoływana jest funkcja funct1() z argumentem 1 (2) . Po wywołaniu funkcji funct1() argument jest przekazywany do zmiennej funkcji o nazwie c (3) . Następnie przydzielana jest pamięć na stercie dla 10-bajtowego ciągu zwanego str (4). Na koniec 5-bajtowy ciąg „abcde” jest kopiowany do nowej zmiennej o nazwie str (5) . Funkcja kończy się, a następnie program main() (6).

Wskaźniki

https://chacker.pl/

Wskaźniki to specjalne fragmenty pamięci przechowujące adresy innych fragmentów pamięci. Przenoszenie danych wewnątrz pamięci jest operacją stosunkowo powolną. Okazuje się, że zamiast przenosić dane, znacznie łatwiej jest śledzić lokalizację elementów w pamięci za pomocą wskaźników i po prostu zmieniać wskaźniki. Wskaźniki są zapisywane w 4 lub 8 bajtach ciągłej pamięci, w zależności od tego, czy aplikacja jest 32-bitowa, czy 64-bitowa. Na przykład, jak wspomniano, do łańcuchów odwołuje się adres pierwszego znaku w tablicy. Ta wartość adresu nazywana jest wskaźnikiem. Deklarację zmiennej ciągu w C zapisuje się w następujący sposób:

Zauważ, że chociaż rozmiar wskaźnika jest ustawiony na 4 lub 8 bajtów, w zależności od architektury, rozmiar łańcucha nie został ustawiony za pomocą poprzedniego polecenia; dlatego dane te są uważane za niezainicjowane i zostaną umieszczone w sekcji .bss pamięci procesu. Oto kolejny przykład; jeśli chcesz zapisać w pamięci wskaźnik do liczby całkowitej, wydasz w programie C następujące polecenie:

Aby odczytać wartość adresu pamięci wskazywanego przez wskaźnik, należy wyrejestrować wskaźnik za pomocą symbolu *. Dlatego jeśli chcesz wydrukować wartość liczby całkowitej wskazywanej przez punkt 1 w poprzednim kodzie, użyj polecenia

printf(„%d”, *point1);

gdzie * służy do usuwania referencji wskaźnika zwanego punktem 1 i wyświetlania wartości liczby całkowitej za pomocą funkcji printf().

Ciągi w pamięci

https://chacker.pl/

Mówiąc najprościej, ciągi znaków to po prostu ciągłe tablice danych znakowych w pamięci. Do łańcucha odwołuje się w pamięci adres pierwszego znaku. Łańcuch jest zakończony lub zakończony znakiem null (\0 w C). \0 jest przykładem sekwencji ucieczki. Sekwencje ucieczki umożliwiają programiście określenie specjalnej operacji, takiej jak znak nowej linii z \n lub powrót karetki z \r. Ukośnik odwrotny zapewnia, że kolejny znak nie będzie traktowany jako część ciągu. Jeśli potrzebny jest ukośnik odwrotny, można po prostu użyć sekwencji ucieczki \\, która wyświetli tylko pojedynczy \. Tabele różnych sekwencji ucieczki można znaleźć w Internecie.

Bufory

https://chacker.pl/

Termin bufor odnosi się do miejsca przechowywania używanego do odbierania i przechowywania danych do czasu, aż będą mogły zostać obsłużone przez proces. Ponieważ każdy proces może mieć swój własny zestaw buforów, niezwykle ważne jest, aby były one proste; odbywa się to poprzez alokację pamięci w sekcji .data lub .bss pamięci procesu. Pamiętaj, że raz przydzielony bufor ma stałą długość. Bufor może przechowywać dowolny, predefiniowany typ danych; jednak dla naszych celów skupimy się na buforach opartych na ciągach znaków, które służą do przechowywania danych wejściowych użytkownika i zmiennych tekstowych.

Sekcja Środowisko/Argumenty

https://chacker.pl/

Sekcja środowisko/argumenty służy do przechowywania kopii zmiennych na poziomie systemu, które mogą być wymagane przez proces w czasie wykonywania. Na przykład uruchomionemu procesowi udostępniana jest między innymi ścieżka, nazwa powłoki i nazwa hosta. Ta sekcja jest zapisywalna, co pozwala na jej wykorzystanie w exploitach związanych z przepełnieniem ciągu formatującego i bufora. Dodatkowo w tym obszarze przechowywane są argumenty wiersza poleceń. Sekcje pamięci znajdują się w przedstawionej kolejności. Przestrzeń pamięci procesu wygląda następująco:

Programy w pamięci

https://chacker.pl/

Kiedy procesy są ładowane do pamięci, są zasadniczo podzielone na wiele małych sekcji. Zajmujemy się tylko sześcioma głównymi sekcjami, które omówimy.

Sekcja .text

Sekcja .text, znana również jako segment kodu, zasadniczo odpowiada części .text binarnego pliku wykonywalnego. Zawiera instrukcje maszynowe umożliwiające wykonanie zadania. Ta sekcja jest oznaczona jako czytelna i wykonywalna, a próba zapisu spowoduje naruszenie zasad dostępu. Rozmiar jest ustalany w czasie wykonywania, kiedy proces jest ładowany po raz pierwszy.

Sekcja .data

Sekcja .data służy do przechowywania inicjowanych zmiennych globalnych, takich jak

int a = 0;

Rozmiar tej sekcji jest ustalany w czasie wykonywania. Należy go jedynie oznaczyć jako czytelny.

Sekcja .bss

Poniższa sekcja stosu (.bss) służy do przechowywania niektórych typów niezainicjowanych zmiennych globalnych, takich jak int a; Rozmiar tej sekcji jest ustalany w czasie wykonywania. Segment ten musi być czytelny i zapisywalny, ale nie powinien być wykonywalny.

Sekcja sterty

Sekcja sterty służy do przechowywania dynamicznie przydzielanych zmiennych i rośnie od pamięci o niższym adresie do pamięci o wyższym adresie. Alokacją pamięci steruje się za pomocą funkcji malloc(), realloc() i free(). Na przykład, aby zadeklarować liczbę całkowitą i przydzielić pamięć w czasie wykonywania, możesz użyć czegoś takiego:

Sekcja sterty powinna być czytelna i zapisywalna, ale nie powinna być wykonywalna, ponieważ osoba atakująca, która uzyska kontrolę nad procesem, może z łatwością wykonać wykonanie kodu powłoki w obszarach takich jak stos i sterta.

Sekcja stosu

Sekcja stosu służy do śledzenia wywołań funkcji (rekurencyjnie) i w większości systemów rośnie od pamięci o wyższym adresie do pamięci o niższym adresie. Jeśli proces jest wielowątkowy, każdy wątek będzie miał unikalny stos. Jak zobaczysz, fakt, że stos rośnie od dużej pamięci do małej pamięci, pozwala na istnienie tematu przepełnienia bufora. Zmienne lokalne istnieją w sekcji stosu.

Segmentacja pamięci

https://chacker.pl/

Temat segmentacji z łatwością mógłby zająć cały rozdział. Jednak podstawowa koncepcja jest prosta. Każdy proces (w dużym uproszczeniu jako program wykonawczy) musi mieć dostęp do własnych obszarów pamięci. Przecież nie chciałbyś, żeby jeden proces nadpisywał dane innego procesu. Dlatego pamięć jest dzielona na małe segmenty i przekazywana procesom w miarę potrzeb. Rejestry, omówione w dalszej części rozdziału, służą do przechowywania i śledzenia bieżących segmentów utrzymywanych przez proces. Rejestry offsetowe są do tego przyzwyczajone  śledź, gdzie w segmencie przechowywane są krytyczne fragmenty danych. Segmentacja opisuje także układ pamięci w wirtualnej przestrzeni adresowej procesu. Segmenty takie jak segment kodu, segment danych i segment stosu są celowo przydzielane w różnych obszarach wirtualnej przestrzeni adresowej w ramach procesu, aby zapobiec kolizjom i umożliwić odpowiednie ustawienie uprawnień. Każdy działający proces otrzymuje własną wirtualną przestrzeń adresową, a ilość miejsca zależy od architektury (np. 32-bitowej lub 64-bitowej), ustawień systemu i systemu operacyjnego. Podstawowy 32-bitowy proces systemu Windows domyślnie otrzymuje 4 GB, z czego 2 GB jest przypisane do strony procesu w trybie użytkownika, a 2 GB jest przypisane do strony procesu w trybie jądra. Tylko niewielka część tej wirtualnej przestrzeni w każdym procesie jest mapowana na pamięć fizyczną i w zależności od architektury istnieją różne sposoby mapowania pamięci wirtualnej na fizyczną poprzez wykorzystanie stronicowania i translacji adresów.

Endian

https://chacker.pl/

W Internet Experiment Note (IEN) 137, „On Holy Wars and a Plea for Peace” z 1980 r., Danny Cohen podsumował Podróże Guliwera Swifta częściowo w następujący sposób, omawiając kolejność bajtów: Guliwer dowiaduje się, że istnieje prawo , ogłoszony przez dziadka obecnego władcy, nakazujący wszystkim obywatelom Lilliputu rozbijanie jajek tylko na małych końcach. Oczywiście wszyscy obywatele, którzy rozbijali jajka na końcu, byli oburzeni proklamacją. Wybuchła wojna domowa pomiędzy Little-Endianami i Big-Endianami, w wyniku której Big-Endianowie schronili się na pobliskiej wyspie, królestwie Blefuscu. Celem artykułu Cohena było opisanie dwóch szkół myślenia podczas zapisywania danych w pamięci. Niektórzy uważają, że bajty o niższym numerze powinny być zapisywane jako pierwsze (nazywane przez Cohena „Little-Endianami”), inni natomiast uważają, że bajty o większym znaczeniu powinny być zapisywane jako pierwsze (tzw. „Big-Endiany”). Różnica naprawdę zależy od używanego sprzętu. Na przykład procesory oparte na procesorach Intel korzystają z metody Little-Endian, podczas gdy procesory oparte na Motoroli korzystają z metody Big-Endian.