Poprzedni :: Spis treści :: Następny
4. Modyfikacja przebiegu programu
4.1 Pierwsza wersja programu
4.2 Zmiana przebiegu
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; |
|
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; |
|
0x080483b8 <main+28>: movl $0x1,0xfffffffc(%ebp) 0x080483bf <main+35>: movl $0x2,0xfffffff8(%ebp) |
|
Przypisanie odpowiednich wartości na zmienne. |
|
funkcja(a, b, 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; |
|
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. |
|
} |
|
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; |
|
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); |
|
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 |
Przyjrzyjmy się dokładniej temu co zawiera stos po wejściu do funkcji
funkcja:
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; |
|
funkcja(1,2,3); |
|
void funkcja (int a, int b, int c) { int *wsk; |
|
wsk = &a - 1; |
|
*wsk += 7; |
|
printf("%d\n", a); } |
Poprzedni :: Spis treści :: Następny