Dzisiaj opiszę wyjątkowy walkthrough, bo wykorzystujący takie ataki jak BoF. Jest to też najtrudniejszy lab, z którym przyszło mi się zmierzyć. Robiłem go około dwóch dni, a wiedza, którą w tym czasie zdobyłem, jest ogromna ze względu na fakt, iż nie miałem wcześniej pojęcia o pisaniu exploitów. W najbliższym czasie opublikuje też rozwiązanie do drugiej maszyny w której dokładnie opisuje przebieg ataku BoF. Zaczynajmy! Zaczniemy od skanu Nmapem. Mamy otwarte dwa porty. Jeden port z uruchomionym serwerem Python i drugi, port 9999, z uruchomioną usługą, której póki co nie znamy. Spróbujmy się połączyć z portem 9999. Program prosi nas o wpisanie danych - prawdopodobnie chodzi o hasło. Zignorujmy więc na razie ten port i zerknijmy na to co kryje się na drugim porcie. Mamy ciekawą grafikę na temat bezpiecznego programowania, jednakże nie jest ona nam przydatna. W kodzie źródłowym strony nic nie ma, a plik robots.txt nie istnieje. Zapuśćmy więc skanowanie Nikto i Dirbem. Nikto nie wykrył niczego ciekawego oprócz folderu bin, do którego zaraz zerkniemy. Sprawdźmy jeszcze wyniki z Dirba. Dirb również nic nie wykrył oprócz folderu, który znalazł też Nikto. Sprawdźmy w takim razie co się w nim znajduje. Mamy jakiś plik exe. Ściągnijmy go i sprawdźmy szczegóły tego pliku. Jest to 32-bitowy program wykonywalny przeznaczony na platformę Windows. W takim razie nie uda nam się go uruchomić na Kali Linuxie. Zerknijmy jednak na ciągi znaków, które się w nim znajdują. W tym celu skorzystamy z polecenia strings. Jedyny ciąg, który jest unikatowy, tzn. nie należy do żadnych bibliotek itd., to shitstorm, więc gdzieś w tym programie znajduje się pewnie taki ciąg znaków. Sprawdźmy czy przypadkiem nie jest on hasłem. Kiedy próbujemy wpisać jakiekolwiek dane przez interfejs tego programu, jesteśmy rozłączani. Przekażmy więc ciąg znaków dzięki użyciu polecenia echo i przekierowania wyjścia jednego programu do wejścia drugiego. Udało się. W prawym dolnym rogu widnieje komunikat o przyznaniu dostępu. Niestety nic to nie zmienia, bo nadal jesteśmy rozłączani. Przeanalizujmy więc dokładniej nasz program, korzystając z bardziej zaawansowanych narzędzi. W takim razie sprawdźmy jak wygląda mniej więcej program w IDA. Zaznaczę, że teraz będę korzystał z Windowsa ze względu na to co będę robił po analizie programu w IDA. Można byłoby też skorzystać z dwóch maszyn wirtualnych na Linuxie; podatna maszyna i Windows. Niestety mój komputer jest za słaby na dwie maszyny wirtualne, więc po prostu przełączyłem się na Windowsa, którego mam zainstalowanego obok Linuxa. Widzimy, że program korzysta z biblioteki Winsock więc pewnie jest to swego rodzaju serwer. Prawdopodobnie ten nasłuchujący na porcie 9999. Dalsze rozgałęzienia to ustawianie najbardziej podstawowych parametrów serwera. Tutaj widzimy obsługę połączeń przychodzących do serwera. Widać, że program odbiera i wyświetla jakieś dane. Po dłuższej analizie nie znalazłem tutaj nic ciekawego. Zaznaczam, że jeżeli chodzi o inżynierie wsteczną to nie znam się na tym kompletnie, więc mogłem coś przeoczyć. Spróbujmy wysłać długi ciąg znaków do serwera i prześledźmy jego zachowanie. Do tego celu użyję Immunity Debuggera. Zanim jednak zacznę korzystać z debuggera, napiszę prosty kod w Pythonie łączący się z serwerem, który zaraz odpalimy. Najpierw tworzymy zmienną buffer, która zawiera tysiąc znaków A. Potem tworzymy wirtualne gniazdo i przy jego użyciu łączymy się ze swoim systemem (równie dobrze mógłby być to localhost) na porcie 9999. Przypuszczam, że z takiego portu po raz kolejny skorzysta maszyna. Odbieramy odpowiedź maszyny i ją wyświetlamy, więc na początku, po prawidłowym połączeniu się, wyświetlamy banner maszyny. Potem wysyłamy nasz duży bufor i po raz kolejny sprawdzamy odpowiedź. Jeżeli program nie ulegnie awarii to do programu zostanie wysłany ciąg znaków "TEST", dzięki temu sprawdzimy czy nie utraciliśmy połączenia po wysłaniu dużych danych. Odpalmy debuggera. Teraz włączmy program komendą run i zerknijmy na konsole programu. Widzimy teraz, że nasz program nasłuchuje i oczekuje na połączenie. Omówmy krótko trzy okna, które będą nam potrzebne. Pierwsze w lewym górnym rogu to kod programu w Assemblerze. Drugie okno znajduje się prawym górnym rogu i wskazuje ono bieżące wartości rejestrów. Ostatnie, mieści się prawym dolnym rogu i wskazuje miejsce aktualnie wykonywanego kodu. Skoro mamy już to omówione, możemy odpalić nasz skrypt w Pythonie i zobaczyć jak zachowuje się program. ,Możemy teraz zobaczyć, że kilka rejestrów w tym rejestr EIP, zostały nadpisane znakami A. Udało nam się ustalić, że nasz program jest podatny na przepełnienie bufora. Wyślijmy teraz wzorzec, który wygenerujemy przez mone. Świetnie! Wzorzec składa się z 1000 znaków i możemy go znaleźć w pliku pattern.txt w lokalizacji: C:\Users\Twoja_nazwa_uzytkownika\AppData\Local\VirtualStore\Program Files (x86)\Immunity Inc\Immunity Debugger\pattern.txt. Wrzućmy go do Pythona do zmiennej buffer, zrestartujmy serwer i odpalmy skrypt ponownie. W pasku w lewym dolnym rogu wpiszmy !mona findmsp, żeby sprawdzić ile znaków jest potrzebnych do nadpisania określonych rejestrów. Jak widzimy, 524. Znak wzorca nadpisał wskaźnik EIP. Skoro znamy te informacje przygotujmy plan działania. Chcemy oczywiście nadpisać pamięć, aż do EIP, a w EIP chcemy wrzucić adres rejestru ESP, a tam umieścić kilka pułapek NOP dla wyrównania danych po których pojawi się nasz shellcode. Kluczem jest tutaj adres rejestru ESP. Większość programów zabezpiecza się przed namierzeniem rejestru ESP i z każdym uruchomieniem programu adres tego rejestru zmienia się - taki mechanizm nazywamy ASLR. Sprawdźmy czy jest on włączony w przypadku tego programu. Skorzystajmy z polecenia !mona modules. Możemy zobaczyć, że nasz program korzysta z kilku plików DLL, ale w przypadku każdego z nich prawie wszystkie zabezpieczenia są włączone, w tym niestety ASLR. Ale nic straconego. Nasz program nie posiada żadnych zabezpieczeń w tym ASLR co oznacza, że nieważne gdzie zostanie uruchomiony, zawsze będzie miał ten sam adres rejestru ESP. Wykorzystajmy ten fakt i poleceniem !mona jmp -r esp skoczmy do rejestru ESP w programie. Skoro znamy adres to mamy już prawie wszystko. Stwórzmy nasz bufor. Najpierw wygenerujmy 524 znaki A. Następnie dodajmy do nich adres ESP, który nadpisze adres powrotu. Pamiętajmy o kolejności bitów - little endian, czyli adres zapisujemy odwrotnie. Dodajmy do naszego bufora jeszcze kilka pułapek NOP. Ja dodałem ich 16 (pułapki NOP to oczywiście znak "\x90"). Na samym końcu dodamy jeszcze shellcode, który dopiero teraz będziemy generować. Plik serwera jest plikiem exe, w dodatku 32-bitowym, więc prawdopodobnie maszyna uruchomiona jest na Windowsie x86. Oczywiście generujemy reverse shella z adresem IP maszyny, z którą ma się połączyć ofiara. Warto dodać, że opcja py pozwala na generowanie shellcodu zgodnego z pythonem. Dzięki temu możemy go od razu przekleić do naszego skryptu. Opcja b określa jakie znaki są niedozwolone - których program nie powinien używać, bo np. w buforze programu mogą wywołać awarię. Najczęściej jest to znak NULL i bez niego spróbujemy. Jeżeli jednak będą problemy z połączeniem, wtedy sprawdzimy jakich znaków nie akceptuje bufor. Zobaczmy jak wygląda kod Pythona po dodaniu nowego adresu; pułapek NOP oraz shellcodu. Można byłoby też wykasować cały kod po wysłaniu naszych złośliwych danych, ale nie jest to konieczne. Odpalmy na maszynie, z którą ma połączyć się serwer, nasłuchiwanie na porcie, który zdefiniowaliśmy i odpalmy kod Pythona. Udało się! Mamy powłokę, z tym, że połączyliśmy się z naszym systemem. Zmieńmy więc adres localhost w Pythonie na adres podatnej maszyny i uruchommy skrypt znowu. Bingo! Jesteśmy teraz prawdopodobnie na systemie Windows, zalogowani jako użytkownik pluck. Zerknijmy na pliki w folderze roboczym. W folderze roboczym jest ciekawy skrypt checksrv.sh, w którym zabijany jest program wineserver oraz używane są komendy typowo Linuxowe. Wineserver służy do uruchamiania różnych programów przeznaczonych na Windowsa w Linuxie. Możemy więc przypuszczać, że znajdujemy się w środowisku emulującym działanie systemu Windows, które znajduje się tak naprawdę na Linuxie. Skoro tak, to uruchommy powłokę w trybie interaktywnym, korzystając z adresu bezwzględnego, dzięki temu powinniśmy odnieść się do ścieżki poza emulowanym środowiskiem. Podnoszenie UprawnieńNajpierw sprawdźmy czy możemy wykonywać programy, z wyższymi uprawnieniami od naszych, bez hasła. Jest tylko jeden taki program, który możemy uruchomić z uprawnieniami roota bez hasła. Spróbujmy to wykorzystać. Najpierw zapoznajmy się z nim.
Jak widać na zdjęciu powyżej, połączyłem się z nowym, wygenerowanym ładunkiem i przy pomocy Pythona wygenerowałem powłokę. Wróćmy teraz do programu, który można uruchamiać bez hasła z uprawnieniami roota. Sprawdźmy czy na stronie: https://gtfobins.github.io/ jest informacja o tym czy możemy skorzystać z polecenia man lub iwconfig do uruchomienia powłoki. Musimy uruchomić polecenie man i włączyć podręcznik prawdopodobnie dowolnego programu. Następnie na końcu podręcznika dopisujemy !/bin/sh i klikamy ENTER, żeby przejść z powrotem do powłoki. Bingo! Jesteśmy rootem. Teraz wylistujmy pliki w katalogu superużytkownika. Zerknijmy na historię basha i plik b.txt. Skończyliśmy właśnie maszynę. Najprawdopodobniej nie zawiera żadnej flagi. Zresztą, zostaliśmy rootem, więc mamy wszystkie uprawnienia na tym labie. Muszę przyznać, że ten lab był bardzo wyczerpujący. Nigdy wcześniej nie pisałem exploitów, oprócz maszyny, którą zrobiłem kilka dni temu i której rozwiązanie wrzucę w ciągu najbliższych dwóch dni. Mimo to takie wyzwanie bardzo mi się spodobało i chyba polubiłem crackowanie aplikacji. Z pewnością jednak jest to dla mnie duża dawka nowej wiedzy. Zauważyłem też, że ostatnio spisywanie rozwiązań do maszyn zajmuje mi mnóstwo czasu. Niedługo też zaczynam studia, więc pewnie dzisiaj albo jutro wrzucę post w którym poinformuję jak często będę publikował na blogu. W każdym razie, bardzo dziękuje za przeczytanie całego walktrough.
0 Comments
Leave a Reply. |