Jak napisać ciekawe CrackMe na CTF – instrukcja krok po kroku

dodał 13 lipca 2016 o 19:01 w kategorii HowTo  z tagami:
Jak napisać ciekawe CrackMe na CTF – instrukcja krok po kroku

Jeśli brakowało Wam ostatnio na naszej stronie tekstów o bardziej technicznym charakterze, to mamy nadzieję, że po lekturze poniższego artykułu będziecie zadowoleni. Autorem wpisu jest Bartosz Wójcik. Dziękujemy!

W dobie coraz popularniejszych konkursów CTF (ang. Capture The Flag) i dominacji na tym polu polskich ekip, takich jak m.in. Dragon Sector, pomyślałem, że ciekawym tematem byłoby zaprezentowanie, jak stworzyć proste CrackMe, wraz ze wskazaniem oryginalnych technik utrudniających jego złamanie i analizę.

crackme-z3s

Jeśli sam kiedykolwiek interesowałeś się inżynierią wsteczną (ang. reverse engineering), próbowałeś sił w konkursach CTF lub chciałeś sam stworzyć CrackMe i utrzeć nosa urwisom z innych zespołów CTF to jest to artykuł dla Ciebie.

Znasz temat? Nie czytaj dalej, tylko spróbuj zdobyć flagę!

Z góry powiem, że jeśli znasz temat i chcesz spróbować swoich sił w CrackMe, które jest tutaj opisane, przestań czytać dalszą część, w której znajdziesz szczegółowy opis mechanizmów zabezpieczeń, pobierz skompilowane CrackMe i spróbuj zdobyć flagę, a dopiero później wróć do artykułu żeby zweryfikować fakty. Poziom CrackMe, jeśli stosować skalę easy, medium i hard oceniam na medium.

Pobierz CrackMeZ3S.zip

Do uruchomienia CrackMe być może będziesz potrzebował bibliotek Visual C++ Redistributable Packages for Visual Studio 2013.

Jeśli pobrałeś CrackMe to nie bądź chytrym liskiem i nie czytaj dalej. Jeśli jednak chciałbyś się dowiedzieć, jak stworzyć CrackMe to zapraszam do dalszej lektury.

Co to jest CrackMe?

Jeśli jesteś czytelnikiem Z3S to zapewne wiesz co to HackMe, może to być specjalnie przygotowana strona internetowa, w której należy znaleźć lukę lub wykorzystać jakąś słabość, aby dostać się do ukrytej treści.

CrackMe to nic innego jak program komputerowy, stworzony tylko i wyłączanie w celu obejścia jego zabezpieczeń i uzyskania poprawnego hasła czy klucza seryjnego. CrackMe były popularne znacznie wcześniej, niż same konkursy CTF, koderzy mogli dzięki temu w kreatywny sposób rywalizować pomysłami na zabezpieczenia z crackerami, którzy próbowali je przełamać.

crackme-z3s-strona-crackmes-de

Do dzisiaj istnieje strona crackmes.de, gdzie znajduje się działające od 20 lat (sic!) archiwum z prawie 3000 (!) CrackMe oraz tutorialami do nich, strona jest aktywna i cały czas dodawane są nowe CrackMe.

Jakie są rodzaje CrackMe?

Jeśli wchodzić w szczegóły to programy CrackMe tradycyjnie dzielą się na kategorie w zależności od tego co jest celem ustalonym przez autora.

Można wyróżnić:

  • CrackMe – celem jest zwyczajowo wygenerowanie klucza seryjnego, pliku rejestracyjnego lub kombinacji „nazwa użytkownika” i pasującego do niego klucza. Wykluczone jest modyfikowanie pliku aplikacji, bo to nie jest cel i tradycyjne CrackMe nie są zabezpieczone przed modyfikacjami w binarce.
  • KeygenMe – jak nazwa wskazuje, należy stworzyć generator kluczy, różni się tym od zwykłego CrackMe, że zwykle wykorzystuje ciekawe algorytmy kryptograficzne i należy tutaj posiadać wiedzę właśnie na temat kryptografii oraz metod szyfrowania, aby utworzyć generator kluczy. Często wykorzystywane są liczby typu BIGNUM oraz algorytmy takie jak ECC, RSA czy DSA, które wymagają łamania wykorzystanych kluczy techniką brute-force.
  • ReverseMe – najbardziej skomplikowana forma CrackMe, której celem jest doprowadzenie np. do sytuacji w której ReverseMe wyświetli jakiś komunikat, np. „Dziękuję za rejestrację”. ReverseMe często są nie tylko bardzo dobrze zabezpieczone jeśli chodzi o same algorytmy rejestracyjne, ale także wykorzystują szereg technik utrudniających zmodyfikowanie pliku aplikacji, bo to jest najczęściej wymagane do osiągnięcia założonego celu (np. dopisanie jakiejś funkcjonalności).
  • UnpackMe – trochę inna forma CrackMe, w której otrzymujemy plik skompresowany, zabezpieczony lub poddany obfuskacji własnym lub komercyjnym oprogramowaniem np. exe-pakerem czy exe-protectorem i naszym celem jest rozpakowanie pliku, czyli przywróceniem go do stanu pierwotnego, najczęściej chodzi o odbudowę tabeli importów, przywrócenie oryginalnego kodu, odtworzenie struktur pliku wykonywalnego, tak żeby plik aplikacji działał bez warstwy zabezpieczenia. W przypadku własnych rozwiązań mogą to być fajne i ciekawe zadania, jednak jeśli zastosowane zostanie komercyjne zabezpieczenie jest to już zabawa dla prawdziwych hardcorów.

Cel naszego CrackMe

W wersjach na konkursy CTF celem jest zwykle zdobycie ukrytej „flagi”. Celem naszego CrackMe będzie odgadnięcie oraz ustawienie poprawnych „kluczy” dostępowych, po odgadnięciu których uzyskamy flagę. Nasze klucze będziemy pobierać z różnych źródeł dla urozmaicenia rozrywki osób próbujących je uzyskać.

Każdy klucz będzie posiadał dodatkową i prostą weryfikację, tak aby nie za bardzo komplikować całości.

System operacyjny i język programowania

Docelowe CrackMe zostanie stworzone na platformę Windows 10 (bez problemu działa także na starszych Windows np. 7) z wykorzystaniem języka C++ skompilowanego do natywnego kodu x86. Wykorzystamy kilka ciekawych mechanizmów i funkcji WinAPI, które być może nie są powszechnie znane. W CrackMe wykorzystamy kodowanie znaków UNICODE, co również może spowodować małe problemy w różnego rodzaju narzędziach do reversingu.

TLS Callbacks

Na dzień dobry wykorzystamy mało znany mechanizm TLS Callbacks. Związany jest on z funkcjonowaniem mechanizmu Thread Local Storage, który pozwala wielowątkowym aplikacjom na wykorzystywanie tych samych zmiennych globalnych, które jednak będą miały inne wartości dla każdego wątku programu. Przykładowo deklarujemy w C++ zmienną i oznaczamy ją specjalnym znacznikiem:

W tym wypadku, każdy wątek w aplikacji będzie posiadał odrębną kopię tej wartości dla swojej dyspozycji.

W ramach tego mechanizmu istnieje coś takiego jak TLS Callbacks. Można to przyrównać do funkcji wejściowej dla bibliotek dynamicznych DLL – czyli DllMain(). System Windows wywoła funkcję zadeklarowaną jako TLS Callback, aby poinformować o przyłączeniu do procesu aplikacji, nowo załadowanych bibliotekach czy utworzonych wątkach, tak samo jak wielokrotnie wywołuje DllMain(), z tą małą różnicą, że w przypadku wykorzystania tego mechanizmu w aplikacji EXE, kod callbacka zostanie wywołany przed samym punktem wejściowym aplikacji (tzw. entrypoint).

Jest to bardzo istotny szczegół, ponieważ teoretycznie pozwala niezauważenie uruchomić dowolny kod, na który nikt nie zwróci uwagi bez wykorzystania odpowiednich opcji debuggera.

Mechanizm TLS Callbacks funkcjonuje od czasów Windows XP, jednak jego działanie nie jest w 100% takie samo dla różnych wersji Windows (nie wszystkie zdarzenia są obsługiwane). Jest on wykorzystywany przez niektóre systemy ochrony oprogramowania do uruchamiania kodu anty-debug przed uruchomieniem samego kodu aplikacji.

W naszym CrackMe wykorzystamy mechanizm TLS Callbacks również do sprawdzenia obecności debuggera.

Jeśli uruchomimy CrackMe pod takim debuggerem jak np. OllyDbg v2 bez żadnych wtyczek ukrywających jego obecność, kod w TLS Callback wykryje jego działanie i zablokuje dalsze ładowanie aplikacji, wszystko będzie wyglądało jakby stanęło w miejscu.

Sprawdzanie kluczy

Metody sprawdzania kluczy będą działać w odrębnych wątkach. Wielowątkowość zawsze jest mniejszą lub większą przeszkodą w debugowaniu aplikacji. Kolejne funkcje weryfikujące klucze będą kaskadowo tworzyć wątki dla następnych metod sprawdzających.

Po wykryciu poprawnego klucza dostępowego, wykorzystamy system zdarzeń (ang. event) do oznaczenia, które klucze dostępowe zostały poprawnie ustawione.

Fałszywy klucz

Zwykle CrackMe wymagają podania numeru seryjnego lub hasła i o tym też nie zapomnimy! W naszym CrackMe będziemy prosić o podanie hasła, sprawdzać skrupulatnie jego poprawność i zapisywać wynik sprawdzenia, jednak w decydującym punkcie CrackMe będzie to ignorowane.

Będzie to jedyny klucz, o jaki poprosi CrackMe z poziomu konsoli, czyli najbardziej będzie rzucał się w oczy. Jednak nasz klucz będzie tylko dodany dla zmyłki.

Dla wciągnięcia atakującego w rozgrywkę, wykorzystamy najpopularniejsze i przestarzałe hashowanie danych bazujące na algorytmie MD5. Wynik porównamy z zakodowanym na stałe hashem dla słowa „fake”.

Hash dla tego krótkiego słowa można będzie bez problemu znaleźć w tablicach z obliczonymi wartościami hash dla popularnych słów i kombinacji literowych tzw. rainbow tables lub wykorzystując łamacz haseł, np. John the Ripper czy hashcat.

Należy pamiętać, że wydłużanie pracy przy analizie kodu jest jedną z najlepszych metod, skutecznie zniechęcającą do analizy we wszelkiego rodzaju zabezpieczeniach i nie należy ignorować tego typu zabezpieczeń ani ich znaczenia. Osoba przeprowadzająca analizę może się po prostu znudzić lub sfrustrować znajdując takie „udogodnienia”, a dla nas będzie to tylko na rękę.

crackme-z3s-mr-burns-deception

Jako ciekawostkę mogę powiedzieć, że często popularne systemy zabezpieczeń do gier nie są projektowane jako „niełamalne”, ale służą jedynie wydłużeniu czasu, w jakim producenci gier będą w stanie sprzedaż maksymalną liczbę kopii w okresie premiery gry. Złamanie zabezpieczenia w takim wypadku i nagłówki prasowe mówiące o tym, że „poległo” jakieś zabezpieczenie to tylko iluzoryczne zwycięstwo crackerów i piratów, którzy kolejny raz pełni zadowolenia i ze stałą pieśnią na ustach „wszystko jest do złamania” mogą się poklepać po plecach, nawet nie zdając sobie sprawy, kto tak naprawdę jest tutaj wygranym.

Klucz 1 – zmienna środowiskowa

Jednym z kluczy będzie zmienna środowiskowa, która musi być poprawnie ustawiona np. poprzez systemowy edytor zmiennych środowiskowych. Dla utrudnienia sprawdzimy standardową zmienną środowiskową Windows – „PROCESSOR_ARCHITECTURE” jednak z drobną literówką, zamiast dwóch „S” będziemy szukać zmiennej „PROCESOR_ARCHITECTURE”.

Sprawdzana wartość będzie taka, jaka jest ustawiona w 64 bitowych systemach z dodatkową spacją na końcu, czyli „AMD64 ”.

Ktoś, kto sprawdzi sobie zmienne środowiskowe np. wykonując komendę konsolową „set”, na pewno zauważy tą wartość, pytanie czy zauważy literówkę oraz dodatkową spację? W Windows 10 można szybko zmienną ustawić z linii komend:

lub przez edytor zmiennych środowiskowych, uruchamiając go przez kombinację klawiszy WIN+R i wpisując „sysdm.cpl”.

Klucz 2 – ukryty klucz ADS

System plików NTFS umożliwia zapisywanie dodatkowych „streamów” w plikach, nazywa się to Alternate Data Stream i można dzięki temu ukryć dodatkowe dane w plikach, tak, że nie będą one widoczne z poziomu Eksploratora Windows. Przykładowo jest to wykorzystywane przez przeglądarki, gdy pobieramy jakieś pliki z Internetu, to na dysku oprócz pobranego pliku, jest tworzony dla niego dodatkowy stream „plik.zip:ZoneIdentifier” z identyfikatorem strefy, określającej gdzie taki plik może być używany. Stąd te wszystkie denerwujące komunikaty ostrzegawcze, gdy próbujesz otworzyć cokolwiek pobranego z Internetu.

crackme-z3s-ads-identyfikator-strefy

Mechanizm ADS jest także wykorzystywany przez malware do ukrywania danych, jednak to ciekawa metoda do wykorzystania w CrackMe i posłużymy się nią do ukrycia kolejnego klucza.

Będziemy go poszukiwać w samym pliku CrackMe, dokładnie w streamie „CrackMeZ3S.exe:Z3S.txt”.

Oczekiwaną wartość klucza można ustawić z okna konsoli, wykorzystując komendę:

i sprawdzić czy udało się utworzyć stream komendą:

Należy zwrócić uwagę na brak spacji w komendzie „echo” przed znakiem przekierowania „>”, łatwo się pomylić, dodatkowa spacja doda błędne dane, a oczekiwanym kluczem jest tylko i wyłącznie „2016.07”.

crackme-z3s-ustawianie-alternate-data-stream-ntfs

Klucz 3 – schowek systemowy

Kolejny klucz będzie poszukiwany w schowku systemowym. CrackMe będzie oczekiwało w nim skopiowanej, konkretnej wartości tekstowej. I nie mówię tutaj o numerze konta bankowego ;)

Tutaj raczej nie trzeba tłumaczyć co trzeba zrobić, aby ustawić ten klucz, CTRL-C i po sprawie.

Klucz 4 – sprawdzanie trybu zgodności

System Windows umożliwia ustawienie odpowiedniego trybu zgodności z wcześniejszymi wersjami Windows dla aplikacji, które niepoprawnie zachowują się w nowszych wersjach systemu operacyjnego. Wykorzystamy to jako kolejny klucz, sprawdzając czy aplikacja uruchomiona jest w trybie Windows Vista.

Sprawdzenie jest o tyle niepozorne, że wygląda jak standardowy kod weryfikujący wersję systemu Windows. Aby ten klucz dostępu przeszedł weryfikację, wystarczy zmienić tryb zgodności we właściwościach pliku „CrackMeZ3S.exe” lub uruchomić je na Windows Vista (używa ktoś?).

crackme-z3s-klucz-4-tryb-zgodnosci

Jeśli ktoś będzie je próbował łamać na czymś starszym niż samo Windows Vista, będzie niestety musiał spatchować kod lub ustawić hooka na funkcję GetVersionEx() i emulować oczekiwane wartości wersji systemu Windows, tak aby wskazywały właśnie na Windows Vista.

Klucz 5 – kliknięcie CTRL-C

W naszym CrackMe podepniemy się pod funkcję obsługującą sygnały wysyłane do aplikacji okienkowej, w tym kombinację klawiszy CTRL-C, czyli zwyczajowo zamykających aplikację. Sprawdzimy czy użytkownik nacisnął kombinację CTRL-C w trakcie działania CrackMe.

Naciśnięcie CTRL-C jednocześnie ustawi flagę tego klucza dostępowego oraz zakończy działanie CrackMe.

Wszystkie klucze ustawione – co dalej?

Jeśli wszystkie wątki z kodem sprawdzającym klucze dostępowe zakończą działanie i wszystkie klucze zostaną wykryte, a użytkownik kliknie na końcu CTRL-C, to z pojedynczych liter kluczy dostępowych zostanie zbudowana flaga.

Przed wyświetleniem tryumfalnego komunikatu z flagą, zostanie ona dodatkowo zweryfikowana przez sprawdzenie jej skrótu kryptograficznego z dodatkową „solą”:

Robimy tak, aby upewnić się, że dane kluczy były poprawne i nikt nie zmodyfikował np. w debuggerze samego kodu weryfikującego, żeby tylko dojść do tego fragmentu kodu.

Antydebugging

Czym by było prawdziwe CrackMe bez metodu utrudniających debuggowanie? W naszym CrackMe też nie zabraknie tego typu atrakcji. Można wykorzystać popularne techniki bazujące na sprawdzaniu obecności debuggerów bazujących na funkcjach WinAPI, takie jak np. IsDebuggerPresent(), jednak ich popularność i powszechna wiedza o nich, z góry stawiają je na przegranej pozycji. Poza tym jak widzę po raz setny IsDebuggerPresent() to chce mi się płakać.

crackme-z3s-dawson-crying

Wykrywanie śledzenia kodu w debuggerze

Dodamy do kodu CrackMe metodę wykrywająca popularne narzędzia wykorzystywane do analizy oprogramowania w czasie ich działania, czyli debuggery. Debuggery pozwalają na śledzenie skompilowanej aplikacji bez dostępu do jej kodów źródłowych. Debugger wyświetla kod skompilowanej aplikacji w formie instrukcji assemblera, umożliwiając śledzenie aplikacji instrukcja po instrukcji, pozwala zatrzymywać działanie aplikacji w wybranych punktach programu (stawia się tzw. pułapki czyli z ang. breakpoints) lub przy próbie wywołania jakiejś funkcji systemowej, np. gdy aplikacja będzie chciała wyświetlić okienko z komunikatem „Twój klucz jest nieprawidłowy” przykładowo wykorzystując funkcję MessageBox().

Wykorzystamy prosty fakt, że debuggowany program, wykonywany krok po kroku w oknie debuggera, niezależnie jaki to jest debugger, jest znacznie wolniejszy, gdyż cały mechanizm debuggowania spowalnia wykonywanie wszystkich instrukcji.

Skąd to spowolnienie? Spójrz jak wygląda standardowa pętla debuggera oparta o funkcje WinAPI a zrozumiesz ile tam się dzieje, dodatkowo czas wydłuża sam użytkownik debuggera, który tu wykona kilka instrukcji, sprawdzi wartości rejestrów, tam spojrzy na dokumentację i tak czas się wydłuża.

Będziemy pobierali czas pomiędzy wykonywaniem oznaczonych bloków kodu i o ile ktoś nie uruchomi tego CrackMe pod emulatorem PC na Commodore 64 albo Atari to nie ma bata, żeby wykonanie kilku prostych instrukcji zajęło przykładowo 5 sekund, a ktoś kto będzie ten kod śledził w oknie debuggera, na pewno spędzi przy tym więcej czasu.

Po detekcji wydłużonych czasów wykonywania, nie wyświetlimy żadnych komunikatów o debuggerze, bo to najgorsze co można zrobić, gdyż w ten sposób dajemy osobie analizującej zabezpieczenie jasną wskazówkę, gdzie tkwi problem. W naszym CrackMe losowo uszkodzimy wewnętrzne bufory danych oraz znaczniki rejestracji dla poszczególnych kluczy dostępowych. Dzięki temu, nawet jeśli ustawione zostaną poprawne klucze dostępowe, to w obecności debuggera finalnie nie uda się wygenerować poprawnej flagi.

Tego typu zabezpieczenia można ominąć stosując wtyczki do debuggerów lub stosując hooki na funkcje odczytujące czas, które będą zwracać do aplikacji fałszywe wyniki.

Opcje kompilatora i linkera

Mimo tego, że CrackMe jest napisane w C++, a nie w assemblerze – można dodatkowo urozmaicić rozrywkę stosując odpowiednie opcje kompilatora i linkera. W naszym CrackMe zastosujemy randomizację bazy poprzez mechanizm ASLR, który sprawi, że nasz plik wykonywalny będzie posiadał domyślnie relokacje i za każdym razem gdy zostanie uruchomiony, system Windows wgra go pod inny adres bazowy w pamięci.

crackme-z3s-opcje-kompilatora

Będzie można zapomnieć o stawianiu pułapek w debuggerze na stałe adresy w pamięci wirtualnej, ponieważ za każdym uruchomieniem kod znajdzie się w innym rejonie pamięci, tak samo adresy funkcji uzyskane przy deasemblacji będą bezużyteczne, innymi słowy trzeba będzie się trochę pomęczyć.

Sprytni reverserzy mogliby jednak usunąć informacje o relokacjach i sprawić, że obraz pliku EXE będzie ładowany cały czas pod domyślny adres bazowy, dlatego dodatkowo ustawimy go na 0. Jest to rzadko stosowana opcja, jednak w przypadku ASLR nawet Microsoft rekomenduje ustawianie adresu bazowego na 0, chociaż sam kompilator nie stosuje domyślnie tej opcji.

Jak można i to zabezpieczenie ominąć, aby ułatwić sobie życie, nie tylko w przypadku tego CrackMe, ale w analizie innych aplikacji? Należałoby przerelokować plik EXE pod dowolny adres bazowy np. domyślnie stosowany 0x400000 i usunąć informacje o relokacjach lub flagę w strukturze nagłówka pliku PE (Portable Executable) oznaczającą, że ten mechanizm jest wykorzystywany.

Koniec

Jak widzicie, sposobów na oryginalne metody weryfikacji jest całkiem sporo, wiele z nich kryje się w rzadko używanych mechanizmach systemu Windows oraz starych funkcjach WinAPI. Z powodzeniem mogą być wykorzystane jako elementy układanek we wszelkiego rodzaju CrackMe. Napiszcie w komentarzach z jakich interesujących technik i nietrywialnych metod sami byście skorzystali lub mieliście okazję je widzieć w innych CrackMe. Do artykułu załączam źródło i skompilowany kod CrackMe. Hasło do źródeł to „CrackMeZ3S”.

Pobierz CrackMeZ3S.zip

O Autorze
Jestem autorem systemu ochrony oprogramowania przed złamaniem oraz inżynierią wsteczną – PELock. Moja firma świadczy usługi z zakresu inżynierii wstecznej oprogramowania, takie jak np. odzyskiwanie utraconych kodów źródłowych czy lokalizacja oprogramowania i gier komputerowych bez dostępu do kodów źródłowych. W wolnym czasie prowadzę bloga SecurityNews, czasami zdarzy mi się napisać artykuł do prasy komputerowej (hi Magazyn Programista), z takich dziedzin jak reverse engineering, analiza malware oraz programowanie niskopoziomowe.