Dll Injection - wywoływanie funkcji - C++
P-Programowanie

Dll Injection - wywoływanie funkcji

dll injection wywoływanie funkcji

Dll Injection jest to technika pozwalająca uruchomić dowolny kod w pamięci innego procesu, przez zmuszenie procesu do załadowania obcego pliku DLL. Istnieje wiele sposobów na wykonanie Dll injection, wszystkie mają pewne wady i zalety. W artykule opiszę w jaki sposób wywołać funkcję innego procesu posługując się Dll Injection.

Zbieranie informacji

Naszym celem jest stworzenie pliku DLL, który ułatwi granie w Sapera. Nie będziemy zajmować się prostym podmienianiem wartości, to zostało opisane we wcześniejszym artykule. Będziemy starać się podpiąć pod różne funkcje gry, jednak w tym celu będziemy musieli zdobyć ich adresy.

Istnieje wiele metod na znajdowanie adresów funkcji. Rzecz jasna musimy posługiwać się debbugerem. Musimy znaleźć jakiś punkt zaczepienia np. założyć breakpoint na wywołanie funkcji API lub szukać zmieniających się wartości w grze.

Saper jest grą dość mało skomplikowaną. Proponuję pobawić się funkcją odpowiedzialną za "wygrywanie" gry. Kiedy wygrywamy grę w saperze dzieje się kilka rzeczy: buźka robi się uśmiechnięta, słychać charakterystyczny dźwięk, pojawia się okienko na wpisanie nicku, pojawia się okienko z najlepszymi wynikami. Musimy znaleźć adres funkcji, która wywoływana jest w momencie wygranej.

Odpalamy CheatEngine. Będziemy szukać wartości mówiącej o przegranej/wygranej. Nie wiemy czy taka zmienna istnieje w grze, możemy to tylko przypuszczać. Aby szybko przechodzić grę, ustawiamy plansze niestandardową, szeroką 100x100 z ilością min 1. Klikamy nową grę i skanujemy pamięć (unknown initial value). Po wygranej grze szukamy zmienionych wartości (changed value). Klikamy nową grę i znowu szukamy zmienionych wartości. Przegrywamy grę i szukamy nowych wartości. Powtarzamy te operację aż do momentu znalezienia 1-3 adresów. Po zmianie wartości znalezionych adresów odkryłem, że adres 01005160 wyświetla status gry (np. gdy podmienimy wartość na 3, to wygramy).

ss1

Niestety jest to dopiero adres zwykłej zmiennej. Stanowi ona bardzo dobry punkt zaczepiania, względem którego na pewno uda nam się namierzyć funkcję odpowiedzialną za "wygrywanie gry". Załóżmy pułapkę na zapis i sprawdźmy co modyfikuje wartość z pod naszego adresu. W tym celu klikamy prawym na adres i wybieramy pozycję "Find out what writes to this address" i potwierdzamy klikając OK. Kilka razy klikamy na nową grę i kilka razy kujemy się.

ss2

Na liście pojawiły się 3 pozycje. Dwie czerwone rejestrowane są w momencie włączenia nowej gry. Zielona w momencie skucia. Odrzucamy operację AND. Pozostałe dwie to rozkazy MOV odpowiedzialne za kopiowanie pamięci. Tutaj metodą prób i błędów wybrałem jedną z funkcji. Czerwona nie prowadziła do niczego więc odrzuciłem ją, przetestowałem wcześniej w debbugerze i pominę ten krok aby było szybciej. Zapiszmy więc adres ostatniej funkcji oznaczonej na zielono, jest to 1003492. Czas wykonać analizę zdobytego adresu.

Odpalamy OllyDbg. Przeciągamy do niego plik Saper.exe (wcześniej zrób jego kopię). Naciskamy CTRL + G i wpisujemy adres naszej funkcji czyli 1003492. Naciskamy F2 aby założyć pułapkę na adres. Naciskamy F9 aby uruchomić grę. Zgodnie z oczekiwaniami pułapka zatrzyma działanie programu w momencie skucia. Przyjrzyjmy się funkcji, jest ona bardzo długa i zaczyna się pod adresem 0100347C. Funkcja jest skomplikowana, posiada wiele skoków i rozkazów, składa się z kilku części i widać że jest odpowiedzialna za wiele rzeczy. Potwierdzają to liczne rozkazy TEST. Powinniśmy zakładać breakpointy na wszystkie CALLe w funkcji i eksperymentować z działaniem Sapera.

Nie musisz znać assemblera aby przeprowadzić prostą analizę. Wystarczy użyć metody prób i błędów, założyć kilka breakpointów i kilka razy skuć się/wygrać.

Możemy przyjąć, że znaleziona funkcja odpowiedzialna jest za skucie, wygranie oraz nową grę. Nakierować nas to może pierwsza linijka funkcji, która wywoływana jest w 3 miejscach w kodzie (czyli skucie, nowa gra i wygrana). Kolejnym dowodem jest to, że modyfikuje zmienną z CheatEngine, która wskazywała status gry (wygrana, przegrana). Ostatnim ważnym faktem jest koniec funkcji. Są tam dwa wywołania co dziwne bez żadnych parametrów. Oczywiście chodzi o okienko najlepszych wyników oraz okienko do wpisania nicku.

Przetestujmy to. Zakładamy breakpoint na jedną z funkcji CALL i przechodzimy grę na planszy niestandardowej. Pułapka zadziałała, nasze przypuszczenia potwierdzają się. Zapisujemy adres początku funkcji 0100347C oraz wywołania okienek 01003505 i 0100350A.

ss3

Ściągamy wszystkie pułapki. Klikamy prawym przyciskiem myszy na główne okno OllyDbg a następnie Search For > All intermodular calls. Wyświetli się lista wszystkich użytych API systemowych. Możemy założyć breakpoint na dowolną z nich, wybór mamy naprawdę duży. Ja wybrałem okienko about, funkcja odpowiedzialna za jego wyświetlenie to ShellAboutW. Zaznaczamy ją i zakładamy pułapkę (F2). Naciskamy F9 i wybieramy z menu Saper - informacje... Pułapka zadziałała. Zapisujemy adres początku funkcji 01003D1D. Wywołuje ona okienko z informacjami.

Posiadamy już kilka przydatnych adresów, mogli byśmy kombinować z wyświetlaniem min, cyferek, planszy oraz z dźwiękami. API odpowiedzialne za dźwięki w grze to PlaySoundW. W ten sposób łatwo znajdziesz funkcję odpowiedzialną za odtwarzanie dźwięków.

Oto zdobyte przez nas adresy:

  • 0x0100347C - koniec gry (wygrana lub przegrana)
  • 0x01003505 - okienko z wynikami
  • 0x01003D1D - okienko about

Uwaga! Jedna funkcja "koniec gry" odpowiedzialna jest za wiele operacji. Z tego powodu możemy przypuszczać, ze będzie ona wywoływana z jakimiś parametrami. Śledzenie parametrów za pomocą debbugera opisałem w artykule Dll Injection Wywoływanie funkcji z parametrami dlatego nie będę robił tego drugi raz. Parametry w naszym wypadku bardzo łatwo zgadnąć: funkcja może być wywołana z parametrem true lub false.

Można to też odczytać podczas analizy gry w debbugerze. Wszystkie wywołania naszej funkcji (są ich 3) poprzedzone są instrukcjami "push 1" lub "push 0".

Tworzenie DLL

Oto pusty szkielet pliku DLL, na jakim będziemy się wzorować:

Podczas załadowania biblioteki wykonywana jest instrukcja DLL_PROCESS_ATTACH, podczas zamknięcia DLL_PROCESS_DETACH. Istnieją także inne przełączniki ale nie będą wykorzystane w tym artykule. extern "C" BOOL __stdcall DllMain jest wymagany w kompilatorze Code::Blocks, jeżeli korzystasz z MVC wystarczy zwykłe BOOL DllMain.

Aby móc wywołać funkcję, której adres posiadamy, niezbędne jest stworzenie wskaźnika funkcyjnego. Jak stworzyć wskaźnik na funkcję opisałem dokładnie w innym artykule o Ukrywaniu funkcji WinApi. Przeczytaj jeden akapit na ten temat aby zrozumieć zasadę wywoływania funkcji przez wskaźnik.

Dodajmy do naszej biblioteki główną funkcje, którą potem wstrzykniemy do programu. Robimy to podczas ładowania biblioteki. Tworzymy nowy wątek (CreateThread) wskazujący na funkcję główną o nazwie Funkcja_saper. W naszej głównej funkcji moglibyśmy się pokusić o skorzystanie z SetTimer ale niepotrzebnie wydłużyło by to kod. Skorzystamy z mniej bezpiecznego rozwiązania, zapętlonej pętli opóźnianej za pomocą funkcji Sleep. Zadaniem pętli będzie przechwytywanie wcisniętych klawiszy i kojarzenie ich z odpowiednimi zadaniami.

 

Po kompilacji kodu powinien pokazać się plik DLL ważący od 6-7kb. Nie zapomnij dodać przełącznika BUILDING_DLL do kompilatora (powinien zrobić to automatycznie podczas wyboru projektu DLL).

Dobrym programem do wstrzykiwania DLLek jest Winject. Pobierz go i skopiuj do jego katalogu nasz plik DLL. Uruchom Sapera i za pomocą Winject podepnij naszą DLLke. Powinien ukazać się komunikat:

ss4

Teraz naciskaj klawisze od 1 do 4 aby zobaczyć efekty. Klawisz 5 wywala DLLke z pamięci.

Podsumowanie

Zapytasz pewnie po co bawić się w Dll Injection skoro można w łatwy sposób napisać trainer modyfikujący wartości w pamięci. Otóż Dll Injection daje nieograniczone możliwości, a używanie WriteProcessMemory pozwala jedynie nadpisywać wartości. Wstrzykując Dllke do procesu, posiadamy dostęp do wszystkich jego funkcji i zmiennych, możemy zakładać hooki na funkcje i kontrolować działanie programu. Nie jest to osiągalne zwykłym nadpisywaniem pamięci.

Przykładowy spis funkcji do pewnej gry, które możesz w łatwy sposób wykorzystać znajdziesz klikając tutaj.

Udostępnij ten artykuł na fejsie lub zostaw komentarz!
Facebook Twitter Google LinkedIn Email

Autor artykułu: Karol
Tagi: ,

Podobne artykuły:

Komentarze:

Użytkownik Damian napisał/a:

08.04.2012 r.


Super kursy tutaj macie, dzieki :)

Użytkownik Maciek napisał/a:

04.11.2014 r.


Wszystko fajnie, tylko mam 64-bitowy system i OllyDbg mi o to krzyczy, szukałem wersji dla mnie, lecz jest w fazie produkcji. Czy miałbyś może jakiś zastępczy program działający pod systemy 64-bitowe?

Użytkownik Karol napisał/a:

04.11.2014 r.


Dobrym debuggerem dla architektury x64 jest IDA. Istnieje wersja darmowa oraz płatna IDA PRO. Niestety, według mnie nie jest tak dobra jak OllyDbg. Być może nie poznałem jej tak dobrze.

Użytkownik Piotrek napisał/a:

06.20.2014 r.


Cześć, a jakby można było za pomocą Dll Injection wczytać pamięć programu?

Użytkownik Karol napisał/a:

06.21.2014 r.


Cześć, zależy co chcesz osiągnąć. Po wstrzyknięciu DLLki do procesu, jej wątek znajduje się już "wewnątrz procesu" a więc masz swobodny dostęp do pamięci, tak jakbyś pisał program i operował na jego pamięci. Po wykonaniu DLL Injection pamięć możesz modyfikować np. wskaźnikami void* (wskaźniki generyczne). Artykuł na ten temat znajduje się na blogu :).

Użytkownik Piotrek napisał/a:

06.21.2014 r.


A no tak dzięki. A wiesz może jak otworzyć okno konsoli za pomocą dllki wstrzykniętej do pamięci aplikacji? Próbowałem AllocConsole(); ale niestety nie moge nic wypisać w konsoli :/

Użytkownik zbyhu napisał/a:

08.27.2014 r.


Witam! mam pytanie, otóż dlaczego gdy próbuje wywołać funkcje w napisanym wcześniej prostym programie, wątek działającej DLL'ki zawiesza się po jednorazowym wkonaniu kodu w programie (tzn program działa, ale wątek dllki zawiesza się na wykonaniu funkcji z wskaźnika ;x (typedef void (__stdcall *okno1)(); // deklaracja wskaznika
okno1 startOkno1 = (okno1)(0x401355); // przypisanie do wskaznika
startOkno1(); // wywolanie
Nie zakończa funkcji tylko się zwiesza chociaż została ona raz wykonana) próbowałem z memorysharp C#, i taki sam efekt, program ma jedną funkcje wypisującą sam text w konsoli oraz pętle while z getch() w sirodku.

Użytkownik Karol napisał/a:

08.27.2014 r.


Cześć. Miałem kiedyś podobny problem, okazało się że adres "który chciałem wywołać" nie był adresem funkcji. Upewnij się, że posiadasz poprawny adres funkcji a nie jakiegoś miejsca w pamięci.
Drugim możliwym problemem jaki przychodzi mi na myśl, jest inna konwencja wywołania funkcji. Być może funkcja nie jest typu __stdcall. Konwencję wywoływania funkcji możesz podpatrzyć w debbugerze po sposobie wywoływania funkcji lub po sposobie przekazywania argumentów. Może się też udać po dekorowaniu nazw przez kompilator. Pisałeś, że program ma jedną funkcję wypisującą tekst. Jeżeli używasz "printf" to na 90% będzie to __cdecl zamiast __stdcall.

Użytkownik zbyhu napisał/a:

08.28.2014 r.


konwersja na __cdecl niewiele pomogła, nie wiem może to i zły adres funkcji, ale jak przetestowałem na identycznym programie, skompilowanym tylko pod gcc zamiast visual C++, to bez problemu wszystko zadziałało i się nie zwiesza, wg mnie na 75% adres w tamtym wypadku też był poprawny dodałem nawet w programie inkrementacje zmiennej w wykonywanej funkcji, żeby zobaczyć który adres nadpisuje tą zmienną, i też wskazał +- tamten obszar pamięci, dałem wyżej breakpointy no i pierwszy adres na którym się zatrzymało to wziąłem jako funkcja. No ale nadal nie działa, może dlatego, że dllka kompilowana pod gcc a program w visual c++? hmm chociaż wtedy w C# też nie powinno działać z programem w gcc, a działa. btw. Dzięki za pomoc., a i jak rozpoznać jakiej konwersji funkcji użyć?

Użytkownik Karol napisał/a:

08.30.2014 r.


Dziwne, ale do zrozumienia. Z VS takie problemy to norma. Czytałem kiedyś o podobnych problemach na forum 4programmers dotyczących epilogu funkcji w ASM. W GCC wszystko działało w porządku, natomiast ten sam program skompilowany pod VS zawieszał się. VS ma swoje mechanizmy, nieco odmienne od przyjętych standardów. VS w dziwny sposób "zaciemnia" i lekko "zmienia kod". Pomóc może użycie flag, które zapewnią brak optymalizacji kodu. Spróbuj przed definicją funkcją, którą wywołujesz, dodać przedrostek "static inline". Jeżeli jest to funkcja importowana stwórz wskaźnik na funkcję z tym przedrostkiem - być może pomoże. Artykuł o konwencjach wywoływania funkcji przeczytasz na moim blogu, artykuł jest gdzieś w dziale C++.



Zachęcam Cię do zostawienia komentarza!

Ilość znaków: 0

Wesprzyj tego bloga!

Zachęcam Cię do polubienia nas na facebooku! Dzięki temu zawsze dowiesz się o najnowszych wpisach!

Jeżeli dasz lajka, wesprzesz moją pracę – wszystkie artykuły na blogu są za darmo oraz są wolne od reklam!

×