[C/Assembler] CALL i adres powrotu

0

Witam.

Próbuję zrobić hooka na funkcje w assemblerze. Robie to tak, że w funkcji, którą chcę przechwycić wycinam ze dwie instrukcje ( kilka bajtów ) i daje tam CALL adresMojejFunkcji. Moja funkcja jest wypełniona nopami, więc moge spokojnie tam skopiować kod, który wyciąłem. W ten sposób nie ma żadnych strat kodu i w mojej funkcji moge dać kod, który wykona sie po wywołaniu przechwyconej funkcji.

Wygląda to mniej więcej tak

// Początek funkcji, którą chce przechwycić

PUSH EBP
MOV EBP, ESP

// tutaj jakis przykładowe kod na początku funkcji
MOV test, 123
MOV zmienna, 123456

wycinam:

MOV test, 123
MOV zmienna, 123456

i zastepuje tam na call, wygląda to teraz mniej więcej tak:

PUSH EBP
MOV EBP, ESP

CALL adresMojejFunkcji

a tu zawartosc mojej funkcji po wklejeniu tam mojego kodu.

PUSH EBP
MOV EBP, ESP

MOV test, 123
MOV zmienna, 123456

nop
nop
nop
nop
nop

RETN

Teraz wystarczyło by jakoś powrócić do funkcji.

I mam pytanie:

Co oznacza PUSH EBP i MOV EBP, ESP na początku każdej funkcji (w OllyDbg) ?
Jest to wrzucenie na stos adresu powrotu?

Jeżeli tak, to jest problem.

Gdyby ten kod, który musze wkleić

MOV test, 123
MOV zmienna, 123456

byłby inny np.

PUSH test
PUSH 93

to moja funkcja wyglądała by tak:

PUSH EBP
MOV EBP, ESP

PUSH test
PUSH 93

nop
nop
nop
nop
nop

RETN

I jeżeli adres powrotu był na stosie, to 93 i zawartość test będzie wyżej na stosie, więc chyba nici z adresu powrotu?

Jak mogę zrobić poprawnie powrót do przechwytującej funkcji?

Z góry dziękuje za pomoc

0

Ale o co chodzi ?
Jak wklejasz call funkcja która jest ( jeśli) prawidłowo napisana to kod
ma powrócić do instrukcji następnej po call i opuścić twoją ->adresMojejFunkcji ,
nie ma tu żadnego wklejania kodu zawartego w adresMojejFunkcji .
Jak wklejasz na żywca kod z push to też nie ma znaczenia bo stos
jest zachowany tyle że nigdzie nie wracasz tylko następuje dalsze wykonanie kodu .
(oczywiście trzeba wyrównać stos instrukcjami pop)

0

To? Ustawienie ramki stosu, która pozwala na łatwiejsze odwoływanie się do zmiennych zrobionych na stosie. Przy wychodzeniu należy ją zwinąć -

mov esp, ebp
pop ebp

albo

leave

A adres powrotu sam się odkłada przy CALLu.

0

Co do rejestru ebp, to trzeba porządnie wyjaśnić o co chodzi.
Dostęp do parametrów zazwyczaj jest możliwy poprzez rejestr esp: [esp+4] to pierwszy parametr, [esp+8] to drugi itd. Wszystko dobrze póki ESP nie zmieni się, a zmieni się na przykład w przypadku odwołania do innej funkcji z parametrami:

push dword [esp+4] ; arg0
; esp zmienił się o -4
push dword [esp+8] ; arg1
; esp ponownie zmienił się o -4
call funkcja

Co tu jest źle? Ano pierwszy push zmienił rejestr esp, więc drugi push wcale nie zaadresuje drugiego parametru tylko pierwszy, dlatego trzeba uwzględnić zmianę stosu w każdym następnym push:

; uu,ii,oo,pp to offsety parametrów ze stosu: +4,+8,+12...
push dword [esp+uu] ;
push dword [esp+ii+4] ; dodatkowe +4
push dword [esp+oo+8] ; dodatkowe +8
push dword [esp+pp+12] ; dodatkowe +12
call funkcja

Równie dobrze będzie z innym rejestrem, ale tu już nie trzeba uwzględniać zmiany esp

mov eax,esp
push dword [eax+4] ; arg0
push dword [eax+8] ; arg1
push dword [eax+12] ; arg2
call funkcja

W powyższym przykładzie esp się zmienia po każdym push, ale eax który pamięta jego oryginalną wartość jest stały, więc nie trzeba nic korygować.

Realny przykład - wrapper dla makra StrIntlEqNIW z shlwapi.h

;#define StrIntlEqNIW(s1, s2, nChar) StrIsIntlEqualW(FALSE, s1, s2, nChar)

StrIntlEqNIW:
    invoke StrIsIntlEqualW, 0, [esp+4+8], [esp+8+4], [esp+12]
    ret    12 ; stdcall

Dlatego ktoś wpadł na pomysł by nie używać rejestru esp do adresowania parametrów funkcji, tylko innego rejestru, padło na EBP ponieważ kiedyś pewnie wydawał się zbędny:

push ebp
mov ebp,esp

push dword[ebp+8] ; arg0
push dword[ebp+12] ; arg1
push dword[ebp+16] ; arg2
call funkcja
...
pop ebp
ret

Dodatkową zaletą jest możliwość zaalokowania pamięci na tymczasowe dane:

push ebp
mov ebp,esp
sub esp, 32 ; liczba podzielna przez 4
...
mov esp,ebp
pop ebp
ret

Pierwsze 3 linie można zastąpić rozkazem enter 32,0 a dwie ostatnie (ale nie ret) leave

enter 32,0
...
leave
ret

Tak zaalokowaną pamięc adresujesz podobnie za pomocą rejestru ebp, tyle że offset jest ujemny. [ebp-32] to początek tej pamięci, a [ebp-1] to koniec, ostatni dostępny bajt.

A na koniec - po co jest push/pop ebp. Zależnie od systemu operacyjnego na którym będzie pracowała Twoja funkcja, musisz zwrócić niektóre rejestry i flagi w postaci niezmienionej. Gdy używasz rejestru ebp do adresowania parametrów, to chyba nie chcesz by jakaś funkcja (którą wywołujesz) go zmieniła? Owszem może go zmienić, ale zanim powróci powinna wpisać do niego taką wartość jaka była na początku. No, chyba że wiesz, że funkcja A nie może tracić czasu na zachowywanie rejestrów, wtedy funkcja ją wywołująca powinna to robić.

0

Wybrano ebp nie dlatego że wydawał się zbędny, a dlatego, że jeszcze za czasów trybu rzeczywistego domyślnym segmentem dla niego był SS (i ciągle jest).
Enter muli. Ręczna zabawa z push/mov/sub jest szybsza...

0

Wypisac w pamieci liczby podzielne przez 4,
podzielne przez 8 wypisac 2 razy
4, 8,8, 12,16,16, 20 ... ( do 100 ), podzieln

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0