poniedziałek, 11 lutego 2013

Hook EAT/IAT: Przykład


Tak więc mając poprzedni kod i rozumiejąc go trochę pokażę w prosty sposób co możemy osiągnąć używając go.

Klasa którą stworzyłem w 1 części będzie służyła za podstawę i zostanie z czasem rozbudowana o więcej rzeczy. Może nawet udostępniona ;) Who knows.

Tak więc załóżmy sobie przykładowego hooka na funkcję pow(z biblioteki cmath) w tablicy eksportów biblioteki msvcrt.dll , tak że każde użycie GetProcAddress będzie teraz zwracało adres naszej funkcji, a nie oryginalnej.

Potrzebne do tego będą nam 3 rzeczy, a mianowicie:
  1. 2 Prototypy funkcji pow:
    • wskazujący na starą funkcję
    • wskazujący na nową funkcję
  2. Napisana własna "redefinicja" funkcji pow
  3. Adres naszej nowej funkcji w pamięci

Kod z komentarzami:

#include "dlldumper.h"
#include <iostream>

using namespace std;

typedef double (*mypow)(double a, double b);

// Prototypy funkcji OldPow - stara, MyPow - nowa
mypow OldPow = NULL, MyPow = NULL;


// Nasza redefiniowana funkcja ktora posluzy za hooka
double fpow(double a, double b)
{
    printf("Hooked POW Result: ");
    return OldPow(a,b);
}

int main()
{
    DLLDumper dll;
    if(!dll.setDLL(L"msvcrt.dll",EAT))
        return 0;

    // Sprawdzamy oryginalny adres pobierajac go prosto z tablicy EAT //
    HMODULE hMod = LoadLibraryA("msvcrt.dll");
    printf("Before overwriting: pow = 0x%p\n",
           GetProcAddress(LoadLibraryA("msvcrt.dll"), "pow"));

    // Zakladamy hooka na pow przekazujac adres naszej nowej funkcji fpow
    // hooEAT zwraca nam oryginalny adres funkcji pow.
    OldPow = (mypow)dll.hookEAT("pow",(void*)&fpow);

    
    // Ponowne sprawdzenie adresu juz po zalozeniu hooka
    printf("After overwriting: pow  = 0x%p\n",
           GetProcAddress(LoadLibraryA("msvcrt.dll"), "pow"));
    // ========================= //

    // Pobranie i przypisanie juz podmienionego adresu z EAT
    MyPow = (mypow)GetProcAddress(hMod, "pow");
    
    // Wywolujemy "oryginalna" funkcje pow i tyle
    cout << MyPow(2,3) << '\n';
    return 0;
}

Listing z wykonania kodu:

Before overwriting: pow = 0x7681b91f
After overwriting: pow  = 0x00401524
Hooked POW Result: 8

Jak widac przy wywolaniu funkcji najpierw wypisuje się na ekran część z naszej funkcji fpow potem dopiero  wykonuje się oryginalna funkcja.

Domyślny flow: call pow -> pow
Nasze obejście: call pow -> call fpow -> pow

Wystarczy zrobić to samo w przypadku funkcji send, recv i voila mamy własny sniffer ;)

Takie podejście jest o tyle lepsze od zwykłego jmp patcha, że po pierwsze jmp patch jest bardzo łatwo wykrywalny przez porównanie adresów funkcji, a po drugie eat jest bardziej uniwersalny i trudniejszy do wykrycia, ponieważ jeśli proces pobiera adres przez GetProcAddress to i tak otrzyma adres naszej funkcji.

W następnej części pokaże przykładowy hook na IAT, a nastepnie sniffer, ale to po dll injectingu, bo takie rzeczy robić najlepiej z "wnętrza" procesu. ;)

niedziela, 3 lutego 2013

Hook EAT/IAT: Poznajemy strukture dll

Jest to pierwsza część serii. Głównym celem będzie podsłuchanie ruchu gry oraz napisanie prostego bota odpowiadającego na zdarzenia zachodzące w grze.

W tej części poznamy budowę dynamicznej biblioteki współdzielonej "od środka" i podstawy do założenia hooka na tablicę importów/eksportów tejże biblioteki. Mam nadzieję, że uda mi się osiągnąć cel.

Naszym celem w pierwszej części będzie podpięcie się pod proces gry i założenie z "wewnątrz" hooka na odpowiednie funkcje, tj.:
recv();
send();
zawarte w Ws2_32.dll.

Zanim zaczniemy myśleć o tym trzeba poznać budowę oraz wyciągnąc odpowiednie adresy z wnętrza biblioteki dynamicznej.

Tutaj niestety kochany microsoft nie służy zbyt wielką pomocą odnośnie swoich struktur. W większości jedynie minimalne opisy. Po małych poszukiwaniach znalazłem co będzie nam potrzebne.

Na początku zaczynamy od wyciągnięcia uchwytu do DLL'ki. Prosta sprawa używamy w tym celu jedynie funkcji GetModuleHandle(Ex).


HMODULE dllHandler = GetModuleHandle(L"msvcrt.dll");
if(dllHandler == NULL)
    cout << "DLL Handler couldn't be resolved.\n";


Kolejnym krokiem będzie wyciągnięcie struktury nagłówka tzw. "PE Header". W tym celu będziemy potrzebowali adres pod który załadowano naszą bibliotekę w module exeka. Niestety struktura która jest nam potrzebna nie jest praktycznie opisana. Najlepszy opis jaki udało mi się znaleźć można podejrzeć tutaj:

IMAGE_DOS_HEADER

Na szczęście główna struktura która będzie nam potrzebna jest opisana przez microsoft:

IMAGE_NT_HEADER

PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((BYTE*)dllHandler + ((PIMAGE_DOS_HEADER)dllHandler)->e_lfanew);
if(ntHeader->Signature != IMAGE_NT_SIGNATURE)
    cout << "NT Header signature error.\n"

Koncepcja wyciągnięcia adresu jest prosta. Adres bazowy + offset. Wiemy, że e_lfanew jest adresem początku wczytanej biblioteki w module naszego exeka. Dla pewności sprawdzamy sygnature struktury, żeby sprawdzić czy trafiliśmy pod dobry adres. Następnie musimy dostać się do kolejnej(na szczęście ostatniej) struktury, która zawiera to czego szukamy. Kolejny raz bez odpowiedniej dokumentacji...


PIMAGE_EXPORT_DIRECTORY ied = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)dllHandler + ntHeader->OptionalHeader.DataDirectory[0].VirtualAddress);

DataDirectory jest to tablica 2 elementowa zawierajaca wskaźniki na EAT/IAT, czyli tablicę eksportów/importów funkcji danej biblioteki. W naszym wypadku 0 oznacza EAT.

Mając już odpowiednią strukturę możemy wyciągnąć w końcu informacje.

PVOID names = (PVOID)((BYTE*)dllHandler + ied->AddressOfNames);
PVOID address = (PVOID)((BYTE*)dllHandler + ied->AddressOfFunctions);
vector<string>vnames;
vector<DWORD*>vaddress;
for(int i=0;i<ied->NumberOfNames;i++)
{
    vnames.push_back((char*)((BYTE*)dllHandler+((DWORD*)names)[i]));
    vaddress.push_back((DWORD*)dllHandler+((DWORD*)address)[i]);
}
for(int i=0;i<vnames.size();i++)
{
    if(vnames[i].compare("printf")==0)
    {
        cout << vnames[i] << " " << vaddress[i] << '\n';
        myprint func = (myprint)GetProcAddress(dllHandler,"printf");
        func("hello world");
    }
}

Z opisu IED wynika, że adresy funkcji sa adresami relatywnymi. Więcej info czym dokładnie są takie adresy można znaleźć tutaj: RVA/VA

Skoro są to adresy relatywne tak więc w pętli nadal musimy korzystać z naszego wzoru Adres bazowy + offset. GetProcAddress jest użyty w celu sprawdzenia czy udało sie znaleźć prawdziwy adres wewnątrz naszego procesu danej funkcji printf. "myprint" jest tutaj definicją wskaźnika na prototyp funkcji printf.

typedef int (*myprint)(const char* format, ...);
Tutaj rodzi się problem. O ile funkcja GetProcAddress znajduje poprawny adres funkcji printf, o tyle nasz wyciągnięty adres różni się od niego. Zapewne jest to związane z tym, że adresy przez nas otrzymane nie są adresami realnymi, a relatywnymi.

Edit 1.

Tak więc po kolejnych paru godzinach kombinowania doszedłem w końcu do rozwiązania problemu. Okazało się, że niepoprawnie wyciągałem adresy z tablicy, a było to spowodowane nieznajomością jej struktury ;) (Kolejny raz dziękujemy microsoftowi za dokumentację...)

Robiłem coś takiego

address = reinterpret_cast<PDWORD>(dllBase + baseFunctionsAddress[i]);

kiedy poprawnym rozwiązaniem było

address = reinterpret_cast<PDWORD>(dllBase + baseFunctionsAddress[baseOrdinalsAddress[i]]);

jak widać różnica w kodzie mała, ale w wyciągniętych danych ogromna.  Tablica ordinals zawiera po prostu wskazania kolejnych adresów funkcji.

Po udanym wykonaniu zadania postanowiłem opakować to w klasę dla łatwiejszego użytku. Co możemy dzięki temu osiągnąć? Przykładowo możemy uzywać funkcji bezpośrednio odwołując się do adresów zawartych w plikach .dll, które te funkcje eksportują bez includowania bibliotek.

Przykład:

#include <iostream>
#include "dlldumper.h"

using namespace std;

typedef void (*myprint)(...);
typedef double (*mypow)(double a, double b);

int main()
{
    DLLDumper dll;
    if(!dll.setDLL(L"msvcrt.dll",EAT))
        return 0;
    myprint print = (myprint)dll.getAddress("printf");
    mypow pow = (mypow)dll.getAddress("pow");
    if(print != NULL)
        print("Hello World %d\n",10);
    if(pow != NULL)
        cout << pow(2,3) << '\n';
    map<string,PDWORD>::iterator it;
    map<string,PDWORD>dllMap = dll.getMapedData();
    int i = 1;
    for(it = dllMap.begin();it!=dllMap.end();it++)
    {
        cout << i++ << ". " << it->first << " Address: " << it->second << '\n';
    }
    return 0;
}

Fragment listingu z wykonania kodu:
Hello World 10
8
...
480. _isalnum_l Address: 0x74a0aad5
481. _isalpha_l Address: 0x749cb4f3
482. _isatty Address: 0x749baf56
483. _iscntrl_l Address: 0x749d8c42
484. _isctype Address: 0x74a0ade5
485. _isctype_l Address: 0x74a0ae1b
486. _isdigit_l Address: 0x749c9dbd
487. _isgraph_l Address: 0x74a0abd3
488. _isleadbyte_l Address: 0x749bae3f
489. _islower_l Address: 0x749cb6a7
490. _ismbbalnum Address: 0x74a1a279
491. _ismbbalnum_l Address: 0x74a1a299
492. _ismbbalpha Address: 0x74a1a1fd
493. _ismbbalpha_l Address: 0x74a1a21d
494. _ismbbgraph Address: 0x74a1a2fb
...
Printf czy pow użyte bez includowania cstdio czy cmath ;) Dzięki tym danym będziemy mogli podmienić adres funkcji bezpośrednio w tablicy eksportów biblioteki na naszą funkcję i podsłuchiwać jakie dane są przesyłane. Wszystko jest też zależne od sposobu w jaki dany proces ładuje bibliotekę, czy bezpośrednio do własnego procesu, czy też wywołuje funkcje z biblioteki, a nie własne ich "kopie".

Ten problem da się rozwiązać przez podmianę adresów funkcji importowanych przez proces ;), dlatego rozwiązanie jest w miarę uniwersalne.