Praktycznie całe zamieszanie dotyczące konwencji wywoływania funkcji kręci się w okół wskaźnika stosu.

Podobne dokumenty
Assembler w C++ Syntaksa AT&T oraz Intela

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

Programowanie Niskopoziomowe

PROGRAMOWANIE NISKOPOZIOMOWE. Struktury w C. Przykład struktury PN.06. c Dr inż. Ignacy Pardyka. Rok akad. 2011/2012

Ćwiczenie nr 6. Programowanie mieszane

Programowanie Niskopoziomowe

Zadanie Zaobserwuj zachowanie procesora i stosu podczas wykonywania następujących programów

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

Programowanie komputerowe. Zajęcia 1

4 Literatura. c Dr inż. Ignacy Pardyka (Inf.UJK) ASK MP.01 Rok akad. 2011/ / 24

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

PROGRAMOWANIE NISKOPOZIOMOWE

Programowanie Obiektowo Zorientowane w języku c++ Przestrzenie nazw

PROGRAMOWANIE NISKOPOZIOMOWE. Adresowanie pośrednie rejestrowe. Stos PN.04. c Dr inż. Ignacy Pardyka. Rok akad. 2011/2012

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

1 Wskaźniki. 1.1 Główne zastosowania wskaźników

Jak napisać listę jednokierunkową?

Wstęp do programowania

Ok. Rozbijmy to na czynniki pierwsze, pomijając fragmenty, które już znamy:

Wstęp do informatyki- wykład 12 Funkcje (przekazywanie parametrów przez wartość i zmienną)

Programowanie - wykład 4

Ćwiczenie 3. Konwersja liczb binarnych

Architektura komputerów. Asembler procesorów rodziny x86

Część 4 życie programu

1 Podstawy c++ w pigułce.

Przepełnienie bufora i łańcuchy formatujace

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Ćwiczenie nr 3. Wyświetlanie i wczytywanie danych

Architektura systemów komputerowych Laboratorium 14 Symulator SMS32 Implementacja algorytmów

Zmienne, stałe i operatory

Wstęp do Programowania, laboratorium 02

Podstawy programowania skrót z wykładów:

Programowanie w C++ Wykład 4. Katarzyna Grzelak. 19 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 37

Podstawy języka skryptowego Lua

Podstawy programowania w C++

Programowanie hybrydowe łączenie C/C++ z asemblerem

Tablice (jedno i wielowymiarowe), łańcuchy znaków

4. Funkcje. Przykłady

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 6. Karol Tarnowski A-1 p.

1 Podstawy c++ w pigułce.

W2 Wprowadzenie do klas C++ Klasa najważniejsze pojęcie C++. To jest mechanizm do tworzenia obiektów. Deklaracje klasy :

Wstęp do informatyki- wykład 11 Funkcje

Instrukcja do ćwiczenia P4 Analiza semantyczna i generowanie kodu Język: Ada

I - Microsoft Visual Studio C++

Czym jest całka? Całkowanie numeryczne

Operacje wejścia/wyjścia odsłona pierwsza

Codecave jest to nieużywana pamięć uruchomionej aplikacji, do której można wstrzyknąć dowolny kod a następnie wykonać go.

Metody Realizacji Języków Programowania

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Adam Kotynia, Łukasz Kowalczyk

Tworzenie projektu asemblerowego dla środowiska Visual Studio 2008.

1. Wypisywanie danych

Architektura komputerów

Wstęp do informatyki- wykład 9 Funkcje

6. Pętle while. Przykłady

Języki i metodyka programowania. Wprowadzenie do języka C

8. Wektory. Przykłady Napisz program, który pobierze od użytkownika 10 liczb, a następnie wypisze je w kolejności odwrotnej niż podana.

Co to jest sterta? Sterta (ang. heap) to obszar pamięci udostępniany przez system operacyjny wszystkim działającym programom (procesom).

MOŻLIWOŚCI PROGRAMOWE MIKROPROCESORÓW

Operatory. Operatory bitowe i uzupełnienie informacji o pozostałych operatorach. Programowanie Proceduralne 1

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 2. Karol Tarnowski A-1 p.

Czym są właściwości. Poprawne projektowanie klas

Wskaźniki w C. Anna Gogolińska

Programowanie strukturalne i obiektowe. Funkcje

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Programowanie niskopoziomowe

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

Rekurencja (rekursja)

C++ - [1-3] Debugowanie w Qt Creator

PROGRAMOWANIE NISKOPOZIOMOWE. Systemy liczbowe. Pamięć PN.01. c Dr inż. Ignacy Pardyka. Rok akad. 2011/2012

Zadanie 2: Arytmetyka symboli

Język C++ zajęcia nr 2

Czym jest DLL Injection

STL: Lekcja 1&2. Filozofia STL

Programowanie w C++ Wykład 3. Katarzyna Grzelak. 12 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 35

#include <iostream> using namespace std; void ela(int); int main( ); { Funkcja 3. return 0; }

Wstęp do programowania. Wykład 1

Programowanie komputerowe. Zajęcia 4

Podstawy informatyki. Elektrotechnika I rok. Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia

Warto też w tym miejscu powiedzieć, że w C zero jest rozpoznawane jako fałsz, a wszystkie pozostałe wartości jako prawda.

Rekurencja. Przygotowała: Agnieszka Reiter

Programowanie na poziomie sprzętu. Programowanie w Windows API

3. Instrukcje warunkowe

1 P roste e t ypy p d a d n a ych c - c ąg ą g d a d l a szy 2 T y T py p z ł z o ł żo ż ne e d a d n a ych c : T BLICE

C++ wprowadzanie zmiennych

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Wykład I. Programowanie II - semestr II Kierunek Informatyka. dr inż. Janusz Słupik. Wydział Matematyki Stosowanej Politechniki Śląskiej

Wprowadzenie w dziedziczenie. Klasa D dziedziczy klasę B: Klasa B klasa bazowa (base class), klasa D klasa pochodna (derived class).

Podstawy programowania 2. Przygotował: mgr inż. Tomasz Michno

Aplikacja Sieciowa wątki po stronie klienta

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 16 kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 27

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

void Pobierz(Student &a); void Wypisz(Student a); void Ustaw_zaliczenia(Student t[],int r); void Wypisz_najlepszych(Student t[],int r, float prog);

Konwersje napis <-> liczba Struktury, unie Scanf / printf Wskaźniki

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 26 marca kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 40

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Listy powiązane zorientowane obiektowo

Podstawy Programowania Podstawowa składnia języka C++

Programowanie proceduralne w języku C++ Podstawy

Jak wiemy, wszystkich danych nie zmieścimy w pamięci. A nawet jeśli zmieścimy, to pozostaną tam tylko do najbliższego wyłączenia zasilania.

Transkrypt:

Krótki artykuł opisujący trzy podstawowe konwencje wywoływania funkcji C++ (a jest ich więcej). Konwencje wywoływania funkcji nie są tematem, na który można się szeroko rozpisać, jednak należy znać i odróżniać ich podstawowe rodzaje, szczególnie bawiąc się w reverse engineering. Informacje wstępne Jak to zwykle bywa, do napisania artykułu skłoniło mnie zapotrzebowanie czytelników. Na wielu forach internetowych można przeczytać o problemach osób, które nie radzą sobie z edycją jakiś prostych funkcji (chodzi o edycję na poziomie debbugera). Zahaczając o temat inżynierii wstecznej konwencje wywoływania funkcji należy znać. O ile operowanie nimi pisząc program w C++ nie niesie większych zmian widocznych gołym okiem, o tyle ciało funkcji zmienia się podczas procesu debugowania. Wskaźnik stosu Praktycznie całe zamieszanie dotyczące konwencji wywoływania funkcji kręci się w okół wskaźnika stosu. Stos jest liniową strukturą danych używaną przez procesor, do przechowywania zmiennych lokalnych, zapamiętywania stanów rejestrów oraz do przekazywania argumentów do funkcji. Dane dodane na stos jako ostatnie, muszą być ściągnięte jako pierwsze. Rejestr ESP wskazuje na wierzchołek stosu. Aby lepiej zobrazować sytuację, wyobraź sobie rejestr ESP jako wskaźnik z jakąś wartością arytmetyczną. Jeżeli ESP wynosi, podczas dodania słowa na stos rozkazem POP wskaźnik stosu powiększy się z na 0. Arytmetyczna wartość jest mniejsza, ale rejestr ESP rośnie w stronę zera, aż na końcu nastąpi przepełnienie stosu. Wywołując dowolny CALL wrzucamy argumenty na stos rozkazami PUSH. Argumenty zawsze wrzucamy w odwrotnej kolejności, wynika to z zasady odczytywania ze stosu. W chwili kiedy program zaczyna wykonywać instrukcję CALL, wrzuca ona na stos adres powrotu, z którego później skorzysta funkcja RET. Biorąc ten fakt pod uwagę, niezbędne jest zachowanie porządku w rejestrze ESP wskazującym wierzchołek stosu. Po wrzuceniu kilku argumentów dla rozkazu CALL, niezbędne jest ściągnięcie ich ze stosu rozkazami POP lub arytmetyczne przesunięcie wskaźnika stosu ESP. W przeciwnym razie po wywołaniu CALL (skoku) do dowolnego miejsca, program nie znajdzie instrukcji powrotu. Karol Trybulec p-programowanie.pl

PUSH 0 ;argument na stos PUSH 0 ;argument na stos CALL funkcja ;wywołanie funkcji add esp, ;przesuwamy stos *= PUSH ;argument na stos PUSH ;argument na stos PUSH eax ;argument na stos CALL funkcja ;wywołanie funkcji add esp, ;przesuwamy stos *= Ponieważ pojedyncze słowo word jest bitowe, zwiększamy wskaźnik stosu (właściwie zmniejszamy, ponieważ rośnie on w stronę zera) o bajty. Dla dwóch argumentów, trzeba zmniejszyć wskaźnik stosu już o bajtów itd. Konwencje wywoływania funkcji W różnych konwencjach wywoływania funkcji stos jest czyszczony z argumentów w różny sposób. stdcall stos za każdym razem czyści funkcja wywoływana cdecl stos za każdym razem czyści obiekt, na rzecz którego wywołujemy funkcje fastcall nie korzystamy ze stosu Konwencja stdcall Jak podaje MSDN, konwencja stdcall jest standardową, jeżeli chodzi o wywoływania funkcji Win API. Oznacza to, że nawet jeżeli nie wybierzemy jawnie żadnej konwencji, funkcja automatycznie zostanie wywołana z konwencją stdcall. Argumenty przekazywane do funkcji są standardowo umieszczane na stosie. #include <iostream> using namespace std; void stdcall potega (int x) cout << "Wynik: " << x*x << endl; int main() potega(); return ; Karol Trybulec p-programowanie.pl

Cechą charakterystyczną jest to, że funkcja sama sprząta stos po jej wywołaniu. Stos czyszczony jest podczas zakończenia funkcji przez rozkaz retn x, gdzie x oznacza ilość parametrów na stosie. Oto ten sam program przepisany do assemblera (FASM): 0 format PE Console.0 include "wina.inc" ;zauważ, że po wywołaniu () funkcji nie przesuwamy wskaźnika stosu push ;wrzucamy argument na stos call potega ;wywołujemy funkcje mov eax, ret ;koniec programu potega: mov eax, dword [esp+] ;argument do EAX mul eax ;mnożymy x*x push eax ;wynik na stos push napis ;napis na stos call [printf] ;wyświetlamy napis add esp, ;przesuwamy wskaźnik stosu retn ;czyscimy stos przesuwając wskaźnik napis db "Wynik: %i", 0Ah, ;wyświetlany napis data import ;importy wymaganych funkcji library msvcrt, 'msvcrt.dll' import msvcrt, printf, 'printf', scanf, 'scanf' end data Widać, że wskaźnik stosu został przesunięty podczas kończenia funkcji. Zaletą stdcall jest szybkość działania i mniejsza ilość zajętego miejsca. Jeżeli funkcję wywołujemy dziesiątki razy w różnych miejscach programu, stos i tak czyszczony jest tylko raz wewnątrz funkcji. Podczas debugowania kodu, możemy zauważyć, że funkcje w konwencji stdcall zaczynają się od znaku podkreślenia, na końcu dodawana jest małpa oraz ilość przekazywanych do funkcji argumentów: _potega@ Funkcje stdcall rozpoznajemy właśnie po tym, że zawsze na ich końcu znajduje się retn x lub ret x. Karol Trybulec p-programowanie.pl

Konwencja cdecl Konwencja cdecl jest drugą najczęściej spotykaną konwencją wywoływania funkcji. Była bardzo często wykorzystywana w języku C. Charakteryzuje się tym, że stos musi zostać wyczyszczony przez program w miejscu wywołania funkcji, a nie przez samą funkcję. Argumenty do funkcji przekazywane są na stosie: #include <iostream> using namespace std; void cdecl potega (int x) cout << "Wynik: " << x*x << endl; int main() potega(); return ; Ponieważ stos nie jest czyszczony wewnątrz funkcji podczas jej zakończenia (tak jak w przypadku stdcall), może się okazać, że kod programu znacznie wzrośnie. Stanie się tak przede wszystkim wtedy, jeżeli będziemy posiadać wiele wywołań funkcji. Szczególnym zastosowaniem konwencji cdecl jest sytuacja, kiedy wywołujemy funkcję ze zmienną ilością parametrów. Przykładem takiej funkcji jest np. printf(). Ponieważ, możemy umieścić w niej dowolną ilość argumentów, funkcja nie może zajmować się czyszczeniem stosu nigdy nie wie ile parametrów zostanie do niej przekazane. Czyszczeniem stosu, czyli przewijaniem wskaźnika stosu, zajmuje się obiekt, na rzecz którego została wywołana funkcja cdecl: Karol Trybulec p-programowanie.pl

0 format PE Console.0 include "wina.inc" ;zauważ, że po wywołaniu () funkcji musimy sami wyczyścić stos push ;wrzucamy argument na stos call potega ;wywołujemy funkcje add esp, ;sami czyścimy stos mov eax, ret ;koniec programu potega: mov eax, dword [esp+] ;argument do EAX mul eax ;mnożymy x*x push eax ;wynik na stos push napis ;napis na stos call [printf] ;wyświetlamy napis add esp, ;przesuwamy wskaźnik stosu ret ;zakonczenie funkcji i skok napis db "Wynik: %i", 0Ah, ;wyświetlany napis data import ;importy wymaganych funkcji library msvcrt, 'msvcrt.dll' import msvcrt, printf, 'printf', scanf, 'scanf' end data Podczas debugowania zauważysz, że funkcje cdecl zaczynają się od znaku podkreślenia: _potega Funkcje cdecl rozpoznajemy po tym, że na ich końcu ret nie posiada żadnego argumentu, jednak nie można pomylić jej z fastcall. Aby tego nie zrobić, trzeba się upewnić, że argumenty nie są pobierane z rejestrów. Konwencja fastcall Generalną zasadą konwencji fastcall, jest przekazywanie argumentów poprzez rejestry, a nie poprzez stos tak jak w wypadku innych funkcji. To jakie rejestry będą używane, zależy od kompilatora wszystkie używają innych standardów. Zarówno GCC jak i MVC działają w tym wypadku na tej samej zasadzie. Dwa pierwsze parametry przesyłane są kolejno w rejestrach ECX oraz EDX, a wszystkie następne już ze stosu (jeśli zajdzie potrzeba wykorzystania większej ilości parametrów). Karol Trybulec p-programowanie.pl

#include <iostream> using namespace std; void fastcall potega (int x) cout << "Wynik: " << x*x << endl; int main() potega(); return ; Używając fastcall program może znacznie przyśpieszyć, w przypadku wywoływania wielu funkcji z małą ilością parametrów. Program nie operuje wtedy na pamięci, a jedynie na rejestrach, które są szybsze. 0 format PE Console.0 include "wina.inc" ;nie uzywamy stosu do przekazywania parametru mov eax, ;wrzucamy argument do EAX call potega ;wywołujemy funkcje mov eax, ret ;koniec programu potega: mul eax ;mnożymy x*x push eax ;wynik na stos push napis ;napis na stos call [printf] ;wyświetlamy napis add esp, ;przesuwamy wskaźnik stosu ret ;zakonczenie funkcji i skok napis db "Wynik: %i", 0Ah, ;wyświetlany napis data import ;importy wymaganych funkcji library msvcrt, 'msvcrt.dll' import msvcrt, printf, 'printf', scanf, 'scanf' end data Używając fastcall stosu nie czyści ani obiekt wywołujący ani sama funkcja wywoływana. Podczas debugowania zauważysz, że funkcje fastcall są poprzedzone znakiem małpy, a na Karol Trybulec p-programowanie.pl

ich końcu znajduje się ponownie znak małpy oraz liczba argumentów: @potega@ Podsumowanie Istnieje kilka innych konwencji wywoływania funkcji w języku C++, lecz nie są one tak często używane jak trzy opisane wyżej. Mam nadzieję, że po przeczytaniu tego prostego artykułu z większą łatwością będziesz w stanie modyfikować różne funkcje zewnętrznych aplikacji, czy to podczas debugowania procesu czy zabaw z dll injection. Nie utrzymanie porządku we wskaźniku stosu ESP zawsze skończy się niespodziewanym crashem aplikacji. Karol Trybulec p-programowanie.pl