Poprzedni :: Spis treści :: Następny


4. Modyfikacja przebiegu programu
4.1 Pierwsza wersja programu
4.2 Zmiana przebiegu


4. Modyfikacja przebiegu programu

4.1 Pierwsza wersja programu

Teraz, gdy już wiadomo jak wygląda pamięć procesu, zrobimy z tej wiedzy użytek. Rozważmy następujący przykład:

przyklad2.c

void funkcja (int a, int b, int c)
{
	int *wsk;
}

int main ()
{
	int a, b;
	a = 1;
	b = 2;
	funkcja(a,b,3);
	a = 5;
	printf("%d\n", a);
}

Cel do jakiego będziemy dążyć to taka modyfikacja kodu funkcji funkcja aby pominięta została instrukcja a = 5 z funkcji main. Aby do tego dojść, prześledźmy co się dzieje w pamięci procesu w trakcie wykonania programu:

int main ()
{
 int a, b;
 
przykład 2 - stos 1
0x0804839c <main+0>:    push   %ebp
0x0804839d <main+1>:    mov    %esp,%ebp
0x0804839f <main+3>:    sub    $0x18,%esp
0x080483a2 <main+6>:    and    $0xfffffff0,%esp
0x080483a5 <main+9>:    mov    $0x0,%eax
0x080483aa <main+14>:   add    $0xf,%eax
0x080483ad <main+17>:   add    $0xf,%eax
0x080483b0 <main+20>:   shr    $0x4,%eax
0x080483b3 <main+23>:   shl    $0x4,%eax
0x080483b6 <main+26>:   sub    %eax,%esp
Wstępne czynności związane z wywołaniem funkcji main (umieszczenie odpowiednich elementów na stosie, oraz rezerwacja pamięci).

	a = 1;
	b = 2;
przykład 2 - stos 2
0x080483b8 <main+28>:   movl   $0x1,0xfffffffc(%ebp)
0x080483bf <main+35>:   movl   $0x2,0xfffffff8(%ebp)
Przypisanie odpowiednich wartości na zmienne.

	funkcja(a, b, 3)
przykład 2 - stos 3
0x080483c6 <main+42>:   movl   $0x3,0x8(%esp)
0x080483ce <main+50>:   mov    0xfffffff8(%ebp),%eax
0x080483d1 <main+53>:   mov    %eax,0x4(%esp)
0x080483d5 <main+57>:   mov    0xfffffffc(%ebp),%eax
0x080483d8 <main+60>:   mov    %eax,(%esp)
0x080483db <main+63>:   call   0x8048394 <funkcja>
Wywołanie funkcji.

void funkcja (int a, int b, int c)
{
	int *wsk;
przykład 2 - stos 4
0x08048394 <funkcja+0>: push   %ebp
0x08048395 <funkcja+1>: mov    %esp,%ebp
0x08048397 <funkcja+3>: sub    $0x4,%esp
Odłożenie na stos EBP z funkcji main i przesunięcie stosu o jedno długie słowo w dół, tak aby zmieściła się zmienna wsk.

}
przykład 2 - stos 5
0x0804839a <funkcja+6>: leave  
0x0804839b <funkcja+7>: ret    
Wyjście z funkcji. Zabranie ze stosu wszystkich elementów dodanych tam podczas wykonywania funkcji oraz zapisanie w EIP adresu powrotu (skok do main).

	a = 5;
przykład 2 - stos 6
0x080483e0 <main+68>:   movl   $0x5,0xfffffffc(%ebp)
Przypisanie wartości 5 na zmienną a (warto zwrócić uwagę jak wygląda odwołanie do zmiennej w assemblerze).

	printf("%d\n", a);
przykład 2 - stos 7
0x080483e7 <main+75>:   mov    0xfffffffc(%ebp),%eax
0x080483ea <main+78>:   mov    %eax,0x4(%esp)
0x080483ee <main+82>:   movl   $0x80484f8,(%esp)
0x080483f5 <main+89>:   call   0x80482d0 <printf@plt>
Wywołanie funkcji printf (tutaj warto zauważyć że łańcuchy są przechowywane w innym miejscem i odwołanie do nich odbywa się poprzez etykietę tłumaczoną później na odpowiedni adres).

}
 
0x080483fa <main+94>:   leave  
0x080483fb <main+95>:   ret    

4.2 Zmiana przebiegu

Przyjrzyjmy się dokładniej temu co zawiera stos po wejściu do funkcji funkcja:

stos po wejściu do funkcji

Aby zmienić przebieg wykonania programu, trzeba dobrać się do adresu powrotu z funkcji funkcja. Będąc wewnątrz funkcji mamy dostęp do następujących pozycji na stosie:

W związku z tym, aby zmodyfikować adres powrotu wystarczy zmodyfikować wartość znajdującą się pod odpowiednim adresem, np:

&a - 1

Zamiast 4 dodawane jest 1 ponieważ &a jest typu int * a więc standardowo każda dodawana liczba ma 4 bajty.

Skoro wiemy już co modyfikować, trzeba jeszcze dowiedzieć się jaki jest adres instrukcji następującej po a = 5. Spójrzmy na odpowiedni fragment kodu assemblerowego prezentowanego przez gdb dla funkcji main:

(gdb) disassemble main
(...)
0x080483db ;<main+63>:   call   0x8048394 <funkcja>
0x080483e0 ;<main+68>:   movl   $0x5,0xfffffffc(%ebp)
0x080483e7 ;<main+75>:   mov    0xfffffffc(%ebp),%eax
(...)
(gdb)

W powyższym przykładzie adres powrotu standardowo wynosi 0x080483e0, a chcemy go zmienić na 0x080483e7. A więc do adresu powrotu należy dodać 7.

Oto kod po wprowadzeniu modyfikacji:

przyklad3.c

void funkcja (int a, int b, int c)
{
	int *wsk;
	wsk = &a - 1;
	*wsk += 7;	
}

int main ()
{
	int a, b;
	a = 1;
	b = 2;
	funkcja(a,b,3);
	a = 5;
	printf("%d\n", a);
}

Poniżej znajduje się omówienie tego co się dzieje na stosie w tak zmodyfikowanym programie

int main ()
{
	int a;
	a = 1;
przykład 3 - stos 1
	funkcja(1,2,3);
przykład 3 - stos 2
void funkcja (int a, int b, int c)
{
	int *wsk;
	
przykład 3 - stos 3
	wsk = &a - 1;
przykład 3 - stos 4
	*wsk += 7;
przykład 3 - stos 5
	printf("%d\n", a);
}
przykład 3 - stos 6

Poprzedni :: Spis treści :: Następny


Valid XHTML 1.0 Strict