We wpisie dotyczącym Bloodhounda wspominałem o potrzebie obfuskacji wielu programów, które przyjdzie nam wykorzystywać w praktyce podczas naszej pracy. W czasie prowadzenia audytów czy testów o charakterze white-boxowym z reguły nie musimy się martwić o zachowanie tzw. low-profile i unikanie detekcji przez Blue Team. Istnieją jednak sytuacje, w których jest to zdecydowanie pożądane. Do takich należy na pewno praca typowo Red Teamowa, badanie zdolności zespołów SOC do wykrycia potencjalnego ataku czy testowanie reguł SIEM. Dlatego omijanie antywirusów i obfuskacja kodu to must-have w arsenale każdego pentestera.
Świat bez AV
W idealnym (dla hakerów czy członków Red Teamu) świecie, bez systemu AV, wszelkie ofensywne działania podejmowane przeciwko firmom czy pojedynczym ludziom byłyby niezwykle proste do przeprowadzenia. Powyższe zdanie można przedstawić inaczej – sytuacja, w której nie posiadamy specjalistycznego systemu antywirusowego jest wymarzonym stanem dla potencjalnego adwersarza. Co do zasady, dostęp do atakowanego systemu można uzyskać na dwa sposoby:
- wykorzystanie podatności w jednym z elementów testowanego systemu,
- skłonienie użytkownika do uruchomienia złośliwego programu.
Nowoczesny software EDR jest w stanie zablokować obydwie metody uzyskania dostępu do testowanej infrastruktury. Przyjmijmy jednak na razie, iż ofiara nie posiada żadnego chroniącego ją oprogramowania. Przeanalizujmy jeszcze do niedawna bardzo popularny sposób na wykonywanie złośliwego kodu dzięki programu MSBuild. Kod źródłowy pliku przekazanego do MSBuild nie różni w swojej istocie od innych shellcode runnerów i z grubsza uruchamia złośliwy kod w trzech krokach:
- alokuje zapisywalny i wykonywalny obszar pamięci dla uruchamianego procesu (metoda VirtualAlloc)
- kopiuje shellcode z tablicy buf do zaalokowanej pamięci (metoda Copy)
- tworzy nowy wątek w procesie rodzica i wykonuje shellcode (metoda CreateThread)
Wykonanie powyższego kodu na komputerze ofiary przynosi spodziewany rezultat w postaci sesji Cobalt Strike.
Rzeczywistość, czyli Windows Defender w akcji
W prawdziwym świecie rzadko spotykamy się z systemami operacyjnymi pozbawionymi ochrony. Uruchomienie powyższego programu z włączonym Windows Defenderem natychmiast wygeneruje ostrzeżenie, a stager nie połączy się z serwerem.
Możemy sobie poradzić z tym na kilka sposobów:
- wyłączamy ochronę antywirusową
- dodajemy nasz malware do wyjątków
- staramy się ominąć AV
Dwie pierwsze metody wymagają od nas posiadania uprawnień administracyjnych (a i tak czasami nie jest to proste!), ponadto wzbudzają dość duże zainteresowanie w systemach monitoringu. Spróbujemy więc tak zaciemnić nasz powyższy kod, aby nie generował ostrzeżeń systemu antywirusowego.
Szyfrowanie
Jedną z podstawowych metod na omijanie antywirusów jest szyfrowanie elementów naszego malware`u, a następnie odszyfrowanie go „w locie”, w trakcie działania programu. Przy pomocy tej metody prawie na pewno uda nam się oszukać sygnatury funkcji skrótu i wzorce bajtowe. Jednak heurystyka może przez cały czas oznaczyć nasz program jako niebezpieczny, ponieważ np. sekwencja wywołań API wskazuje na jego złośliwy charakter. Zacznijmy od zaszyfrowania shellcodu przy pomocy funkcji XOR, z wykorzystaniem prostego, pythonowego skryptu i jednobajtowego klucza. Prościej się nie da.
Wynikiem działania powyższej funkcji jest pythonowa lista bajtów, którą należy wykorzystać w naszym projekcie .csproj i dostarczyć do programu MSBuild. Należy oczywiście pamiętać o dodaniu odpowiedniej linijki do pliku .csproj, która „odwróci” proces XORowania podczas kompilacji projektu.
Ku mojemu zaskoczeniu, powyższe rozwiązanie wystarcza aby nie wzbudzić podejrzeń Defendera z włączoną tylko opcją „Real-time protection”. Przy pomocy działającego beacona CS byłem w stanie spawnować kolejne sesje, wstrzykiwać złośliwy kod w dowolne procesy mojego użytkownika, wykonywać komendy Mimikatza, a choćby dowolne polecenia powłoki dzięki komendy „shell”. Wszystko to przy włączonym Defenderze, na razie bez zaznaczonego feature`a „Cloud-delivered protection”
Warto też nadmienić, z kronikarskiego obowiązku, iż nie używam domyślnej konfiguracji dla Beacona CS. Ruch pomiędzy Team Serverem, a Beaconem ma przypominać prawidłową, dozwoloną komunikację jednego z windowsowych serwisów. Samo wstrzykiwanie kodu do procesów również wygląda nieco inaczej niż domyślnie. O tym w jaki sposób Cobalt Strike wykonuje Process Injection może przeczytać tutaj.
Cloud-delivered Protection
Na szczęście, system Windows domyślnie zapewnia również ochronę dostarczaną z chmury, która w dużym stopniu rozszerza i ulepsza standardową ochronę „Real-Time Protection”. Nie będę w tym momencie szeroko opisywać tej funkcjonalności, myślę, iż poniższa grafika dużo wyjaśnia w tym zakresie.
Włączona opcja ochrony z chmury uniemożliwia uruchomienie naszego malware`u i prawidłowo oznacza plik .csproj jako złośliwy. Przyczyną takiego stanu rzeczy jest prawdopodobnie ciąg wywołań Platform Invocation Services (P/Invoke), który umożliwia wywoływanie kodu natywnego z kodu zarządzanego (managed code) jak C# czy VBA. Zastanówmy się w jaki sposób możemy wywołać natywną metodę WinAPI w inny sposób niż klasyczny P/Invoke. Z pomocą przychodzą nam delegaty języka C#, które w dużym uproszczeniu są podobne do wskaźników na metody w języku C++. Delegat może równie dobrze wskazywać na funkcję natywną, dostępną w bibliotekach .dll WinApi. Aby wywołać taką metodę, należy najpierw uzyskać adres funkcji eksportowanej dzięki funkcji GetProcAddress, która przyjmuje uchwyt do biblioteki oraz nazwę eksportowanej funkcji (lub jej numer porządkowy). Później wystarczy już tylko zamienić niezarządzany kod na delegat, dzięki funkcji GetDelegateForFunctionPointer. Wywołanie klasycznego MessageBoxa wygląda więc następująco:
Powyższy program powinien uruchomić okienko o tytule zdefiniowanym w b i treści zawartej w zmiennej a. Wystarczy teraz stworzyć analogiczny zapis dla funkcji VirtualAlloc, CreateThread oraz WaitForSingleObject. przez cały czas pozostaje jednak kwestia obecności stringów „VirtualAlloc” i „CreateThread” w kodzie, które mogą zwrócić uwagę Defendera. Możemy więc spróbować umieścić w kodzie zXORowane powyższe wartości, a następnie odwrócić wynik działania tej funkcji w toku działania programu. Przykładowe wywołanie funkcji CreateThread będzie więc wygląda mniej więcej tak:
Analogicznie postępujemy z pozostałymi funkcjami.
Drum roll
Projekt uruchamiam na zaktualizowanym systemie Windows 10, z uruchomionym Defenderem oraz Cloud-delivered protection.
Chwilę po uruchomieniu programu otrzymano sesję Beacona CS. Czasami bywa tak, iż Windows Defender aktywuje się podczas pracy z Beaconem, dlatego postanowiłem wywołać kilka poleceń.
Jak widzimy powyżej, sesja Cobalt Strike nie napotyka żadnych trudności ze strony Defendera, mimo wywoływania dość agresywnych poleceń (shell oraz mimikatz).
Podsumowanie
Wnioskiem z powyższego eksperymentu powinna być świadomość, iż podstawowe systemy antywirusowe (a do takich zalicza się Windows Defender) okazują się niewystarczające w starciu z zaobfuskowanym, złośliwym oprogramowaniem. Proste zaciemnienie kodu wystarcza, aby AV pozwolił malware na swobodne działanie w systemie. Warto również podkreślić, iż sposób, który przedstawiłem powyżej jest bardzo prosty w implementacji i nie wymaga znajomości zaawansowanych metod obfuskacji i unikania systemu antywirusowego. W celu zapewnienie pełniejszego bezpieczeństwa w organizacji powinniśmy rozważyć implementację zaawansowanych systemów EDR takich jak Cynet, CrowdStrike czy Cortex XDR.
Credits
Many thanks for this post: https://kymb0.github.io/malwaredev-bypass-av-xml/, which show me the way how to use delegates in terms of AV Evasion and to https://www.linkedin.com/in/aurimasrudinskis/ for teaching me some other obfuscation technqiues. Moreover, I would like to say „thank you” to https://www.linkedin.com/in/mantvydasb/ for https://www.ired.team/ project. Priceless space for everyone who are keen on hacking.