Ta witryna korzysta z plików cookies. Korzystając z witryny akceptujesz: polityka prywatności.   [×]
Online: 0x07 (7)

haker.info – Etyczny hacking🕊️

Spreading knowledge like a virus.

📖 Jakie są rejestry procesora x86-64 (64-bitowego)?

🕰

Rejestry można sobie wyobrazić jako specjalna, wewnętrzna pamięć w procesorze. Niektórzy nie lubią tu określenia „pamięć”, gdyż rejestrów się nie adresuje. Mimo wszystko można je porównać do „pojemników na dane komputerowe”. I to takich do których jest bardzo szybki dostęp, więc operacje z ich użyciem wykonywane są bardzo szybko. Bezpośredni dostęp do rejestrów jako programiści możemy uzyskać w języku Asembler. Odwołuje się do nich poprzez ich nazwy (np. rax, rbx, rcx...).

Jeśli na przykład chcemy wykonać dodawanie to wpisujemy dwie wartości do dwóch różnych rejestrów i wykonujemy na nich operację sumowania odpowiednią instrukcją procesora (wynik też będzie w określonym rejestrze). Zatem za pomocą rejestrów procesora możemy przekazywać dane do przetworzenia, ale też np. uzyskać dane wynikowe, które zwraca określona instrukcja czy funkcja. Jeśli dane nie mieszczą się w rejestrze, czyli np. jest to długi ciąg tekstu, to wykonujemy wtedy adresowanie. Fragment tekstu do przetworzenia umieszczamy w zmiennej (takiej „komórce” pamięci) o wymyślonej nazwie, a do rejestru podajemy tylko adres do tego miejsca w pamięci (przeważnie jest to nazwa tej zmiennej poprzedzona specjalną dyrektywą pobierającą adres).

Rejestry ogólnego przeznaczenia

Rejestry ogólnego przeznaczenia mogą być używane do różnych celów. Przy wprowadzeniu ich nadano im domyślne użycie np. akumulator przechowuje wyniki obliczeń, a licznik służy do tworzenia pętli. Mimo to możemy tych rejestrów używać różnie. Schematy tych rejestrów przedstawiono na rysunku.

Rejestr znaczników – flag (RFLAGS)

Rejestr znaczników, nazywany też rejestrem flag procesora przechowuje bieżący stan procesora . Można w nim wyróżnić flagi kontrolne, statusu oraz systemowe. W architekturze 16 bitowej rejestr ten miał nazwę FLAGS i miał rozmiar 16 bitów. Natomiast w 32-bitowej był już EFLAGS. A tryb 64 bitowy rozszerzył ten rejestr do 64 bitów i nazywany jest RFLAGS. Poszczególne znaczniki to po prostu bity. Określenie flagi pasuje tutaj, gdyż określony bit tak jak tradycyjna flaga może być podniesiony (wartość 1) lub opuszczony (wartość 0).Każda flaga coś sygnalizuje. Można to też porównać do diody, która jest zaświecona lub zgaszona. Schemat rejestru flag przedstawiono na rysunku. Bity zaznaczone na szaro są zarezerwowane i jeśli się je odczyta to będą miały wartość zero.

Przy programowaniu typowych aplikacji w języku Asembler należy znać następujące flagi:

  • CF (ang. carry flag) – flaga przeniesienia. Ustawiana jest, gdy ostatnia operacja arytmetyczna spowodowała przeniesienie (ang. carry) poza najstarszy bit wyniku lub pożyczenie (ang. borrow) z najstarszego bitu wyniku. W przeciwnym wypadku jest zerowana.
  • PF (ang. parity flag) – flaga parzystości. Ustawiana jest, gdy liczba bitów o wartości jeden w najmłodszym bajcie rezultatu niektórych operacji jest parzysta. W przeciwnym wypadku flaga jest zerowana.
  • AF (ang. auxiliary carry flag) – flaga przeniesienia pomocniczego. Ustawiana jest, gdy ostatnia operacja arytmetyczna spowodowała przeniesienie (ang. carry) poza trzeci bit wyniku lub pożyczenie (ang. borrow) z trzeciego bitu wyniku. W przeciwnym wypadku jest zerowana.
  • ZF (ang. zero flag) – flaga zerowa. Ustawiana jest, gdy ostatnia operacja arytmetyczna lub logiczna zwróciła w wyniku wartość zero. W przeciwnym wypadku flaga jest zerowana.
  • SF (ang. sign flag) – flaga znaku. Ustawiana jest, gdy ostatnia operacja arytmetyczna zwróciła liczbę ujemną. W przeciwnym wypadku jest zerowana. W prostych słowach flaga ta jest ustawiana zgodnie z najstarszym bitem wyniku (bitem znaku).
  • DF (ang. direction flag) – flaga kierunku. Ustawiana jest przez programistę, zależnie od tego w którym kierunku chce przetwarzać ciągi znaków (napisy). Związana jest z rozkazami operującymi na napisach.
  • OF (ang. overflow flag) – flaga przepełnienia. Ustawiana jest, gdy ostatnia operacja dała w wyniku wartość, która jest zbyt duża lub zbyt mała, aby zmieścić się w operandzie docelowym. W przeciwnym wypadku jest zerowana.

Pozostałe flagi przedstawione na rysunku wcześniej to flagi systemowe i raczej nie używa się ich w typowych aplikacjach.

Rejestr wskaźnika instrukcji (RIP)

Rejestr RIP zawiera adres następnej instrukcji do wykonania. Chciałbym tutaj wspomnieć dodatkowo o adresowaniu relatywnym do wskaźnika instrukcji, który jest dostępny w trybie 64-bitowym. Jest to bardzo przydatne przy tworzeniu kodu relokowalnego/wstrzykiwalnego, który musi być niezależny od miejsca w pamięci. Jeśli adresujemy względnie do rejestru RIP, to po zmianie miejsca kodu, który wstrzykniemy do pamięci nasze odwołania (adresy) nie zostaną zniszczone (nie będą nieprawidłowe).

W architekturze 32-bitowej z tego rodzaju adresowania korzystało się przy wykonywaniu skoków (JMP) czy wywołań procedur (CALL). Z wejściem trybu 64-bitowego możliwy jest dodatkowo swobodny dostęp do miejsc w pamięci korzystając z rejestru wskaźnika instrukcji RIP.

Rejestry segmentowe

W trybie 64-bitowym dostępne są tylko rejestry segmentowe CS, FS i GS (rysunek dalej). Nie używa się tutaj już segmentowego modelu pamięci. Można sobie wyobrazić, że pamięć jest ciągłą przestrzenią. Mimo to czasem używa się segmentów GS i FS jako bazę adresową. Warto zaznaczyć, że z tych rejestrów korzysta się m.in. tworząc kod wstrzykiwalny (shellcode) w systemach Windows. W trybie 64-bitowym za pomocą rejestru GS (w trybie 32-bitowym za pomocą FS) możemy się dostać do struktury Thread Environment Block (TEB), gdzie po wykonaniu określonych kroków możemy uzyskać adresy funkcji Windows API potrzebne dla naszego shellcode/payload.

Rejestry koprocesora (FPU / x87)

Nazwa koprocesor pochodzi z dawnych czasów i oznacza osobny element wspomagający procesor w obliczeniach. W tym rozdziale przedstawiono rejestry koprocesora arytmetycznego nazywanego też jednostką zmiennoprzecinkową (ang. floating-point unit, FPU) czy krótko x87. Mimo, że określenie koprocesor pozostało, to aktualnie jest on zawarty w układzie procesora, a nie oddzielnie.

Koprocesor arytmetyczny powstał, aby przeprowadzać obliczenia na liczbach z większą dokładnością. Główne typy danych używane przy operacjach zmiennoprzecinkowych to:

  • Wartość pojedynczej precyzji o rozmiarze 32 bitów,
  • Wartość podwójnej precyzji o rozmiarze 64 bitów,
  • Wartość o rozszerzonej precyzji o rozmiarze 80 bitów.

Rejestry koprocesora (x87) zachowują się jak stos. Korzystając z jego instrukcji możliwe jest wykonywanie obliczeń relatywnie od wierzchołka stosu, ale nic nie stoi na przeszkodzie, aby odwoływać się do poszczególnych rejestrów indywidualnie (po nazwie np. st0, st1, st2, ..., st7).

Przy opisywaniu koprocesora x87 warto wspomnieć o rejestrze statusu nazywanym FPU Status Word. To w nim są informacje o aktualnym stanie jednostki zmiennoprzecinkowej. Bity od 13 do 11 w tym rejestrze wskazują na wierzchołek stosu określany jako TOP. Jak wcześniej wspomniano rejestry FPU są w formie stosu. Bity określane jako TOP zawierają fizyczny indeks wierzchołka stosu rejestrów.

Schemat rejestru statusu dla jednostki zmiennoprzecinkowej przedstawiono na rysunku dalej. Oprócz TOP wspomnę też o bitach C0...C3. Otóż są one ustawiane przez rozkazy porównania wartości. Pozostałe znaczniki (IE, DE, ZE etc.) dotyczą wyjątków (ang. exceptions).

Oprócz rejestru statusu istnieje też rejestr kontroli jednostki zmiennoprzecinkowej (rysunek dalej). Znacznik PC w tym rejestrze określa dokładność:

  • 00b – pojedyncza precyzja (24 bity),
  • 01b – zarezerwowane,
  • 10b – podwójna precyzja (53 bity)
  • 11b – rozszerzona podwójna precyzja (64 bity).

Natomiast RC dotyczy zaokrąglania, a jego możliwe wartości to:

  • 00b – zaokrąglanie do najbliższej wartości lub wartości parzystej w przypadku równej odległości,
  • 01b – zaokrąglanie w dół,
  • 10b – zaokrąglanie w górę,
  • 11b – zaokrąglanie w kierunku zera.

Pozostałe znaczniki to maski wyjątków. Określają one czy ma zostać rzucony wyjątek danego rodzaju. Wyjątki są domyślnie wyciszone (maskowane). Możemy zatem ustawić, które wyjątki chcemy aktywować.

Rejestry MMX

Procesor o architekturze x86-64 posiada osiem rejestrów związanych z technologią MMX. Rozmiar każdego z tych rejestrów to 64 bity. A ich nazwy to MM0, MM1, MM2..., aż do MM7. Rozszerzenie to powstało, aby sprawniej i wydajniej obsługiwać multimedia. Niektórzy rozwijają skrót MMX jako Multimedia eXtensions.

Rejestry MMX są nałożone (mapowane) na młodszą (dolną) 64-bitową część 80-bitowych rejestrów koprocesora (FPU, ang. floating-point unit). Z tego powodu wykonywanie operacji przez rozkazy z tego zestawu instrukcji powoduje też modyfikacje rejestrów jednostki zmiennoprzecinkowej (koprocesora x87).

Rozszerzenie MMX posiada własny zestaw instrukcji do transferu danych, konwersji, operacji arytmetycznych, logicznych etc. Należy też zaznaczyć, że rozszerzenie to korzysta z techniki nazywanej Single Instruction Multiple Data (SIMD) co w tłumaczeniu z języka angielskiego oznacza „pojedyncza instrukcja – wiele danych”. Dzięki technice SIMD możliwe jest np. dodanie kilku wartości z operandu źródłowego do innych kilku wartości z operandu docelowego w sposób równoległy. Oznacza to wyraźne przyspieszenie przetwarzania danych przez aplikacje, które używają rozkazów typu SIMD. Rejestry rozszerzenia MMX w formie prostego schematu przedstawiono na rysunku dalej.

Rejestry SSE / AVX / AVX-512

Rozszerzenia SSE (SSE2, SSE3, SSSE3, SSE4…) oraz AVX to kolejny krok do zwiększenia wydajności poprzez przetwarzanie typu SIMD (Single Instruction Multiple Data).

Wartości podawane do instrukcji tego typu mogą być wektorami (ang. vector) lub skalarami (ang. scalar). Operacje na wektorach określane są też jako packed. Warto znać te terminy w języku angielskim, aby łatwiej zrozumieć oficjalne dokumentacje AMD64 Architecture Programmer's Manual oraz Intel 64 and IA-32 Architectures Software Developer's Manual.

Wektor z danymi posiada kilka wartości, które jak już było wspomniane przy rozdziale o MMX są przetwarzane równolegle. Natomiast przy operacjach skalarnych działania wykonywane są na pojedynczych wartościach. Rejestry SSE i AVX przedstawiono na rysunku dalej.



Wykaz literatury (bibliografia)

[1] Dawid Farbaniec, 2021 – Asembler x64. Laboratorium, ISBN: 9788382457032
[2] Advanced Micro Devices Inc., 2017 – AMD64 Architecture Programmer's Manual
[3] Intel Corporation, 2019 – Intel 64 and IA-32 Architectures Software Developer's Manual

Komentarze czytelników

Kontakt

mgr inż. Dawid Farbaniec  //  haker.iиfø

E-mail:
office@haker.info

Odwiedź mnie również na:

 

Wyrazy uznania: 📜 Credits page

Wszystkie treści umieszczone na tej witrynie są chronione prawem autorskim. Surowo zabronione jest kopiowanie i rozpowszechnianie zawartości tej witryny bez zgody autora. Wszelkie opublikowane tutaj treści (w tym kody źródłowe i inne) służą wyłącznie celom informacyjnym oraz edukacyjnym. Właściciele tej witryny nie ponoszą odpowiedzialności za ewentualne niezgodne z prawem wykorzystanie zasobów dostępnych w witrynie. Użytkownik tej witryny oświadcza, że z zamieszczonych tutaj danych korzysta na własną odpowiedzialność. Wszelkie znaki towarowe i nazwy zastrzeżone zostały użyte jedynie w celach informacyjnych i należą wyłącznie do ich prawnych właścicieli. Autor tej witryny w czasie tworzenia treści nie działa w imieniu firm, których technologie czy produkty opisuje — chyba, że jest to jasno oznaczone. Korzystając z zasobów witryny haker.info oświadczasz, że akceptujesz powyższe warunki oraz politykę prywatności.