The DJVU/STOP ransomware, since it was first detected in 2018, has been intermittently observed as part of ransomware attacks, but has not gained the notoriety of other ransomware like REvil or Conti. However, there has been a resurgence, with a new variant of DJVU/STOP becoming one of the most widespread file-encrypting malware of 2021. So much so that it has become a global nuisance, targeting entities across the United States, Europe, Asia, South American, and Africa. It has been detected in popular software crack packages or adware bundles, which are usually found on torrent sites and other platforms.
In this 2 part blog series we will delve into the modus operandi of the DJVU/STOP ransomware, its technicalities and features, how it identifies victims, and maintains persistence.
Modus Operandi of the DJVU/STOP Ransomware
DJVU/STOP ransomware is a file encryption Trojan malware that secretly intrudes a victim’s computer and encrypts all the files to make them inaccessible. After which, it drops a ransom note notifying the victim of the encryption. The note also demands a specific sum of money as ransom for the decryption keys that will decrypt the compromised files and make them accessible to the victim.
The Loader
- The analyzed sample (hash) acts as a loader program to deploy the ransomware crypter as the final stage of the infection chain.
- It has been observed that there are multiple shellcode executions to make the delivery of the crypter as inconspicuous as possible in order to evade security.
- The loader also performs a few Operational Security (OPSEC) checks before the initial stage is executed.
Stage 1
- At the initial stage the loader enumerates the victim system by reading system attributes to retrieve information such as username, processor architecture, system path, Windows root directory, network drives, among other details required to further infect the machine.
- The loader allocates a memory region with READ/WRITE/EXECUTE permissions and dumps a position-independent shellcode to execute later.
Stage 2
- At this stage, the primary shellcode dumps a secondary shellcode into an allocated memory.
- The secondary shellcode performs “process-hollowing” to deliver the final crypter payload inside a newly spawned loader process with a different process ID.
- The newly created process possesses a set of instructions, as a result of process hollowing, that can initiate encryption on the victim system and carry out network communications.
Ransom Stage
- At the final stage, the ransomware is active on the victim system and starts encrypting its files, before delivering the infamous ransom note.
Technical Analysis
Working of the Loader
The loader allocates a memory using VirtualAlloc Win32 API with function definition:
LPVOID VirtualAlloc( [in, optional] LPVOID lpAddress, [in] SIZE_T dwSize, [in] DWORD flAllocationType, [in] DWORD flProtect ); |
In the loader, the following arguments are used to call VirtualAlloc:
The allocated memory page is read/write and execute capable. While on the surface this may appear trivial, it is a common behavior seen in even the most sophisticated of malware to execute malicious code on a victim machine while being discreet.
flProtect : 0x40 flAllocationType: 0x1000 dwSize: lpAddress: 0x0 |
Memory with zero bytes:
The loader populates the allocated memory, from the previous step, with the shellcode.
The execution flow is then transferred to the newly allocated shellcode as seen below. And the CPU now points to the first instruction of the malicious shellcode as it starts executing.
Working of the Stage 1 Shellcode
The stage 1 primary shellcode is programmed to inject the secondary shellcode, which is responsible for “process hollowing,” to deliver the final ransomware crypter code.
The primary shellcode delivers modules into its address space, including but not limited to:
- ntdll.dll
- Kernel32.dll
Address resolution is done by obtaining the address of one of the crucial functions in the kernel32 module which iskernel32.GetProcAddress. Malware authors use kernel32.GetProcAddressto dynamically resolve Win32 APIs to evade scrutiny of code analysis by looking at the import table of the program. A few interesting function addresses are also resolved to be used later in the program.
Once the malware obtains the address of kernel32.GetProcAddress function, it is used to resolve the addresses of following functions:
- kernel32.GlobalAlloc
- kernel32.VirtualAlloc
- kernel32.SetLastError
- kernel32.Sleep
- kernel32.CreateToolHelp32Snapshot
- kernel32.Module32First
- kernel32.CloseHandle
After resolving the addresses of various functions, the shellcode starts executing two Win32 APIs:
- CreateToolHelp32Snapshot
- Module32First
Arguments passed to CreateToolhelp32Snapshot are:
- dwFlags: 0x08 (TH32CS_SNAPMODULE)
- the32ProcessID: 0x0 (Current Process)
HANDLE CreateToolhelp32Snapshot( [in] DWORD dwFlags, [in] DWORD th32ProcessID ); |
Arguments passed to Module32First function are:
- hSnapshot: 0x108 (Snapshot handle to previous snapshot returned fromCreateToolHelp32Snapshot)
- Lpme: 0x19F768 (A pointer to a MODULEENTRY32 structure)
BOOL Module32First ( [in] HANDLE hSnapshot, [in, out] LPMODULEENTRY32 lpme ); |
The above-mentioned codes are functions commonly used to perform malicious activities. In this instance:
- CreateToolHelp32Snapshot is used to create a snapshot, which contains heaps, modules, and threads, used by the processes of a given process.
- Module32First is used to traverse the modules present in the snapshot provided by CreateToolHelp32Snapshot.
- The loader retrieves the shellcode in this manner and then proceeds to allocate memory and write shellcode.
Working Of Stage 2 Shellcode
The Stage 1 shellcode allocates memory with formerly noted arguments for the stage 2 shellcode, using the VirtualAlloc Win32 API.
The next step involves writing shellcode into the allocated memory. The control is then transferred to the shellcode writing logic via “call 4AE04F2”.
The previous call lands in the code logic given below, which iteratively writes stage 2 shellcode into the allocated buffer.
Once the shellcode is transferred, the control is assigned to the shellcode by a JMP command as shown below:
After the jump, the CPU points to the beginning of the stage 2 shellcode. This shellcode is responsible for performing “process-hollowing” to deliver a ransomware crypter payload that encrypts user data. The second stage shellcode then loads user32.dll, ntdll.dll, and kernel32.dll modules into the current process.
Using kernel32.GetProcAddress, addresses of the following interesting functions are resolved:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The shellcode checks for a specific file named “apfHQ.” Our best assumption is that the file is used as a marker to identify victims. Even though the string is very common in various malware families, the exact reason for this behavior is still unknown.
The shellcode then creates a windows class using two APIs, namely user32.RegisterClassExA and user32.CreateWindwoExA. The name given to the class is “saodkfnosa9uin.”
The previously created window class is used to create a hidden window using user32.CreateWindowExA as shown below:
Process Hollowing
As mentioned above, the objective of the second stage shellcode is to perform process hollowing to deploy a ransomware crypter to encrypt the user data.
Process hollowing is a covert way of injecting malicious code into a benign and trustworthy process to evade detection and security. An intuitive explanation for process hollowing is carving the legit and original instruction of the benign application to replace it with a malicious one, without letting the application know about the change in code made by the threat actor. This is why attackers create a process in a suspended state and then resume it after the hollowing is done.
Basic Steps Involved in Performing Hollowing
- The first step is to select a candidate PE image(application) for hollowing. In this case the loader executable is respawned with the final ransomware payload.
- Win32 API kernel32.GetModuleFileNameA is then used to obtain the name of the loader executable which is later used in the kernel32.CreateProcessA API.
- The shellcode obtains information that needs to be used later in the process creation. This information consists of startup information of the process and command-line provided to the newly created process.
- The ransomware then launches a new process with the following parameters: “–Admin IsNotAutoStart IsNotTask.”
- Subsequently the malware creates a process in a suspended state. The kernel32.CreateProcessA is used to create the process, using arguments such as target executable, startup info, and command line parameters, etc.
- Next, the Process Environment Block retrieves the base image address of the executable from which the process is created. Threat actor needs to know the base address to unmap the real PE image(code) and then introduce the malicious one.
- The kernel32.ReadProcessMemory is used to obtain the image address of the image.
- The unmapping process is done by calling the ntdll.ZwUnmapViewOfSectionA function:
- Now the malware tries to allocate a virtual memory in the memory space of the remote process created. This memory will be used to host the ransomware code in the newly spawned process.
- The kernel32.VirtualAllocEx is specifically used to create the memory allocations in the remote address spaces, as seen below:
- The final payload is stored within the loader. The payload begins with an “MZ” byte indicating that it is an executable. This needs to be extracted for further analysis, since it is the final crypter the loader deploys from the new process.
- In the next step the malware writes the payload into the remote process memory of the newly spawned process via CreateProcessA.
- Now the malware can write the final payload in the address space of the remote process using ntdll.ZwWriteVirtualMemory/NtWriteProcessMemory as shown below:
- Writing new malicious code(hollowing) into the newly created process is as shown below. The malware iteratively writes bytes into the remote process which is in a suspended state. The changes are made segment by segment(.text, .data etc).
- Next, the Entry Point used by the Windows loader to start executing the program code of the application, is changed.
- This information can be found in the primary thread created with the suspended process. The kernel32.GetThreadContext is used to retrieve the thread-specific information.
- Once the Entry Point is changed, the changes are committed via the kernel32.SetThreadContext API, and the suspended process executes the malicious code.
- As a final step, the thread is resumed in the target process, which wakes from the suspended state and executes the ransomware code. The kernel32.ResumeThread API is used to resume the remote thread.
- At this stage, we can observe multiple instances of the loader program in the process listing. And handles opened by the loader are now closed using kernel32.CloseHandle.
- Finally the loader exits via kernel32.ExitProcess and terminates the execution.
- In the memory process listing there will be a process with a different ID, which is the same loader executable but with different code logic hollowed into it at the process hollowing stage.
- This process with the new PID will be responsible for encrypting the victim.
In the second part of the blog post we will continue to analyze the ransomware crypter code after obtaining the newly created process from the memory dump.