HermeticWiper: A detailed analysis of the destructive malware that targeted Ukraine
This blog post was authored by Hasherezade, Ankur Saini and Roberto Santos
Disk wipers are one particular type of malware often used against Ukraine. The implementation and quality of those wipers vary, and may suggest different hired developers.
The day before the invasion of Ukraine by Russian forces on February 24, a new data wiper was unleashed against a number of Ukrainian entities. This malware was given the name “HermeticWiper” based on a stolen digital certificate from a company called Hermetica Digital Ltd.
This wiper is remarkable for its ability to bypass Windows security features and gain write access to many low-level data-structures on the disk. In addition, the attackers wanted to fragment files on disk and overwrite them to make recovery almost impossible.
As we were analyzing this data wiper, other research has come out detailing additional components were used in this campaign, including a worm and typical ransomware thankfully poorly implemented and decryptable.
We obtained samples and in this post we will take apart this new malware.
Behavioral analysis
First, what we see is a 32 bit Windows executable with an icon resembling a gift. It is not a cynical joke of the attackers, but just a standard icon for a Visual Studio GUI project.
It has to be run as Administrator in order to work, and does not involve any UAC bypass techniques. As we will later find out, the name of the sample also (slightly) affects its functionality; if the name starts with “c” (or “C”, as it is automatically converted to lowercase) the system will also reboot after execution.
Once run, the sample works silently in the background. For several minutes we may not notice anything suspicious.
Only if we watch the sample using tools like Process Explorer, we can notice some unusual actions. It calls various IOCTLs, related to retrieving details about the disks:
…including FSCTL_GET_RETRIEVAL_POINTERS
and FSCTL_MOVE_FILE
which can remind of files defragmentation*.
[*] Note, that at the low-level, files may not be kept in a filesystem in one continuous chunk (as we see them at high-level), but in multiple chunks, stored in the various sectors of the disk. Defragmentation is related to consolidating those chunks, and fragmentation – to splitting them.
However, further examination has shown that the effect here is the opposite of defragmentation. In fact, the data gets more fragmented as a result of the malware execution.
The disk status regarding data fragmentation, before and after the malware execution, can be checked in the following images:
This is probably made in order to escalate the created damage: the more fragmented the file is, the more difficult it is to carve it out from the raw disk image, and reconstruct it forensically.
As the execution progresses, at some point, we may realize that some applications stopped working. It is because of the fact that some files, including system DLLs, have been overwritten with random data.
Example: an application failed to run because of a system DLL being trashed:
If we now view the raw image of the disk (i.e. using HxD), we can notice that some sectors have been also overwritten with random data:
Not surprisingly, on reboot our Windows OS will no longer work:
But what exactly happened underneath? Let’s have a closer look…
Used components
The initial sample: 1bc44eef75779e3ca1eefb8ff5a64807dbc942b1e4a2672d77b9f6928d292591 – comes with several PE files in its resources:
The names chosen for the resources (DRV_X64
, DRV_X86
, DRV_XP_X86
, DRV_XP_X64
) suggest that they are a version of the same driver, dedicated to different versions of Windows: appropriately 32 or 64 bit version, or a legacy version for Windows XP. Each of them is in compressed form. By checking the dumped files by the Linux file
command, we can see the following output:
file DRV_XP_X86 DRV_XP_X86: MS Compress archive data, SZDD variant, original size: 13896 bytes
To find out how they are loaded, we need to have a look at the sample that carries them.
Fortunately, the sample is not obfuscated. We can easily find the fragment that is responsible for finding the appropriate version of the driver:
The buffers are then decompressed with the help of the LZMA algorithm:
This format of compression is supported by a popular extraction tool, 7zip. We can also make our own decoding tool, basing on the malware code (example).
As a result we get 4 versions of legitimate drivers from the EaseUS Partition Master – just as reported by ESET (source).
- 2c7732da3dcfc82f60f063f2ec9fa09f9d38d5cfbe80c850ded44de43bdb666d
- 23ef301ddba39bb00f0819d2061c9c14d17dc30f780a945920a51bc3ba0198a4
- 8c614cf476f871274aa06153224e8f7354bf5e23e6853358591bf35a381fb75b
- 96b77284744f8761c4f2558388e0aee2140618b484ff53fa8b222b340d2a9c84
Based on the timestamps in the PE headers, the builds of the drivers are pretty old. Probably they have been stolen by the attackers from an original, legitimate software bundle. Each of them comes with a Debug directory, including a PDB path. Example:
Driver overview
The drivers leveraged by HermeticWiper are part of the Suite from EaseUS, a legitimate software that brings to the user disk functionalities like partitioning and resizing. As told, this tool is legitimate so no one was detecting the sample in VirusTotal at the time of the attack:
Looking inside the driver, we can see typical functions. The driver creates the required device and establishes some Dispatch Routines, as can be seen in the following image:
The internals of the driver are quite straightforward. In order to access the driver from usermode we need to use CreateFile
API function and the name of the device under which the driver was installed (\.EPMNTDRV
) along with the partition ID. Example shown below:
This string is important to understand the driver capabilities. As you can see, this drivers code will convert this sent string from usermode to integer and will use that integer as an input to the `saveReferenceHardDisk` helper function. As it can be extracted from the images, this helper function will save a reference to the physical disk (DeviceHarddisk[num]Partition0) in FsContext attribute:
This behaviour can has been tested also in real time. We can see how the leading backslash is removed prior to convert this value to integer type:
IRP_MJ_CREATE function will save a Device Object pointer for the hard disk in FsContext2 attribute, returned by getDeviceObject helper function. The DeviceObject pointer in getDeviceObject is used to find IRP_MJ_CREATE function will save a Device Object pointer for the hard disk in FsContext2 attribute, returned by getDeviceObject helper function. The DeviceObject pointer in getDeviceObject is used to find the disk.sys associated device object by traversing to the lowest device object leveraging IoGetLowerDeviceObject function. To confirm that the lower device object is indeed the one we are looking for we check the ServiceKeyName of the object with “Disk” which indicates that its looking for the disk.sys object as the ServiceKeyName for that object is “Disk”. These objects will be used later in read and write operations. That means that, when different operations are requested to the driver from usermode, the real operation will be performed over the machine physical disks.
Next images show how the driver builds the incoming requests and forwards them to the lower level devices:
By using FsContext2 field saved by a CreateFile operation performed from usermode, this driver could be seen as a proxy driver where IRPs are handled by underlying devices. In a nutshell, this legitimate driver lets the attackers bypass some windows security mechanisms which would ideally be forbidden from usermode such as writing to certain sectors of the raw disk.
Implementation of the Wiper
This malware is designed to maximize damage done to the system. It does not only overwrite the MBR, but goes further: walking through many structures of the filesystem and corrupting all of them, also trashing individual files.
We know that this executable is going to somehow abuse those drivers in order to implement the wiper functionality. Yet, the question arises, how exactly is it implemented?
It is worth to note that Windows (since Vista) introduced limitations, thanks to which only the sectors at the beginning of the disk can be written to from usermode (with the help of the standard windows drivers). If we want to write to further sectors, i.e. overwrite MFT (Master File Table) we need some custom workarounds. (More explanation given here.)
In case of Petya (as well as NotPetya, which used the same component), this workaround was implemented by an alternative “kernel” that was booting (instead of Windows) on machine restart, and doing the overwrite. In case of the HermeticWiper, the authors decided for an easier way: they used another driver, that was able to do such overwrites.
First, the malware parses NTFS structures, and stores information about them in the internal structures. For implementing the reads, standard system devices being used. After the needed data is collected, the additional (EaseUS) driver comes into play: it is used as a proxy to write into the collected sectors.
The attack can be divided into several phases:
- Preparation, including:
- Installation of the additional driver (EaseUS)
- Disabling system features that may help in recovery, or in noticing of the attack
- Data collection: walking through NTFS structure, collecting sectors and files that are going to be overwritten. Also, the random data of appropriate size is generated for the further overwrite.
- Trashing (at this stage the EaseUS driver is utilized): the collected sectors are being overwritten by the previously generated random data
At the end, the system may be automatically rebooted.
Execution flow
Let’s now have a look at the malware sample, to see how those phases are implemented in detail.
Preparations
First the sample parses command line arguments. They will have minor impact on the execution – may just alter how long the sample is going to sleep between the execution of the particular phases.
Then, the sample proceeds to set privileges that are needed in order to execute the actions that are going to be performed. Two privileges are being set in the main function of the malware: SeShutdownPrivilege
(that allows to reboot the system) and SeBackupPrivilege
(that allows to manipulate system backups):
Here comes and interesting twist: the string defining SeShutDownPrivilege
is composed on the stack, and one chunk in between is missing:
This missing chunk wnPr
is then being filled at the position that is calculated depending on the first character of the current executable name. Due to this, the string becomes complete (and the privilege is set properly) only in the case if the sample has a name starting from “c”.
The reason why the authors decided for such unusual alteration of the flow is not sure. It may be just to obfuscate this particular, suspicious string. It is also common for malware authors to use a name check as an anti-sandbox technique (since sandboxes may assign to samples some predictable names: in the case if such name was detected, sample may exit, so that its behavior cannot be tracked by the Sandbox). However, here the change in the sample behavior is very minor – it affects only the reboot functionality, not the main mission of the malware.
Driver Installation
If the privilege setting was successful, the malware proceeds to the installation of the driver:
The installation function takes several steps.
First, the system is fingerprinted, so that the malware can select the most appropriate version of the driver to be used. Depending on the Windows version, and the bitness (32 or 64 bit), the resource is selected.
Before installing the driver, the crash dump mechanism is being disabled:
Crash Dumps are usually being made if the full system crashes, possibly because of a bug/instability in a driver. They contain information about the full status of the system, and on what exactly happen, in order to help debugging. Disabling crashes before the installation suggests that the authors of the malware have some level of distrust in the used drivers, or believe that the executed operation posses some risk of crashing the system. That’s why they want to be extra sure that if it eventually happens, the Administrators will have a harder time to find the reason.
Then, they check if the driver is already installed. They do it by sending there and IOCTL, that is supposed to retrieve information about the drive geometry. If this operation has failed, it means the driver is not there, and they can proceed with the installation.
The installation is done by first generating a pseudorandom, 4-character long name for the driver, from the hardcoded charset. The function also makes sure that the file with the generated name does not exist yet.
Then, the compressed version of the file is being dropped. And finally, the driver is decompressed from it.
The decompressed driver is installed as a service:
At this point, the newly dropped files are also added to the structures that will be further passed to the wiping functions – so that the files can be overwritten at low level. More about it is described in section “Data collection”.
The installation function (denoted as create_driver_svc
) first enables yet another privilege: SeLoadDriverPrivilege
(which is required to allow loading drivers):
Then the driver is added as a system service, and started:
This triggers execution of the DriverEntry
function, and since that point, the driver is residing in memory.
After the successful installation, the registry keys related to the service, as well as the dropped files, are deleted, to make the new driver more difficult to spot:
We must note, that file deletion does not interfere in the functionality of the driver. It is still loaded in memory (till the next reboot) and will be available for the further use.
Disabling shadow copies
It is a common action done by ransomware to delete shadow copies. It is supposed to destroy system backups, and paralyze the recovery. In this case, we can see the sample disabling the Shadow copy Service:
Data Fragmentation
During our analysis, we noticed that the malware fragments the files present on the disk (as opposite of defragmentation).
Before the fragmentation routine, it changes some settings related to explorer:
This is probably to hide the information about the file status to the user, to keep them in blind for as long time as possible.
Below function shows how the fragmentation routine is executed:
The standard windows directories are being excluded:
This can be done both to save time (by not corrupting standard files), and to avoid the interference with system stability.
The file fragmentation process can be seen in next images:
The fragmentation algorithm implementation is achieved by using different IOCTL_CODES (FSCTL) as FSCTL_GET_RETRIEVAL_POINTERS and FSCTL_GET_MOVE_FILES. The code looks pretty similar to a defragmentation code. But in this case, is being modified in order to fragment, where file chunks are splitted and moved to free clusters in the disk.
Data collection
After those preparations, malware enters the second stage of the execution: data collection. In casual ransomware cases, we may see sometimes that prior to the encryption, malware iterates through various directories, and makes a list of files that it is going to attack. This case is analogous, but much more interesting, because the authors iterate not through directories (at high level, using windows API), but at low level, through NTFS file system, reading various structures and parsing them manually. To enumerate them, they send IOCTLs through standard Windows devices (the newly installed driver is not used yet).
Data storage
The output of this parsing is stored in custom structures which we managed to reconstruct, and defined in the following way:
struct elemStr { elemStr *fLink; elemStr *bLink; chunkStr *chunkPtr; DWORD diskNumber; BYTE *randomBufToWrite; DWORD sizeBuffer; }; struct chunkStr { partitionStr *fLink; partitionStr *bLink; LARGE_INTEGER offset; QWORD chunk_size; };
They both are linked lists.
The first one elemStr
defines the element that will be overwritten. Its size is retrieved, and the random buffer dedicated for its overwrite is generated:
The “chunk” represents a continuous block of physical addresses to be overwritten.
So in general, the malware will use these structures in a 2 step process. First step will collect all the data. The second step will wipe this data, using the previous created structure.
Collected elements
As seen before, these structures will be sent to functions that will perform the data corruption, at a very low level. The structures that are collected for later destruction are presented below.
Own executable and the dropped drivers
We have seen that the attackers were interested in cleaning their trace. To accomplish that, they will delete their own executable from disk, even tough the binary itself keeps running and in memory. As any other task performed in the filesystem by HermeticWiper, the way of deleting their binary is slightly different as other malwares do. The attackers first manage to find which offset the binary occupies in raw, and finally they will overwrite that specific offset.
The dropped files (compressed and uncompressed driver) were added to the same structure, just after the the installation.
The Boot Sector
One of the attackers motivation is making devices incapable of loading the OS. The first step followed is enumerating all physical devices, as well as partitions. For that, a simple loop is used that tries to open a handle to HardDisk[num], where num is iterated from 0 to 100:
All this information is then stored into a diskStruct structure that contains data as the disk number. In this case, chunkElement will describe raw addresses of boot sectors. In that regard, an especial mention is made to C:System Volume Information
. The attackers will add to boot_sectors structure this folder contents:
According to Microsoft, “The Mount Manager maintains the Mount Manager remote database on every NTFS volume in which the Mount Manager records any mount points defined for that volume. The database file resides in the directory System Volume Information on the NTFS volume” (Windows Internals, 6th edition). So this technique is also created for increasing damage. Finally, all these collected offsets will be overwritten as the malicious binary was, leveraging the EasyUS driver.
Reserved Sectors and MFT
As before, the malware will brute-force again against the PhysicalDrive ID to find valid drive IDs. Then it uses IOCTL_DISK_GET_DRIVE_LAYOUT_EX to retrieve information about all the primary partitions present on the drive and reads the first sector from that partition. Other information required to read one sector from the disk is retrieved by using the IOCTL_DISK_GET_DRIVE_GEOMETRY_EX.
Once the first sector of a partition is read then the callback function passed by the malware is invoked on this sector.
Depending on the filesystem type if its FAT then it wipes all the Reserved Sectors, the boot record sectors in FAT filesystem are part of Reserved Sectors. In case of NTFS the malware wipes the MFT and MFTMirror (backup MFT) present on the disk, the purpose of which is to make the recovery of the data harder.
Each file on an NTFS volume is represented by a record in a special file called the master file table (MFT). In case the MFT becomes corruptible then MFT mirror is read in an attempt to recover the original MFT, whose first record is identical to the first record of the MFT. MFT table is the index on which the filesystem relies, having information like where a file resides. Without MFT, the system will be unable to know were folders and files are, or modification dates, etc.
Bitmap and LogFile
In an attempt to hinder the recovery, Bitmap and LogFile are overwritten as well for all the logical drives present on the system. The logical drives are retrieved by GetLogicalDriveStringsW in this case. These structures are also important when doing recovery and postmortem investigation. $Bitmap contains information about free and occupied clusters and $Logfile contains a log of transactions that happened in the filesystem.
Also user files will be impacted by data destruction. We have discovered that the malware will overwrite as well almost everything inside C:/Documents and settings. In modern Windows, Documents and Settings will point to C:/Users. This folder contains users data folders (for example, My Documents or Desktop are located in these folders). Some files are skipped in this process, as the ones under APPDATA but in general, every file that is contained under these folders will be overwritten.
Collecting clusters to erase the whole disk
The final part of the data collection is to get information required to wipe all the occupied clusters on the disk. To get this information the malware uses FSCTL_GET_VOLUME_BITMAP IOCTL which gives us information about all the occupied and free clusters on the disk. The malware traverses all the logical disks and uses FSCTL_GET_VOLUME_BITMAP to retrieve the bitmap, every bit denotes a cluster in the bitmap, a value of 1 implying that the cluster is occupied and 0 meaning that the cluster is free. The bitmap retrieved with the IOCTL is traversed bit by bit and all the occupied clusters are added to the wiping structure which is described above in the post, one thing to note here is that malware combines all the contiguous clusters and these contiguous multiple clusters are denoted by a single chunk structure opposed to earlier usages where one chunk structure denoted a single cluster.
Finally, all occupied clusters will be collected in a diskStr typed structure for its destruction
How is this all performed?
Through the entire post its been told that some NTFS properties (like attributes, indexes, etc) are being used in order to collect data, that will be wiped after. We will like to show an example of how attackers implemented that functionalityn and show the level of sophistication.
For that, we will take as example the code responsible in collecting the Windows log files:
After this call, some data structures are filled, containing data regarding physical disk properties and the folder name itself. Our first reference to the NTFS filesystem is found in the way that the HANDLE is retrieved. This folder is oppened as a NTFS stream:
Eventually, the code will reach the following point. The first call will parse $INDEX_ROOT attribute, and the functionality is relatively similar and simpler than the second one, where $INDEX_ALLOCATION attribute is used. Additional information about these NTFS attributes can be found here. We will assume that the list of elements is long enough to have an $INDEX_ALLOCATION and we will deep into this call:
It is important to have in mind the parameters sent for a better understanding of the whole process. First two parameters (nFileIndexLow and nFileIndexHigh) are used for calling the function FSCTL_GET_NTFS_FILE_RECORD, which will retrieve a NTFS record. After some checks (for example, the magic value), we will pop out in a function that we have called callback_when_attribute_is_found. Note that the first parameter sent to this function will be the $INDEX_ALLOCATION (0x20) value that was previously sent:
What this function will do is to iterate through all NTFS attributes that are part of the record. To do that, the code will have to find the offset to the first attribute. This offset is just 2 bytes long, as is relative to the structure. The layout of the header is demonstrated below:
A NTFS File record will follow this structure:
Record Header |
Attribute |
Attribute |
Attribute |
If we still remember the $INDEX_ALLOCATION (0x20), it becomes handy now. Attributes will start with a specific TypeCode, as $INDEX_ALLOCATION is. So, if one of the attributes matches the selected type that was required, the first callback function (the one sent steps before as a parameter) will be triggered:
In the case there is not matching TypeCode but an $ATTRIBUTE_LIST is found, that will mean that exists more attributes, but these cannot fit into $MFT table. In this rare case, the malware will continue processing these extra attributes and will call recursively the first function.
Lets check what this callback will do. Remember that this callback function, in our case is indexAllocation_Callback_CollectAllfiles. The first step will be recovering the stream that this attribute points to. As $INDEX_ALLOCATION is an attribute meant for directories, makes sense this stream being an index array (block indexes):
As this is an index array, these indexes will point to something. This something is, as you would imagine, NTFS records. In raw disk, these type of indexes look like that:
As indexes point to records, all of these records will be sent, recursively, once more to the initial function. But this time the callback function will be different, also the typecode:
So this time, every record sent will behave differently. $DATA attributes will be looked for instead of $INDEX_ALLOCATION ($DATA contains file data). Also, the executed callback function will be different (named now dataExecuting). By using the disk properties that were sent in the first call combined with information gathered from indexes, this callback will locate the exact location of the file in disk. The last step for these files, as for all the ones that we have summarized in this report is being added as a member to a elemStr
* structure. The offsets contained in this structures, as stated, will be overwritten by the malware in the last steps:
Data overwriting
Finally, after all data is collected, the malware starts overwriting. The elemStr
structure is passed into the function, and all the elements on the linked list are being processed:
The overwriting function uses the installed driver in order to gain the write access to the sectors. It opens the device, and then walks through all the collected chunks, by their offsets. It uses WriteFile
to fill it with the previously prepared, random data.
Example below shows a fragment of a log from our experiments, when we dumped the content of particular structures during malware execution: first data collection, and then usage of the filled structures to wipe out the sectors on the disk:
Conclusion
As can be seen, by leveraging legitimate but flawless signed code, the attackers are capable of bypassing some Windows security mechanisms. This is extremely harmful because user applications are not meant to have this level of control in kernel space, for security reasons.
Also, we would like to state that recovery in this case is complicated. The attackers first fragment files on disk, and finally, will overwrite all of these fragments. Even without the last step (indiscriminate disk trashing), the combination of fragmentation and wiping of required structures (like $MFT) would be enough to make recovery almost impossible.
Our final thoughts are about the special focus that cybercriminals put in hiding their tracks. Maybe, that part is the final stage of a bigger operation. In fact, ESET recently described other related artifacts here, and they connect them to the same actor and campaign. Being part of a bigger picture can explain why attackers are so interesting in corrupting files like $LogFile and Windows events.
Malwarebytes detects this disk wiper as Trojan.HermeticWiper.
The post HermeticWiper: A detailed analysis of the destructive malware that targeted Ukraine appeared first on Malwarebytes Labs.
If you like the site, please consider joining the telegram channel or supporting us on Patreon using the button below.