sobota, 12 grudnia 2009

Analizator stosu

Dawno nic nie napisałem, ale przez ten miesiąc zwyczajnie nie było kiedy. Poszukiwanie pracy, użeranie się z urzędasami w PUPie, dalsze poszukiwanie pracy. Ostatecznie znalazłem "pracę". Pomagam przy projekcie prowadzonym przez Politechnikę. Napisałem "pracę", bo tak naprawdę to robię to póki co charytatywnie i jeśli doktor będzie zadowolony to będę miał pracę. Zadania dostaje zróżnicowane. Na początek dostałem płytkę drukowaną z dwoma mikrokontrolerami i polecenie opracowania procedury testowej dla elektronika, który je robi. Teraz piszę przenośny (windows linux) moduł do rysowania wykresów funkcji.

Analizator stosu doczekał się czegoś co można określić betą. Czyli u mnie działa i robi to co ma robić, ale nie został jeszcze należycie przetestowany i nie doczekał się wdrożenia. Wrzuciłbym cały kod, ale jest go nieco za dużo, więc tylko najciekawsze fragmenty.

Radzi sobie zarówno ze zwykłymi funkcjami jak i wywołaniami wirtualnymi.
Wymogiem jest aby ramka stosu była skonstruowana zgodnie z tym co opisałem wcześniej.
Wynikiem działania są kolejne adresy powrotne.

Wydobycie aktualnego BP i adresu powrotnego:
void GetCurrentFrame(void * basepointer, void * retaddr)
{

__asm
{
mov eax, ebp
mov eax, [eax]
mov basepointer, eax
mov eax, ebp
add eax, 0x4
mov eax, [eax]
mov retaddr, eax
}
}

Wydobycie kolejnej ramki:
bool GetNextFrame(void * basepointer, void * retaddr)
if (0 == *(unsigned int *)basepointer)
{

return false;
}

else
{
__asm
{
mov eax, basepointer
add eax, 0x4
mov eax, [eax]
mov retaddr, eax
mov eax, basepointer
mov eax, [eax]
mov basepointer, eax
}

return true;
}
}

Wydobycie adresu bazowego (o tym już pisałem wcześniej):
void GetBaseAddress(unsigned int & baseAddr)
{

HMODULE proces = GetModuleHandleA(0);
baseAddr = (unsigned int)proces;
}

Na początku należy wywołać GetCurrentFrame, potem w pętli GetNextFrame. Adres bazowy można wyciągnąć w dowolnym momencie.
Osobiście ubrałem to sobie w klasy, a ten kod (jak wspomniałem) jest tak na szybko uproszczony, żeby się go dało zaprezentować na tym blogu.

Na koniec chciałbym życzyć wszystkim aby rok 2010 okazał się owocniejszy od wszystkich minionych.

PS.
Właśnie odkryłem, że blogger zapisuje sobie datę utworzenia posta, a nie publikacji. Data publikacji 01.01.2010.

wtorek, 1 grudnia 2009

Analiza stosu i funkcje wirtualne

Dziś garść informacji o tym jak działają funkcje wirtualne.

Przede wszystkim dla tych co nie wiedzą czym jest funkcja wirtualna źródła: [1],[2] i [3].

O tym jak działa wywołanie funkcji i jak istotną rolę w tym procederze pełni stos napisałem tutaj.

Przykładowy kawałek kodu w C++:
class A {
public
:
virtual
void fun();
__declspec(noinline) void fun2();
};

class
B : public A {
public
:
virtual
void fun();
};

int
main(int argc, char * argv[])
{

A * a = new A();
A * b = new B();
a->fun();
b->fun();
a->fun2();
b->fun2();
}


Wywołania klasyczne:
; 36   :  a->fun2();
mov eax, edi
call ?fun2@A@@QAEXXZ ; A::fun2
; 37 : b->fun2();
mov eax, esi
call ?fun2@A@@QAEXXZ ; A::fun2

Wywołania wirtualne:
Po kompilacji:
; 26 : a->fun();
mov eax, DWORD PTR [edi]
mov edx, DWORD PTR [eax+4]
mov ecx, edi
call edx
; 27 : b->fun();
mov eax, DWORD PTR [esi]
mov edx, DWORD PTR [eax+4]
mov ecx, esi
call edx


Przy rozwiązaniu statycznym:
1. do rejestru eax ładowany jest adres obiektu, czyli wskaźnik this,
2. wykonywana jest instrukcja call z adresem funkcji fun2.

W przypadku wirtualnym:
1. do rejestru eax ładowane są pierwsze 4 bajty obiektu,
2. do rejestru edx ładowana jest pamięć spod adresu eax + 4,
3. do rejestru ecx ładowany jest adres obiektu, czyli wskaźnik this,
4. wykonywana jest instrukcja call z zawartością rejestru edx.

Na co tu zwrócić uwagę?
1. przy wywołaniu statycznym adres funkcji jest na stałe zapisany w kodzie,
2. przy wywołaniu wirtualnym adres funkcji jest ładowany z pamięci,
3. adres funkcji wirtualnej znajduje się w tablicy metod wirtualnych
4. adres tej tabeli jest zapisany jako pierwsze pole w obiekcie.

Tak to wygląda na stosie:
EBP
-0xXX
-0x4
0x0
+0x4
+0x8
Co
lokalne
lokalne
oldBP
retAddr
Parametry funkcji
Rejestr
<-ESP

<-EBP




Dokładnie tak samo jak w przypadku statycznym.

Pod adresem powrotnym znajduje się opcode ff To jest dwubajtowa instrukcja. Jeden na ff i jeden na parametr. Dokładny opis wszystkich instrukcji i parametrów można znaleźć w [4] i [5].

Jak te informacje zamienić na adres funkcji ?
Jeszcze nie wiem, ale mam dwa pomysły:

1. Wszystko wskazuje na to, że poszukiwana wartość zapisana jest jedynie w tablicy metod wirtualnych. Można pokusić się o analizę kodu wywołania pod kątem indeksu metody w tablicy. Mając ten indeks wystarczy odczytać [[this]+indeks].
2. Można też olać dokładny adres funkcji i zapisać sobie jedynie zawartość retAddr. Jeżeli mamy adres 0x01234568 oraz wiemy, że funkcja a - 0x01234000 i b - 0x01235000, to jasne jest, ze była to funkcja a.

Pierwsze rozwiązanie jest fajne, bo daje nam na talerzu to czego chcemy.
Drugie rozwiązanie jest fajne, bo:
a. działa nie zależnie od tego czy wywołanie jest statyczne czy dynamiczne,
b. powinno działać nie zależnie od użytego kompilatora, czy wybranych ustawień budowania

Wniosek ?
Rozwiązanie drugie jest wyborem lepszym. Jest prostsze w implementacji i bardziej uniwersalne. Będzie wymagało jednak stworzenia dodatkowego narzędzia, które zamieni zapisane dane na adresy funkcji.

Źródła:
1) Wikibooks - Funkcje wirtualne
2) Wikipedia - Metoda wirtualna
3) What is a "virtual member function"?
4) Intel Instruction Set A-M
5) Intel Instruction Set N-Z