niedziela, 1 lutego 2026

Analiza techniczna wybranych funkcji malware DynoWiper

Poniżej zamieściliśmy kilka szczegółowych informacji dotyczących złośliwego oprogramowania DynoWiper (na podstawie jednej z próbek - source.exe). Plik ten został zidentyfikowany w incydentach wymierzonych w niektóre polskie spółki z sektora energetycznego pod koniec 2025 roku. W celu zaznajomienia się ze szczegółowym przebiegiem kampanii odsyłamy do raportu CERT Polska. Również na blogu firmy ESET pojawiły się informacje dotyczące między innymi analizowanej przez nas próbki. Nasza analiza została przeprowadzona metodą statyczną (między innymi dekompilacja z użyciem Ghidra) oraz dynamiczną, co pozwoliło na dokładne przyjrzenie się niektórym mechanizmom.

Plik #1 (source.exe)
MD5: C4379DA51E8B9E86EC3DE934F9373F4A
Architektura: 32 bit

Poniżej zrzut tablicy importu:

 

Widzimy, że jest importowania tylko jedna biblioteka - kernel32.dll. Po nazwach importowanych funkcji można wstępnie określić wykonywane aktywności przez malware. Dla przykładu funkcja LoadLibraryExW() służy do załadowania dodatkowej biblioteki w trakcie działania programu. Funkcja wywoływana jest 3 razy więc szybko można zlokalizować jeden z kluczowym fragmentów złośliwego kodu. Analizując funkcje zaczynającą się od FUN_0040976 możemy zobaczyć pobierany za pomocą GetProcAddress() adres innej funkcji. W tym przypadku to SystemFunction036(). 

Oto ten fragment:

 

  if (Ptr == (FARPROC)0x0) {

    hModule = LoadLibraryExW(L"ADVAPI32.DLL",(HANDLE)0x0,0x800);

    if ((hModule == (HMODULE)0x0) &&

       ((DVar2 = GetLastError(), DVar2 != 0x57 ||

        (hModule = LoadLibraryExW(L"ADVAPI32.DLL",(HANDLE)0x0,0), hModule == (HMODULE)0x0)))) {

      piVar1 = __errno();

      *piVar1 = 0x16;

      FUN_004120bb_call_Invalid_param_handler();

      return 0x16;

    }

    Ptr = GetProcAddress(hModule,"SystemFunction036");


W oficjalnej dokumentacji (https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) funkcji RtlGenRandom na stronie Microsoft będzie taka informacja:

"This function does not have an import library. You must use the GetProcAddress function to dynamically link to Advapi32.dll. The name of the function in Advapi32.dll is SystemFunction036."

RtlGenRandom() jest pomocniczą funkcją dla innej funkcji (znajdziemy ją pod adresem 0x401f40), która służy do wygenerowania losowych danych (którymi jak się później okaże będą nadpisywane pliki w systemie plików).

Poniżej jej fragment, gdzie widać pętlę wykonywaną 624 razy.

 

  uVar2 = FUN_00405ceb_call_RtlGenRandom();

  param_1[1] = uVar2;

  iVar3 = 1;

  puVar4 = param_1 + 2;

  do {

    uVar2 = (uVar2 >> 0x1e ^ uVar2) * 0x6c078965 + iVar3;

    iVar3 = iVar3 + 1;

    *puVar4 = uVar2;

    puVar4 = puVar4 + 1;

  } while (iVar3 < 0x270);

 

Stała 0x6c078965 (1812433253) to standardowa wartość używana w algorytmie MT19937 (Mersenne Twister). Źródło: https://en.wikipedia.org/wiki/Mersenne_Twister

Zastosowanie algorytmu MT19937 zamiast standardowych funkcji kryptograficznych API Windows świadczy o optymalizacji kodu pod kątem szybkości, co jest kluczowe przy tego typu atakach (szybkim “zniszczeniu” systemu plików).

Cofnijmy się jeszcze to entry point w pliku z malware.

W Optional Header mamy adres punktu wejścia do aplikacji. Jest to 0x81F6 co widać poniżej:

 


Po wczytaniu pliku do CodeBrowser i użyciu mechanizmu automatycznej analizy możemy wyświetlić zawartość funkcji entry. Możemy zauważyć, że w pliku znajduje się odwołanie do pliku PDB Source.pdb. Zawartość odczytujemy z sekcji Debug Data.

 

 

Jest to możliwe, gdyż wewnątrz nagłówka PE jest sekcja IMAGE_DEBUG_DIRECTORY. Jak widać zawiera ona ścieżkę, pod którą znajdował się plik .pbd na komputerze atakującego. Mamy też nazwę użytkownika pod jaką pracował atakujący. GUID to unikalny ID przypisany do konkretnej kompilacji (tego samego build) i wynosi on 9ba97ad6-2a69-4035-860a-67cd1ed799e5.

 

Analizując samą funkcję entry widać, że głównie są wywoływane funkcje przygotowujące środowisko systemowe ale jedna z ostatni funkcji prowadzi nas do głównej funkcji programu (0x403020).


Poniżej zawartość głównej funkcji 0x403020. Zmienna local_13b0 to duza struktura (5028 bajtów) tworzona na stosie, która jest buforem gdzie mamy również dane, którymi będą nadpisywane pliki. Operowanie na stosie ma dwie zalety: jest szybkie i nie wymaga użycia zewnętrznych funkcji - np. malloc().

 

void FUN_00403020_main(void)


{

  uint local_13b0 [1257];

  uint local_c;

  

  local_c = DAT_004275c0 ^ (uint)local_13b0;

  FUN_00403a30_Init_MersenneTwister(local_13b0);

  FUN_00401f40_Init_Crypto_MT(local_13b0);

  FUN_00402e40_selected_wipe_by_overwrite();

  Sleep(5000);

  FUN_00402f30_total_wipe_by_delete();

  FUN_00407600_security_check_cookie(local_c ^ (uint)local_13b0);

  return;

}

 

Dwie główne funkcje odpowiadające za “niszczenie” plików są pod adresami 0x402e40 oraz 0x402f30. Pierwsza funkcja nadpisuje wybranie pliki z pominięciem istotnych dla systemu katalogów (ich listę można znaleźć w raporcie CERT). Druga funkcja (0x402f30) próbuje skasować wszystkie plik (do których ma uprawnienia)

Te funkcje są dość dobrze opisane we wspomnianym raporcie. Tutaj skupimy się na kilku elementach. Pierwsza funkcja wykorzystuje wygenerowane losowe dane o długości 16 bajtów, którymi następnie nadpisuje pliki. Poniżej przykład 16 bajtowej losowej wartości (dolna część ze screenshot od wartości 0x3C do 0xF7).

 

Pliki nadpisywane są w wielu miejscach tą wartością co widać poniżej na przykładzie pliku:

i dalej:

Poniżej fragment wywoływanej funkcji która odpowiada za kasowanie plików (druga wspomniana funkcja - FUN_00402f30):

 

          if (((byte)local_2c8.dwFileAttributes & 0x10) != 0) {

            pppppWVar5 = local_30;

            if (7 < local_1c) {

              pppppWVar5 = (LPCWSTR ****)local_30[0];

            }

            SetFileAttributesW((LPCWSTR)pppppWVar5,0x80);

            FUN_00402290_recursive_delete((LPCSTR)local_60); //rekurencja.

          }

          pppppWVar5 = local_30;

          if (7 < local_1c) {

            pppppWVar5 = (LPCWSTR ****)local_30[0];

          }

          DeleteFileW((LPCWSTR)pppppWVar5);


Można zauważyć, że funkcja sprawdza każdy znaleziony obiekt (plik lub katalog) za pomocą maski z fragmentu if (((byte)local_2c8.dwFileAttributes & 0x10) != 0. Jeśli to katalog to ustawiany jest domyślny zestaw atrybutów. To wykonuje funkcja SetFileAttributesW() z atrybutem 0x80 - FILE_ATTRIBUTE_NORMAL. Malware używa tego wywołania, aby zdjąć z folderów atrybuty takie jak "Tylko do odczytu" (Read-only) czy "Ukryty" (Hidden). Następnie wywołuje siebie (funkcja FUN_00402290) jeszcze raz aby skasować np. wszystkie pliki w tym katalogu (chyba że w katalogu jest podkatalog więc wykonuje jeszcze raz co powyżej aż do “przejścia” wszystkich podkatalogów). Bez zdjęcia atrybutu READONLY nie byłoby możliwe skasowanie pliku. 

Tutaj jest też niewielki błąd, który popełnił atakujący. Za pomocą DeleteFileW() nie da się skasować katalogu. Niemniej zawartość katalogu będzie usunięta, więc można przyjąć, że cel i tak jest osiągnięty.

Należy pamiętać, że w systemie Windows nawet zdjęcie atrybutu nie pozwoli na usunięcie niektórych plików gdyż działa kilka mechanizmów ochronnych. Dla większej skuteczności, program powinien być też uruchomiony z wysokimi uprawnieniami.

Analiza głównych funkcji próbki wskazują, że narzędzie działa szybko i “niszczy” system plików skutecznie.

 

Źródła:

 

[1] https://cert.pl/uploads/docs/CERT_Polska_Raport_Incydent_Sektor_Energii_2025.pdf

[2] https://www.welivesecurity.com/en/eset-research/dynowiper-update-technical-analysis-attribution/