Summary
Darkside ransomware is the malware family responsible for the Colonial Pipeline attack on May 7 2021 as described at https://www.zdnet.com/article/darkside-the-ransomware-group-responsible-for-colonial-pipeline-cyberattack-explained/. The binary contains an encrypted configuration that will be decrypted using a custom algorithm, which reveals a 22-byte buffer that describes different actions performed by the malware. These actions include: checking the system language and avoiding to encrypt Russian language machines, deleting Shadow copies, wiping Recycle Bin, ignore specific files, directories and file extensions, killing specific processes, deleting specific services, etc. The ransomware can perform privilege escalation using the CMSTPLUA COM interface and achieves persistence by installing itself as a service. The files are encrypted using the custom Salsa20 implementation, with the Salsa20 matrix being encrypted by the public RSA key hard-coded in the binary. Darkside uses multithreading with I/O completion ports to communicate between the main thread and the worker threads responsible for file encryptions. It’s important to mention that the process generates a random Salsa20 matrix using the RDRAND and RDSEED instructions, as opposed to earlier versions that use the RtlRandomEx function.
Analyst: @GeeksCyber
Technical analysis
SHA256: 0A0C225F0E5EE941A79F2B7701F1285E4975A2859EB4D025D96D9E366E81ABB9
The malware comes with an encrypted configuration that is decrypted using a custom algorithm:
The custom decryption algorithm consists of 4 subtraction operations by 0x10101010 each time and then some addition operations, as shown below:
For each DLL to be loaded, there is a hash function that is applied to the DLL name, and the 4-byte result is compared to hardcoded values:
For example, the following value corresponds to kernel32.dll:
The following DLLs are expected to be loaded: ntdll, kernel32, advapi32, user32, gdi32, ole32, oleaut32, shell32, shlwapi, wininet, netapi32, wtsapi32, activeds, userenv, mpr, rstrtmgr. The process retrieves the address of multiple export functions based on similar hash values computed using the same algorithm:
The decrypted configuration is presented below and is composed of the RSA-1024 exponent (0x010001 = 65537), 0x80-byte RSA-1024 modulus, victim UID, 22 configurations bytes (will be detailed further on) and the aPLib-compressed configuration:
The binary uses an aPLib-decompression algorithm to decrypt different strings. The following list represents the directories to avoid in the encryption process:
The following files will be ignored by the ransomware:
If the file’s extension belongs to the following list, then the file will not be encrypted by the process:
The binary intends to delete folders that contain the word “backup” in their name:
A feature not used by the malware would use the following strings decompressed as the other ones (our guess is that the actor would try to kill the SQL-related processes in order to encrypt databases):
The following processes will not be terminated by the file:
If a process name contains any of the following strings, it will be killed by the binary:
There is also a list of services to be stopped and deleted, as shown in the figure below:
The list of C2 servers is also obtained using the same algorithm:
The process reveals a message that will be utilized to set a custom wallpaper that contains important instructions for the victim:
The content of the ransom note is also written in the process memory, as shown in figure 17:
The following table describes the actions that the malware takes depending on the configuration decrypted above:
Offset | Enabled | Description |
0x00 | Yes | FAST encryption mode |
0x01 | Yes | Unknown (not used) |
0x02 | No | Attempt to log on as a user on the machine |
0x03 | Yes | Encrypt DRIVE_REMOVABLE, DRIVE_FIXED and DRIVE_REMOTE type of drives |
0x04 | Yes | Retrieve the domain controllers and probably an attempt to spread further |
0x05 | Yes | Check system language and avoid the Russian language |
0x06 | Yes | Delete volume shadow copies |
0x07 | Yes | Delete files and folders from Recycle Bin |
0x08 | No | Self deletion |
0x09 | Yes | Ignore specific directories |
0x0a | Yes | Ignore specific files |
0x0b | Yes | Ignore specific file extensions |
0x0c | Yes | Wipe “backup” directories |
0x0d | Yes | Unknown (not used) |
0x0e | Yes | Kill specific processes |
0x0f | Yes | Stop and delete specific services |
0x10 | Yes | Set Desktop wallpaper |
0x11 | Yes | Drop ransom note |
0x12 | Yes | Change icon of new encrypted files |
0x13 | Yes | Create a mutex |
0x14 | Yes | Unknown (not used) |
0x15 | Yes | Communication with the C2 servers |
The malware uses the NtQueryInstallUILanguage and NtQueryDefaultUILanguage APIs to determine the language of the system and compares the result with 0x419 (Russian language identifier). If there is a match between these two values, then the malware exits:
There is a call to the RegCreateKeyExW function, which is supposed to create (or open if it already exists) the “Software\Microsoft\Cryptography” registry key, as follows:
The malware extracts the “MachineGuid” value from the above registry key, as presented in the next figure:
A custom hashing algorithm that generates 8 lowercase hexadecimal characters is implemented by the process (the “MachineGuid” value is the input, and the algorithm applies 8 times):
The value computed above (let’s call it RansomPseudoValue) will be used in the following constructions:
- Service name: <RansomPseudoValue>
- Service display name: <RansomPseudoValue>
- Ransom note: README<RansomPseudoValue>.TXT
- Wallpaper: %PROGRAMDATA%\<RansomPseudoValue>.BMP
- Each encrypted file will have the following name: <Original filename><RansomPseudoValue>
- Icon file: %PROGRAMDATA%\<RansomPseudoValue>.ico
- Registry key created: HKCR\<RansomPseudoValue>\DefaultIcon=%PROGRAMDATA%\<RansomPseudoValue>.ico
The binary uses the SHTestTokenMembership API to verify if the user belongs to the Administrators groups (0x220 = 544 in decimal):
We’ll split the analysis into 3 different parts depending on the user’s privileges: low level privileges, administrative privileges, and SYSTEM privileges.
Low Level privileges
The malware attempts a UAC bypass that uses the CMSTPLUA COM interface as described at https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512. It utilizes ZwOpenProcessToken to open the access token associated with the process (0x8 = TOKEN_QUERY – required to query an access token):
The NtQueryInformationToken function is used to get the group accounts associated with the token (0x2 = TokenGroups) and it checks if the administrators group can be found in the TOKEN_GROUPS structure:
There is a call to the CoInitialize routine in order to initialize the COM library on the current thread, as highlighted in figure 27:
As presented so far, the binary uses a lot of lower level APIs (from ntdll). It allocates a new memory area using the ZwAllocateVirtualMemory API (0x3000 = MEM_COMMIT | MEM_RESERVE and 0x4 = PAGE_READWRITE):
We have encountered a call to an undocumented API function called LdrEnumerateLoadedModules:
The file executes CoGetObject with the object name as Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}, as highlighted below:
Basically, it will relaunch the malware with SYSTEM privileges:
Administrative privileges
As in the first case, the binary uses ZwOpenProcessToken to open the access token associated with the process (0x8 = TOKEN_QUERY – required to query an access token):
The NtQueryInformationToken API is utilized to retrieve the token’s user account (0x1 = TokenUser):
The malicious process uses LookupAccountSidW to obtain the name of the account associated with the SID provided as the input, as shown in figure 35:
There are 3 different comparison operations that compare the domain name (the name of the computer in our case) with “NT AUTHORITY”, “AUTORITE NT” and “NT-AUTORITAT” (basically, it tries to determine if the user account is SYSTEM or not):
The OpenSCManagerW routine is utilized to establish a connection to the service control manager:
The process tries to open a service called <RansomPseudoValue> (which doesn’t exist at this time):
Because the service doesn’t exist, it will be created by the malware for persistence purposes, as shown in the following pictures:
The newly created service is started, and the binary launches itself as a service:
SYSTEM privileges
The malicious binary can run with no arguments, one, two, or three arguments (these cases will be described later on). As we can see below, it uses CommandLineToArgvW to obtain pointers to the command line arguments (argv[0] is the executable name) + the number of arguments:
The WTSQueryUserToken API is utilized to obtain the primary access token of the logged-on user specified by session 1:
OpenWindowStationW is used to open the “Winsta0” windows station (the interactive window station), 0x40000 – WRITE_DAC – modify the DACL in the security descriptor for the object:
The DACL (discretionary access control list) of the “Winsta0” windows station is modified by calling the NtSetSecurityObject routine with the 0x4 = DACL_SECURITY_INFORMATION parameter:
There is a call to OpenDesktopW that is utilized to open the “Default” desktop object with the argument 0x40081 = WRITE_DAC | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS, as follows:
The DACL of the “Default” desktop object is modified by calling the NtSetSecurityObject function with the 0x4 = DACL_SECURITY_INFORMATION parameter:
The malware creates a mutex called “Global\4787658f1cc4202b8a15e05dd0323fde” (this value has been computed before this operation and represents a custom “hash” value of the malware), which makes sure that there is only one instance of the ransomware running at a time (if the mutex already exists, then the malware quits):
The ransomware forces the system not to enter sleep mode and not to turn off the display while the process is running, one of the parameters being 0x80000001 = ES_CONTINUOUS | ES_SYSTEM_REQUIRED:
The file changes the privilege to SE_PRIVILEGE_ENABLED in order to enable the token’s privileges (note the TOKEN_PRIVILEGES structure) by a function call to ZwAdjustPrivilegesToken:
The CreateThread API is used to create a new thread, as described in the next figure:
A list of valid drives on the system is extracted using the GetLogicalDriveStringsW routine:
The ransomware is looking for DRIVE_REMOVABLE (0x2) and DRIVE_FIXED (0x3) drives, as highlighted in figure 54:
All files and directories from Recycle Bin are deleted by the process. It starts to enumerate via a FindFirstFileExW API call:
As presented below, the files are deleted using the DeleteFileW function, and the directories are removed using the RemoveDirectoryW routine:
The binary uses COM objects and WMI commands to delete volume shadow copies. It calls the CoCreateInstance function to create a single object of the class IWbemLocator with the CLSID {dc12a687-737f-11cf-884d-00aa004b2e24} (Ref. https://forum.powerbasic.com/forum/user-to-user-discussions/source-code/25222-wmi-wrapper-functions):
There is also a new IWbemContext interface with the CLSID {44aca674-e8fc-11d0-a07c-00c04fb68820} (Ref. https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmi/3485541f-6950-4e6d-98cb-1ed4bb143441) created via a CoCreateInstance function call:
Using the IWbemLocator object, the process calls the ConnectServer API to connect to the local “ROOT\CIMV2” namespace and retrieves a pointer to a IWbemServices object, as follows:
There is a call to CoSetProxyBlanket performed by the ransomware, as described in the next figure (0xA = RPC_C_AUTHN_WINNT – NTLMSSP, 0x3 = RPC_C_AUTHN_LEVEL_CALL and 0x3 = RPC_C_IMP_LEVEL_IMPERSONATE):
The process executes the following SQL query “SELECT * FROM Win32_ShadowCopy” to obtain an enumerator of all shadow copies, and then it deletes each of the shadow copy objects via the DeleteInstance method:
A list of all services and their status is retrieved by calling the EnumServicesStatusExW function (0x30 = SERVICE_WIN32, 0x3 = SERVICE_STATE_ALL):
Each service name is compared to the list that was decrypted at the beginning of the analysis:
The malware opens the targeted services by calling the OpenServiceW routine (0x10020 = DELETE | SERVICE_STOP):
Every targeted service is stopped and deleted using ControlService and DeleteService, as displayed in figure 65:
The NtQuerySystemInformation API returns an array of SYSTEM_PROCESS_INFORMATION structures (one for each process running on the system, 0x5 = SystemProcessInformation):
Each process name is compared to the list that was decrypted in the beginning, as displayed below:
For every targeted process, the binary opens the process and terminates it and all of its threads:
The binary creates an ico file called <RansomPseudoValue>.ico, as displayed below:
A new registry key called <RansomPseudoValue> is created using the RegCreateKeyExW function, as shown in figure 70:
The DefaultIcon subkey is created, and it specifies the path for the newly created ico file:
The malware calls the SHChangeNotify routine to notify the shell to update its icon cache (0x08000000 = SHCNE_ASSOCCHANGED, 0x1000 = SHCNF_FLUSH):
A new file called %PROGRAMDATA%\<RansomPseudoValue>.BMP is created using the CreateFileW function:
Moving forward, there is a registry key opened by calling the RegCreateKeyExW API, as shown in the next picture:
The “WallPaper” value inside the registry key is changed to the location of the newly created BMP file:
After all of these activities, the Desktop has been changed to the following image:
Thread activity – sub_4095AB
The thread starts by decrypting the following information:
The version of the Darkside ransomware is also decrypted and represents the latest version analyzed in the wild (2.1.2.3):
Another JSON structure is decrypted by the binary and will be used to collect data about the local machine:
One more time, the process checks the type of the drives and is looking for DRIVE_REMOVABLE (0x2), DRIVE_FIXED (0x3) and DRIVE_REMOTE (0x4):
The GetDiskFreeSpaceExW function is used to retrieve information about the targeted drives, such as the total amount of space and the total amount of free space:
NtDuplicateToken is utilized to duplicate an existing token and to obtain a handle to a new access token (0xC = TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY and 0x2 = TokenImpersonation):
The thread’s impersonation token is changed via a call to the ZwSetInformationThread routine, as shown in figure 83 (0x5 = ThreadImpersonationToken):
The ransomware retrieves the username associated with the current thread, as well as the NetBIOS name of the local machine:
The current language of the machine is retrieved from the “LocaleName” value, as presented below:
NetGetJoinInformation is used to get the join status information for the local computer:
The product name of Windows can be extracted by querying the “ProductName” value and the Windows product ID can be extracted by querying the “ProductId” value, as shown in the following pictures:
The malware constructs the following JSON, which contains data to be exfiltrated to the C2 server:
The final data looks like in the following JSON form:
The data from above is encrypted by a custom encryption algorithm:
The result of the encryption operation is base64-encoded, as shown below:
The following function is used to generate 2 random 4-byte values that will be utilized in the network communications. It uses instructions such as RDRAND and RDSEED to generate random numbers (if these are supported), but we’ll provide a deeper understanding of it when we discuss file encryption (it’s also used to generate the Salsa20 matrix):
The parameters of the network request have the following structure: random_number1=base64(encryptionresult)&random_number2=victim_uid:
The InternetOpenW function is called using a user agent decrypted by the malware as a parameter:
InternetConnectW is utilized to connect to one of the C2 servers (baroquetees[.]com) on port 443:
The process creates an HTTP request handle using the HttpOpenRequestW routine, as shown in figure 100:
There is also a call to the InternetSetOptionW API that is used to set the security flags for the handle (0x1f = INTERNET_OPTION_SECURITY_FLAGS):
The binary sends the POST request to the C2 server using HttpSendRequestW:
The status code returned by the server is retrieved using the HttpQueryInfoW API (0x13 = HTTP_QUERY_STATUS_CODE):
Interestingly, the ransomware doesn’t expect a 200 status code but a 500 (Internal Server Error). If the status code isn’t 500, then the process repeats the steps described so far using the second C2 server, rumahsia[.]com:
This last idea concludes our analysis of this thread. We continue to analyze the main thread.
The binary enumerates the volumes available on the machine and uses the CreateFileW routine to open them:
DeviceIoControl is utilized to get information about the type, size, and nature of a disk partition (0x70048 = IOCTL_DISK_GET_PARTITION_INFO_EX):
A new thread is created by the file using CreateThread:
Thread activity – sub_407558
The only action the thread does is using the GetLogicalDriveStringsW API to retrieve the valid drives on the local machine:
If a volume doesn’t have a drive letter associated with it, then the ransomware does that using the SetVolumeMountPointW API, as highlighted in the following picture:
The malicious process targets the following types of drives – DRIVE_REMOVABLE (0x2), DRIVE_FIXED (0x3) and DRIVE_REMOTE (0x4):
The CreateFileMappingW function is used to create a named file mapping object (name “Local\\job0-<Process Id>” means the object is created in the session namespace):
The binary maps a view of the file mapping into the address space of the process by calling the MapViewOfFile routine (0xf001f = FILE_MAP_ALL_ACCESS):
A named event object called “Local\\job0-<Process Id>-Event” is created by the binary:
The ransomware launches itself with 3 parameters, and the new process will execute the encryption operations:
OpenMutexW is utilized to open a named mutex called “Global\\T-job0-<Process Id>” (which doesn’t exist at this time) – 0x100000 = SYNCHRONIZE:
The event object created earlier is opened by calling the OpenEventW API (0x1f0003 = EVENT_ALL_ACCESS), as displayed in figure 118:
The file creates an I/O completion port that isn’t associated with a file handle, which will be used by the main thread to send data that will be encrypted to worker threads:
Two different threads that will take care of the files’ encryption are created using the CreateThread routine:
The ransom note README<RansomPseudoValue>.TXT is created and populated in every directory the malware encrypts:
The process doesn’t encrypt some certain files, as displayed in the next figure:
A list of file extensions decrypted at the beginning of the execution is also excluded from the encryption process:
Every targeted file is opened and read using the CreateFileW and ReadFile functions:
The file extension is changed to also include <RansomPseudoValue>, as shown below:
There is a second function call to CreateIoCompletionPort that associates the existing I/O completion port with the FileHandle parameter:
The RSA public exponent and the RSA modulus will be used in the encryption process of the Salsa20 matrix, as we’ll describe later on:
The ransomware checks to see if the RDRAND and RDSEED instructions are supported by the processor. If that’s the case, it will use one of them to generate 56 random bytes, and 8 NULL bytes are added to the resulting buffer (Salsa20 matrix -> custom Salsa20 implementation). If none of these are supported, the malware uses the rdtsc instruction to generate deterministic timestamps that will provide a 64-byte Salsa20 matrix:
The thread poses a custom implementation of the RSA-1024 algorithm (it doesn’t rely on Windows APIs). Basically, the data d will produce a ciphertext = (d^exponent)%modulus. The raw modulus calculation is performed using addition and subtraction and part of the implementation is presented in the following figures:
The Salsa20 matrix is encrypted using the custom RSA implementation, as shown in figure 133:
There is a custom “hash” function applied to the above encryption result, which produces a 16-byte output:
The file content that will be encrypted is appended to the buffer that will be sent to the worker threads:
The Salsa20 matrix is also added to the buffer, and it will be utilized by the worker threads to encrypt the files:
Thread activity – sub_405E7C (File encryption)
The file content is encrypted using a custom Salsa20 implementation and the ciphertext overwrites the plaintext in the buffer:
A snippet of the custom implementation is presented below:
The encrypted content is written to the initial file, followed by the encrypted Salsa20 matrix and the hash value, as displayed in the following figures:
This last idea concludes our analysis of this thread. We continue to analyze the main thread.
If the current directory contains “backup”, then the malware deletes it:
The main thread sends the buffer described above (which includes file content to be encrypted etc.) to the worker threads by calling the PostQueuedCompletionStatus routine:
We’ve also identified a function that we believe it’s used to propagate the malware to domain controllers (we didn’t have one in our environment). It calls functions such as DsGetDcNameW, DsGetDcOpenW and DsGetDcNextW:
Darkside enumerates all network shares using the NetShareEnum API and encrypts each one of them by the main encryption routine described so far:
Thread activity – sub_4096A4
The following JSON is decrypted by the thread:
The file opens the following registry key by calling RegCreateKeyExW:
The Product ID is retrieved again by calling the RegQueryValueExW function:
The machine GUID is extracted from the registry and represents a unique identifier for the machine:
After the encryption finishes, the malware sends encryption statistics to the C2 server, such as: victim ID, uid, number of encrypted files, size of encrypted files, number of skipped files and elapsed time. The final JSON structure looks like the following:
As already described so far regarding the C2 communication, the buffer is encrypted with a custom algorithm and base64-encoded. The request sent to the C2 server is presented in the next picture:
If the self deletion feature would be enabled, Darkside would delete itself using ShellExecuteW:
As we specified at the beginning of the analysis, the binary can run with different parameters:
- 1 parameter: filename – only this file will be encrypted
- 2 parameters: “-path” directory – only this directory will be encrypted
- 3 parameters: “-work” worker0 job0-<Process Id> – this is spawned by the initial process, already described
A particular case is handled by the ransomware differently when it deals with a shortcut file (.lnk file). Basically, the binary wants to extract the full path to the file from this link. It calls the CoCreateInstance API with the CLSID of {000214F9-0000-0000-C000-000000000046} (IShellLinkW interface):
Unfortunately, Scylla didn’t help us here and it couldn’t provide us the methods. We’ve found that the next 2 function calls are used to extract the path of the file/directory:
The file extracted above is encrypted as usual:
References
MSDN: https://docs.microsoft.com/en-us/windows/win32/api/
Fakenet: https://github.com/fireeye/flare-fakenet-ng
Analysis of Darkside Ransomware v1.8.6.2: https://chuongdong.com/reverse%20engineering/2021/05/06/DarksideRansomware/
Fireeye report: https://www.fireeye.com/blog/threat-research/2021/05/shining-a-light-on-darkside-ransomware-operations.html
https://gist.github.com/api0cradle/d4aaef39db0d845627d819b2b6b30512
https://forum.powerbasic.com/forum/user-to-user-discussions/source-code/25222-wmi-wrapper-functions
INDICATORS OF COMPROMISE
C2 domains: baroquetees[.]com, rumahsia[.]com
SHA256: 0A0C225F0E5EE941A79F2B7701F1285E4975A2859EB4D025D96D9E366E81ABB9
Created files: README<RansomPseudoValue>.TXT, %PROGRAMDATA%\<RansomPseudoValue>.BMP, %PROGRAMDATA%\<RansomPseudoValue>.ico
Service Name: <RansomPseudoValue>, Service display name: <RansomPseudoValue>
Registry key: HKCR\<RansomPseudoValue>\DefaultIcon=%PROGRAMDATA%\<RansomPseudoValue>.ico
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:79.0) Gecko/20100101 Firefox/80.0 (prone to False Positives)
This was a great in-depth article and very thorough! Best one I have read so far.
Thank you for this. Awesome. Learned and will be learning tons from this.
So either we should install an Anti Virus or add Russian as an additional language to our keyboard languages 🤣
Well put together, will be rereading this quite a bit. Thanks for sharing.