Introduction
Mamona is a ransomware strain used by the GLOBAL GROUP ransomware actor. Threat actor analysts have established that this group is a continuation of previous
ransomware groups Mamona RIP and Black Lock. This analysis dives deeper into one of the two newly (July 2025) shared samples.
One of the main interesting points is that this ransomware strain has no command-and-control section and as such it is more difficult to detect using network-based signatures.
Encryption is configurable meaning the operator can target the local or one or more remote systems and specific files and folders using a set number of
commandline arguments.
Mamona has the following features and behaviours:
- Detect debugging through a call to the Win32 API DebugBreak
- Obfuscates imported functions from specific DLLs through PEB Walk and import hashing
- Prevents execution on already ransomed systems using the mutex: Global\\Fxo16jmdgujs437
- Attempts to clear the recycle bin using the Win32 API SHEmtpyRecycleBinA
- Attempts to escalate privileges and obtain privileges from the winlogon.exe or TrustedInstaller.exe process
- Attempts to clear the event logs
- Disables a specific subset of processes and services
- Contains a specific section called .config which contains the with XOR-encoded README
- Encrypts only files based on a specific subset of extensions
- Remove itself from the target system using the command cmd.exe /C ping 127.0.0.7 -n 3 > Nul & Del /f /q "%s"
Hash (SHA256) |
---|
c5f49c0f566a114b529138f8bd222865c9fa9fa95f96ec1ded50700764a1d4e7 |
For feedback, questions and comments feel free to contact me directly.
DebugBreak to Break Debugging
We will start the analysis of the Mamona ransomware sample starts in the main function. At the end of the first block, a condition is checked and a jump is taken, either directly to the end of the main function (exiting our ransomware) or continuing along the long vertical line of succeeding blocks. This all depends on the result of the call to sub_4012F0. The focus of sub_4012F0 is on a call to DebugBreak and corresponding exception handling. In case the DebugBreak call fails, eax returns 0 and execution of the ransomware resumes. This is the default behaviour when the executable is not attached to any sort of debugger. Should an exception occur, for example when the executable is attached to a debugger, the call to DebugBreak is likely to trigger an exception. The custom exception handler implemented by the ransomware will then check if the exception pointer is equal to the value of 0x80000003, which corresponds to EXCEPTION_BREAKPOINT. If they are equal, eax will return 1, and the ransomware will stop its execution, as such hindering any dynamic debugging/reverse engineering attempts.
Command Line Arguments
If DebugBreak does not detect a debugger, the ransomware will continue and starts parsing the supplied
commandline argument. It actually performs this operation twice. The first time, at 0x4028B4, where it will
only iterate over all supplied arguments to determine if the -log argument is supplied. This argument
denotes that verbose logging should be enabled and will execute all blocks that contain a call to WriteConsoleW.
Only later on, at 0x402BB0 will it start processing all supplied argument and store its values for later use.
Below is a comprehensive overview of all supported arguments.
Argument | Description |
---|---|
-path | Defines the specific paths to files/directories that should be encrypted |
-threads | Defines the number of threads to use during encryption, default is the number of processors returned from SystemInfo.dwNumberOfProcessors |
-delay | Defines the specific amount of time to wait before starting the encryption process (max 86400 sec/24h) |
-time | Defines the time when to start the encryption process in HH:MM format |
-u | Defines the username to use for authentication |
-p | Defines the password to use for authentication |
-H | Defines the specific host to target |
-sub | Defines the specific subnets to scan |
-code | Defines the password needed to execute the ransomware |
-skip-local | Defines that local drive encryption should be skipped |
-skip-net | Defines that network encryption should be skipped |
-keep | Defines that the ransomware executable should not be deleted |
-log | Defines that extensive logging should be enabled |
Decrypting the config file
After it identified the absence/presence of the -log argument, the ransomware will call GetModuleHandleW on the current
executable. This returns the base address of the current module, or in other words it will point to the beginning of this executable. Subsequently, the ransomware then queries
eax+3Ch(0x40293E), which points to the e_lfanew field in the DOS header. By doing so,
it has now obtained the offset to the PE header. With this offset in hand, the ransomware then 'jumps' to the start of the PE header by adding the base address of the executable
to the PE header offset. The pointer to the PE header is then stored in esi(0x402941).
The ransomware then queries the value at [esi+14h](0x402946). At this location within the PE header when
including the PE magic bytes, the size of the Optional Header is stored.
Next, ecx is incremented by 0x18 or 24 (0x40294A). This means that the ransomware adds the PE signature size, the size
of the COFF file header and the size Optional header together. Then, by adding esi, which points to the start of the PE header, to the total header size,
ecx now points to the start of the section table (0x402959).
Next, the total NumberOfSections is retrieved (0x40298B) and a check takes place to determine if the NumberOfSections is 0 or not. If
the Section Table contains one or more sections, the ransomware will loop over all of the sections and will compare the section name against
a hardcoded value: .config, as can be noticed by the blocks at 0x4029A2 and 0x4029B1.
If the .config section exists the ransomware will read 0x860 bytes into a predefined buffer and will decode the buffer
with the hardcoded XOR key 0x42.
By writing a simple section parser in C++, we can statically extract and decode the .config section from the sections table and obtain the exact
contents of this section. As we discover, this specific section contains the information ransomware victims will normally find in a README file
that states how to contact the ransomware operators and restore access to their lost data.
PEB Walk - Part 1
After the .config section has been decrypted, execution is returned to the main function and quickly afterwards, sub_401370 is called.
In sub_401370, we identify three immediate calls to sub_405900, all with one specific argument: a hardcoded hex value. To determine the
importance of these hex values, let us first analyze sub_405900. This ransomware sample forced me to properly revisit the documentation and
understand how the FS register works, in part, I have to thank rvsec0n for the well documented blog post.
The PEB cannot be directly referenced, so in general, the TEB is obtained through the FS register, and then the obtained offset is incremented
by 0x30 to obtain the PEB. We see the combination of these operations occurring at 0x405914 and 0x40591D.
One of the components of the Process Environment Block is the LDR table. This table is obtained by the ransomware by adding 0x0C to the obtained offset of the PEB. This
operation occurs at 0x405928. The LDR table contains pointers to multiple lists containing the loaded modules, such as the InMemoryOrderModuleList,
InInitializationOrderModuleList as well as the InLoadOrderModuleList. This last list is a doubly linked list which contains a list of DLLs to
process and is located at offset 0x0C which is obtained by the ransomware at location 0x40592F.
Now that the InLoadOrderModuleList has been obtained, ebx points to the Flink of the first module. A check is performed to determine if the
end of the linked list has been reached. If this is not the case, the loop will be entered. Else, the loop will be skipped.
The main loop (running from 0x405952 to 0x4059F5) loops over the full InLoadOrderModuleList and retrieves the full DLL path string.
The retrieval of the full DLL path string can be identified at location 0x405952. In a sub-loop, the full DLL path string is converted to lowercase.
Then, in another sub-loop, the filename + extension are retrieved. Lastly, and most importantly, the filename + extension are hashed and the
resulting output is compared against the input argument of this function.
This explains the hardcoded hex values that were supplied to this function, as these then must correspond to three unique DLLs.
Upon further analysis of sub_405900, it can be identified that if none of the DLLs in the InLoadOrderModuleList correspond to the input hash value, the ransomware will try
to load kernel32.dll, advapi32.dll and shell32.dll. Based on the fact that
sub_405900 is called three times, and if the input arguments do not correspond to any DLL in the InLoadOrderModuleList, three specific DLLs will be imported, it can be
hypothesized that these three DLLs correspond to the hash values. This can be proven by the hash generation block at 0x405A78
which is identical to the block at 0x405D90 where the output value is subsequently compared against the input buffer at 0x405A88.
To close out, the final block at 0x405AB3 needs to be addressed. Here, the return value will contain the address pointing to the DllBase. In
other words, the return value points to the start of the DLL. This will only be returned, when the DLL is already in the InLoadOrderModuleList.
If this is not the case, the DLL is loaded through the call to LoadLibrary.
PEB Walk - Part 2
Now that sub_405900 is dissected, let's focus back on sub_401370. The return value of sub_405900 is
either a pointer to the specific DLL, or it is null. It is checked if each call returned a pointer to the specific DLL before continuining. If one of the functions did not return
a pointer, the function sub_401370 is exited. If all functions properly returned, the block at 0x4013B2 is entered.
Immediately, we can recognize a distinct pattern: a hard coded hex value and the pointer to either kernel32.dll, advapi32.dll or
shell32.dll is supplied as input arguments for a call to sub_405AE0. After each call, the return value in eax
is then stored in a global variable.
By diving into sub_405AE0, initial checks can be identified which ensure that the supplied pointer to the DLL actually is a DLL.
These checks include identifying the existance of the MZ and PE headers. Subsequently, the function will loop over all imported functions,
hash each function name and compare the hash against the supplied hardcoded hex value. In total, 18 functions from kernel32.dll, 3 functions from
advapi32.dll and 1 function from shell32.dll are imported this way.
The result is each time stored in a unique global variable for later reference/use. By using cross referencing in IDA, we identify that not all of the imported functions will be used.
Therefore, a best effort attempt at identifying the specific functions that are used later is made. We use the C++ code below, to mimic the hashing function and by adding functions
often utilized by ransomware from all three DLLs, we can identify corresponding matches. In some cases, we can also utilize the function signature
(type and count of arguments) passed to the global variable to identify potential matches.
Below is a list of the imported dlls and corresponding hashes as well as the imported functions and corresponding hashes:
Imported DLL | Corresponding hash |
---|---|
kernel32.dll | 0x8E1E9d32 |
advapi32.dll | 0x84FE3906 |
shell32.dll | 0xC0B94CC9 |
Imported functions | Corresponding hash |
---|---|
CreateMutexW | 0x8D7EE0E0 |
CreateFileW | 0x9EEB112D |
WriteFile | 0x7ED9CC8D |
CloseHandle | 0xEBC51524 |
GetFileSize | 0x8D7EE0E0 |
ReadFile | 0xFD63715E |
SetFilePointerEx | 0x2BDAFF0C |
GetFileAttributesW | 0x6437560 |
SetFileAttributesW | 0x2F4D0EEC |
MoveFilesExW | 0x6CBFCCED |
GetModuleFileNameW | 0x4D5FA9E0 |
GetLogicalDrives | 0x4F1D3C4A |
GetDriveTypeW | 0x4E4EFCF5 |
** | 0xB0286BF8 |
CreateIoCompletionPort | 0xD11E718D |
CreateThread | 0x9CE6A30E |
WaitForSingleObject | 0x5B55B9D7 |
PostQueuedCompletionStatus | 0x285141AF |
CryptAcquireContextW | 0xDF72101A |
CryptGenRandom | 0xA14E93CF |
CryptReleaseContext | 0x7F5B4B1A |
SHEmtpyRecycleBinA | 0xBBC7846D |
** I have not been able to find out the corresponding function for 0xB0286BF8
Later on in the main function, sub_403E50 is used to (re-)import CryptGenRandom from advapi32.dll.Mutex creation and taking the trash out
If the PEB walk has succeeded, the ransomware will continue by creating a unique mutex with the name: Global\\Fxo16jmdgujs437.
Directly after calling CreateMutexW, the ransomware calls GetLastError.
It then performs a check to see if the last error is equal to 0xB7 which corresponds to ERROR_ALREADY_EXISTS.
As such, it can be concluded that the ransomware will only resume operations if this specific error has not been received. Notice, CreateMutexW
returns an open handle, the ransomware will only close this specific handle after cleanup (discussed later) at 0x403C1D.
Upon resumption, the ransomware will then go on to call the, by PEB-walk imported, function SHEmtpyRecycleBinA
with the arguments 0, 0, 7. The second argument denotes that all Recycle Bins on all drives should be emptied.
The third argument denotes that no dialog boxes should be displayed nor should any sound be played.
Clearing event logs
Within the main function, a call to sub_405BC0 takes place. This functions appears to clear the event logs.
First, a handle to ntdll.dll is obtained, after which the addresses for the functions NtOpenKey and
NtClose are queried. If this operation is successfull, a loop is entered, where the ransomware loops over each of the event logs:
Application, Security, System, Setup and
ForwardedEvents. For each event log, the ransomware tries to clear the logs by calling ClearEventLogW.
Should this fail, the ransomware calls BackupEventLog. It seems that this operation was potentially aimed at overwriting the event
log, but this would not succeed as the output file name is the literal string NUL.
Escalating Privileges
After clearing the event logs, the ransomware calls sub_406170 from main which contains further interesting operations. It starts with a call
to sub_405EC0.
Function sub_405EC0 obtains the current token information from the current process.
The handle to the process token will be passed to sub_405CB0, which is used to try to adjust the current privileges, if required. For each of 11 hardcoded
privileges, the ransomware will check if it currently has obtained said privilege and if it hasn't, it will try to delegate the privilege to itself using AdjustTokenPrivilege
Then, in sub_405EC0, the hardcoded string winlogon.exe is passed to the function sub_405D90.
This function loops over all processes using Process32FirstW and Process32NextW and if the corresponding process supplied
as the input argument is found, it will then return the process ID. Using the process ID, the malware will first try to open the process using PROCESS_QUERY_INFORMATION | PROCESS_VM_READ.
If this fails it will try with access right PROCESS_QUERY_LIMITED_INFORMATION. Should this fail too, the function will return. In any other case, it will
obtain the process tokens using the desired access mask of TOKEN_ALL_ACCESS. If this should fail, it will retry it with the access mask of
TOKEN_QUERY. Next, using sub_405E40, the ransomware will attempt to duplicate the token obtained from
winlogon.exe, and delegate the same 11 privileges as mentioned before to said token. It will then call ImpersonateLoggedOnUser
with the duplicated, high-privilege token.
If all operations so far have succeeded, the ransomware will attempt to open the Service Control Manager with SC_MANAGER_ALL_ACCESS. If this succeeds,
a handle to the service TrustedInstaller will be obtained. Then, it will be checked if the service status can be retrieved, or if the service can be started
and the start operation did not result in the error SERVICE_ALREADY_RUNNING. Should these checks pass, the ransomware will try the exact same
privilege escalation process as with the winlogon.exe process.
Killing services
With the elevated privileges, the ransomware now returns back to sub_406170, where it will attempt to open the Service Control Manager
with SC_MANAGER_ALL_ACCESS. If this succeeds it will iterate over a hardcoded list of services and attempt to terminate the service
(using ControlService) and subsequently attempt to delete the service with a call to DeleteService.
Killing processes
Once all services have been iterated over, the ransomware continues by obtaining a handle to ntdll.dll to retrieve the process addresses of
NtOpenProcess as well as NtTerminateProcess. Then, it will start iterating over a set of hardcoded processes
and for each process it will attempt to terminate the process.
After completing the iterator, the function ends by calling the Win32 API RevertToSelf to undo the previous escalation of privileges and returns to main.
Encryption worker setup
Before the encryption process is actually started, the selected encryption mode will be determined. If either network-only or local-only has been selected, either a local thread pool or remote thread pool
will be created respectively. If both modes have been selected, both a local and remote thread pool will be created. One of the required input values for the creation of a thread pool
is the maximum number of workers that should be spun up consectively. This number, even if only a remote thread pool is used, is determined by the number of processors available on the
current system, which is retrieved from a call to GetNativeSystemInfo.
Thread pool creation itself takes place in sub_404CA0. First, an I/O completion port is created using the number of processors as the number of concurrent threads.
Furthermore, it will create a new thread pointing the start address of the thread to sub_405000. The process within the new thread will call
NtRemoveIoCompletion in a do {...} while () loop. Specifically, the last parameter supplied to this function dictates that the
queue will wait forever until there's more work to process. Within the loop, each completion packet starts up a worker which then retrieves the per-job object via the key, runs the object's next
step and likely marks it as finished when completed. For now, the loop is empty, meaning this thread will be in waiting mode until jobs are added.
Note: New jobs can be added to the queue by using the high level API PostQueuedCompletionStatus or lower level NtSetIoCompletion.
Encrypting the local system
The ransomware will determine what drives and paths to encrypt based on the supplied commandline arguments. If no paths have been supplied, the ransomware will encrypt
all drives. To do so, it will use GetLogicalDrives and loop over the results with GetDriveTypeW to determine
the valid drives.
Once it has obtained all valid drives or supplied paths in the arguments, it will check if a delay has been specified and will delay execution using NtDelayExecution
for the specified amount of seconds.
For both drives and supplied paths, it will loop over them, and determine, by calling GetFileAttributesW, if the object is a file or directory. If it is a file,
a direct call to sub_401B50 will take place. This appears to be an overarching function which is responsible for reading the input
file and performing the encryption before writing the encrypted blob back to the file. Without diving into the specific encryption process, there seems to be a hardcoded key being used:
xcrydtednotstill_amazingg_time!!. If the custom path points to a folder, the function sub_401770 is called.
Function sub_401770 will loop over all files in the directory, and for each file it finds that has one of the extensions listed below, it will add a job
to the I/O completion port for encryption
Valid file extensions | |
---|---|
.exe | .dll |
.msi | .sys |
.ini | .lnk |
Encrypting the network
If the ransomware operator chooses to encrypt the network, the ransomware will first try to obtain local network information using the function sub_404360. Here, the local IP address of the system as well as the domain information will be retrieved primarily using GetAdaptersAddresses and GetComputerNameW. Finally, it will obtain the handles of the following functions from the secur32.dll in the subfunction sub_4064E0:
Targeted DLLs |
---|
LsaConnectUntrusted |
LsaLookupAuthenticationPackage |
LsaCallAuthenticationPackage |
LsaDeregisterLogonProcess |
LsaFreeReturnBuffer |
Next, back in sub_404500, a network scan will take place to identify all network shares. The setup of the prerequisites for scanning the subnet takes place in sub_404940. Within this function, the already familiar NtSetIoCompletion can be once again identified. As seen before, NtSetIoCompletion receives a virtual function table, with a predefined set of functions. We identify that the function sub_404C00 jumps to sub_403FE0.
Several interesting operations can be identified within this function. For example, it performs IPv4 ICMP echo requests by using IcmpCreateFile and IcmpSendEcho. Furthermore, it attempts network hash authentication through sub_406570, using Kerberos and a combination of LsaConnectUntrusted, LsaLookupAuthenticationPackage and WNetAddConnection2W. Lastly, it performs network share enumeration by calling NetShareEnum. Ultimately, it returns with a list of enumerated network shares to be encrypted.
The encryption routine is called by sub_404500 and as expected it uses the I/O completion port again, with another predefined set of functions residing in another unique virtual function table. Within this vftable, the function sub_404C10 is responsible for calling the previously discussed function sub_402680 which calls the main encryption function.
After sending the encryption tasks to the I/O completion port, sub_404500 returns execution back to main.
Thank you and well done
Regardless of the encryption mode that was selected, the created I/O completion port needs to be told all tasks have been completed and the port can be shut down. This specific operation takes place in sub_404D80, where the function PostQueuedCompletionStatus is called. The PostQueuedCompletionStatus is particularly responsible for posting an I/O completion packet, telling the port it can and will be shutdown. After this operation has succeeded, any open handles are closed before returning to main.
Creating the wallpaper
Once the encryption process has been completed, a call to sub_405370 can be identified. This function looks interesting as it relies
on the PEB walk to import further functions before performing several operations. Using our previously mentioned tool, we can identify the used DLLs and functions
and identify that two new DLLs are used and a total of 16 functions are imported:
Imported DLL | Corresponding hash |
---|---|
user32.dll | 0x86A4AF70 |
gdi32.dll | 0x86A4AF70 |
Imported functions | Corresponding hash |
---|---|
GetDC | 0x9F329E09 |
ReleaseDC | 0xFCD551AA |
SystemParametersInfoW | 0xE328CF3E |
FillRect | 0x38766F37 |
DrawTextW | 0x77A89D4C |
CreateCompatibleDC | 0xDA03C35D |
CreateCompatibleBitmap | 0xF106F453 |
GetDeviceCaps | 0x770B6E19 |
DeleteDC | 0x2B9DC79C |
SelectObject | 0x9AD2AC39 |
DeleteObject | 0xEA45C72C |
SetBkMode | 0x881F6820 |
SetTextColor | 0x5F7115D2 |
CreateFontW | 0x9EEE65E4 |
GetDIBits | 0x14870CA1 |
CreateSolidBrush | 0x110B295 |
Once the function import process has been completed, a file with the name wallpaper.bmp is created. Once the file is successfully created, the function SystemParametersInfoW is used to set the actual wallpaper. The first parameter passed to it is the uiAction, where the value 0x14 corresponds with SPI_SETDESKWALLPAPER. The second parameter passed to it is the uiParam, with a value of 0x0 since no uiParam is expected when using SPI_SETDESKWALLPAPER. The third parameter holds the path to the wallpaper file. The fourth, and last, parameter is the fWinIni value 0x3 which corresponds to SPIF_SENDWININICHANGE, meaning the WM_SETTINGCHANGE message will be broadcasted after updating the user profile.
Cleanup
If the commandline argument -keep was not supplied, the ransomware will delete itself by executing the command: cmd.exe /C ping 127.0.0.7 -n 3 > Nul & Del /f /q "%s", where it will place the the result from GetModuleFileNameW into the format string, effectively pointing to itself. The ping is used as a delaying mechanism which should provide the ransomware enough time to exit before the executable is removed. Furthermore, the ransomware also marks itself for deletion by calling SetFileInformationByHandle. This can be gleaned from the second and third argument. The value 0x4 for the FileInformationClass corresponds to FILE_DISPOSITION_INFO. This struct contains exactly one value: DeleteFile, which is set to the value of 1 on line 0x405319.
Closing off
After the cleanup function, the ransomware exits and should have successfully achieved its objectives.
Thank you for reading this extensive analysis of the Mamona ransomware variant. For feedback, questions and comments feel free to contact me directly.