Emotet jest aktualnie jedną z najbardziej medialnych rodzin złośliwego oprogramowania. Dzięki swojej modularnej architekturze może bardzo łatwo adaptować się do nowych warunków bez modyfikacji głównego komponentu.
Pierwsze wersje Emoteta zostały zaobserwowane w roku 2014. W tamtych czasach był używany jako trojan bankowy – obecnie jako spammer i dropper innych rodzin złośliwego oprogramowania.
Ostatnio autorzy rodziny zdecydowali się na zmianę protokołu komunikacji i technik obfuskacji kodu. Może mieć to związek z niedawno opublikowanymi narzędziami pozwalającymi badaczom na łatwe pobieranie próbek z serwerów C21 oraz detekcję maszyn zainfekowanych Emotetem2.
W tym artykule przyjrzymy się najciekawszym funkcjom Emoteta oraz przedstawimy niektóre ostatnio wprowadzone zmiany.
Analizowana próbka: 500221e174762c63829c2ea9718ca44f
Odpakowane binarium: e8143ef2821741cff199eeda513225d7
Spis treści
Techniki utrudniające analizę
Zaciemnienie przepływu kodu
W celu utrudnienia życia analitykom badającym oprogramowanie, autorzy skorzystali z obfuskacji kodu. Każda funkcja została podzielona na bloki instrukcji, które następnie zostały umieszczone w strukturze przypominającej maszynę stanów.
Odtworzenie oryginalnej struktury kodu jest możliwe, ale nietrywialne. Analiza kodu w tej postaci jest trochę utrudniona, ale na szczęście nadal możliwa.
Graf instrukcji zobfuskowanej głównej funkcji
Zaszyfrowane łańcuchy znaków
Wszystkie użyte ciągi znaków są zaszyfrowane, tak jak miało to miejsce w poprzednich wersjach. Jednak w obecnej wersji klucz do deszyfrowania za pomocą operacji xor nie jest przekazywany jako argument funkcji, tylko znajduje się przed zaszyfrowanymi danymi.
Przykład zaszyfrowanych ciągów znaków
Struktura zaszyfrowanych tekstów
Deszyfrowanie może być zrealizowane za pomocą krótkiego skryptu w języku Python.
Kod służący do odszyfrowania zaszyfrowanych tekstów
WinAPI
Kolejną metodą utrudniania życia analitykom jest ukrywanie wywołań funkcji API Windowsa. Zastępuje się je wywołaniami autorskiej funkcji szukającej danego API w załadowanych bibliotekach programu.
Emotet wywołuje funkcji API za pomocą funkcji szukającej hashy nazw funkcji. Nie jest to nic nowego – jednak w przeciwieństwie do poprzednich wersji, najnowsza wersja dokonuje wyszukania dopiero w momencie zamiaru wywołania danej funkcji, a nie na początku wykonywania programu.
Użycie funkcji wyszukującej danego API
Funkcja licząca hasz dla danej nazwy funkcji
To utrudnienie jest równie proste do obejścia. Trzeba jedynie zaimplementować funkcję haszującą i porównać wyniki wykonania dla standardowych bibliotek ze stałymi zawartymi w kodzie.
Ważnym punktem jest oznaczenie typu stałej w funkcji find_api na typ nowo stworzonej wartości enum. To pozwoli dekompilatorowi na automatyczne wstawienie nazw API w wywołaniach funkcji wyszukującej.
Usuwanie poprzednich wersji oprogramowania
Podczas analizy zdeszyfrowanych ciągów znaków, natrafiono na listę słów obecnych w poprzednich wersjach. Była ona używana do generowania ścieżek systemowych, w których następnie zapisywany był główny moduł Emoteta. Wydawało się to dziwne, ponieważ metoda ta została zastąpiona kompletnie losowym generatorem ścieżek.
Po bliższej inspekcji i potwierdzeniu przez użytkownika @JRoosen3, okazało się, że lista słów jest używana do usunięcia binariów pozostawionych przez poprzednie wersje Emoteta.
Część funkcji odpowiedzialnej za usuwanie starych wersji Emoteta
Klucz publiczny RSA
Klucz publiczny RSA jest szyfrowany tak samo jak łańcuchy znaków. Klucz jest użyty do szyfrowania kluczy AES używanych podczas połączenia z serwerem C2. Dzięki temu nawet podsłuchanie całości komunikacji między zainfekowaną maszyną i serwerem, nie daje możliwości odszyfrowania pakietów.
Do deszyfracji klucza można zatem użyć tego samego algorytmu co przy tekstach:
W wyniki otrzymujemy klucz zakodowany w formacie DER, który może zostać przetworzony przy pomocy kolejnego skryptu:
Wynikowy klucz publiczny w formacie PEM
Lista serwerów C2
Metoda dekodowania serwerów C2 nie zmieniła się względem poprzednich wersji. Są one nadal zapisane jako 8-bajtowe bloki, zawierające spakowane adres IP oraz port.
Komunikacja
Generowanie ścieżek URL
Generowanie ścieżek za pomocą konkatenacji losowych słów z listy zostało porzucone na korzyść całkowicie losowych ciągów znaków alfanumerycznych.
Każda ścieżka składa się z losowej liczby znaków oddzielonymi znakiem /.
Nowy algorytm użyty do generowania losowych ścieżek URL
Co więcej, zamiast prostego umieszczania pakietu w ciele zapytania HTTP, jest on teraz załączany jako plik.
Metoda generowania losowych nazw załączników oraz plików jest dosyć podobna do algorytmu użytego do generowania ścieżek URL.
Część funkcji odpowiedzialnej za zakodowanie danych jako załącznik
Przykładowe zapytanie przedstawione w programie Wireshark
Struktura zapytań
Sposób wykonywania zapytań do serwera C2, to część kodu, w której wprowadzono najwięcej zmian. Struktura pakowania pakietów „Protobuf” została zastąpiona autorskim algorytmem.
Szyfrowanie pakietów
Dokładnie jak w poprzednich wersjach, wszystkie pakiety są szyfrowane za pomocą szyfru AES w trybie CBC, z IV ustawionym na wartości null. Klucz AES jest najpierw generowany za pomocą funkcji CryptGenKey, a następnie szyfrowany za pomocą klucza publicznego RSA i dołączony na koniec każdego pakietu.
Dodatkowo, do weryfikacji integralności pakietów, do pakietu dołączany jest również hasz SHA-1 zawartości pakietu.
Struktura zaszyfrowanych pakietów
Struktura pakietu
Pakiety z rozkazami są dodatkowo kompresowane i opakowane w prostej strukturze.
Struktura pakietu przedstawiona za pomocą narzędzia dissect.cstruct
Kompresja pakietów
Kolejna zaobserwowana zmiana, to algorytm użyty do kompresji i dekompresji przychodzących i wychodzących pakietów.
Historycznie w tym celu używany był algorytm zlib, jednak przy okazji ostatnich aktualizacji zostało to zmienione. Ciężko stwierdzić dokładną nazwę nowego algorytmu, lecz funkcja evolution_unpack4 z projektu quickbms poprawnie dekompresuje dane otrzymane z serwerów C2.
Pseudokod nowego algorytmu kompresującego
Postanowiono zaimplementować algorytm dekompresji w języku Python, wyniki eksperymentu podano poniżej.
Struktura pakietu rejestracyjnego
Jak zostało wspomniane wcześniej, protokół protobuf został porzucony na rzecz własnego rozwiązania autora.
Jednym z zaobserwowanych komend pakietów jest pakiet rejestrujący maszynę w botnecie.
Struktura tego pakietu może być prosto przedstawiona jako struktura w języku C:
Struktura pakietu rejestracyjnego przedstawiona za pomocą narzędzia dissect.cstruct
Podsumowanie
Celem tego artykułu było przybliżenie badaczom wprowadzonych zmian w Emotecie.
Emotet po raz kolejny okazał się zaawansowanym zagrożeniem, potrafiącym szybko dostosować się do zmieniających się warunków.
Analiza jedynie pobieżnie prezentuje funkcje Emoteta i powinna być traktowana jako wprowadzenie, a nie pełny opis jego możliwości. Zachęcamy czytelników do skorzystania z zawartych informacji i dalszego przeciwdziałania operacjom botnetu.
Dalsze informacje o działaniach Emoteta
Odniesienia
1: https://d00rt.github.io/emotet_network_protocol/
2: https://github.com/JPCERTCC/EmoCheck
4: https://github.com/mistydemeo/quickbms/blob/master/unz.c#L5501