BlackCat Ransomware Analysis
Sample Overview
We are Presented with the following sample:
md5: FF8A7DD8B1CB0420DD18810041D172A7
SHA256: ecea6b772742758a2240898ef772ca11aa9d870aec711cffab8994c23044117c
SHA1: cc166bc3eaa024aac4a2cdc02174ae87fcf47e28
It’s an x86 Windows Portable executable, and the strings contain many references to Cargo
which is a package manager for Rust
language which indicates the executable is written with Rust.
There is also a long string which may be the Configuration used by the sample.
Another notable string is
cmd.exe /c for /F "tokens=*" %1 in ('wevtutil.exe el') DO wevtutil.exe cl "%1"
which is a command to clear event logs.
Behavioral Analysis
running the malware didn’t cause any action to happen which may be for two reasons, The first one is that it may be performing some anti-analysis checks that detected the analysis workstation or it needs a command line argument.
the second assumption was the right one when finding a USAGE:
string in the strings of the sample, trying to run it with -h
and I got a usage message in the command line.
It becomes clear that the malware requires an access token to operate, but actually, any supplied input after the --access-token
parameter will work.
The ransomware did work and encrypted the machine.
But let us investigate what happened.
My analysis machine is monitored by Sysmon
so using a simple PowerShell script I can review a lot, like:
- Spawned processes
Get-WinEvent -FilterHashtable @{Logname = "Microsoft-Windows-Sysmon/Operational" ; ID = 1 ; StartTime = "8/29/2023 11:15:50"} | Where-Object {$_.properties[20].Value -match "sample1"} | Format-List @{label = "CommandLine" ; Expression = {$_.properties[10].value}}
we can see it doing the following:
- deleting Event logs (didn’t work because of an error in the syntax).
- deleting volume shadow copies.
- Increase the number of outstanding requests allowed.
- enables the evaluation of symbolic links from remote to local machine and to remote also.
- retrieve the Universally Unique Identifier (UUID) of the computer’s system product.
- arp table lookup.
-
stops IIS.
- Propagation Method
Get-WinEvent -FilterHashtable @{Logname = "Microsoft-Windows-Sysmon/Operational" ; ID = 3 ; StartTime = "8/29/2023 11:15:50"} | Where-Object {$_.properties[4].Value -match "sample1"} | Format-List @{label = "destination ip" ; Expression = {$_.properties[14].value}}
we can see that the malware is trying to connect over port 137(netbios-ns)
to all the machines in my local network, maybe as an infection method, We will look closely in the code analysis section.
then dropping the note and background and other stuff.
Code Analysis
It’s noticeable that the sample doesn’t resolve many APIs dynamically and uses the imported functions so I edited an Ida script to help clean the mess generated by the rust compiler and logging mechanism by breaking all the important imported functions.
import idaapi
import idautils
def imp_cb(ea, name, ord):
if not name:
print ("%08x: ord#%d" % (ea, ord))
for ref in idautils.XrefsTo(ea):
print(hex(ref.frm))
ida_dbg.add_bpt(ref.frm, 1, ida_idd.BPT_DEFAULT)
else:
print ("%08x: %s (ord#%d)" % (ea, name, ord))
for ref in idautils.XrefsTo(ea):
print(hex(ref.frm))
ida_dbg.add_bpt(ref.frm, 1, ida_idd.BPT_DEFAULT)
return True
nimps = idaapi.get_import_module_qty()
print ("Found %d import(s)..." % nimps)
for i in range(0, nimps):
name = idaapi.get_import_module_name(i)
if not name:
print ("Failed to get import module name for #%d" % i)
continue
if name == "KERNEL32" or name == "ADVAPI32" or name == "WS2_32":
print ("Walking-> %s" % name)
idaapi.enum_import_names(i, imp_cb)
print ("Execution finished...")
The code starts by registering a custom handler to 0x0C00000FD STATUS_STACK_OVERFLOW
exception.
Then enumerating the registry key SOFTWARE\Microsoft\Cryptography
to get the MAchineGuid
value.
the malware always tries to locate cmd.exe in the same directory of the malware but when not found it will execute it from the system32
directory.
the malware then opens a handle to a null device.
Then create a named pipe with a name generated randomly using a BCryptGenRandom
API, which is used in Inter Process Communications
to receive the output of any executed command.
In the rest of the functionalities, the malware creates different threads to do all the work. The malware starts Its real work after checking the command line arguments supplied with privilege escalation.
Privilege escalation
the malware first checks the privileges that it runs with to know whether it needs a privilege escalation or not using different methods.
checking the RID.
checking the process token.
for privilege escalation, it uses COM object with CLSID:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}
which is the auto-elevated CMSTPLUA interface that will launch a new process with the same arguments but in elevated permissions.
Propagation
the malware then creates a socket to enumerate the surrounding devices which is obtained from executing an “arp -a” command.
Processes Spawning
After elevation, we can see the malware executes the previously mentioned command line processes in the behavioral analysis section.
Service Kill
The malware looks for the services mentioned in the configuration and kills them using ControlService
with 1 as a control signal which indicates to close the service.
Processes Kill
The same happens for processes like services, the malware gets the running processes using CreateToolhelp32Snapshot
and enumerates for the processes mentioned in the configurations using Process32FirstW
and Process32NextW
APIs, then terminates them using TerminateProcess
The malware adds a small sleep
in all the enumeration to be quieter.
Drive Enumeration
the malware starts to enumerate all possible drive letters “A-Z” to process all available ones.
Then enumerating all volumes using FindFirstVolumeW
and FindNextVolumeW
File Encryption
The malware takes the following steps in the process of encrypting files:
- Enumerate all the files and directories (Ransome note dropped in each directory).
- Test the file opened against the roles in the configurations (if the configurations prevent encrypting it, the handle will be closed) otherwise it will be added to the encryption queue.
- Rename the encrypted file adding the extension mentioned in the configurations.
- Encrypts data using a randomly generated AES Key stored encrypted using the public key in configurations in each encrypted file.
- The encrypted content then is written back to the file.
A note needed to be added here is that the encryption implementation in the sample is so complex compared to other ransomwares out there, although a lot of its code is not reachable in this sample may be due to configuration constraints, there is a reference to `ChaCha20Aes` encryption which is faster and more complex than normal AES encryption, also the sample seems to implement different modes to encrypt large files to make the process of encryption faster.
Arguments
Other parameters are self-explained in the usage message, the most essential one is the --access-token
which will cause the malware to run.
Yara rule
rule BlackCat : Ransomware
{
meta:
description = "Detection Rule for BlackCat Ransomware"
email = "amr.ashraf.re@outlook.com"
author = "Amr Ashraf"
strings:
$mz = {4D 5A} // MZ header
$string1 = "enable_self_propagation" ascii
$string2 = "enable_esxi_vm_snapshot_kill" ascii
$string3 = "RECOVER-${EXTENSION}-FILES.txt" ascii
$string4 = "win7_plus=true" ascii
$string5 = "\\.\\pipe\\__rust_anonymous_pipe1__." ascii
$string6 = "MaxMpxCt /d 65535" ascii
$string7 = "Speed: Mb/s, Data: Mb/Mb, Files processed: /, Files scanned:" ascii
condition:
($mz at 0) and (4 of ($string*))
}
Configuration Extractor
import sys
import json
import binascii
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python BlackCat_config_Extractor.py BlackCat_Sample")
else:
try:
file_path = sys.argv[1]
with open(file_path, 'rb') as file:
content = file.read()
offset = content.find(binascii.unhexlify(b"7B22636F6E6669675F696422"))
if offset == -1:
print("\nunable to find configuration offset\n\n")
sys.exit(1)
cfg = content[offset: offset+8000].strip()
config = json.loads(cfg.decode('utf-8'))
print(config)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
IOC
SHA256 : ecea6b772742758a2240898ef772ca11aa9d870aec711cffab8994c23044117c
MITRE ATT&CK
T1007 – System Service Discovery T1047 - Windows Management Instrumentation T1057 - Process Discovery T1059 – Command and Scripting Interpreter T1082 – System Information Discovery T1135 - Network Share Discovery T1140 – Encode/Decode Files or Information T1485 – Data Destruction T1486 – Data Encrypted For Impact T1490 – Inhibit System Recovery T1543.003 – Create or Modify System Process T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control T1559 - Inter-Process Communication T1202 – Indirect Command Execution