Necurs to jeden z większych botnetów na świecie – według informacji podawanych na stronie MalwareTech jest to choćby kilka milionów infekcji, z czego średnio kilkaset tysięcy jest aktywnych. Zainfekowane komputery rozsyłają e-maile ze spamem do wielu odbiorców – zwykle stylizowane są na prośbę o poprawienie faktury czy potwierdzenie zamówienia. W ich załącznikach znajduje się skrypty, które po uruchomieniu instalują malware – zwykle Locky, który po infekcji szyfruje pliki użytkownika i żąda za nie okupu. Necurs jest siecią hybrydową – połączeniem modelu scentralizowanego Command and Control, zapewniającego szybkie wydawanie komend do botów, z modelem peer-to-peer, który uniemożliwia wyłączenie całości botnetu poprzez przejęcie pojedynczego serwera. Nie jest zatem zaskoczeniem tak wielki sukces tego malware’u.
Zachowanie
Malware próbuje się połączyć do serwera C2, którego adres IP jest generowany na kilka różnych sposobów. Po pierwsze, kilka adresów znajduje się bezpośrednio w samej próbce (w postaci zaszyfrowanej – więcej o tym niżej). jeżeli połączenie się nie powiedzie, to Necurs generuje do 2048 pseudolosowych domen (przy użyciu DGA), które zależą od obecnej daty (nowe domeny tworzone są co 4 dni) oraz ziarna pseudolosowości, zapisanego na stałe w próbce – w tym analizowanym przypadku była to liczba 5. jeżeli któraś z domen jest zarejestrowana i odpowiada na połączenie zgodnie z protokołem, jest ona zapisywana jako adres. Wreszcie, jeżeli wszystkie z tych metod zawiodły, adres C2 jest pobierany od „peerów” – początkowa lista około dwóch tysięcy par IP+port sąsiadów jest zaszyfrowana w próbce. Podczas analizy, Necurs korzystał z tej ostatniej metody, gdyż żadna z domen utworzonych poprzez DGA nie była zarejestrowana lub nie odpowiadała. Nie jest jednak wykluczone, iż w przyszłości autor skorzysta z tej metody.
Jeśli w końcu udało się połączyć z głównym serwerem, pobierana jest od niego (przy użyciu autorskiego protokołu zbudowanego na bazie HTTP) lista informacji – w dalszej części opisu będę je nazywał „zasobami”. Każdy zasób jest identyfikowany 64-bitową liczbą. Przypuszczalnie jest to jakiś hash sensownej nazwy używanej w kodzie źródłowym, ale po kompilacji oczywiście nie mamy do niej dostępu. Niemniej, analizując jak konkretne zasoby są używane, mogliśmy niektóre z nich zidentyfikować.
Przykładowe informacje, które C2 wysyła to: nowa lista sąsiedztwa (ok. 2000 par IP+port), nowa lista domen C2, komenda odczekania określonego czasu (zazwyczaj kilkanaście-kilkadziesiąt minut), czy komenda pobrania i uruchomienia biblioteki DLL. Praktycznie każda odpowiedź zawiera rozkaz poczekania – ma to prawdopodobnie na celu zmniejszenie obciążenia serwera.
W głównym module nie było jednak tego, co najbardziej nas interesowało – wysyłania maili. Jak się okazuje, funkcja ta znajduje się w jednej z pobieranych bibliotek DLL. Ta niestety była napisana już w C++, co przełożyło się na zwiększenie objętości kodu (używane były szablony) i wydłużenie analizy. Większość informacji udało się na szczęście wywnioskować behawioralnie, tj. debugując malware i obserwując dane, jakie wysyła (oczywiście jeszcze przed ich zaszyfrowaniem).
Jak się okazuje, payload (istotne dane) jest w formacie JSON. Niestety wszystkie klucze są dość nieczytelne i nie da się odgadnąć ich znaczenia jedynie po ich nazwie – np. „dg3XGB9” oznacza obecny czas uniksowy. Jest kilka formatów wiadomości protokołu komunikacji, spośród których zanalizowałem jedynie kilka najciekawszych – w szczególności prośbę bota o format e-maila do wysłania i oczywiście odpowiedzi serwera na nią. Necurs korzysta z dość prostego języka skryptowego, aby randomizować wysyłane maile. Przykładowy plik wygląda następująco:
Widać, iż używane są zmienne lokalne (zadeklarowane poprzez dyrektywę %%var), predefiniowane funkcje, jak np. rndnum, ale są też odniesienia do zewnętrznych danych – np. [file.doc] – te zmienne pobierane są w osobnym żądaniu. Sprawdziliśmy zawartość załączników, i wbrew nazwie (file.doc), są to archiwa ZIP, wewnątrz których znajduje się pojedynczy plik JS, który po uruchomieniu pobiera i wywołuje Zepto, nowy wariant ransomware’u Locky.
Analiza techniczna
Necurs stosuje kilka technik utrudniających jego analizę. Przykładowo, każde połączenie z serwerem następuje losowo: albo do adresu podanego w argumencie funkcji, albo do adresu będącego hashem tamtego. Poza tym, na kilka różnych sposobów wykrywana jest wirtualizacja (np. poprzez instrukcje „vmcpuid”, czy „in al”). Niektóre środowiska do analizy malware’u polegną również na próbie sprawdzenia, czy Facebook i losowa domena rozwiązują się do tego samego adresu IP. Wiele tekstów („zasobów”) zaszytych w próbce jest zaszyfrowanych, podobnie jak cała komunikacja – zarówno z peerami, jak i z C2 (zarówno w głównej części malware’u, jak i pobranej DLL-ki).
Zasoby
Dane wewnątrz binarki są ukryte w osobnej sekcji – w pliku znajdują się dwie o nazwie „.reloc”, z których druga zawiera zasoby. Pierwsze cztery bajty tej sekcji zawierają klucz do deszyfracji – same zaszyfrowane dane zaczynają się na offsecie 0x18. Każdy kolejny bajt jest xorowany z kluczem, który przy każdej iteracji ulega przeliczeniu zgodnie z algorytmem LCG: K’=K*0x19661f+0x3c6ef387. Po zdekodowaniu, kolejne zasoby są skonkatenowanymi strukturami o następującym formacie:
Rozmiar ostatniego pola jest równy size>>8. Każdy zasób ma swój unikatowy identyfikator – znajdują się tam między innymi początkowe klucze do komunikacji z peerami oraz C2, czy same adresy peerów.
Komunikacja P2P
Komunikacja P2P jest niestety znacznie bardziej skomplikowana. Całość wymiany informacji zachodzi po protokole UDP. Najbardziej zewnętrzna warstwa komunikacji wygląda następująco:
Wysyłane dane (data) są zaszyfrowane kluczem obliczonym jako suma pola key, oraz pierwszych 32 bitów klucza publicznego z zasobów z binarki. Algorytm szyfrujący wygląda następująco:
Suma kontrolna wysyłana jako drugie pole wiadomości to po prostu ostatnia wartość klucza po zakończeniu szyfrowania – zwracane res w powyższym kodzie.
Odkodowane dane składają się na kolejną strukturę:
Rozmiar drugiego pola to size_flag>>4, natomiast najniższe cztery bity pierwszego pola przyjmują różne wartości w zależności od typu wiadomości – pierwsza wiadomość (ta nawiązująca kontakt) na przykład ma je zawsze wyzerowane.
Kolejny etap zależy od typu wiadomości, np. dla wiadomości „powitalnej” jest to:
W przypadku odpowiedzi peera na powitanie, jest to:
Całość jest podpisana kluczem zaszytym w zasobach binarki. Najważniejszym polem w tej strukturze jest resources – lista zasobów w takim samym formacie jak opisany powyżej, które zawierają m.in. adresy C2. Co ciekawe, peery nie mają możliwości przesłania nowej listy sąsiedztwa – są one wysyłane jedynie przez sam C2 w podpisanej wiadomości. Prawdopodobnie zabieg ten ma na celu uniknięcie zatruwania list sąsiedztwa, gdyż wiadomo, iż te od serwera pochodzą z autoryzowanego źródła.
Komunikacja z C2
Protokół komunikacji z C2 jest nieco podobny, chociaż wykorzystuje nieco inne funkcje szyfrujące i struktury, a także używa protokołu HTTP (metoda POST) zamiast surowych gniazd UDP. Pierwszy etap jest taki sam (struktura outer_layer), tyle iż pole data[] jest zaszyfrowane algorytmem ze zmienionymi stałymi:
Zdeszyfrowane dane to następująca struktura:
Pierwsze pole zawiera losowo wygenerowane dane, prawdopodobnie po to, aby zwiększyć entropię przesyłanych danych i nie dało się np. zauważyć identycznych początkowych bajtów w komunikacji.
Zawartość pola payload (może być skompresowana, w zależności od 2. bitu pola flags) zależy od typu wiadomości (command). jeżeli cała wiadomość to żądanie wysłania pliku (command=1), to payload zawiera po prostu skrót SHA-1 tegoż. Z drugiej strony, jeżeli jest to okresowe pobranie komend do wykonania (command=0), payload to dość rozbudowana struktura – lista zasobów, jednak o innym formacie niż poprzednio omówiona. Składa się ona ze skonkatenowanych zasobów:
W zależności od typu (pole type) zasobu, dane mają różny format:
Typ 4 jest zwykle używany do danych tekstowych, i prawdopodobnie dlatego jest dodawana jedynka do rozmiaru zasobu (na bajt zerowy).
Klient wysyła do C2 listę takich zasobów. Niektóre z nich zidentyfikowaliśmy:
- ziarno algorytmu DGA (ze stałych w binarce)
- liczba sekund od uruchomienia malware’a
- Unixowy timestamp momentu uruchomienia
- wersja systemu operacyjnego i jego język
- lokalne IP
- port UDP na którym nasłuchiwane są połączenia P2P
- hash obecnej listy sąsiedztwa
Odpowiedź serwera jest w bardzo podobnym formacie – po odszyfrowaniu zawartość zależy od rodzaju żądania. jeżeli była to prośba o plik, serwer odsyła tenże (często w postaci skompresowanej, w zależności od flag). jeżeli natomiast było to okresowe pytanie o komendy, serwer odsyła listę zasobów w takim samym formacie jak powyższy. Niektóre z nich można interpretować jako komendy, np. „odczekaj N milisekund” czy „wyloguj użytkownika” (chociaż tej ostatniej nigdy w praktyce nie otrzymałem).
Przykładowe sparsowane zasoby otrzymane od serwera:
Wśród pokaźnej liczby zasobów najważniejsze to nowa lista peerów (tylko wtedy, gdy jej hash jest różny od wysyłanego), czy komenda pobrania i uruchomienia modułu DLL. Ten ostatni typ ma swoją własną strukturę do celów komunikacji, również składającą się z połączonych „pod-zasobów” o następującej formie:
Interpretacja tej komendy to rozkaz uruchomienia DLL-ki o zadanym SHA-1 z parametrami podanymi w cmdline – w praktyce parametrami tymi są adresy C2 (wraz ze ścieżkami HTTP), z którymi nastąpi połączenie.
Komunikacja z C2 spamowym
Ostatnim protokołem, który omówię w tym zestawieniu, jest komunikacja pobranej DLLki, której zadaniem jest rozsyłanie spamu.
Wysyłane informacje są w następującej formie (przesyłane jako dane POST po HTTP):
Algorytm szyfrujący:
Po zdeszyfrowaniu dostajemy już surowe dane (chyba iż użyta była flaga kompresji – wówczas trzeba je dodatkowo odpakować – jak się okazuje, użyta do tego celu została mało popularna biblioteka QuickLZ). Są one w formacie JSON – niestety klucze są mocno obfuskowane, zatem trzeba było się domyślać ich znaczenia. Przykładowy JSON:
Ostatecznie, jako jedno z pól w otrzymanym słowniku dostajemy skrypt do tworzenia pseudolosowych maili (przykład na górze artykułu), a jako kilka innych – listę parametrów do tego skryptu (np. eng_Names). W osobnym żądaniu możemy poprosić serwer o podanie zawartości dowolnego z parametrów – wówczas otrzymamy np. listę imion, które mogą zostać podstawione do szablonu, czy kilka różnych plików (w Base64), które mogą zostać wysłane jako załącznik.
Przykładowa komunikacja
Zdaję sobie sprawę, iż zrozumienie wszystkich wymienionych struktur i przypadków, w których są one użyte, jest dość trudne, zatem utworzyłem uproszczony schemat działania protokołu. Przykładowy przepływ danych mógłby wyglądać tak:
Hashe próbek:
fe929245ee022e3410b22456be10c4f1 - spakowany oryginalny plik.
35be639c5618272f70a0bbfbc25d4465 - pobrana DLLka.
Reguły YARY: