.. _re2: ================================================= Inżynieria wsteczna, część 2 — analiza dynamiczna ================================================= Data: 17.11.2020, 19.11.2020 .. toctree:: .. contents:: Narzędzia ========= - gdb (debugger) - Python 3 Obsługa gdb ----------- Uruchomienie gdb:: gdb ./program Najważniejsze polecenia: - ``help``: listuje polecenia - ``help ``: pokazuje dokumentację do polecenia - ``break nazwa_funkcji``: ustawia breakpoint na daną funkcję - ``break *0x12345``: ustawia breakpoint na dany adres - ``hbreak nazwa_funkcji``: ustawia breakpoint na daną funkcję używając breakpointa sprzętowego - ``attach ``: wpina debugger w już uruchomiony program - ``run``: uruchamia program - ``run abc def``: uruchamia program z argumentami - ``run wyjscie``: uruchamia program z przekierowanym wejściem / wyjściem - ``start``: jak ``run``, ale zatrzymuje program na funkcji ``main`` (nie działa, jeśli nie mamy symboli) - ``p wyrażenie``: drukuje wartość wyrażenia (składnia jak w C), oprócz funkcjonalności języka C można również używać wartości rejestrów (np. pisząc ``$rax``) - ``p/x wyrażenie``: drukuje wartość wyrażenia jako liczbę szesnastkową - ``x/20bx adres``: pokazuje zawartość pamięci pod danym adresem, jako 20 bajtów w systemie szesnastkowym - ``x/10gx adres``: jak wyżej, ale jako 10 liczb 64-bitowych - ``x/10i adres``: disassembluje 10 instrukcji pod danym adresem - ``x/s adres``: pokazuje null-terminated string pod danym adresem - ``display``: działa dokładnie jak ``x``, ale wykonuje się samo po każdym wykonaniu kodu (w szczególności polecam ``display/20i $pc``, aby mieć podgląd wykonywanych instrukcji) - ``c``: kontynuuje wykonanie aż do następnego breakpointa - ``si``: wykonuje jedną instrukcję procesora - ``ni``: jak ``si``, ale w przypadku instrukcji ``call`` wykonuje całą funkcję na raz - ``s``: wykonuje jedną linijkę kodu źródłowego (nie działa, jeśli program nie był skompilowany z informacjami dla debuggera) - ``n``: ma się do ``s`` jak ``ni`` do ``si`` - ``fin``: wykonuje program aż do momentu, gdy aktualna funkcja się zakończy - ``bt``: pokazuje stos wywołań funkcji - ``frame ``: ustawia kontekst na daną ramkę ze stosu wywołań (ewaluacja wyrażeń będzie używała zmiennych z danej ramki) - ``set = ``: ustawia wartość zmiennej bądź rejestru - ``kill``: przerywa debugowany program (przydatne, jeżeli nieodwracalnie go popsuliśmy) - ``Ctrl-C`` gdy program jest wykonywany: zatrzymuje wykonanie tak, jakby został trafiony breakpoint Pełna dokumentacja: https://sourceware.org/gdb/current/onlinedocs/gdb/ Mechanizm LD_PRELOAD ==================== Przy analizie dynamicznej przydatna bywa możliwość dołączenia własnego kodu do istniejącego programu. Służy do tego mechanizm ``LD_PRELOAD`` — jest to zmienna środowiskowa, którą możemy ustawić na ścieżkę do naszej biblioteki współdzielonej. Przy uruchamianiu dowolnego programu zlinkowanego dynamicznie ta biblioteka będzie automatycznie ładowana razem z programem. W takiej bibliotece możemy: - definiować własne funkcje (które będą przesłaniać funkcje z biblioteki systemowej) - jeśli chcemy użyć przesłonionych funkcji, możemy użyć ``dlsym`` z ``RTLD_NEXT`` - wykonać dodatkowy kod przed startem programu, prze zawarcie go w funkcji bez argumentów oznaczonej przez ``__attribute__((constructor))`` Przykład: zeszłoroczne zadanie zaliczeniowe =========================================== Program: :download:`bsk01` Zadanie: znaleźć poprawne hasło do programu. .. 1. Ładujemy do programu GHIDRA .. 2. Widzimy, ze hasło musi mieć 29 znaków i jest sprawdzane przez funkcję ``check`` .. 3. Próbujemy zdekompilować funkcję ``check`` i ze smutkiem stwierdzamy, że jej kod nie ma żadnego sensu .. 4. Zauważamy, że funkcja ``decrypt`` operuje na kodzie funkcji ``check`` .. 5. Używamy debuggera, by zdobyć rozszyfrowany kod funkcji ``check`` z pamięci: .. .. - ładujemy program pod gdb .. - stawiamy breakpoint na funkcji: ``break check`` .. - uruchamiamy program wpisujemy losowe 29-znakowe hasło .. - próbujemy przejść przez funkcję ``check`` w debuggerze, ale dostajemy wyjątek, spowodowany przez użycie software breakpointu (i użycie kodu instrukcji breakpoint zamiast zaszyfrowanych danych) .. - próbujemy jeszcze raz, tym razem przez ``hbreak check`` .. - tym razem kod zostaje odszyfrowany poprawnie .. - zrzucamy kod funkcji do pliku: ``dump memory check.bin 0x4040f0 0x404170`` .. .. 6. Używamy ``readelf -a bsk01``, żeby dowiedzieć się, jakiej pozycji w pliku odpowiada adres ``0x4040f0`` (funkcja ``check``) — jest to ``0x30f0`` .. 7. Używamy Pythona, by podmienić kod funkcji ``check`` na rozszyfrowaną wersję:: .. .. with open('bsk01', 'rb') as f: .. orig = f.read() .. with open('check.bin', 'rb') as f: .. patch = f.read() .. .. data = bytearray(orig) .. data[0x30f0:0x3170] = patch .. .. with open('bsk01-patched', 'wb') as f: .. f.write(data) .. .. 8. Ładujemy tą wersję do programu GHIDRA i dowiadujemy się, jak działa ``check`` .. 9. Piszemy kod do łamania hasła metodą brute force:: .. .. #include .. int *correct_pass = 0x404060; .. int *key = 0x4040e0; .. void (*decrypt) (int *, int *) = 0x4011e0; .. __attribute__((constructor)) void h4x(void) { .. for (int i = 0; i < 0x1d; i++) { .. for (int j = 32; j < 128; j++) { .. int tab[2] = { .. j, 0xfeed .. }; .. decrypt(tab, key); .. if (tab[0] == correct_pass[i]) { .. putchar(j); .. } .. } .. } .. putchar('\n'); .. } .. .. 10. Kompilujemy kod i ładujemy go do pamięci naszego programu:: .. .. gcc -shared -fpic h4x.c -o h4x.so .. LD_PRELOAD=./h4x.so ./bsk01 .. .. 11. Otrzymujemy poprawne hasło.