Self Deletion

This article was published on the 30th of January 2023.

Malware is evasive in nature, mainly to not get caught initially. Once a specific malware sample gets in the hands of a researcher, it is usually only a matter of time before a complete dissection is posted publicly. In some cases, the malware can be used extensively without leaving (too much) traces, as is often the case with espionage malware, especially the more advanced families. Other types of malware, such as ransomware or wipers, cause such havoc that their execution cannot stay unnoticed.

Both these types, and anything in-between, may want to make it harder for their sample to get to an analyst, which is when a self-destruction function can be included in the malware. This article will cover one such example to explain the concept and the applied techniques.

Table of contents

Sample information

The sample that is analysed in this article can be found based upon the following information.

MD5: e61518ae9454a563b8f842286bbdb87b
SHA-1: 82d29b52e35e7938e7ee610c04ea9daaf5e08e90
SHA-256: 9ef7dbd3da51332a78eff19146d21c82957821e464e8133e9594a07d716d892d
Size: 280064 bytes

Additionally, one can download it from Malware Bazaar or MalShare.

Purpose

When investigating an incident, two things are vital: logs and files. The logs provide insight as to what happened, from a machine and network perspective. The logs often refer to files, which might be part of a log, or might be a reference to a file on a machine, drive, or website. If the referred file is local, from the investigator’s point of view, it is retrievable and within the environment that is most in control of the blue team. On the contrary, a malicious file on a threat actor controller server is not in control of the defensive team.

The purpose of the self destruction mechanism within the malware, is to make it harder for the defending team to get the malware sample, which can then be analysed in-detail. The more time the blue team has to spend to get and analyse the sample, the greater the attacker’s advantage is.

Race conditions

Whenever a file is in use, it cannot be deleted from the operating system. As such, an executable cannot delete itself directly, as the code to execute that action is present in the file that is to-be deleted. To resolve this, one can use a race condition. Before going into the specifics, the concept of a race condition will be explained.

Assume that a main function has a variable called x, which is equal to zero. Additionally, two threads are created. The first thread increments x with one, while the second thread decrements x with one. Both threads are executed 10 000 times each, as scheduled by the operating system.

Based on logic, one would say that x is incremented 10 000 times, and decremented 10 000 times, making the end result equal to the starting value of the variable, which is zero. However, in practice, the outcome is rather different. This difference occurs due to the moment at which the value of x is obtained and written. If a thread obtains the value of x between that, any action performed by another thread to alter x is allowed but ignored.

The moment the incrementing thread obtains the value of x, it has a local copy. If the decrementing thread obtains the value of x prior to the incremented value being written, the value of the local copy is equal in both threads. The threads then, respectively, increment and decrement the value. Assuming this is the first iteration where the original value of x is zero, the altered values are then 1 and -1 respectively. When storing the thread’s outcome back in x, either of the threads has to go first. If the decremented value is written first, after which the incremented value is written, the value of x will be 1. As such, after an increment and decrement of x, the value has only been incremented.

The scenario above describes a single run of two threads, where the difference is already possible but not always occurring. The run with 10 000 increments and decrements is highly likely to be influenced by the race condition, meaning the value of x is not reliably zero after all iterations have been finished.

The Java code below shows the above in code form to further elaborate and illustrate the concept.

public class RaceCondition {
 
    public static int x;
    private static final int THREAD_COUNT = 4;
    private static final int LOOP_COUNT = 10000;
 
    public static void main(String[] args) {
        x = 0;
        System.out.println("The starting value of x equals: " + x);
 
        List<Runnable> threads = new ArrayList<>();
 
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
 
        for (int i = 0; i < LOOP_COUNT; i++) {
            Incrementer incrementer = new Incrementer();
            Decrementer decrementer = new Decrementer();
            threads.add(incrementer);
            threads.add(decrementer);
        }
 
        for (Runnable thread : threads) {
            executor.execute(thread);
        }
 
        executor.shutdown();
 
        while (executor.isTerminated() == false) {
            //Wait until all threads have been executed
        }
 
        System.out.println("The variable x equals: " + x);
    }
}
 
public class Incrementer implements Runnable {
 
    @Override
    public void run() {
        int localX = RaceCondition.x;
 
        for (int i = 0; i < 100; i++) {
            localX++;
        }
        for (int i = 0; i < 99; i++) {
            localX--;
        }
 
        RaceCondition.x = localX;
    }
}
 
public class Decrementer implements Runnable {
 
    @Override
    public void run() {
        int localX = RaceCondition.x;
 
        for (int i = 0; i < 99; i++) {
            localX++;
        }
        for (int i = 0; i < 100; i++) {
            localX--;
        }
 
        RaceCondition.x = localX;
    }
}

Note the verbose increment and decrement logic in the respective Run functions is present to ensure the calculation takes a bit more time, increasing the time span where the race condition can occur.

There are numerous ways to solve race conditions, where the simplest is to ensure only one thread at a time is using a specific field or function, the specifics of which are out-of-scope for this article.

To summarise, race conditions show that the order of the events matters, much like the deletion of a file by that file.

Example: Whispergate Wiper

The WhisperGate wiper, as I analysed in this blog for Trellix, deletes itself once it has finished wiping the device, the code for which is given below.

void sleepAndSelfDelete(void)
 
{
  CHAR self [260];
  char command [524];
 
  GetModuleFileNameA(NULL,self,0x104);
  sprintf(command,"cmd.exe /min /C ping 111.111.111.111 -n 5 -w 10 > Nul & Del /f /q \"%s\"",self);
  runArbitraryCommand(command);
  return;
}

At first, using GetModuleFileNameA, the location of the given executable is retrieved based on the given module handle. Since the first argument, hModule, is null, the location of the current executable is returned within the character array named self.

Using sprintf, the command template is completed with the path to the current executable, after which the complete command is executed in another function. Once this function returns, the executable shuts down immediately.

The command itself pings 111.111.111.111 five times, with 10 milliseconds delay between each request, the output of which is written into a void. Once that finishes, the delete command is used to remove the current file, without asking for confirmation.

Due to the delay of the ping, the executable can shut down before the file deletion takes place, thus ensuring the malware’s file is not in-use at that point in time, so it can be deleted.

Conclusion

The example above is one of many ways via which one can delay the execution of a process, and thus ensure that the current process is terminated before that moment in time. While the exact implementation may differ, the concept will remain the same. Knowing how this technique will help to recognise such behaviour, both when looking at logs and when looking at malicious code.


To contact me, you can e-mail me at [info][at][maxkersten][dot][nl], or DM me on BlueSky @maxkersten.nl.