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
Cześć. Rozumiem, że trafiłeś na ten mini kurs ponieważ chcesz rozpocząć swoją przygodę z MIPS’em w kontekście bezpieczeństwa, poznać assembly i zagłębić się w proces reverse engineeringu różnych aplikacji opartych o tą właśnie architekturę. No i super, w takim razie czeka Cię trochę wiedzy do przyswojenia i masa ciekawych, nowych rzeczy do odkrycia ;)
Mam nadzieję, że ta seria mini tutoriali (obecnie czytasz część pierwszą) będzie przede wszystkim odpowiedzią na pytanie czy ten temat w ogóle jest dla Ciebie, chociaż liczę na to, że tak i że nie odpuścisz ;) Zarezerwuj sobie trochę spokojnego czasu, odpal terminal i jedziemy.
Środowisko
Na początek system operacyjny, na którym będziemy sobie działać. Tutorial ten będzie oparty o system Debian chociaż jeśli masz już doświadczenie z linuksem to nie powinieneś mieć kłopotu z innym systemem tego typu.
Konfigurując swoje środowisko mamy tutaj do wyboru co najmniej dwa wyjścia. Instalacja systemu operacyjnego wybranej architekturze lub też trochę prostsze rozwiązanie jakim jest pakiet Buildroot – super fajne narzędzie do budowania oprogramowania dla systemów wbudowanych bardzo różnych architektur. Na nim będziemy działać. Do tego jeszcze będziemy potrzebowali emulatora o nazwie Qemu.
Instalacja Qemu
Bierzemy się za instalację Qemu:
~$ apt-get install qemu-user-static
Git. Mamy to.
Instalacja & konfiguracja Buildroot
Pobierz sobie najnowszego Buildroor’a. Na dzień, w którym to pisałem najnowszą wersją była ta z dnia 2019.02.9.
~$ wget https://buildroot.org/downloads/buildroot-2019.02.9.tar.gz ~$ tar zxf buildroot-2019.02.9.tar.gz ~$ cd buildroot-2019.02.9
Teraz musisz skonfigurować ten pakiet a następnie go skompilować. Odpalasz konfigurator:
~$ make menuconfig
Buildroot wymaga niektórych bibliotek do działania (jak np. ncurses,bc czy unzip). Jeśli ich nie posiadasz, możesz je zainstalować za pomocą apt-geta (“apt-get libncurses5-dev libncursesw5-dev bc unzip”).
Jeśli po wykonaniu polecenia “make menuconfig” zobaczysz konfigurator jak poniżej na obrazku to znaczy, że się udało.
Konfiguracja
Z menu startowego Buildroot’a wybierz “Target options”. Konfigurator ma wybraną standardową architekturę (jak na obrazku poniżej) dlatego musimy ją zmienić na MIPSa.
Klikasz enter i jesteś już w menu z dostępnymi opcjami. Powinieneś teraz zaznaczyć opcję “MIPS (little endian)” a później < Select > jak na obrazku poniżej. UWAGA – w tej części kursu zostawiamy na razie opcję 64-bitową tak więc zrób to uważnie i NIE zaznaczaj “MIPS64”.
Po dokonaniu wyboru powinna widnieć nazwa “Target architecture (MIPS (little endian))” zamiast i386 tak jak to widać na obrazku powyżej. Teraz kliknij w < Exit > aby powrócić do głównego menu.
Okej. Wejdź teraz do zakładki “Toolchain” z głównego menu (obrazek poniżej):
Tutaj musisz wybrać opcję “uClibc-ng” dla “C library”. Powinna być ustawiona standardowo (obrazek poniżej). Gdyby jednak było inaczej to wejdź w opcje “C library” i wybierz pozycję “uClibc-ng”.
Nie wychodź jeszcze do głównego menu. Zjedź kursorem na dół i poszukaj opcji “Build cross gdb for the host”. Będziemy potrzebowali debuggera dla MIPS’a tak więc i ta opcja musi być zaznaczona (obrazek poniżej).
Super. Masz już skonfigurowanego Buildroot’a. Przejdź teraz do menu głównego (< Exit > i jesteś). Zapisz konfigurację wybierając z menu na dole opcję “< Save >”. Powinien pojawić Ci się formularz z potwierdzeniem zapisania konfiguracji, którą stworzyłeś (obrazek poniżej). Klikasz “OK“ i lądujesz w terminalu.
W terminalu podajesz “make” i enter. To wszystko. Aha, jeszcze jedno. Proces kompilacji może zająć dłuższą chwilę tak więc możesz iść sobie po kawę czy coś – tak zawsze radzą programiści geeki jak im się coś będzie długo kompilować. Niech tak będzie.
Jeśli wszystko się zrobiło prawidłowo bez błędów to masz już gotowe środowisko do pracy! W katalogu “buildroot-2019.02.9/output/host/usr/bin” znajdziesz wszystkie potrzebne Tobie narzędzia.
Mamy już wszystko co potrzeba tak więc w dalszej części kursu przejdziemy już do omawiania kodu.
Zaczynamy
Instrukcje warunkowe
Stały i podstawowy element programowania, który pozwala wykonywać konkretne operacje w zależności od wcześniej spełnionego warunku oraz jeden z głównych tematów naszej dzisiejszej lekcji. Jeśli jakimś cudem nie spotkałeś się z tym terminem, ogólną koncepcję masz poniżej:
Jeśli mi się teraz kompiluje
to idę po kawę
Programuję sobie dalej
Może być też taka sytuacja:
Jeśli mi się teraz kompiluje
to idę po kawę
w innym przypadku:
to idę tylko po cukierki ;)
Programuję sobie dalej
W obu przypadkach “jeśli mi się teraz kompiluje” to na pewno jestem teraz w drodze, bo “idę po kawę” a później “Programuję sobie dalej”. Jeśli jednak “mi się NIE kompiluje” – to w pierwszym przykładzie przejdę odrazu do “Programuję sobie dalej” natomiast w drugim najpierw “idę po cukierki” a dopiero później “Programuję sobie dalej”. Uff.
Jak to teraz technicznie wygląda w C. Kod poniżej:
#include <stdio.h> int main() { int d = 4; if(d == 4) { puts("warunek spelniony"); } return 0; }
Kompilacja:
~$ mipsel-linux-gcc -static -o lesson01 lesson01.c
Proces debugowania
W tym rozdziale nauczysz się jak taki program możesz sam debugować. Rozumiem, że masz już odpalony jeden terminal. Potrzebny będzie Ci także drugi.
W pierwszym uruchamiamy debugger gdb, ustawiamy breakpointa na funkcję “main” oraz uruchamiamy tryb nasłuchiwania na połączenie:
~$ mipsel-linux-gdb lesson01 … (gdb) break main (gdb) target remote 127.0.0.1:7331
[Pierwszy terminal]
Dobra, teraz nasz debugger nasłuchuje na porcie 7331 i oczekuje, aż badany program zgłosi się do niego.
W drugim terminalu uruchom skompilowany program ale zanim to zrobisz, skopiuj sobie emulator qemu do bieżącego folderu aby było Tobie łatwiej:
~$ cp `which qemu-mipsel-static` . ~$ sudo chroot . ./qemu-mipsel-static -g 7331 lesson01
[Drugi terminal]
Teraz zerknij na pierwszy terminal. Debugger powinien odebrać połączenie z emulatora:
(gdb) target remote 127.0.0.1:7331 Remote debugging using 127.0.0.1:7331 0x00400190 in __start ()
Wygląda dobrze. Zobaczmy razem jak wygląda funkcja “main” oraz przedstawiona w niej instrukcja warunkowa. Do tego posłużymy się poleceniem “disassemble main” w gdb.
Rejestry
Rozpoczniemy naszą analizę kodu od zaznajomienia się z rejestrami. Rejestr jest taką komórką w pamięci procesora, która przechowuje pewne istotne wartości. MIPS standardowo posiada 32 (32 bitowych) rejestrów do ogólnych zastosowań oraz 32 rejestry zmiennoprzecinkowe.
Masz je przedstawione w tabelce poniżej:
Rejestr | Nazwa w assembly | Opis |
r0 | $zero | Zawsze zawiera 0 |
r1 | $at | Zarezerwowany |
r2 – r3 | $v0 – $v1 | Wartości zwracane przez funkcje |
r4 – r7 | $a0 – $3 | Argumenty funkcji |
r8 – r15 | $t0 – $t7 | Rejestry ogólnego przeznaczenia |
r16 – r23 | $s0 – $s7 | Rejestry ogólnego przeznaczenia tzw. “Saved registers” |
r24 – r25 | $t8 – $t9 | Rejestry ogólnego przeznaczenia |
r26 – r27 | $k0 – $k1 | Rejestry zarezerwowane dla kernela |
r28 | $gp | Rejestr wskaźnika danych globalnych |
r29 | $sp | Rejestr wskaźnika stosu |
r30 | $fp | Rejestr wskaźnika ramki |
r31 | $ra | Adres powrotu |
Zwróć uwagę na to, że do niektórych rejestrów programista nie ma dostępu ($k0 – $k1). Istnieją także rejestry od $f0 do $f31, które są przeznaczone do liczb zmiennoprzecinkowych.
Analiza kodu
Zapoznałeś się już z rejestrami zatem możemy przystąpić do analizy. Wyjaśnienie znajdziesz pod obrazkiem, tymczasem zerknij na kod funkcji “main” poniżej:
Trochę się tutaj dzieje. Dla przypomnienia Twój program napisany w języku C ma zadeklarowaną zmienną typu integer z wartością “4”. Jeśli zmienna “d” ma wartość “4” co jest w tym przypadku prawą – zostanie wyświetlony komunikat.
Zobacz teraz jak to robi MIPS:
li v0, 4 – (load immediate) wrzuca wartość typu integer (naszą “4”) do tak zwanego rejestru “v0“
sw v0, 24(s8) – (store word) zapisuje 4 bajtową wartość (word – słowo) z rejestru “v0” do pamięci pod adres rejestru “s8” + offset 24.
Instrukcji typu “store” jest także znacznie więcej. Do niektórych możemy zaliczyć: “sb” – store byte, “sh” – store halfword czy “sd” – store doubleword. Instrukcje te dla odmiany działają w odwrotną stronę (co do instrukcji “load”): biorą wartość/rejestr z lewej strony a następnie zapisują ją po stronie prawej po przecinku.
Zagadnienie pamięci, stosu będzie omawiane w dalszych częściach kursu tak więc na tą chwilę wyobraź sobie tylko że pamięć to taki duży schowek z wieloma przegródkami z góry na dół. O instrukcji “sw” więcej poczytasz tutaj.
lw v1, 24(s8) – wrzuca wartość ”4” z pamięci, pod którą wcześniej była zapisana tym razem do rejestru “v1”. Dlaczego? Ponieważ do rejestru..
li v0, 4 – ..wędruje druga wartość integer “4” z instrukcji “if ( 4 == ” w Twoim programie.I w końcu przychodzi czas na instrukcję warunkową:
bne v1, v0, 0x4003e0 <main+80> – Branch if Not Equal to immediate – w tłumaczeniu na nasze:
bne x, y, [skocz pod adres tu podany jeśli v0 nie jest równe v1]
W miejsce “x” oraz “y” mamy wstawione rejestry “v0” i “v1”. Procesor będzie działał tak:
Sprawdzę “v0” z “v1” i sprawdzę czy są równe.
Jeśli są -> idę dalej (obrazek “funkcja main” – linia +48)
Jeśli NIE są -> skoczę pod adres 0x4003e0 (obrazek “funkcja main” – linia +80)
Jeśli kiedyś programowałeś w językach takich jak C lub Perl to spotkałeś się na pewno z taką instrukcją jak “goto etykieta;”, która powoduje skok w dowolne miejsce w programie.
MIPS posiada całą gamę takich instrukcji, które możemy podzielić na trzy grupy:
- Instrukcje odnoszące się do rejestru PC*: rozpoczynające się od “b” czy “j” (np. “jr”) oraz adresu docelowego, pod który następuje skok. Można je porównywać właśnie z omawianym wcześniej “goto”.W obrazku “funkcja main” w linijce 140 występuje taki właśnie skok pod adres rejestru “ra”.
- Instrukcje typu “jump and link” lub “branch and link”, zakończone są końcówką “al”, o których pomówimy sobie jeszcze nieco więcej w kolejnych częściach kursu.
- Instrukcje warunkowe takie jak “beq”, “bgez”, “bne” itp, które pozwalają na skok tylko po spełnieniu określonych warunków. Taki właśnie przykład spotkaliśmy w naszym kodzie (“bne v1, v0, 0x4003e0”). Oczywiście instrukcja “bne” nie jest odosobniona i takich instrukcji jest więcej jak chociażby:
- beq rejestr1, rejestr2, adres_skoku
branch of equal – skok jeśli wartości rejestrów są równe - bgez rejestr1, adres_skoku
branch on greater or equal zero – skok jeśli wartość rejestru jest równa lub większa od zera - bgtz rejestr1, adres_skoku
branch on greater than zero – skok jeśli wartość rejestru jest większa od zerai tak dalej.
PC* inaczej “program counter” jest specjalnym rejestrem, który przechowuje adres następnej wykonywanej instrukcji w programie (następna po tej, którą wykonuje w danym momencie).
Analizując kod często spotkasz się z sytuacją, kiedy to przy skoku do funkcji będą występowały dwie instrukcje takie jak:
- beq rejestr1, rejestr2, adres_skoku
- załadowanie adresu funkcji do rejestru “$t9” za pomocą instrukcji “lw” np. “lw t9, adres”
- skok pod adres funkcji za pomocą instrukcji “jalr t9” lub “jr t9”
Na dzisiaj tyle. Teraz zadania domowe ;)
Zadania domowe
1. Dla leniuchów:
Linków do manuala nie będzie. Musisz odnaleźć resztę instrukcji warunkowych dla MIPSa i przeanalizować jak one działają.
2. Dla komandosów:
Zrozumieć i wytłumaczyć jak działa poniższy kod:
Małe hinty:
- załóżmy, że tam gdzie jest nazwa funkcji “printf” – program wypisze literkę “X” lub “Y”
- traktuj linię od +132 w dół (do +156) jak koniec działania programu
- nie skupiaj się na instrukcjach “bal” czy “nop”, najważniejsze jest to, co program robi w operacjach warunkowych
- sprawdź sobie jak działa instrukcja “b”, będzie Tobie łatwiej
- czerwony kolor nic nie znaczy – po prostu zakryłem kod, który na tą chwilę jest zbędny do analizowania i mógłby odciągać od głównego rozwiązania
- nie tylko instrukcje rozpoczynające się od “b” są tutaj kluczowe ale też pewna instrukcja.. ;)
Ajj, chyba za dużo podpowiedzi jak na to zadanie. Działaj i nie poddawaj się ;)
- Mateusz Wójcik – Squadron31
Bardzo fajny tutorial :D Aż mi się łezka w oku zakręciła na wspomnienie pisaniu programu w assemblerze właśnie na MIPSa.. :’)
Ale szczerze nie spotkałem jeszcze takich programów na żywo.. gdzie można się z takimi spotkać?
(niekoniecznie z wykluczeniem konkretnych firm zajmujących się m.in. reversem :D)
@MateuszWójcik
Jak czytam kod, to oczy mi krwawią od nieoptymalizacji. Wciśnięcie czegoś do pamięci, po czym natychmiastowy odczyt do tego samego (pół biedy innego) rejestru? To ma jakieś uzasadnienie w (powalonej) architekturze MIPS?
Czekam z Colą i orzeszkami na tłumaczenie nop po skokach :)
Świetny tutorial – proponuję na początek do testów użyć wersji online https://godbolt.org/
Trzeba wybrać kompilator MIPS gcc 5.4 (a nie MIPS64 ….) . Przy okazji ładnie pokoloruje fragmenty kody vs. odpowiednie fragmenty kodu asemblera
Kiedy bedzie wiecej? nop ;)
Witam!
Bardzo podoba mi się Twój poradnik, ale niestety jestem troche jeszcze zielony z linuxem i nie wiem jak rozwiązać ten problem(błąd skopiowany prosto z konsoli):
checking whether mknod can create fifo without root privileges… configure: error: in `/root/buildroot-2019.02.9/output/build/host-tar-1.29′:
configure: error: you should not run configure as root (set FORCE_UNSAFE_CONFIGURE=1 in environment to bypass this check)
See `config.log’ for more details
make: *** [package/pkg-generic.mk:231: /root/buildroot-2019.02.9/output/build/host-tar-1.29/.stamp_configured] Błąd 1
No i już próbowałem uruchamiając komendę „make” jako „nieroot” ale nic nie dało. Brak narazie mi pomysłów. Dziękuję z góry za pomoc.
Komunikat błędu mówi, że wiecej szczegółów znajdziesz w pliku 'config.log’. Zaglądałeś tam?
Jeśli w 100% ufasz Makefile’owi oraz podprogramom przez niego uruchamianymi, to mam dla Ciebie NIEBEZPIECZNIE rozwiązanie:
1. przejdź na roota
2. $> export FORCE_UNSAFE_CONFIGURE=1
3. $> make
Jeśli nie chcesz NIEBEZPIECZNEGO rozwiązania stosować, to już musisz pogrzebać czemu jako nie-root nie dało się tego uruchomić.
Ten sam problem u mnie :/
cd /root/buildroot-2019.02.9
export FORCE_UNSAFE_CONFIGURE=1 && make
I będzie działać :)
make[1]: Leaving directory '/home/pawel/buildroot-2019.02.9/output/build/host-gcc-initial-7.4.0/build’
package/pkg-generic.mk:238: recipe for target '/home/pawel/buildroot-2019.02.9/output/build/host-gcc-initial-7.4.0/.stamp_built’ failed
make: *** [/home/pawel/buildroot-2019.02.9/output/build/host-gcc-initial-7.4.0/.stamp_built] Error 2
co jest nie tak?
Hej Paweł :)
Napisz proszę na jakim linuksie działasz?
Czy nie wkradł się błąd?:
bne x, y, [skocz pod adres tu podany jeśli v0 nie jest równe v1]
Sprawdzę “v0” z “v1” i sprawdzę czy są równe.
Jeśli są -> skoczę pod adres 0x4003e0 (obrazek “funkcja main” – linia +80) – TUTAJ BEZ SKOKU??
Jeśli NIE są -> idę dalej (obrazek “funkcja main” – linia +48) – CHYBA TUTAJ SKOK??
Paweł, zgadza się, dzięki za czujność, poprawimy :)
s/Tobie/Ci/g
hxxps://sjp.pwn.pl/poradnia/haslo/ci-czy-tobie;5696.html
li v1, 24(s8) – wrzuca wartość ”4” z pamięci, pod którą wcześniej była zapisana tym razem do rejestru “v1”. Dlaczego? Ponieważ do rejestru..
Tutaj chyba powinno być lw a nie li :)
W pierwszej lekcji jest podany program w C
Nie zainstalowało się środowisko programistyczne
chociaż wszystko jakby zgodnie z planem bo kompilacja przebiegła pomyślnie
i pierwszy terminal zadziałał poprawnie, a drugi nie widzi pliku programu.
Jeśli powinienem przepisać ten program w C, to brakuje mi platformy.
pozdrawiam
2 razy prosiłem o pomoc
Nie mogę ruszyć z miejsca bo pomocy nie otrzymałem
Wszystko zainstalowałem kompilacja zakończona
Pierwszy terminal oczekuje drugi error
przypuszczam że nie mam skompilowanego programu napisanego w c, bo nie zainstalowało się chyba środowisko programistyczne, jeśli poprawnie to nazywam i nie miałem w czym przepisać programu, a więc skompilować
Bardo proszę o wsparcie
Dziękuję
kompilacja jest tutaj: mipsel-linux-gcc -static -o lesson01 lesson01.c
póżniej sprawdź czy powstał plik lesson01: ls -al
póżniej kopiujesz qemu-mipsel-static
i dalej zgodnie z poradnikiem…
Tak w zasadzie to wystarczy tylko dobrze skopiować polecenia do konsoli.
Czy potrzeba drogiego sprzetu laptopa by moc zaczac sprobowac swoich sil?!