Inline hook-dolaczanie kodu i przekazywanie parametrow

0

Witam ponownie, we wcześniejszym wątku pomógł mi MSM za co bardzo Ci dziękuje, ale mam jeszcze 2 sprawy do obgadania. Chciałbym zrobić 2 rzeczy:

1)Jeżeli funkcja X się wykona, dodaję na jej koniec własny kod. Tak aby funkcja X wykonywała bez problemu swój kod, ale żeby także na końcu wywołała moje instrukcje. Ogólnie mowiąc - powiększenie funkcji X.

Przykład.

void MojaFunkcja(void)
{
     cout << "Pies pogryzł Ali kota";
}

void FunkcjaX(void)
{
       cout << "Ala ma kota" << endl;
       cout << "Ciesze sie z tego bardzo!" << endl;
       MojaFunkcja(); //Wykonanie metodą inline hook
}

To wszystko oczywiście miałoby się odbywać metodą inline hook.

2) Drugą sprawą jest to, żeby założyć inline hook'a na funkcję X i w czasie jej wywołania przez program przekierowywała parametry (argumenty) do mojej funkcji. Funkcja X ma bezproblemowo wykonywać swój kod, tylko po prostu te wartości argumentów, które zostały do niej użyte zostały przekierowane do mojej.

Przyklad:


void MojaFunkcja(int inter, char *ch)
{
      cout << "Wartosci parametrow Funkcji X to:" << endl;
      cout << "INT: " << inter << endl;
      cout << "CHAR*: " << ch << endl;
}

void FunkcjaX(int inter, char *ch)
{
       run(inter); //Jakas tam przykladowa operacja
       run2(ch); //Jakas tam przykladowa operacja
}

Bede niezmiernie wdzięczny za pomoc.

0

Ave!
Postaram się odpowiedzieć na obydwa pytania na raz. Niestety teraz będzie trochę trudniej, bo wstawka assemblerowa jest niezbędna.

Kod:

#include <iostream>
#include <windows.h>

using namespace std;

void Function1(char *who)
{
        cout << who << " was here." << endl;
}

extern "C" {
void Podrobka(char *who)
{
        cout << "Who the fuck is " << who << "?" << endl;
}
}

extern "C" {
void wrapper(void);                // te wszystkie deklaracje wrappera
__asm(                             // służą temu żeby był on widoczny zarówno na poziomie kompilacji
        ".globl _wrapper\n"        // przez gcc (deklaracja C++-owa) i na poziomie asemblerowego backendu
        "_wrapper:\n"              // (labele _wrapper:). Extern "c" zapobiega dekoracji kodu przez C++ i ułatwia odwoływanie się z asemblera.

        "push dword ptr [esp+8]\n" // magia :(
        "call _Podrobka\n"         // Jest to wrzucenie parametrów do funkcji 'podróbki'
        "add esp, 4\n"             // i wywołanie jej.

        "pop ecx\n"                // zachowanie adresu powrotu do wywołującej funkcji (czyli oryginalnej)

        "push ebp\n"               // te kilka operacji:
        "mov ebp, esp\n"           // ważne - wykonanie pierwszych operacji wykonywanych przez funkcję
                "sub esp, 8\n"             // są one nadpisane przez naszą 'tramp' więc trzeba je wykonać tutaj.
                "mov eax, dword ptr ss:[ebp+8]\n" // MOGĄ WYGLĄDAĆ INACZEJ DLA KAŻDEJ FUNKCJI, trzeba to sprawdzić za 
                                   // pomocą disassemblera

        "jmp ecx\n"                // powrót do wywołującej funkcji.
        );
}

void hooker(char *tramp, int t_size, DWORD addr)
{

    DWORD old;
    VirtualProtect((LPVOID)addr, t_size, PAGE_EXECUTE_READWRITE, &old);

    memcpy((LPVOID)addr, tramp, t_size);

    VirtualProtect((LPVOID)addr, t_size, old, &old);
}

int main()
{
        char tramp[] = {0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax addr
                        0xFF, 0xD0, // call eax (TYM RAZEM CALL.)
                        0x90, 0x90 }; // nop-y 

        DWORD addr_hook_wrapper = (DWORD)wrapper;
        DWORD addr_puts = (DWORD)Function1;

        memcpy(tramp + 1, &addr_hook_wrapper, 4);
        hooker(tramp, 9, addr_puts);

        Function1("Justin Bieber");
        cout << "wszystko OK";
        return 0;
} 

Tzn. Będzie działać na gcc i wymaga do kompilacji flagi -masm=intel

Ogólna zasada działania:

  1. sprawdzamy jak zaczyna się patchowana funkcja. W moim przypadku - tak:

    004013EE  /$ 55             PUSH EBP
    004013EF  |. 89E5           MOV EBP,ESP
    004013F1  |. 83EC 08        SUB ESP,8
    004013F4  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
  2. Tworzymy trampolinę (dopełniamy ją NOP-ami żeby pasował ilością bajtów rozpoczęcia funkcji).

  3. Tworzymy wrappera - skomentowałem go najlepiej jak mogłem więc @seealso podany kod.

  4. Wrzucamy trampolinę, uruchamiamy, wszystko działa.

Trochę inaczej może to wyglądać w Visual Studio (jeśli używasz) - nie mam pod ręką więc nie przerobię chwilowo, ale trzeba przepisać wstawkę asemblerową (najlepiej wrzucić ją w funkcję typu naked).

0

Hm. Moze na poczatek zajmijmy sie pierwszym pytaniem, bo bardziej na tym mi zalezy. Te drugie opcjonalnie, wiec dajmy sobie na razie spokoj. A wiec. Kod assemblerowy w cudzyslowiu ktory podales nie jest przyjmowany przez kompilator visuala, z tego co wyczytalem jest to blad skladniowy coś jak np. deklaracja DW(double word). Ten blad tyczy sie wszystkich linii w cudzysłowie. Err: "inline assembler syntax error in 'opcode'; found 'bad token'". Dodawalem tokeny na koniec, ale nie pomaga.

Na poczatek, chcialbym sobie uproscic, a mianowicie funkcje nie beda nic przyjmowaly ani nic nie zwracaly. A wiec stos chyba nie jest tu potrzebny (mowie tu chyba bo nie znam za bardzo assemblera). Wykonalem dosc prymitywna operacje w wyniku czego otrzymalem swoista petla, a dokladniej wykonywane sa ciągle instrukcje funkcji "Podrobka". Troche mnie to dosc zdziwilo bo zadanie mialo na celu wywolac najpierw oryginalna a pozniej podrobiona funkcję razem wziętą. A wykonuje się tylko ta podrobiona, czyli dokładnie to co w poprzednim wątku było omawiane. Kod ktory jest ponizej wywoluje tylko funkcję 'Podrobka'. Dla mnie jest obojetne ktora funkcja prędzej zostanie wywołana, dla mnie ważne jest to aby obydwie zostaly uruchomione. A jeszcze jedno, tą "pętle" zatrzymac chcę.

void Function1()
{
    cout << "Running oryginal function1..." << endl;
}

void Podrobka()
{
    cout << "Who the fuck is Justin Bieber?" << endl;
};

void wrapper(void)
{
    __asm
    {
        call Podrobka;
        jmp Function1;
    }
}

void hooker(char *tramp, int t_size, DWORD addr)
{

    DWORD old;
    VirtualProtect((LPVOID)addr, t_size, PAGE_EXECUTE_READWRITE, &old);

    memcpy((LPVOID)addr, tramp, t_size);

    VirtualProtect((LPVOID)addr, t_size, old, &old);
}

int main()
{
    char tramp[8];
    tramp[0] = (char)0xB8; //mov eax, addr
    tramp[5] = (char)0xFF; tramp[6] = (char)0xD0; //call eax
    tramp[7] = (char)0x90; tramp[8] = (char)0x90; //nop-y

    DWORD addr_hook_wrapper = (DWORD)wrapper;
    DWORD addr_puts = (DWORD)Function1;

    memcpy(tramp + 1, &addr_hook_wrapper, 4);
    hooker(tramp, 9, addr_puts);

    Function1();
    getchar();
    return 0;
}

Sory, że dość mocno zmieniłem Twój kod, ale od bardziej prymitywnego chciałbym zacząć. Wracając.. jak to poprawnie zrobić?

0

Ten blad tyczy sie wszystkich linii w cudzysłowie. Err: "inline assembler syntax error in 'opcode'; found 'bad token'". Dodawalem tokeny na koniec, ale nie pomaga.

Cytując ostatnie zdanie w moim poście:

Trochę inaczej może to wyglądać w Visual Studio (jeśli używasz) - nie mam pod ręką więc nie przerobię chwilowo, ale trzeba przepisać wstawkę asemblerową (najlepiej wrzucić ją w funkcję typu naked).

Patrz również np. http://msdn.microsoft.com/en-[...]ry/5f7adz6y%28v=vs.80%29.aspx (naked jest zresztą tutaj konieczne nie 'zalecane')

Sory, że dość mocno zmieniłem Twój kod, ale od bardziej prymitywnego chciałbym zacząć. Wracając.. jak to poprawnie zrobić?

Widzisz, nie chodzi o to że ja chcę komplikować kod tylko o to że prościej się nie da. Jesli nie potrzebujesz parametrów, wyrzuć po prostu push i add esp przed twoja funkcja.

void wrapper(void)
{
        __asm
        {
                call Podrobka;
                jmp Function1;
        }
}

To że to działa to cud, gdyby nie twoje niezwykłe szczęście powinno wywalać twój program a u Ciebie robi tylko nieskończoną pętlę...

Pierwsze kilka bajtów funkcji jest nadpisywane przez tramp. Nie może działać funkcja której początek nie istnieje.
Musisz te operacje wykonać w trampolinie, przed wróceniem do funkcji.
Cytując KRZYCĄCY komentarz:

ważne - wykonanie pierwszych operacji wykonywanych przez funkcję
są one nadpisane przez naszą 'tramp' więc trzeba je wykonać tutaj.
MOGĄ WYGLĄDAĆ INACZEJ DLA KAŻDEJ FUNKCJI, trzeba to sprawdzić za pomocą disassemblera

Mój kod:

                "push ebp\n"               // te kilka operacji:
                "mov ebp, esp\n"           // ważne - wykonanie pierwszych operacji wykonywanych przez funkcję
                "sub esp, 8\n"             // są one nadpisane przez naszą 'tramp' więc trzeba je wykonać tutaj.
                "mov eax, dword ptr ss:[ebp+8]\n" // MOGĄ WYGLĄDAĆ INACZEJ DLA KAŻDEJ FUNKCJI, trzeba to sprawdzić za 
                                   // pomocą disassemblera

I jeszcze raz dwa pierwsze kroki:

Ogólna zasada działania:

  1. sprawdzamy jak zaczyna się patchowana funkcja. W moim przypadku - tak:

004013EE /$ 55 PUSH EBP
004013EF |. 89E5 MOV EBP,ESP
004013F1 |. 83EC 08 SUB ESP,8
004013F4 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]

  1. Tworzymy trampolinę (dopełniamy ją NOP-ami żeby pasował ilością bajtów rozpoczęcia funkcji).
0

Mówisz, że mam wypisać te instrukcje, które będą nadpisywane przez trampoline, czyli pierwsze kilka bajtów. W twoim przypadku 6 bajtów, a skąd mam wiedzieć ile u mnie? Może chodzi Tobie o 4 pierwsze instrukcje assemblerowe funkcji (te które jak mówisz tworzą kilka pierwszych bajtow). Jeżeli tak to u mnie wychodzi 9 bajtów. Ekhmm, chyba źle to zrozumiałem.

0

Musisz to sprawdzić przez deasemblację funkcji którą masz zamiar spatchować.

Parę przykładowych początków funkcji (z binarki stworzonej przez gcc):

00401315    /$ 55             PUSH EBP
00401316    |. 89E5           MOV EBP,ESP
00401318    |. 53             PUSH EBX
00401319    |. 83EC 14        SUB ESP,14
0040131C    |. 83E4 F0        AND ESP,FFFFFFF0
0040131F    |. B8 00000000    MOV EAX,0
004012D0    /$ 55             PUSH EBP
004012D1    |. 89E5           MOV EBP,ESP
004012D3    |. 83EC 08        SUB ESP,8
004012D6    |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
004012D9    |. 8845 FF        MOV BYTE PTR SS:[EBP-1],AL
00401800    |$ 55             PUSH EBP
00401801    |. 89E5           MOV EBP,ESP
00401803    |. 83EC 08        SUB ESP,8
00401806    |. 8B0D 20404000  MOV ECX,DWORD PTR DS:[404020]
0040180C    |. 85C9           TEST ECX,ECX
0040180E    |. 74 02          JE SHORT a.00401812

Pierwsze instrukcje (push ebp, mov ebp, esp, sum esp, xxx) to bardzo często się powtarzający motyw w skompilowanych programach - tak zwana ramka stosu.

Dla odmiany coś z msvcrt:

6FF6C5B9 pr> 6A 0C            PUSH 0C
6FF6C5BB     68 28C6F66F      PUSH msvcrt.6FF6C628
6FF6C5C0     E8 71D2FEFF      CALL msvcrt.6FF59836
6FF6C5C5     33C0             XOR EAX,EAX
6FF6C5C7     33FF             XOR EDI,EDI
6FF6C5C9     397D 08          CMP DWORD PTR SS:[EBP+8],EDI

Załóżmy że będziemy działać na tym:

00401315    /$ 55             PUSH EBP
00401316    |. 89E5           MOV EBP,ESP
00401318    |. 53             PUSH EBX
00401319    |. 83EC 14        SUB ESP,14
0040131C    |. 83E4 F0        AND ESP,FFFFFFF0
0040131F    |. B8 00000000    MOV EAX,0

Chcemy wykonać call do jakiejś swojej funkcji - czyli musimy na początek wrzucić asemblerowe mov eax addr; call eax. Taka instrukcja wygląda w heksach tak:

0xB8, 0x??, 0x??, 0x??, 0x??
0xFF, 0xD0

Czyli zajmuje 6 bajtów 7 bajtów. Nadpisujemy więc początek funkcji i otrzymujemy:

00401316       E8 5D43F411    CALL 12345678
0040131B       14             __Problem__
0040131C    |. 83E4 F0        AND ESP,FFFFFFF0
0040131F    |. B8 00000000    MOV EAX,0

Problemem jest ten bajt 0x14 - nie pasuje do żadnej instrukcji i na pewno spowoduje wywalenie się programu. Trzeba go więc zastąpić NOP-em.

00401316       E8 5D43F411    CALL 12345678
0040131B       90             NOP
0040131C    |. 83E4 F0        AND ESP,FFFFFFF0
0040131F    |. B8 00000000    MOV EAX,0

Jeszcze jeden problem - Ten patch usunął pierwsze inestukcje. Nie możemy tak po prostu usuwać operacji z kodu maszynowego, trzeba to naprawić - więc należy dodać usunięte instrukcje, tzn.

00401315    /$ 55             PUSH EBP
00401316    |. 89E5           MOV EBP,ESP
00401318    |. 53             PUSH EBX
00401319    |. 83EC 14        SUB ESP,14

Na koniec trampoliny.

0

Ok. Jeżeli początek mojej oryginalnej funkcji wygląda tak:

004114C0                 push    ebp
004114C1                 mov     ebp, esp
004114C3                 sub      esp, 0C0h
004114C9                 push    ebx

to mój wrapper powinien wyglądać mniejwiecej tak? (na razie program się wywala...). Nopy wypelnia trampolina.

void wrapper(void)
{
        __asm
        {

                call Podrobka

                pop ecx

                push ebp
        mov ebp, esp
        sub esp, 0Ch
        push ebx

        jmp ecx
        }
}
0

Skoro początek wygląda tak:

00000000  55                push ebp
00000001  89E5              mov ebp,esp
00000003  83EC0C            sub esp,byte +0xc
00000006  53                push ebx

Masz tutaj już 7 wymaganych bajtów pamięci.

W takim razie nie musisz już nic dopisywać do trampoliny - musi mieć ona dokładnie 7 znaków.

0

Może będzie to na skróty, ale użyj Microsoft Detours :)

0
004114C0                 push    ebp
004114C1                 mov     ebp, esp
004114C3                 sub      esp, 0C0h
004114C9                 push    ebx

Tutaj jest 9 bajtow, a u ciebie nagle wzielo się 7. Skąd, skoro widać tylko 6 bajtów, dlaczego?

00000000  55                push ebp
00000001  89E5             mov ebp,esp
00000003  83EC0C         sub esp,byte +0xc
00000006  53                push ebx

^- 6 bajtow

Zrobiłem tak, ale wciąż się wywala:

void wrapper(void)
{
    __asm
    {
        call Podrobka

        pop ecx

        push ebp
        mov ebp, esp
        sub esp, 0xc0 //nie moge zrobic takiej operacji jak ty pokazales "byte + 0xC0"
        push ebx

        jmp ecx
    }
}

int main()
{
     char tramp[] = {0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax addr
                          0xFF, 0xD0}; // call eax
                         //0x90, 0x90 }; NOP-y nie sa wypelniane tak jak kazales

    DWORD addr_hook_wrapper = (DWORD)wrapper;
    DWORD addr_puts = (DWORD)Function1;

    memcpy(tramp + 1, &addr_hook_wrapper, 4);
    hooker(tramp, 7, addr_puts);

    Function1();
    getchar();
    return 0;
}

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