BlackCat Ransomware Analysis

7 minute read


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 {$[20].Value -match "sample1"} | Format-List @{label = "CommandLine" ; Expression = {$[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 {$[4].Value -match "sample1"} | Format-List @{label = "destination ip" ; Expression = {$[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):
      ida_dbg.add_bpt(ref.frm, 1, ida_idd.BPT_DEFAULT)
    print ("%08x: %s (ord#%d)" % (ea, name, ord))
    for ref in idautils.XrefsTo(ea):
      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)
  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.



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.


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
        description = "Detection Rule for BlackCat Ransomware"
        email = ""
        author = "Amr Ashraf"
        $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
        ($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_Sample")
            file_path = sys.argv[1]
            with open(file_path, 'rb') as file:
                content =

            offset = content.find(binascii.unhexlify(b"7B22636F6E6669675F696422"))

            if offset == -1:
                print("\nunable to find configuration offset\n\n")

            cfg = content[offset: offset+8000].strip()

            config = json.loads(cfg.decode('utf-8'))

        except Exception as e:
            print(f"Error: {e}")


SHA256 : ecea6b772742758a2240898ef772ca11aa9d870aec711cffab8994c23044117c


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