Our experts conduct in-depth forensic investigations to trace attacks, recover encrypted data, and restore business operations with minimal downtime.

Gallery

Contacts

39b Alpha Park, Cleveland, OH 44143

+1 (844) 969-6683

Case Study DFIR Report Uncategorized

In a recent incident response, our Digital Forensics and Incident Response (DFIR) team discovered that SafePay ransomware had been deployed on the victim’s machines. The first confirmed activity of SafePay ransomware was in September 2024, and since its inception, the group has increased its activity, adding more victims to its Data Leak Site (DLS). The group’s targets include the public and private sectors worldwide, and notable targets from the US and UK. 

In the case that Proven Data worked on, the encrypted files used the .safepay extension, and the ransom note was named readme_safepay.txt. After initial triage, our analysts identified a malicious autorun registry value “6F22-C16F-0C71-688A” on one of the endpoints. This autorun executes the malicious ransomware DLL file located at “C:\locker.dll”. The DLL is executed through the legitimate and signed Microsoft command-line utility “Regsvr32.exe”.

It’s important to note that while regsvr32.exe is a legitimate utility for registering and unregistering DLLs, it has also been misused by malicious actors. They often exploit this tool to load malicious DLLs into memory and execute them, evading security measures that may not monitor the execution of modules launched through the legitimate regsvr32.exe utility. The following value was assigned to the registry value mentioned earlier to execute the malicious DLL with custom command-line parameters:

C:\Windows\SysWOW64\regsvr32.exe" /n "/i:-pass=<REDACTED> -enc=3 -uac -selfdelete" C:\locker.dll

Analysis 

In this section, we will provide a comprehensive and detailed analysis of the capabilities of the ransomware DLL. We were unable to find any publicly available samples for comparison and will only analyze the malicious DLL that we have obtained.

Initial triage

Our initial analysis of the DLL shows that the threat actors have set the compilation timestamp of the DLL to zero. This is a very common technique we see used by threat actors to mislead analysts or hide the exact compilation timestamp of their malware.

Ransomware DLL timestamp field in the PE headers set to zero.
Ransomware DLL timestamp field in the PE headers set to zero.

The command line used to execute the malicious DLL mentioned earlier includes the flag “/n,” which instructs “regsvr32.exe” not to run the DLL’s DllRegisterServer export. Additionally, the “/i” flag, followed by the ransomware’s command-line arguments, is used to execute the ransomware. The “/i” flag directs “regsvr32.exe” to invoke the DllInstall export, which serves as the entry point for executing the ransomware DLL.

Obfuscation 

Upon further investigation of the malicious DLL, we identified two primary obfuscation techniques that the ransomware employed to complicate the analysis process and conceal its core functionalities. The first notable technique we encountered was inlined encrypted stack strings. In this method, the string is assembled on the stack during runtime and subsequently decrypted for use. This technique poses challenges for two main reasons: first, the string is built on the stack at runtime, and second, the decryption routine is polymorphic.

Inlined encrypted stack string and its  polymorphic decryption loop.
Inlined encrypted stack string and its polymorphic decryption loop.

It’s essential to note that this technique closely resembles ADVobfuscator string encryption, which has been previously observed in Conti and many other ransomware variants that borrow code from.

We developed an IDAPython script that searches the entire binary for patterns of stack string reconstruction, extracts those strings, decrypts them, and ultimately patches them in place. This script was designed to facilitate our analysis process, making it easier to focus on the ransomware core functions.

After successfully decrypting and patching the strings, we ultimately obtain a more visually appealing IDB file that is ready for our analysis. 

IDB file after decrypting and patching the strings in place
IDB file after decrypting and patching the strings in place.

The second obfuscation technique we encountered was dynamic API resolution by hash. In this technique, the ransomware encodes the API function names as hashes and resolves them at runtime by walking the exported functions table for the relevant DLL, hashing all of the exported function names, and comparing the hashes until a match is found. This is such a common obfuscation technique that we encounter it in almost every malware sample whatsoever. 

The ransomware resolving APIs from hardcoded hash tables and storing the resolved function addresses in another table for later reference.
The ransomware resolving APIs from hardcoded hash tables and storing the resolved function addresses in another table for later reference.

For this technique, we developed an IDAPython script that will locate the hash tables, extract the hashes, resolve them, and finally rename the corresponding location in memory with the name of the resolved function. 

SafePay’s Command-Line Arguments

Option Meaning 
-uac The ransomware checks for an elevated token and enables UAC bypass through COM if not running with elevated privileges. If the flag is unset, it assumes it has administrative rights without checking.
-log Enables the ransomware logging system
-network Starts network parser, locates network shares to encrypt 
-netdrive Starts the network driver parser, locates network(remote) drives to encrypt
-pass=The password provided by the threat actor is used to generate the key used for internal configuration decryption.
-enc=Encryption percentage 
-selfdelete Delete itself after encryption 
-path=Local or remote path to encrypt 

Most ransomware maintain a list of command-line options to (not to) carry out some actions. Likewise, “SafePay” uses a set of command-line arguments that are provided to the ransomware executable as the arguments as we saw earlier. One of the most notable command-line arguments used is the “-pass=” argument. This argument is used to provide a password that is used inside the ransomware for internal configuration decryption. 

The ransomware check for the provided password before proceeding to internal configuration decryption.
The ransomware check for the provided password before proceeding to internal configuration decryption.

One intriguing aspect of this ransomware is how it stores and accesses its internal settings (configuration data). Unlike what we have commonly observed, where configuration data is stored in the data section of the binary, this ransomware stores its configuration data in the “.debug” section of the DLL. This method is quite unusual.

Hex view of the internal and encrypted ransomware configuration stored in .debug section.
Hex view of the internal and encrypted ransomware configuration stored in .debug section

The ransomware utilizes the SHA512  hash of the provided password to generate an XChaCha20 key for decrypting the ransomware configuration.

The ransomware’s internal configuration decryption function.
The ransomware’s internal configuration decryption function.

After decrypting the configuration data, it calculates the hash value over the decrypted data using MurmurHash v3 and compares that against the expected hash value, which is hardcoded in the decrypted configuration data.

The decrypted configuration data stored in the ransomware includes various elements, such as: the contents of the ransomware note, a list of processes and services that should be terminated during execution, ransomnote’s name, the ransomware extension to be added to the locked files, and most importantly, the ransomware’s public key.

Text view of part of the decrypted ransomware configuration.
Text view of part of the decrypted ransomware configuration.
VBINDIFF comparing encrypted and decrypted internal configuration data.
VBINDIFF comparing encrypted and decrypted internal configuration data.

To streamline the analysis process, we created an IDAPython script that decrypts and patches the configuration in place using the provided password.

Aside from this, the other arguments are stored in global variables and accessed during the ransomware execution.

The ransomware check for user provided command-line arguments and setting global variable flags accordingly for later reference.
The ransomware checks for user-provided command-line arguments and sets global variable flags accordingly for later reference.

When the “-log” argument is specified, the ransomware creates a log file located at “C:\ProgramData”. If a local or remote path is specified for encryption through the “-path=” argument, a log file with that path name will be created; otherwise, the log file name will be “auto.log.safepay”, which is used to track the ransomware execution in case “Auto encryption” is used, i.e., no remote or local paths are provided. Below we show the contents of the “auto.log.safepay” which is created when the ransomware is executed without specifying an encryption path.

The contents of the auto.log.safepay, obtained after executing the ransomware with “-log” command-line argument in a local analysis environment.
The contents of the auto.log.safepay, obtained after executing the ransomware with “-log” command-line argument in a local analysis environment.

It is noteworthy to mention that on the victim’s endpoint where the ransomware sample was found, the ransomware was executed without the “-log” argument. As a result, no log file was created, and the above screenshot is the log file when executing the ransomware sample in a local analysis environment for demonstration purposes.

A couple more options supported by the ransomware are the “-netdrive” and “-network” options. These options control whether or not network drives or shared resources should be encrypted during the ransomware encryption process.

The ransomware checking for “-netdrive” and “-network” command-line arguments to decide whether to proceed to encrypting network drives or shared resources.
The ransomware checking for “-netdrive” and “-network” command-line arguments to decide whether to proceed to encrypting network drives or shared resources.

Self-protection

To protect itself from any external tampering, it creates a new access control list (ACL), adds the ACCESS_DENIED access control entry (ACE) using RtlAddAccessDeniedAce, and then copies the remaining entries from the old ACL to the new one, effectively safeguarding against external tampering attempts.

The ransomware Adding ACCESS_DENIED access control entry using RtlAddAccessDeniedAce to protect itself against external tampering.
The ransomware Adding ACCESS_DENIED access control entry using RtlAddAccessDeniedAce to protect itself against external tampering.

Preparation for encryption 

Before proceeding with encryption, the ransomware creates a mutex object using either a default name specified in its internal configuration data or, if a path to encrypt is provided with the “-path=” argument, it hashes the path using SHA512 and generates a mutex name by inserting a hyphen (“-”) after every 5 characters of the resulting hash (the hyphen isn’t inserted for the last group of characters).

Mutex object name check based on whether auto encryption is used or the “-path” command-line argument was specified for directed encryption.
Mutex object name check based on whether auto encryption is used or the “-path” command-line argument was specified for directed encryption.

The use of a mutex in ransomware is so common to restrict the execution of the ransomware to one process and avoid having multiple instances running simultaneously.

After creating a mutex, it proceeds to establish persistence by adding an “Autorun” registry value that points to the ransomware DLL on disk. It is worth mentioning, though, that the “Autorun” is removed at the end of encryption. 

During the preparation process, it seeks to enable elevated privileges such as “SeDebugPrivilege” and “SeTakeOwnership.” These privileges grant the ransomware additional capabilities, including the ability to tamper with other processes, terminate them, read from restricted files, write to restricted files, and much more.

The ransomware checking for “SeDebugPrivilege” and “SeTakeOwnership” elevated privileges and enabling those in case they are not enabled.
The ransomware checking for “SeDebugPrivilege” and “SeTakeOwnership” elevated privileges and enabling those in case they are not enabled.

After gaining elevated privileges, it continues by terminating blacklisted processes and services. Below is a list of processes and service names that the ransomware is configured to terminate.

BlackListed processes and services
sqloracleocssddbsnmpsynctimeagntsvcisqlplussvcxfssvcconmydesktopserviceocautoupdsencsvcfirefoxtbirdconfigmydesktopqosocommdbeng50sqbcoreserviceexcelinfopathmsaccessmspubfaronenoteoutlookpowerpntsteamthebatthunderbirdvisiowinwordwordpadnotepadwuaucltonedrivesqlmangrvsssqlsvc$memtasmepocsmsexchangesophosveeambackupGxVssGxBlrGxFWDGxCVDGxCIMgr

Following that, it performs several activities to inhibit recovery and forensic efforts. Among these activities are emptying the Recycle Bin on all drives and deleting volume shadow copies. 

The ransomware carrying anti-forensic activities and inhibiting data recovery.
The ransomware carrying anti-forensic activities and inhibiting data recovery.

The screenshot below shows several commands executed via an admin command prompt to delete volume shadow copies using “vssadmin” and “wmic”. It also disables the automatic recovery environment (WinRE) on the default boot entry to prevent recovery.

Since executing these commands requires an elevated command prompt, one of the tasks before encryption is to bypass UAC via an elevated COM interface {3E5FC7F9-9A51-4367-9063-A120244FBEC7}, in this case abusing the Microsoft Connection Manager Admin API Helper for Setup COM object (cmstplua.dll), allowing the execution of such elevated commands.

The ransomware check and bypass for UAC.
The ransomware check and bypass for UAC.

SafePay also tries impersonation if the -netdrive, -network, or -path (manual encryption) arguments are specified.

It starts by opening its primary token to inspect its elevation state. If the process is running with TokenElevationTypeFull (full admin rights), it returns and doesn’t proceed with the token impersonation(theft); otherwise, it continues to attempt token theft.

The ransomware check for TokenElevationTypeFull token before proceeding to token theft (impersonation).
The ransomware checks for TokenElevationTypeFull token before proceeding to token theft (impersonation).

The token theft is basically implemented as a two-step process, first retrieving the linked elevated token of the ransomware process.

Retrieving the linked token of the ransomware process.
Retrieving the linked token of the ransomware process.

Secondly, it loops over processes running on the system, trying to locate a process with an elevated token to impersonate.

Iterating over processes running on the system looking for an elevated process to impersonate its primary token.
Iterating over processes running on the system looking for an elevated process to impersonate its primary token.

Creating Encryption Threads

First, the ransomware creates a random filename by hashing data from the data section using SHA512 and generating a filename with hyphens (“-“) inserted after every four characters of the resulting hash (no hyphen is inserted after the final group). The file extension is set to the ransomware’s configured extension (.safepay).

Later during root path traversal, the ransomware creates this file at each root path using the CREATE_NEW dwCreationDisposition value. If the file creation fails (or if the file already exists), the ransomware skips traversing that root path. This serves as a check to prevent the ransomware from re-scanning a root path. The ransomware uses SetFileInformationByHandle with the FileDispositionInfo value to delete the file when its handle is closed.

The ransomware creating a root path traversal marker file with a randomly generated name.
The ransomware creating a root path traversal marker file with a randomly generated name.

After this the ransomware detects AES-NI and SSE support, using AES-256-CBC if available or falling back to XChaCha20 otherwise, then queries the processor count from the PEB (minimum two) and creates an I/O completion port with NumberOfConcurrentThreads equal to the processor count, which serves as a work queue from which encryption threads retrieve file‐processing requests. For each processor, it spawns an encryptFile thread in a suspended state, hides it from debuggers via NtSetInformationThread using the ThreadHideFromDebugger information class, sets the thread priority (doubling the value for each subsequent thread), and then resumes execution. If at least one thread starts successfully, the function returns 1; otherwise, it frees the allocated resources and returns 0.

Creating I/O completion port with NumberOfConcurrentThreads equal to the processor count obtained earlier from the PEB.
Creating I/O completion port with NumberOfConcurrentThreads equal to the processor count obtained earlier from the PEB.

Creating Search Threads 

If the -path= argument is not provided, the ransomware traverses each drive/share (if the -network argument is provided) using a queue-based approach and encrypts the data on it. All spawned threads are initially suspended and hidden from debuggers via NtSetInformationThread using the ThreadHideFromDebugger information class. When an impersonation token is available, it is assigned to the thread using the ThreadImpersonationToken information class. The threads are then resumed.

The ransomware creating file search threads.
The ransomware creating file search threads.

Local Drives 

The ransomware will first do an anti-debug check after resolving GetThreadContext using its hash, set CONTEXT_FLAGS to check hardware debug registers (DR0-DR3), and immediately call ExitProcess if any debug register is non-zero (indicating that the ransomware is running under a debugger with a hardware breakpoint set). This serves as an anti-debugging check.

The ransomware checking debug registers for potential hardware breakpoints.
The ransomware checking debug registers for potential hardware breakpoints

Next, it retrieves the current logical‐drives bitmask via GetLogicalDrives, inverts it to identify unused drive letters, and begins enumerating all volumes with FindFirstVolumeW/FindNextVolumeW, skipping any volume that already has a mount point.

For each volume without a mount point, it calls DeviceIoControl (control code : 0x70048) to retrieve partition info, and if the partition type GUID matches EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 (Basic Data System) on a fixed or removable disk, it invokes the mount function, which selects the next free drive letter, removes it from the bitmask, concatenates a backslash with the drive letter, and calls SetVolumeMountPointW to assign that letter.

Mounting available data volumes on fixed or removable disks.
Mounting available data volumes on fixed or removable disks.

The ransomware then retrieves the logical‐drives bitmask and iterates through drives A–Z. For each fixed or removable drive, it spawns a search thread to traverse that drive. Each path uses the prefix “\?” to disable normal path parsing and length checks and the suffix “*” as a wildcard.

Network Drives / Shares

If either the -netdrive or -network argument is passed, the ransomware first impersonates another process with the same authentication ID using the previously described method. It then creates a thread to launch the network-based searcher threads.

The thread checks if the -netdrive argument is passed. If it is, it gets the logical‐drives bitmask and iterates drives A–Z for each remote drive (network-mapped drive), and it spawns a search thread to traverse that drive.

Creating and adding network-based search threads to enumerate possible network drives or shares.
Creating and adding network-based search threads to enumerate possible network drives or shares.

If the -network argument is passed, it calls a function that scans network resources for shared folders using a recursive approach. The function opens a network enumeration handle with WNetOpenEnumW and iterates through resources using WNetEnumResourceW. For each resource, it checks the container bit – if set, it recursively calls itself to explore sub-containers; if not, it treats the entry as a share, appends “\*” to the share path, and spawns a search thread for that share.

The ransomware scanning for shares.
The ransomware scanning for shares.

Path (Manual encryption)

If the -path argument is provided, the ransomware recursively encrypts the data at that path by spawning a search thread for that path (while performing impersonation as well).

Search Thread

The function begins by validating the input path by checking if the previously generated filename exists at the root path (details are discussed before). It then writes a ransom note in the current directory and uses FindFirstFileExW/FindNextFileW to iterate over all files in that directory. Whenever it encounters a non‐blacklisted subdirectory, it constructs a wildcard path and pushes it onto the vector for later processing; this vector serves as a queue, enabling a queue-based traversal of directories. For each file that isn’t blacklisted and has a nonzero size, it builds the full path, checks if the file is marked read‐only, and if so attempts to clear that flag with SetFileAttributesW; if successful (or if the file wasn’t read‐only to begin with), it calls encryptFile. Once all entries in the current directory have been processed, it pops the next directory path from the vector to continue the loop. This repeats until no more directory paths remain.

The search thread function, scanning a given path for potential directories and files to encrypt later.
The search thread function, scanning a given path for potential directories and files to encrypt later.

Below is a list of file extensions, file names, and directories that the ransomware skips.

File Extensions
.safepay.exe.dll.pdb.386.cmd.ani.adv.ps1.cab.msi.msp.com.nls.ocx.mpa.cpl.mod.hta.prf.rtp.rpd.bin.hlp.shs.drv.wpx.bat.rom.msc.spl.msu.ics.key.lnk.hlp.sys.drv.cur.idx.ldf.ini.reg.apk.ttf.otf.fon.fnt.dmp.tmp.pif.wav.wma.dmg.app.ipa.xex.wad.msu.icns.theme.diagcfg.diagcab.diagpkg.msstyles.gadget.woff.part.sfcache.winmd.icl.deskthemepack.nomedia
Filenames
readme_safepay.txtautorun.infboot.inibootfont.binbootsect.bakdesktop.iniiconcache.dbntldrntuser.datntuser.dat.logntuser.inithumbs.db
Directories 
$WinREAgent$Windows.~btpublicconfig.msiintelmsocache$recycle.bin$windows.~wstor browserbootwindows ntmsbuildmicrosoftall userssystem volume informationperflogsapplication datawindowswindows.oldappdatacommon fileswindows defenderwindowsappwindowspowershellusosharedwindows securityprogram filesprogram files (x86)programdatadefaultmozilla firefox

File Encryption

The encryptFile function first attempts to open the target file for overlapped access; if that fails it kills any processes which are using the file via Restart Manager APIs: it calls RmStartSession to begin a session, RmRegisterResources to register the file, and RmGetList twice (first to determine how many processes are locking the file and then to retrieve their RM_PROCESS_INFO), then for each non-explorer, non-critical, non-self process, it invokes TerminateProcess and pauses briefly for the process to be terminated. RmEndSession is used at the end to end the session and clean up.

The ransomware using Restart Manager APIs to terminate locking processes, locking access to the target file to encrypt.
The ransomware using Restart Manager APIs to terminate locking processes, locking access to the target file to encrypt.

Once the file is opened, encryptFile allocates a file encryption context and calls RtlGenRandom to generate 32 random bytes. That buffer is clamped into a Curve25519 private key, used to derive the local public key and a shared secret with the peer’s public key, then immediately hashed with SHA-512 and zeroed out. The first 32 bytes of the hash become the encryption key and the next 24/16 bytes the nonce/IV, which initiates either XChaCha20 or AES-256-CBC depending on AES-NI support. Next, the routine appends the ransomware extension to the filename, renames the file via SetFileInformationByHandle, and records the file’s size, handle, public key, and the chosen cipher type in the encryption context.

File encryption function setting up the file encryption context.
File encryption function setting up the file encryption context.

Finally, it binds the file handle to a global I/O completion port, throttles submission if too many operations are pending, posts the first overlapped encryption request, and on failure, cancels pending I/O, closes handles, frees memory, and returns NULL.

Adding target file handle to the global I/O completion port.
Adding target file handle to the global I/O completion port

The file encryption thread sits in a loop pulling completed I/O packets from the completion port and driving each file through a five-state machine. In INITIAL_STATE, it immediately issues an overlapped read of the next block. When that read completes (READ_STATE), it uses the encPercentage (1–10, default value: 10) to decide whether to encrypt this 1 MB chunk or skip it: it keeps a simple counter of chunks processed within each 10 MB window, increments it on every read, and encrypts only when the counter reaches the configured spacing so that exactly encPercentage chunks are processed per window. 

Selective encryption logic processes 1 MB chunks and encrypts only a configurable percentage (encPercentage) within each 10 MB window.
Selective encryption logic processes 1 MB chunks and encrypts only a configurable percentage (encPercentage) within each 10 MB window.

It then advances the file offset and moves on to encrypt. In ENCRYPT_WRITE_STATE, it calls the encryption function on the in-memory buffer and transitions to WRITE_STATE, where WriteData queues the encrypted data to disk. 

Write encrypted chunk.
Write encrypted chunk.

Once the end of the file is detected (either by comparing offsets against the file size or upon receiving an EOF status), the code enters WRITE_FOOTER_STATE, constructs an 80-byte footer with length and key material, and writes it out. 

Here is the structure for the footer

typedef struct _FILE_ENCRYPTION_FOOTER {

    uint32_t FileSizeLow;

    uint32_t FileSizeHigh;

    uint8_t  publicKey[32];

    uint8_t  localPublicKey[32];

    uint8_t  encPercentage;

    uint8_t  encryptionMode;

} FILE_ENCRYPTION_FOOTER;

Finally, in CLEANUP_STATE, it flushes file buffers, increments the global encrypted-files counter, and frees all resources for that context. Any error at any point jumps straight to cleanup for that file before continuing.

Write encrypted metadata at the end of the file.
Write encrypted metadata at the end of the file.

Self-deletion 

As noted earlier, the ransomware employs a variety of anti-forensics measures in an attempt to hide and delete evidence, making the forensics analysis process more challenging. A self-delete mechanism is supported by the ransomware only if it was executed with the “self-delete” command-line argument.

The ransomware checking for “self-delete” option to decide whether to self-delete after encryption or not.
The ransomware checking for “self-delete” option to decide whether to self-delete after encryption or not.

To ensure safe deletion of the ransomware DLL i.e (not recoverable with conventional recovery and data carving techniques), the ransomware first overwrites the file contents with zeros before it is finally deleted 

The ransomware self-delete function implementation.
The ransomware self-delete function implementation.

Authors

  • Hassan Faraz

    Specializing in malware reverse engineering and data recovery, Hassan Faraz develops decryption solutions for vulnerable ransomware and creates custom tools to extract data from various file formats such as VHD, VMDK, MDF, backup formats, etc. He has a proven track record of recovering critical file formats with minimal data loss, helping organizations mitigate the impact of cyberattacks.

  • Mohamed Talaat is a self-taught security researcher specializing in malware analysis, threat intelligence, and tracking APT groups and crimeware. With a background in engineering and over three years of independent research, his focus is on ransomware recovery, evaluating recovery paths, analyzing encryption schemes, and occasionally developing decryptors. Passionate about reverse engineering and low-level analysis, he also writes YARA rules to help detect and hunt emerging threats and campaigns.

  • Laura Pompeu is an editor and content strategy leader at Porthas, bringing over 10 years of digital media experience. Leveraging her background in journalism, SEO, and marketing, Laura shapes cybersecurity and technology content to be insightful yet accessible.

Leave a comment

Your email address will not be published. Required fields are marked *