Rozwiązanie zadania CTF „All your sixty four are belong to Embler”

dodał 3 czerwca 2019 o 07:33 w kategorii Info  z tagami:
Rozwiązanie zadania CTF „All your sixty four are belong to Embler”

Zgodnie z wcześniejszą zapowiedzią publikujemy rozwiązanie zadania CTF przygotowanego przez Security Operations Center EXATEL, z którym mogliście się zmierzyć w ten weekend. Istotą tego zadania były różnice i błędy.

Tytuł samego zadania, czyli „All your sixty four are belong to Embler”, jest nieprzypadkowy – tak naprawdę zawiera on dwie podpowiedzi, które mogą już na starcie ułatwić zrozumienie struktury zadania. Jednocześnie zauważenie podpowiedzi w żadnym razie nie jest potrzebne do rozwiązania zadania. Można je rozwiązać, nie wiedząc nawet o ich istnieniu.

Sam tytuł powinien się nam niemal podświadomie kojarzyć z jednym z najsłynniejszych błędów językowym w popkulturze, który zaznaczył swoje miejsce w programach telewizyjnych, filmach, tekstach piosenek, komiksach, a nawet w modzie. Mowa oczywiście o błędzie w tłumaczeniu japońskiej gry Zero Wing z 1989 roku:

Jeśli tytuł naszego zadania zapiszemy zaraz pod oryginalnym tekstem z gry zawierającym błąd językowy, a następnie zakreślimy oba różniące się od siebie fragmenty – zobaczymy obie podpowiedzi:

Sekwencja „base64” oraz fonetycznie zapisane słowo „asembler” to nazwy dwóch mechanizmów użytych w zadaniu – poprawne rozpoznanie miejsca użycia obu mechanizmów jest już oczywiście konieczne, by zadanie rozwiązać.

Spójrzmy jednak na samą treść zadania.

Jeśli na pierwszy rzut oka nie wiemy jeszcze, czym jest ten tekst, wyszukanie kilku początkowych linii zadania w sieci najpewniej skieruje nas do refrenu piosenki Kultu „Baranek” autorstwa Stanisława Staszewskiego. Jednak również refren piosenki nie jest oryginalnym tekstem.

Właściwy, oryginalny tekst, którego modyfikację mamy przed sobą, to fragment opisu pasterki Zosi pochodzący z drugiej części dramatu „Dziady” autorstwa Adama Mickiewicza.

W tym momencie możemy już wprost nakreślić, na czym polega zadanie od strony technicznej: w tekst fragmentu dzieła polskiego romantyzmu wbudowana została odpowiednio zakodowana pętla kryptograficzna w asemblerze, która po uruchomieniu odszyfruje sekwencję tekstową – szukaną flagę.

Poniżej, krok po kroku pokażemy, jak to zadanie zrealizować.

Jeśli porównamy dostępny w internecie oryginalny tekst „Dziadów” z tekstem naszego zadania – zauważymy liczne błędy, literówki i dodatkowe znaki w stosunku do oryginalnego tekstu dramatu:

Zapisując skrupulatnie wszystkie znaki, którymi tekst naszego zadania różni się od tekstu dramatu (zachowując oczywiście kolejność wystąpienia modyfikacji) otrzymamy następujący łańcuch tekstowy:

=AQdCBQeJ7PkAhBiFsMwaLzwSjhAPELxLSw6i8KXUiGcoWSFodw8GALauGrRfgm0zMws

Tutaj należy zaznaczyć, że końcowa flaga chroniona jest pętlą kryptograficzną – przeoczenie lub pomyłka w zapisie nawet jednego znaku różniącego oba teksty (w tym znaków różniących się jedynie wielkością) czyni zadanie nierozwiązywalnym.

W tym kontekście na rozwiązującego zadanie czekają dwa kolejne utrudnienia. Ponieważ mamy do czynienia z prawie 200-letnim tekstem, możemy napotkać w sieci wersje różniące się od siebie jednym lub większą liczbą znaków przestankowych, a nawet liter – brak wiedzy o tym, której dokładnie wersji użyto w trakcie tworzenia zadania, powodowałby, że zadania nie dałoby się rozwiązać. Z tego powodu umieszczono w zadaniu kolejną podpowiedź – kilka wyrazów, których nie znajdziemy w oryginale dzieła Mickiewicza, a tylko w pobocznym opisie utworu na stronie – wolnelektury.pl:

Wyrazy te zostały umieszczone na stronie jako pomoc dla interpretujących utwór, zaznaczając motyw literacki opisany w danym fragmencie (umożliwiając czytającym wyszukanie wskazanych motywów w innych dziełach). Ponieważ słowa dodano do treści zadania dokładnie w miejscu ich wystąpienia w tekście na stronie (zachowując oczywiście wielkości liter), wskazano w ten sposób dokładną wersję tekstu w sieci, z której należy skorzystać, by wyszukać ciąg różnic.

Ostatnim utrudnieniem wbudowanym w sam tekst zadania, wymagającym od rozwiązującego znajomości zasady działania algorytmu base64, było zachowanie w wersji tekstu z portalu wolnelektury.pl oryginalnej pisowni jednego słowa: „różeczką”. Pomimo iż na portalu wyraz został zapisany przez „z”, w oryginale tekstu autor użył słowa zawierającego literę „ż” – jeśli postąpimy więc metodycznie, zapisując wszystkie znaki występujące w tekście, które dodano lub na które zamieniono znaki wersji z portalu wolnelektury.pl, nasza sekwencja będzie zawierać również znaki nie-alfanumeryczne (średniki, przecinki, dwukropki) oraz literę „ż” – wszystkie nienależące do domyślnego alfabetu algorytmu base64 (ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/) – osoba świadoma zawartości oryginalnego alfabetu base64 będzie mogła usunąć niepasujące znaki i otrzymać szukany końcowy ciąg.

Mając poprawnie zapisany ze znaków różnic w tekście domniemany ciąg base64, od razu widzimy, że jest z nim wciąż coś nie tak: znak „=” znajduje się po jego nieprawidłowej stronie (w kodowaniu base64 znak ten jest opcjonalnie umieszczany jako wyrównanie kodowanej treści na samym końcu sekwencji).

Jest to kolejna podpowiedź – ciąg został zapisany od tyłu, tak więc należy go odwrócić, by poprawnie sekwencję zdekodować.

Po odwróceniu i zdekodowaniu widzimy, że wynikowa treść jest binarna – gołym okiem możemy stwierdzić, że ma stosunkowo wysoką entropię, więc wstępnie moglibyśmy założyć, że treść została skompresowana lub zaszyfrowana:

Żadne z powyższych założeń nie jest jednak prawdą.

Jeśli nie dojdziemy do właściwej odpowiedzi sami, pomoże nam drugie słowo tytułowej podpowiedzi – binarna sekwencja to kod maszynowy reprezentujący program w asemblerze. By nie nakładać na gracza dodatkowej konieczności analizy dostępnych architektur procesorów i ręcznego dopasowywania dostępnych op-kodów do odpowiadających im mnemoników, użyto najbardziej oczywistej na dzień dzisiejszy (choć nieco już przestarzałej) architektury – 32-bitowego kodu maszynowego dla procesora x86, znajdującego się dzisiaj w mniejszej lub większej ilości w oprogramowaniu wszystkich naszych komputerów osobistych.

Jednym ze sposobów na podejrzenie zdeasemblowanej treści odpowiadającej kodowi maszynowemu jest skopiowanie szesnastkowej sekwencji do sekcji kodu zatrzymanego 32-bitowego procesu w debugerze. Na poniższym przykładzie sekwencję wstawiono w zatrzymany debugerem Ollydbg proces kalkulatora Windows, nadpisując zwyczajnie startową sekwencję kodu maszynowego programu, wskazaną przez rejestr EIP po zatrzymaniu procesu przez debuger:

W kodzie umieszczone zostały celowo 3 błędy, które trzeba zauważyć i poprawić, by pętla kryptograficzna mogła się poprawnie wykonać. Wymagać to będzie podstawowej znajomości języka asembler na poziomie analizy kodu i najprostszych niskopoziomowych mechanizmów, takich jak programowe liczniki czy zasada działania pętli i stosu.

Na wstępie kod odkłada na stosie cztery 32-bitowe stałe tworzące po połączeniu 16-bajtową zaszyfrowaną sekwencję z naszą szukaną flagą:

Druga część kodu maszynowego zawiera podwójnie zagnieżdżoną pętlę kryptograficzną ADD -> ROL -> XOR -> ROR i jest odpowiedzialna za deszyfrację 16-bajtowej sekwencji na stosie.

Zadanie można rozwiązać na kilka sposobów – po poprawieniu błędów w kodzie i przeanalizowaniu jego działania możemy od podstaw w dowolnym języku stworzyć własny deszyfrator, który odszyfruje szukaną sekwencję. Najprościej będzie jednak po naprawieniu kodu po prostu go uruchomić i stawiając pułapkę na samym końcu kodu, poczekać aż zewnętrzna pętla skończy pracę i odszyfruje flagę.

Dodatkowo należy mieć na uwadze jeszcze jeden element – jak można zauważyć zewnętrzna pętla sterowana wartością w rejestrze EDX zlicza od zera, inkrementując licznik co 1 aż do kolejnej wartości zero. Oznacza to, że program zakończy działanie dopiero po przepełnieniu 32-bitowej wartości EDX, czyli wykonaniu przez zewnętrzną pętlę 2^32 iteracji. Innymi słowy, żeby odszyfrować naszą flagę pętla musi wykonać się ponad 4 miliardy razy. Wykonanie pętli zawierającej kopię przedstawionego algorytmu np. w Pythonie zajęłoby kilka dni – natomiast wykonanie przedstawionego algorytmu w jego obecnej postaci (czyli w czystym asemblerze) zajmie procesorowi i5 taktowanemu częstotliwością 2.6 GHz ok. 8 minut.

Pierwszy błąd w kodzie zadania, który musimy poprawić, jeśli chcemy uruchomić kod maszynowy, to niepotrzebny skok znajdujący się zaraz za sekwencją odkładającą szyfrogram na stosie. Jeśli przeanalizujemy działanie skoku, zobaczymy, że omija on dwie kolejne instrukcje następujące po nim, w tym instrukcję inicjującą wskaźnik kryptogramu na stosie (MOV EAX,ESP) przez skopiowanie wskaźnika stosu do rejestru EAX – jeśli nie wykonamy instrukcji inicjującej wskaźnik, proces w pierwszej instrukcji po skoku spróbuje odczytać 1 bajt spod adresu wskazanego przez niezainicjalizowany przez nas rejestr EAX i najpewniej zakończy działanie po wystąpieniu wyjątku. Mamy więc dwie opcje: możemy usunąć instrukcję JMP, wypełniając ją NOP-ami (sekwencja 90 90). Możemy też zmienić zakres skoku na znajdującą się zaraz za nim instrukcję, zerując offset w argumencie (zmieniając sekwencję z EB 04 na EB 00):

Kolejne dwa błędy umieszczono w dwóch pozostałych instrukcjach skoku, sterujących obiema pętlami. Jak zauważymy, argumenty zawierające offset obu skoków są wyzerowane – odwrotnie niż przypadku pierwszej pętli instrukcje te nie wykonają więc żadnej pracy i niezależnie od wyniku instrukcji dekrementacji licznika w rejestrze CL oraz inkrementacji licznika EDX procesor przejdzie do instrukcji umieszczonej zaraz za instrukcjami skoku.

Jak widać, licznikiem pierwszej pętli jest 8-bitowy rejestr CL inicjowany wartością 15 (0x0F) przed wejściem w pętlę. Licznik zlicza do zera, iterując wewnętrzną pętlę po kolejnych bajtach aktualnego stanu 16-bajtowego bufora kryptogramu. Oznacza to, że pierwszy skok powinien zapętlać kod w miejsce następujące zaraz po instrukcji MOV CL,0F inicjującej licznik pętli wewnętrznej:

Podobnie musimy naprawić zakres drugiej, zewnętrznej pętli. Ponieważ wewnętrzna pętla iteruje po kolejnych 16 bajtach kryptogramu, wykonując na każdym operację arytmetyczną ADDROLXORROR, musimy przed rozpoczęciem kolejnej iteracji pętli wewnętrznej zresetować jej licznik CL i przestawić wskaźnik pozycji w kryptogramie wskazywany przez EAX na początek kryptogramu – czyli efektywnie zapętlić kod drugiej pętli do instrukcji MOV EAX,ESP:

W tym miejscu powinniśmy już mieć działający kod, który wystarczy tylko uruchomić. Na jego zakończenie i odszyfrowanie flagi zaczekamy spokojnie, umieszczając pułapkę w pierwszej instrukcji za zewnętrzną pętlą:

Krokując wykonanie w debugerze, możemy podejrzeć poprawność operacji arytmetycznych i pracę całości pętli kryptograficznej – bufor po odpowiednio 1 i 2 iteracji zewnętrznej pętli deszyfrującej powinien wyglądać następująco:

Ostatecznie, po wykonaniu przez zewnętrzną pętlę dokładnie 4 294 967 296 iteracji kod zakończy działania, zatrzymując się na pułapce, a w miejscu kryptogramu na stosie zobaczymy następującą treść:

W ten sposób otrzymujemy szukaną przez nas flagę „ESC{greenbastrd}” zawierającą nazwę ukrytego alter ego Bubblesa, bohatera serialu „Trailer Park Boys”:

Gratulujemy wszystkim Czytelnikom, którzy zmierzyli się z tym zadaniem, w szczególności Jackowi W., b oraz adrb, którym udało się zdobyć flagę.